def connect(self): self.empty_selects = 0 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.secure: if ssl: cert_path = os.path.join(G.COLAB_DIR, 'startssl-ca.pem') with open(cert_path, 'wb') as cert_fd: cert_fd.write(cert.CA_CERT.encode('utf-8')) self.sock = ssl.wrap_socket(self.sock, ca_certs=cert_path, cert_reqs=ssl.CERT_REQUIRED) else: msg.log( 'No SSL module found. Connection will not be encrypted.') if self.port == G.DEFAULT_PORT: self.port = 3148 # plaintext port msg.log('Connecting to %s:%s' % (self.host, self.port)) try: self.sock.settimeout(30) # Seconds before timing out connecting self.sock.connect((self.host, self.port)) if self.secure and ssl: self.sock.do_handshake() except socket.error as e: msg.error('Error connecting:', e) self.reconnect() return self.sock.setblocking(False) msg.log('Connected!') self.reconnect_delay = G.INITIAL_RECONNECT_DELAY utils.set_timeout(self.select, 0) self.auth()
def update_view(buf, view=None): view = view or get_view(buf['id']) visible_region = view.visible_region() viewport_position = view.viewport_position() # deep copy selections = [x for x in view.sel()] MODIFIED_EVENTS.put(1) try: view.run_command('floo_view_replace_region', { 'r': [0, view.size()], 'data': buf['buf'] }) except Exception as e: msg.error('Exception updating view: %s' % e) utils.set_timeout(view.set_viewport_position, 0, viewport_position, False) view.sel().clear() view.show(visible_region, False) for sel in selections: view.sel().add(sel) if 'patch' in G.PERMS: view.set_read_only(False) else: view.set_status( 'Floobits', 'You don\'t have write permission. Buffer is read-only.') view.set_read_only(True)
def connect(self): self.empty_selects = 0 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.secure: if ssl: cert_path = os.path.join(G.COLAB_DIR, 'startssl-ca.pem') with open(cert_path, 'wb') as cert_fd: cert_fd.write(cert.CA_CERT.encode('utf-8')) self.sock = ssl.wrap_socket(self.sock, ca_certs=cert_path, cert_reqs=ssl.CERT_REQUIRED) else: msg.log('No SSL module found. Connection will not be encrypted.') if self.port == G.DEFAULT_PORT: self.port = 3148 # plaintext port msg.log('Connecting to %s:%s' % (self.host, self.port)) try: self.sock.settimeout(30) # Seconds before timing out connecting self.sock.connect((self.host, self.port)) if self.secure and ssl: self.sock.do_handshake() except socket.error as e: msg.error('Error connecting:', e) self.reconnect() return self.sock.setblocking(False) msg.log('Connected!') self.reconnect_delay = G.INITIAL_RECONNECT_DELAY utils.set_timeout(self.select, 0) self.auth()
def create_buf(path, always_add=False): if not utils.is_shared(path): msg.error( 'Skipping adding %s because it is not in shared path %s.' % (path, G.PROJECT_PATH)) return if os.path.isdir(path): command = 'git ls-files %s' % path try: p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, cwd=path) stdoutdata, stderrdata = p.communicate() if p.returncode == 0: for git_path in stdoutdata.split('\n'): git_path = git_path.strip() if not git_path: continue add_path = os.path.join(path, git_path) msg.debug('adding %s' % add_path) utils.set_timeout(Listener.create_buf, 0, add_path) return except Exception as e: msg.debug("Couldn't run %s. This is probably OK. Error: %s" % (command, str(e))) for dirpath, dirnames, filenames in os.walk(path): # Don't care about hidden stuff dirnames[:] = [d for d in dirnames if d[0] != '.'] for f in filenames: f_path = os.path.join(dirpath, f) if f[0] == '.': msg.log('Not creating buf for hidden file %s' % f_path) else: utils.set_timeout(Listener.create_buf, 0, f_path) return try: buf_fd = open(path, 'rb') buf = buf_fd.read().decode('utf-8') rel_path = utils.to_rel_path(path) msg.log('creating buffer ', rel_path) event = { 'name': 'create_buf', 'buf': buf, 'path': rel_path, } Listener.agent.put(event) except (IOError, OSError): msg.error('Failed to open %s.' % path) except Exception as e: msg.error('Failed to create buffer %s: %s' % (path, str(e)))
def select(self): if not self.sock: msg.error('select(): No socket.') return self.reconnect() try: # this blocks until the socket is readable or writeable _in, _out, _except = select.select([self.sock], [self.sock], [self.sock]) except (select.error, socket.error, Exception) as e: msg.error('Error in select(): %s' % str(e)) return self.reconnect() if _except: msg.error('Socket error') return self.reconnect() if _in: buf = ''.encode('utf-8') while True: try: d = self.sock.recv(4096) if not d: break buf += d except (socket.error, TypeError): break if buf: self.empty_selects = 0 self.protocol(buf) else: self.empty_selects += 1 if self.empty_selects > 10: msg.error('No data from sock.recv() {0} times.'.format( self.empty_selects)) return self.reconnect() if _out: for p in self.get_patches(): if p is None: SOCKET_Q.task_done() continue try: msg.debug('writing patch: %s' % p) self.sock.sendall(p.encode('utf-8')) SOCKET_Q.task_done() except Exception as e: msg.error('Couldn\'t write to socket: %s' % str(e)) return self.reconnect() utils.set_timeout(self.select, 100)
def push(): reported = set() while Listener.views_changed: view, buf = Listener.views_changed.pop() if view.is_loading(): msg.debug( 'View for buf %s is not ready. Ignoring change event' % buf['id']) continue if 'patch' not in G.PERMS: continue vb_id = view.buffer_id() if vb_id in reported: continue if 'buf' not in buf: msg.debug('No data for buf %s %s yet. Skipping sending patch' % (buf['id'], buf['path'])) continue reported.add(vb_id) patch = FlooPatch(view, buf) # Update the current copy of the buffer buf['buf'] = patch.current buf['md5'] = hashlib.md5(patch.current.encode('utf-8')).hexdigest() if Listener.agent: Listener.agent.put(patch.to_json()) else: msg.debug('Not connected. Discarding view change.') while Listener.selection_changed: view, buf, ping = Listener.selection_changed.pop() # consume highlight events to avoid leak if 'highlight' not in G.PERMS: continue vb_id = view.buffer_id() if vb_id in reported: continue reported.add(vb_id) sel = view.sel() highlight_json = { 'id': buf['id'], 'name': 'highlight', 'ranges': [[x.a, x.b] for x in sel], 'ping': ping, } if Listener.agent: Listener.agent.put(highlight_json) else: msg.debug('Not connected. Discarding selection change.') utils.set_timeout(Listener.push, 100)
def select(self): if not self.sock: msg.error('select(): No socket.') return self.reconnect() try: # this blocks until the socket is readable or writeable _in, _out, _except = select.select([self.sock], [self.sock], [self.sock]) except (select.error, socket.error, Exception) as e: msg.error('Error in select(): %s' % str(e)) return self.reconnect() if _except: msg.error('Socket error') return self.reconnect() if _in: buf = ''.encode('utf-8') while True: try: d = self.sock.recv(4096) if not d: break buf += d except (socket.error, TypeError): break if buf: self.empty_selects = 0 self.protocol(buf) else: self.empty_selects += 1 if self.empty_selects > 10: msg.error('No data from sock.recv() {0} times.'.format(self.empty_selects)) return self.reconnect() if _out: for p in self.get_patches(): if p is None: SOCKET_Q.task_done() continue try: msg.debug('writing patch: %s' % p) self.sock.sendall(p.encode('utf-8')) SOCKET_Q.task_done() except Exception as e: msg.error('Couldn\'t write to socket: %s' % str(e)) return self.reconnect() utils.set_timeout(self.select, 100)
def push(): reported = set() while Listener.views_changed: view, buf = Listener.views_changed.pop() if view.is_loading(): msg.debug('View for buf %s is not ready. Ignoring change event' % buf['id']) continue if 'patch' not in G.PERMS: continue vb_id = view.buffer_id() if vb_id in reported: continue if 'buf' not in buf: msg.debug('No data for buf %s %s yet. Skipping sending patch' % (buf['id'], buf['path'])) continue reported.add(vb_id) patch = FlooPatch(view, buf) # Update the current copy of the buffer buf['buf'] = patch.current buf['md5'] = hashlib.md5(patch.current.encode('utf-8')).hexdigest() if Listener.agent: Listener.agent.put(patch.to_json()) else: msg.debug('Not connected. Discarding view change.') while Listener.selection_changed: view, buf, ping = Listener.selection_changed.pop() # consume highlight events to avoid leak if 'highlight' not in G.PERMS: continue vb_id = view.buffer_id() if vb_id in reported: continue reported.add(vb_id) sel = view.sel() highlight_json = { 'id': buf['id'], 'name': 'highlight', 'ranges': [[x.a, x.b] for x in sel], 'ping': ping, } if Listener.agent: Listener.agent.put(highlight_json) else: msg.debug('Not connected. Discarding selection change.') utils.set_timeout(Listener.push, 100)
def create_buf(path, always_add=False): if not utils.is_shared(path): msg.error('Skipping adding %s because it is not in shared path %s.' % (path, G.PROJECT_PATH)) return if os.path.isdir(path): command = 'git ls-files %s' % path try: p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, cwd=path) stdoutdata, stderrdata = p.communicate() if p.returncode == 0: for git_path in stdoutdata.split('\n'): git_path = git_path.strip() if not git_path: continue add_path = os.path.join(path, git_path) msg.debug('adding %s' % add_path) utils.set_timeout(Listener.create_buf, 0, add_path) return except Exception as e: msg.debug("Couldn't run %s. This is probably OK. Error: %s" % (command, str(e))) for dirpath, dirnames, filenames in os.walk(path): # Don't care about hidden stuff dirnames[:] = [d for d in dirnames if d[0] != '.'] for f in filenames: f_path = os.path.join(dirpath, f) if f[0] == '.': msg.log('Not creating buf for hidden file %s' % f_path) else: utils.set_timeout(Listener.create_buf, 0, f_path) return try: buf_fd = open(path, 'rb') buf = buf_fd.read().decode('utf-8') rel_path = utils.to_rel_path(path) msg.log('creating buffer ', rel_path) event = { 'name': 'create_buf', 'buf': buf, 'path': rel_path, } Listener.agent.put(event) except (IOError, OSError): msg.error('Failed to open %s.' % path) except Exception as e: msg.error('Failed to create buffer %s: %s' % (path, str(e)))
def get_or_create_chat(cb=None): def return_view(): global LOG_LEVEL G.CHAT_VIEW_PATH = G.CHAT_VIEW.file_name() G.CHAT_VIEW.set_read_only(True) if G.DEBUG: LOG_LEVEL = LOG_LEVELS['DEBUG'] if cb: return cb(G.CHAT_VIEW) def open_view(): if not G.CHAT_VIEW: p = os.path.join(G.COLAB_DIR, 'msgs.floobits.log') G.CHAT_VIEW = G.ROOM_WINDOW.open_file(p) utils.set_timeout(return_view, 0) # Can't call open_file outside main thread utils.set_timeout(open_view, 0)
def reconnect(self): try: self.sock.close() except Exception: pass G.CONNECTED = False self.room_info = {} self.buf = '' self.sock = None self.authed = False self.reconnect_delay *= 1.5 if self.reconnect_delay > 10000: self.reconnect_delay = 10000 if self.retries > 0: msg.log('Floobits: Reconnecting in %sms' % self.reconnect_delay) utils.set_timeout(self.connect, int(self.reconnect_delay)) elif self.retries == 0: sublime.error_message('Floobits Error! Too many reconnect failures. Giving up.') self.retries -= 1
def reconnect(self): try: self.sock.close() except Exception: pass G.CONNECTED = False self.room_info = {} self.buf = '' self.sock = None self.authed = False self.reconnect_delay *= 1.5 if self.reconnect_delay > 10000: self.reconnect_delay = 10000 if self.retries > 0: msg.log('Floobits: Reconnecting in %sms' % self.reconnect_delay) utils.set_timeout(self.connect, int(self.reconnect_delay)) elif self.retries == 0: sublime.error_message( 'Floobits Error! Too many reconnect failures. Giving up.') self.retries -= 1
def update_view(buf, view=None): view = view or get_view(buf['id']) visible_region = view.visible_region() viewport_position = view.viewport_position() # deep copy selections = [x for x in view.sel()] MODIFIED_EVENTS.put(1) try: view.run_command('floo_view_replace_region', {'r': [0, view.size()], 'data': buf['buf']}) except Exception as e: msg.error('Exception updating view: %s' % e) utils.set_timeout(view.set_viewport_position, 0, viewport_position, False) view.sel().clear() view.show(visible_region, False) for sel in selections: view.sel().add(sel) if 'patch' in G.PERMS: view.set_read_only(False) else: view.set_status('Floobits', 'You don\'t have write permission. Buffer is read-only.') view.set_read_only(True)
def reconnect(self): if self.reconnect_timeout: return new_buf_out = collections.deque() total_len = 0 while True: try: item = self.buf_out.popleft() except IndexError: break if item['name'] == 'term_stdout': total_len += len(item['data']) if total_len > MAX_BYTES_TO_BUFFER: continue new_buf_out.appendleft(item) self.buf_out = new_buf_out if self.sock: self.remove_fd(self.sock.fileno()) try: self.sock.shutdown(2) except Exception: pass try: self.sock.close() except Exception: pass self.sock = None self.authed = False self.reconnect_delay *= 1.5 if self.reconnect_delay > 10000: self.reconnect_delay = 10000 self.reconnect_timeout = utils.set_timeout(self.connect_to_internet, self.reconnect_delay)
def set_timer(self, sec): self.remove_timer() self.timer = set_timeout(self.autoplay,sec)
def apply_patch(patch_data): buf_id = patch_data['id'] buf = BUFS[buf_id] view = get_view(buf_id) DMP = dmp.diff_match_patch() if len(patch_data['patch']) == 0: msg.error('wtf? no patches to apply. server is being stupid') return msg.debug('patch is', patch_data['patch']) dmp_patches = DMP.patch_fromText(patch_data['patch']) # TODO: run this in a separate thread if view: old_text = get_text(view) else: old_text = buf.get('buf', '') md5_before = hashlib.md5(old_text.encode('utf-8')).hexdigest() if md5_before != patch_data['md5_before']: msg.warn('starting md5s don\'t match for %s. this is dangerous!' % buf['path']) t = DMP.patch_apply(dmp_patches, old_text) clean_patch = True for applied_patch in t[1]: if not applied_patch: clean_patch = False break if G.DEBUG: if len(t[0]) == 0: msg.debug('OMG EMPTY!') msg.debug('Starting data:', buf['buf']) msg.debug('Patch:', patch_data['patch']) if '\x01' in t[0]: msg.debug('FOUND CRAZY BYTE IN BUFFER') msg.debug('Starting data:', buf['buf']) msg.debug('Patch:', patch_data['patch']) if not clean_patch: msg.error('failed to patch %s cleanly. re-fetching buffer' % buf['path']) return Listener.get_buf(buf_id) cur_hash = hashlib.md5(t[0].encode('utf-8')).hexdigest() if cur_hash != patch_data['md5_after']: msg.warn( '%s new hash %s != expected %s. re-fetching buffer...' % (buf['path'], cur_hash, patch_data['md5_after']) ) return Listener.get_buf(buf_id) buf['buf'] = t[0] buf['md5'] = cur_hash if not view: save_buf(buf) return selections = [x for x in view.sel()] # deep copy regions = [] for patch in t[2]: offset = patch[0] length = patch[1] patch_text = patch[2] region = sublime.Region(offset, offset + length) regions.append(region) MODIFIED_EVENTS.put(1) view.run_command('floo_view_replace_region', {'r': [offset, offset + length], 'data': patch_text}) new_sels = [] for sel in selections: a = sel.a b = sel.b new_offset = len(patch_text) - length if sel.a > offset: a += new_offset if sel.b > offset: b += new_offset new_sels.append(sublime.Region(a, b)) selections = [x for x in new_sels] SELECTED_EVENTS.put(1) view.sel().clear() region_key = 'floobits-patch-' + patch_data['username'] view.add_regions(region_key, regions, 'floobits.patch', 'circle', sublime.DRAW_OUTLINED) utils.set_timeout(view.erase_regions, 1000, region_key) for sel in selections: SELECTED_EVENTS.put(1) view.sel().add(sel) now = datetime.now() view.set_status('Floobits', 'Changed by %s at %s' % (patch_data['username'], now.strftime('%H:%M')))
def open_view(): if not G.CHAT_VIEW: p = os.path.join(G.COLAB_DIR, 'msgs.floobits.log') G.CHAT_VIEW = G.ROOM_WINDOW.open_file(p) utils.set_timeout(return_view, 0)
def set_timer(self, sec): self.remove_timer() self.timer = set_timeout(self.autoplay, sec)