def wrapped(*args, **kwargs): if reactor.is_ready(): return func(*args, **kwargs) if warn: msg.error('ignoring request (%s) because you aren\'t in a workspace.' % func.__name__) else: msg.debug('ignoring request (%s) because you aren\'t in a workspace.' % func.__name__)
def read(self): sock_debug('Socket is readable') if self._needs_handshake and not self._do_ssl_handshake(): return buf = ''.encode('utf-8') while True: try: d = self._sock.recv(65536) if not d: break buf += d except (AttributeError): return self.reconnect() except (socket.error, TypeError): break if buf: self._empty_reads = 0 # sock_debug('read data') return self._handle(buf) # sock_debug('empty select') self._empty_reads += 1 if self._empty_reads > (3000 / G.TICK_TIME): msg.error('No data from sock.recv() {0} times.'.format(self._empty_reads)) return self.reconnect()
def _connect(self, host, port, attempts=0): if attempts > (self.proxy and 500 or 500): msg.error('Connection attempt timed out.') return self.reconnect() if not self._sock: msg.debug('_connect: No socket') return try: self._sock.connect((host, port)) select.select([self._sock], [self._sock], [], 0) except socket.error as e: if e.errno == iscon_errno: pass elif e.errno in connect_errno: msg.debug('connect_errno: ', str_e(e)) return utils.set_timeout(self._connect, 20, host, port, attempts + 1) else: msg.error('Error connecting: ', str_e(e)) return self.reconnect() if self._secure: sock_debug('SSL-wrapping socket') self._sock = ssl.wrap_socket(self._sock, ca_certs=self._cert_path, cert_reqs=ssl.CERT_REQUIRED, do_handshake_on_connect=False) self._q.clear() self._buf_out = bytes() self.emit('connect') self.connected = True
def _on_part(self, data): msg.log(data['username'], ' left the workspace') user_id = str(data['user_id']) try: del self.workspace_info['users'][user_id] except Exception: msg.error('Unable to delete user %s from user list' % (data))
def read(self): sock_debug('Socket is readable') if self._needs_handshake and not self._do_ssl_handshake(): return buf = ''.encode('utf-8') while True: try: d = self._sock.recv(65536) if not d: break buf += d # ST2 on Windows with Package Control 3 support! # (socket.recv blocks for some damn reason) if G.SOCK_SINGLE_READ: break except AttributeError: sock_debug('_sock is None') return self.reconnect() except (socket.error, TypeError) as e: sock_debug('Socket error:', e) break if buf: self._empty_reads = 0 # sock_debug('read data') return self._handle(buf) # sock_debug('empty select') self._empty_reads += 1 if self._empty_reads > (3000 / G.TICK_TIME): msg.error('No data from sock.recv() {0} times.'.format(self._empty_reads)) return self.reconnect()
def join_workspace(self, context, host, name, owner, possible_dirs=None): utils.reload_settings() # legacy urls in emacs... if owner and owner[:2] == "r/": owner = owner[2:] if not utils.can_auth(): success = yield self.create_or_link_account, context, host, False if not success: return utils.reload_settings() possible_dirs = possible_dirs or [] for d in possible_dirs: info = utils.read_floo_file(d) if not info: continue try: parsed_url = utils.parse_url(info["url"]) except Exception: parsed_url = None if ( parsed_url and parsed_url["host"] == host and parsed_url["workspace"] == name and parsed_url["owner"] == owner ): self.remote_connect(context, host, owner, name, d) return try: d = utils.get_persistent_data()["workspaces"][owner][name]["path"] except Exception: d = "" if d and os.path.isdir(d): self.remote_connect(context, host, owner, name, d) return d = d or os.path.join(G.SHARE_DIR or G.BASE_DIR, owner, name) join_action = utils.JOIN_ACTION.PROMPT while True: d = yield self.user_dir, context, "Save workspace files to: ", d if not d: return d = os.path.realpath(os.path.expanduser(d)) if not os.path.isdir(d): y_or_n = yield self.user_y_or_n, context, "%s is not a directory. Create it? " % d, "Create Directory" if not y_or_n: return utils.mkdir(d) if not os.path.isdir(d): msg.error("Couldn't create directory", d) continue join_action = utils.JOIN_ACTION.DOWNLOAD if os.path.isdir(d): self.remote_connect(context, host, owner, name, d, join_action) return
def _handle(self, data): self._buf_in += data if self._handling: return self._handling = True while True: before, sep, after = self._buf_in.partition(b'\n') if not sep: break try: # Node.js sends invalid utf8 even though we're calling write(string, "utf8") # Python 2 can figure it out, but python 3 hates it and will die here with some byte sequences # Instead of crashing the plugin, we drop the data. Yes, this is horrible. before = before.decode('utf-8', 'ignore') data = json.loads(before) except Exception as e: msg.error('Unable to parse json: ', str_e(e)) msg.error('Data: ', before) # XXXX: THIS LOSES DATA self._buf_in = after continue name = data.get('name') self._buf_in = after try: msg.debug('got data ' + (name or 'no name')) self.emit('data', name, data) except Exception as e: api.send_error('Error handling %s event.' % name, str_e(e)) if name == 'room_info': editor.error_message('Error joining workspace: %s' % str_e(e)) self.stop() self._handling = False
def open_workspace_settings(self): if not self.agent: return try: webbrowser.open(self.agent.workspace_url + '/settings', new=2, autoraise=True) except Exception as e: msg.error("Couldn't open a browser: %s" % (str_e(e)))
def _connect(self, attempts=0): if attempts > (self.proxy and 500 or 500): msg.error("Connection attempt timed out.") return self.reconnect() if not self._sock: msg.debug("_connect: No socket") return try: self._sock.connect((self._host, self._port)) select.select([self._sock], [self._sock], [], 0) except socket.error as e: if e.errno == iscon_errno: pass elif e.errno in connect_errno: return utils.set_timeout(self._connect, 20, attempts + 1) else: msg.error("Error connecting:", e) return self.reconnect() if self._secure: sock_debug("SSL-wrapping socket") self._sock = ssl.wrap_socket( self._sock, ca_certs=self._cert_path, cert_reqs=ssl.CERT_REQUIRED, do_handshake_on_connect=False ) self._q.clear() self.reconnect_delay = self.INITIAL_RECONNECT_DELAY self.retries = self.MAX_RETRIES self.emit("connect") self.connected = True
def prompt_join_hangout(self, hangout_url): join = yield self.ok_cancel_dialog, 'This workspace is being edited in a hangout. Would you like to join the hangout?' if not join: return try: webbrowser.open(hangout_url, new=2, autoraise=True) except Exception as e: msg.error("Couldn't open a browser: %s" % (str(e)))
def _upload(self, path, text=None): size = 0 try: if text: # TODO: possible encoding issues with python 3 buf = text else: with open(path, 'rb') as buf_fd: buf = buf_fd.read() size = len(buf) encoding = 'utf8' rel_path = utils.to_rel_path(path) existing_buf = self.get_buf_by_path(path) if existing_buf: buf_md5 = hashlib.md5(buf).hexdigest() if existing_buf['md5'] == buf_md5: msg.log('%s already exists and has the same md5. Skipping.' % path) return size msg.log('Setting buffer ', rel_path) try: buf = buf.decode('utf-8') except Exception: buf = base64.b64encode(buf).decode('utf-8') encoding = 'base64' existing_buf['buf'] = buf existing_buf['encoding'] = encoding existing_buf['md5'] = buf_md5 self.send({ 'name': 'set_buf', 'id': existing_buf['id'], 'buf': buf, 'md5': buf_md5, 'encoding': encoding, }) return size try: buf = buf.decode('utf-8') except Exception: buf = base64.b64encode(buf).decode('utf-8') encoding = 'base64' msg.log('Creating buffer ', rel_path) event = { 'name': 'create_buf', 'buf': buf, 'path': rel_path, 'encoding': encoding, } self.send(event) except (IOError, OSError): msg.error('Failed to open %s.' % path) except Exception as e: msg.error('Failed to create buffer %s: %s' % (path, unicode(e))) return size
def join_workspace(self, context, host, name, owner, possible_dirs=None): utils.reload_settings() # legacy urls in emacs... if owner and owner[:2] == "r/": owner = owner[2:] if not utils.can_auth(): success = yield self.create_or_link_account, context, host, False if not success: return utils.reload_settings() possible_dirs = possible_dirs or [] for d in possible_dirs: info = utils.read_floo_file(d) if not info: continue try: parsed_url = utils.parse_url(info['url']) except Exception: parsed_url = None if parsed_url and parsed_url['host'] == host and parsed_url['workspace'] == name and parsed_url['owner'] == owner: self.remote_connect(context, host, owner, name, d) return try: d = utils.get_persistent_data()['workspaces'][owner][name]['path'] except Exception: d = '' if d and os.path.isdir(d): self.remote_connect(context, host, owner, name, d) return # TODO: make per-host settings fully general host_share_dir = G.AUTH.get(host, {}).get('share_dir') d = d or os.path.join(host_share_dir or G.SHARE_DIR or G.BASE_DIR, owner, name) join_action = utils.JOIN_ACTION.PROMPT while True: d = yield self.user_dir, context, 'Save workspace files to: ', d if not d: return d = os.path.realpath(os.path.expanduser(d)) if not os.path.isdir(d): y_or_n = yield self.user_y_or_n, context, '%s is not a directory. Create it? ' % d, "Create Directory" if not y_or_n: return utils.mkdir(d) if not os.path.isdir(d): msg.error("Couldn't create directory", d) continue join_action = utils.JOIN_ACTION.DOWNLOAD if os.path.isdir(d): self.remote_connect(context, host, owner, name, d, join_action) return
def create_workspace(self, context, host, owner, name, api_args, dir_to_share): prompt = 'Workspace name: ' api_args['name'] = name api_args['owner'] = owner while True: new_name = yield self.user_charfield, context, prompt, name name = new_name or name try: api_args['name'] = name r = api.create_workspace(host, api_args) except Exception as e: msg.error('Unable to create workspace ', str_e(e)) editor.error_message('Unable to create workspace: %s' % str_e(e)) return if r.code < 400: workspace_url = 'https://%s/%s/%s' % (host, owner, name) msg.log('Created workspace ', workspace_url) self.remote_connect(context, host, owner, name, dir_to_share, utils.JOIN_ACTION.UPLOAD) return msg.error('Unable to create workspace: ', r.body) if r.code not in (400, 402, 409): try: r.body = r.body['detail'] except Exception: pass editor.error_message('Unable to create workspace: %s' % r.body) return if r.code == 402: try: r.body = r.body['detail'] except Exception: pass yes = yield self.user_y_or_n, context, '%s Open billing settings?' % r.body, "Yes" if yes: webbrowser.open('https://%s/%s/settings#billing' % (host, owner)) return if r.code == 400: # TODO: strip leading dots/dashes/etc name = re.sub('[^A-Za-z0-9_\-\.]', '_', name) prompt = 'Workspace names may only contain [A-Za-z0-9_\-\.]. Choose another name: ' continue yes = yield self.user_y_or_n, context, 'Workspace %s/%s already exists. Overwrite?' % (owner, name), 'Yes' if yes: # TODO: this doesn't set permissions on the workspace correctly self.remote_connect(context, host, owner, name, dir_to_share, utils.JOIN_ACTION.PROMPT) return prompt = 'Workspace %s/%s already exists. Choose new name: ' % (owner, name)
def on_input(self, workspace_name, dir_to_share=None): if dir_to_share: self.dir_to_share = dir_to_share if workspace_name == '': return self.run(dir_to_share=self.dir_to_share) try: self.api_args['name'] = workspace_name self.api_args['owner'] = self.owner msg.debug(str(self.api_args)) r = api.create_workspace(self.host, self.api_args) except Exception as e: msg.error('Unable to create workspace: %s' % str_e(e)) return sublime.error_message('Unable to create workspace: %s' % str_e(e)) workspace_url = 'https://%s/%s/%s' % (self.host, self.owner, workspace_name) msg.log('Created workspace %s' % workspace_url) if r.code < 400: utils.add_workspace_to_persistent_json(self.owner, workspace_name, workspace_url, self.dir_to_share) return self.window.run_command('floobits_join_workspace', { 'workspace_url': workspace_url, 'upload': dir_to_share }) msg.error('Unable to create workspace: %s' % r.body) if r.code not in [400, 402, 409]: try: r.body = r.body['detail'] except Exception: pass return sublime.error_message('Unable to create workspace: %s' % r.body) kwargs = { 'dir_to_share': self.dir_to_share, 'workspace_name': workspace_name, 'api_args': self.api_args, 'owner': self.owner, 'upload': self.upload, 'host': self.host, } if r.code == 400: kwargs['workspace_name'] = re.sub('[^A-Za-z0-9_\-\.]', '-', workspace_name) kwargs['prompt'] = 'Invalid name. Workspace names must match the regex [A-Za-z0-9_\-\.]. Choose another name:' elif r.code == 402: try: r.body = r.body['detail'] except Exception: pass if sublime.ok_cancel_dialog('%s' % r.body, 'Open billing settings'): webbrowser.open('https://%s/%s/settings#billing' % (self.host, self.owner)) return else: kwargs['prompt'] = 'Workspace %s/%s already exists. Choose another name:' % (self.owner, workspace_name) return self.window.run_command('floobits_create_workspace', kwargs)
def floobits_join_workspace(workspace_url, d='', upload_path=None): editor.line_endings = _get_line_endings() msg.debug('workspace url is %s' % workspace_url) try: result = utils.parse_url(workspace_url) except Exception as e: return msg.error(str(e)) if d: utils.mkdir(d) else: try: d = utils.get_persistent_data()['workspaces'][result['owner']][result['workspace']]['path'] except Exception: d = os.path.realpath(os.path.join(G.COLAB_DIR, result['owner'], result['workspace'])) prompt = 'Save workspace files to: ' if not os.path.isdir(d): while True: d = vim_input(prompt, d, 'dir') if d == '': continue d = os.path.realpath(os.path.expanduser(d)) if os.path.isfile(d): prompt = '%s is not a directory. Enter an existing path or a path I can create: ' % d continue if not os.path.isdir(d): try: utils.mkdir(d) except Exception as e: prompt = 'Couldn\'t make dir %s: %s ' % (d, str(e)) continue break d = os.path.realpath(os.path.abspath(d) + os.sep) try: utils.add_workspace_to_persistent_json(result['owner'], result['workspace'], workspace_url, d) except Exception as e: return msg.error('Error adding workspace to persistent.json: %s' % str(e)) G.PROJECT_PATH = d vim.command('cd %s' % G.PROJECT_PATH) msg.debug('Joining workspace %s' % workspace_url) floobits_stop_everything() try: conn = VimHandler(result['owner'], result['workspace']) if upload_path: conn.once('room_info', lambda: G.AGENT.upload(upload_path)) reactor.connect(conn, result['host'], result['port'], result['secure']) except Exception as e: msg.error(str(e)) tb = traceback.format_exc() msg.debug(tb) if not G.TIMERS: start_event_loop()
def on_input(self, workspace_name, dir_to_share=None): if dir_to_share: self.dir_to_share = dir_to_share if workspace_name == '': return self.run(dir_to_share=self.dir_to_share) try: self.api_args['name'] = workspace_name self.api_args['owner'] = self.owner msg.debug(str(self.api_args)) r = api.create_workspace(self.api_args) except Exception as e: msg.error('Unable to create workspace: %s' % unicode(e)) return sublime.error_message('Unable to create workspace: %s' % unicode(e)) workspace_url = 'https://%s/%s/%s/' % (G.DEFAULT_HOST, self.owner, workspace_name) msg.log('Created workspace %s' % workspace_url) if r.code < 400: utils.add_workspace_to_persistent_json(self.owner, workspace_name, workspace_url, self.dir_to_share) return self.window.run_command('floobits_join_workspace', { 'workspace_url': workspace_url, 'agent_conn_kwargs': { 'get_bufs': False } }) msg.error('Unable to create workspace: %s' % r.body) if r.code not in [400, 402, 409]: try: r.body = r.body['detail'] except Exception: pass return sublime.error_message('Unable to create workspace: %s' % r.body) kwargs = { 'dir_to_share': self.dir_to_share, 'workspace_name': workspace_name, 'api_args': self.api_args, 'owner': self.owner, } if r.code == 400: kwargs['workspace_name'] = re.sub('[^A-Za-z0-9_\-\.]', '-', workspace_name) kwargs['prompt'] = 'Invalid name. Workspace names must match the regex [A-Za-z0-9_\-\.]. Choose another name:' elif r.code == 402: try: r.body = r.body['detail'] except Exception: pass return sublime.error_message('%s' % r.body) else: kwargs['prompt'] = 'Workspace %s/%s already exists. Choose another name:' % (self.owner, workspace_name) return self.window.run_command('floobits_create_workspace', kwargs)
def create_workspace(self, context, host, owner, name, api_args, dir_to_share): prompt = "Workspace name: " api_args["name"] = name api_args["owner"] = owner while True: new_name = yield self.user_charfield, context, prompt, name name = new_name or name try: api_args["name"] = name r = api.create_workspace(host, api_args) except Exception as e: msg.error("Unable to create workspace ", str_e(e)) editor.error_message("Unable to create workspace: %s" % str_e(e)) return if r.code < 400: workspace_url = "https://%s/%s/%s" % (host, owner, name) msg.log("Created workspace ", workspace_url) self.remote_connect(context, host, owner, name, dir_to_share, utils.JOIN_ACTION.UPLOAD) return msg.error("Unable to create workspace: ", r.body) if r.code not in (400, 402, 409): try: r.body = r.body["detail"] except Exception: pass editor.error_message("Unable to create workspace: %s" % r.body) return if r.code == 402: try: r.body = r.body["detail"] except Exception: pass yes = yield self.user_y_or_n, context, "%s Open billing settings?" % r.body, "Yes" if yes: webbrowser.open("https://%s/%s/settings#billing" % (host, owner)) return if r.code == 400: # TODO: strip leading dots/dashes/etc name = re.sub("[^A-Za-z0-9_\-\.]", "_", name) prompt = "Workspace names may only contain [A-Za-z0-9_\-\.]. Choose another name: " continue prompt = "Workspace %s/%s already exists. Choose another name: " % (owner, name)
def on_input(self, workspace_name, dir_to_share=None): if dir_to_share: self.dir_to_share = dir_to_share if workspace_name == '': return self.run(dir_to_share=self.dir_to_share) try: self.api_args['name'] = workspace_name self.api_args['owner'] = self.owner msg.debug(str(self.api_args)) api.create_workspace(self.api_args) workspace_url = 'https://%s/r/%s/%s' % (G.DEFAULT_HOST, self.owner, workspace_name) print('Created workspace %s' % workspace_url) except HTTPError as e: err_body = e.read() msg.error('Unable to create workspace: %s %s' % (unicode(e), err_body)) if e.code not in [400, 402, 409]: return sublime.error_message('Unable to create workspace: %s %s' % (unicode(e), err_body)) kwargs = { 'dir_to_share': self.dir_to_share, 'workspace_name': workspace_name, 'api_args': self.api_args, 'owner': self.owner, } if e.code == 400: kwargs['workspace_name'] = re.sub('[^A-Za-z0-9_\-]', '-', workspace_name) kwargs['prompt'] = 'Invalid name. Workspace names must match the regex [A-Za-z0-9_\-]. Choose another name:' elif e.code == 402: try: err_body = json.loads(err_body) err_body = err_body['detail'] except Exception: pass return sublime.error_message('%s' % err_body) else: kwargs['prompt'] = 'Workspace %s/%s already exists. Choose another name:' % (self.owner, workspace_name) return self.window.run_command('floobits_create_workspace', kwargs) except Exception as e: return sublime.error_message('Unable to create workspace: %s' % unicode(e)) add_workspace_to_persistent_json(self.owner, workspace_name, workspace_url, self.dir_to_share) on_room_info_waterfall.add(on_room_info_msg) self.window.run_command('floobits_join_workspace', { 'workspace_url': workspace_url, 'agent_conn_kwargs': { 'get_bufs': False } })
def select(self): if not self.conn: msg.error('select(): No socket.') return self.reconnect() while True: if self.agent: self.agent.tick() out_conns = [] if len(self.to_emacs_q) > 0: out_conns.append(self.conn) try: _in, _out, _except = select.select([self.conn], out_conns, [self.conn], 0.05) 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 = '' while True: try: d = self.conn.recv(4096) if not d: break buf += d except (socket.error, TypeError): break if buf: self.empty_selects = 0 self.handle(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: while len(self.to_emacs_q) > 0: p = self.to_emacs_q.pop(0) try: msg.debug('to emacs: %s' % p) self.conn.sendall(p) except Exception as e: msg.error('Couldn\'t write to socket: %s' % str(e)) return self.reconnect()
def prejoin_workspace(self, workspace_url, dir_to_share, api_args): try: result = utils.parse_url(workspace_url) except Exception as e: msg.error(str_e(e)) return False host = result.get("host") if not api.get_basic_auth(host): raise ValueError( "No auth credentials for %s. Please add a username and secret for %s in your ~/.floorc.json" % (host, host) ) try: w = api.get_workspace_by_url(workspace_url) except Exception as e: editor.error_message("Error opening url %s: %s" % (workspace_url, str_e(e))) return False if w.code >= 400: try: d = utils.get_persistent_data() try: del d["workspaces"][result["owner"]][result["name"]] except Exception: pass try: del d["recent_workspaces"][workspace_url] except Exception: pass utils.update_persistent_data(d) except Exception as e: msg.debug(str_e(e)) return False msg.debug("workspace: ", json.dumps(w.body)) anon_perms = w.body.get("perms", {}).get("AnonymousUser", []) msg.debug("api args: ", api_args) new_anon_perms = api_args.get("perms", {}).get("AnonymousUser", []) # TODO: prompt/alert user if going from private to public if set(anon_perms) != set(new_anon_perms): msg.debug(str(anon_perms), str(new_anon_perms)) w.body["perms"]["AnonymousUser"] = new_anon_perms response = api.update_workspace(workspace_url, w.body) msg.debug(str(response.body)) utils.add_workspace_to_persistent_json(w.body["owner"], w.body["name"], workspace_url, dir_to_share) return result
def run_agent(owner, workspace, host, port, secure, upload): if G.AGENT: msg.debug('Stopping agent.') reactor.stop() G.AGENT = None try: auth = G.AUTH.get(host) if not auth: success = yield link_account, host if not success: return auth = G.AUTH.get(host) conn = SublimeConnection(owner, workspace, auth, upload) reactor.connect(conn, host, port, secure) except Exception as e: msg.error(str_e(e))
def __init__(self): self.to_emacs_q = [] self.net_buf = '' self.agent = None self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.bind(('localhost', 4567)) self.sock.listen(1) self.user_inputs = {} self.user_input_count = 0 if sys.version_info[0] == 2 and sys.version_info[1] == 6: # Work around http://bugs.python.org/issue11326 msg.error('Disabling SSL to work around a bug in Python 2.6. Please upgrade your Python to get SSL. See http://bugs.python.org/issue11326') G.SECURE = False G.DEFAULT_PORT = 3148
def select(self, timeout=0): if not self._protos: return readable = [] writeable = [] errorable = [] fd_map = {} for fd in self._protos: fileno = fd.fileno() if not fileno: continue fd.fd_set(readable, writeable, errorable) fd_map[fileno] = fd if not readable and not writeable: return try: _in, _out, _except = select.select(readable, writeable, errorable, timeout) except (select.error, socket.error, Exception) as e: # TODO: with multiple FDs, must call select with just one until we find the error :( if len(readable) == 1: readable[0].reconnect() return msg.error('Error in select(): %s' % str(e)) raise Exception("can't handle more than one fd exception in reactor") for fileno in _except: fd = fd_map[fileno] self._reconnect(fd, _in, _out) for fileno in _out: fd = fd_map[fileno] try: fd.write() except Exception as e: msg.error('Couldn\'t write to socket: %s' % str(e)) return self._reconnect(fd, _in) for fileno in _in: fd = fd_map[fileno] try: fd.read() except Exception as e: msg.error('Couldn\'t read from socket: %s' % str(e)) fd.reconnect()
def _do_ssl_handshake(self): try: sock_debug('Doing SSL handshake') self._sock.do_handshake() except ssl.SSLError as e: sock_debug('Floobits: ssl.SSLError. This is expected sometimes.') if e.args[0] in [ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE]: return False except Exception as e: msg.error('Error in SSL handshake:', str_e(e)) else: sock_debug('Successful handshake') self._needs_handshake = False editor.status_message('SSL handshake completed to %s:%s' % (self.host, self.port)) return True self.reconnect() return False
def floobits_setup_credentials(): prompt = 'You need a Floobits account! Do you have one? If no we will create one for you [y/n]. ' d = vim_input(prompt, '') if d and (d != 'y' and d != 'n'): return floobits_setup_credentials() agent = None if d == 'y': msg.debug('You have an account.') token = binascii.b2a_hex(uuid.uuid4().bytes).decode('utf-8') agent = RequestCredentialsHandler(token) elif not utils.get_persistent_data().get('disable_account_creation'): agent = CreateAccountHandler() if not agent: msg.error('A configuration error occured earlier. Please go to floobits.com and sign up to use this plugin.\n\n' 'We\'re really sorry. This should never happen.') return try: reactor.connect(agent, G.DEFAULT_HOST, G.DEFAULT_PORT, True) except Exception as e: msg.error(str(e)) msg.debug(traceback.format_exc())
def floobits_complete_signup(): msg.debug('Completing signup.') if not utils.has_browser(): msg.log('You need a modern browser to complete the sign up. Go to https://floobits.com to sign up.') return floorc = utils.load_floorc() username = floorc.get('USERNAME') secret = floorc.get('SECRET') msg.debug('Completing sign up with %s %s' % (username, secret)) if not (username and secret): return msg.error('You don\'t seem to have a Floobits account of any sort.') webbrowser.open('https://%s/%s/pinocchio/%s' % (G.DEFAULT_HOST, username, secret))
def _handle(self, data): self._buf += data while True: before, sep, after = self._buf.partition(b"\n") if not sep: return try: # Node.js sends invalid utf8 even though we're calling write(string, "utf8") # Python 2 can figure it out, but python 3 hates it and will die here with some byte sequences # Instead of crashing the plugin, we drop the data. Yes, this is horrible. before = before.decode("utf-8", "ignore") data = json.loads(before) except Exception as e: msg.error("Unable to parse json: %s" % str(e)) msg.error("Data: %s" % before) # XXXX: THIS LOSES DATA self._buf = after continue name = data.get("name") try: msg.debug("got data " + (name or "no name")) self.emit("data", name, data) except Exception as e: print(traceback.format_exc()) msg.error("Error handling %s event (%s)." % (name, str(e))) if name == "room_info": editor.error_message("Error joining workspace: %s" % str(e)) self.stop() self._buf = after
def select(self, timeout=0): if not self._protos: return readable = [] writeable = [] errorable = [] fd_map = {} for fd in self._protos: fileno = fd.fileno() if not fileno: continue fd.fd_set(readable, writeable, errorable) fd_map[fileno] = fd if not readable and not writeable: return try: _in, _out, _except = select.select(readable, writeable, errorable, timeout) except (select.error, socket.error, Exception) as e: # TODO: with multiple FDs, must call select with just one until we find the error :( for fileno in readable: try: select.select([fileno], [], [], 0) except (select.error, socket.error, Exception) as e: fd_map[fileno].reconnect() msg.error('Error in select(): ', fileno, str_e(e)) return for fileno in _except: fd = fd_map[fileno] self._reconnect(fd, _in, _out) for fileno in _out: fd = fd_map[fileno] try: fd.write() except ssl.SSLError as e: if e.args[0] != ssl.SSL_ERROR_WANT_WRITE: raise except Exception as e: msg.error('Couldn\'t write to socket: ', str_e(e)) msg.debug('Couldn\'t write to socket: ', pp_e(e)) return self._reconnect(fd, _in) for fileno in _in: fd = fd_map[fileno] try: fd.read() except ssl.SSLError as e: if e.args[0] != ssl.SSL_ERROR_WANT_READ: raise except Exception as e: msg.error('Couldn\'t read from socket: ', str_e(e)) msg.debug('Couldn\'t read from socket: ', pp_e(e)) fd.reconnect()
def create_workspace(self, data, workspace_name, dir_to_share, owner=None, perms=None): owner = owner or G.USERNAME workspace_name = data.get('response', workspace_name) prompt = 'workspace %s already exists. Choose another name: ' % workspace_name try: api_args = { 'name': workspace_name, 'owner': owner, } if perms: api_args['perms'] = perms api.create_workspace(api_args) workspace_url = utils.to_workspace_url({'secure': True, 'owner': owner, 'workspace': workspace_name}) msg.debug('Created workspace %s' % workspace_url) except HTTPError as e: err_body = e.read() msg.error('Unable to create workspace: %s %s' % (unicode(e), err_body)) if e.code not in [400, 402, 409]: return msg.error('Unable to create workspace: %s' % str(e)) if e.code == 400: workspace_name = re.sub('[^A-Za-z0-9_\-]', '-', workspace_name) prompt = 'Invalid name. Workspace names must match the regex [A-Za-z0-9_\-]. Choose another name:' elif e.code == 402: try: err_body = json.loads(err_body) err_body = err_body['detail'] except Exception: pass return sublime.error_message('%s' % err_body) else: prompt = 'Workspace %s/%s already exists. Choose another name:' % (owner, workspace_name) return self.get_input(prompt, workspace_name, self.create_workspace, workspace_name, dir_to_share, owner, perms) except Exception as e: return msg.error('Unable to create workspace: %s' % str(e)) G.PROJECT_PATH = dir_to_share self.remote_connect(workspace_url, lambda this: this.protocol.create_buf(dir_to_share))
def join_workspace(self, data, owner, workspace, dir_to_make=None): d = data['response'] workspace_url = utils.to_workspace_url({'secure': True, 'owner': owner, 'workspace': workspace}) if dir_to_make: if d: d = dir_to_make utils.mkdir(d) else: d = '' if d == '': return self.get_input('Give me a directory to sync data to: ', G.PROJECT_PATH, self.join_workspace, owner, workspace) d = os.path.realpath(os.path.expanduser(d)) if not os.path.isdir(d): if dir_to_make: return msg.error("Couldn't create directory %s" % dir_to_make) prompt = '%s is not a directory. Create it? ' % d return self.get_input(prompt, '', self.join_workspace, owner, workspace, dir_to_make=d, y_or_n=True) try: G.PROJECT_PATH = d utils.mkdir(os.path.dirname(G.PROJECT_PATH)) self.remote_connect(workspace_url) except Exception as e: return msg.error("Couldn't create directory %s: %s" % (G.PROJECT_PATH, str(e)))
def on_input(self, dir_to_share): file_to_share = None dir_to_share = os.path.expanduser(dir_to_share) dir_to_share = os.path.realpath(utils.unfuck_path(dir_to_share)) workspace_name = os.path.basename(dir_to_share) workspace_url = None # TODO: use prejoin_workspace instead def find_workspace(workspace_url): r = api.get_workspace_by_url(workspace_url) if r.code < 400: return r try: result = utils.parse_url(workspace_url) d = utils.get_persistent_data() del d['workspaces'][result['owner']][result['name']] utils.update_persistent_data(d) except Exception as e: msg.debug(str_e(e)) def join_workspace(workspace_url): try: w = find_workspace(workspace_url) except Exception as e: sublime.error_message('Error: %s' % str_e(e)) return False if not w: return False msg.debug('workspace: %s', json.dumps(w.body)) # if self.api_args: anon_perms = w.body.get('perms', {}).get('AnonymousUser', []) new_anon_perms = self.api_args.get('perms').get( 'AnonymousUser', []) # TODO: warn user about making a private workspace public if set(anon_perms) != set(new_anon_perms): msg.debug(str(anon_perms), str(new_anon_perms)) w.body['perms']['AnonymousUser'] = new_anon_perms response = api.update_workspace(workspace_url, w.body) msg.debug(str(response.body)) utils.add_workspace_to_persistent_json(w.body['owner'], w.body['name'], workspace_url, dir_to_share) self.window.run_command('floobits_join_workspace', {'workspace_url': workspace_url}) return True if os.path.isfile(dir_to_share): file_to_share = dir_to_share dir_to_share = os.path.dirname(dir_to_share) try: utils.mkdir(dir_to_share) except Exception: sublime.error_message( 'The directory %s doesn\'t exist and I can\'t create it.' % dir_to_share) return floo_file = os.path.join(dir_to_share, '.floo') info = {} try: floo_info = open(floo_file, 'r').read() info = json.loads(floo_info) except (IOError, OSError): pass except Exception: msg.error('Couldn\'t read the floo_info file: %s' % floo_file) workspace_url = info.get('url') try: utils.parse_url(workspace_url) except Exception: workspace_url = None if workspace_url and join_workspace(workspace_url): return for owner, workspaces in utils.get_persistent_data( )['workspaces'].items(): for name, workspace in workspaces.items(): if workspace['path'] == dir_to_share: workspace_url = workspace['url'] if join_workspace(workspace_url): return auth = yield editor.select_auth, self.window, G.AUTH if not auth: return username = auth.get('username') host = auth['host'] def on_done(owner): msg.log('Colab dir: %s, Username: %s, Workspace: %s/%s' % (G.COLAB_DIR, username, owner[0], workspace_name)) self.window.run_command( 'floobits_create_workspace', { 'workspace_name': workspace_name, 'dir_to_share': dir_to_share, 'upload': file_to_share or dir_to_share, 'api_args': self.api_args, 'owner': owner[0], 'host': host, }) try: r = api.get_orgs_can_admin(host) except IOError as e: sublime.error_message('Error getting org list: %s' % str_e(e)) return if r.code >= 400 or len(r.body) == 0: on_done([username]) return orgs = [[org['name'], 'Create workspace owned by %s' % org['name']] for org in r.body] orgs.insert(0, [username, 'Create workspace owned by %s' % username]) self.window.show_quick_panel( orgs, lambda index: index < 0 or on_done(orgs[index]))
def on_input(self, workspace_name, dir_to_share=None): if dir_to_share: self.dir_to_share = dir_to_share if workspace_name == '': return self.run(dir_to_share=self.dir_to_share) try: self.api_args['name'] = workspace_name self.api_args['owner'] = self.owner msg.debug(str(self.api_args)) r = api.create_workspace(self.host, self.api_args) except Exception as e: msg.error('Unable to create workspace: %s' % str_e(e)) return sublime.error_message('Unable to create workspace: %s' % str_e(e)) workspace_url = 'https://%s/%s/%s' % (self.host, self.owner, workspace_name) msg.log('Created workspace %s' % workspace_url) if r.code < 400: utils.add_workspace_to_persistent_json(self.owner, workspace_name, workspace_url, self.dir_to_share) return self.window.run_command('floobits_join_workspace', { 'workspace_url': workspace_url, 'upload': dir_to_share }) msg.error('Unable to create workspace: %s' % r.body) if r.code not in [400, 402, 409]: try: r.body = r.body['detail'] except Exception: pass return sublime.error_message('Unable to create workspace: %s' % r.body) kwargs = { 'dir_to_share': self.dir_to_share, 'workspace_name': workspace_name, 'api_args': self.api_args, 'owner': self.owner, 'upload': self.upload, 'host': self.host, } if r.code == 400: kwargs['workspace_name'] = re.sub('[^A-Za-z0-9_\-\.]', '-', workspace_name) kwargs[ 'prompt'] = 'Invalid name. Workspace names must match the regex [A-Za-z0-9_\-\.]. Choose another name:' elif r.code == 402: try: r.body = r.body['detail'] except Exception: pass if sublime.ok_cancel_dialog('%s' % r.body, 'Open billing settings'): webbrowser.open('https://%s/%s/settings#billing' % (self.host, self.owner)) return else: kwargs[ 'prompt'] = 'Workspace %s/%s already exists. Choose another name:' % ( self.owner, workspace_name) return self.window.run_command('floobits_create_workspace', kwargs)
def floobits_join_workspace(workspace_url, d='', upload_path=None): editor.line_endings = _get_line_endings() msg.debug('workspace url is %s' % workspace_url) try: result = utils.parse_url(workspace_url) except Exception as e: return msg.error(str(e)) if d: utils.mkdir(d) else: try: d = utils.get_persistent_data()['workspaces'][result['owner']][ result['workspace']]['path'] except Exception: d = os.path.realpath( os.path.join(G.COLAB_DIR, result['owner'], result['workspace'])) prompt = 'Save workspace files to: ' if not os.path.isdir(d): while True: d = vim_input(prompt, d, 'dir') if d == '': continue d = os.path.realpath(os.path.expanduser(d)) if os.path.isfile(d): prompt = '%s is not a directory. Enter an existing path or a path I can create: ' % d continue if not os.path.isdir(d): try: utils.mkdir(d) except Exception as e: prompt = 'Couldn\'t make dir %s: %s ' % (d, str(e)) continue break d = os.path.realpath(os.path.abspath(d) + os.sep) try: utils.add_workspace_to_persistent_json(result['owner'], result['workspace'], workspace_url, d) except Exception as e: return msg.error('Error adding workspace to persistent.json: %s' % str(e)) G.PROJECT_PATH = d vim.command('cd %s' % G.PROJECT_PATH) msg.debug('Joining workspace %s' % workspace_url) floobits_stop_everything() try: conn = VimHandler(result['owner'], result['workspace']) if upload_path: conn.once('room_info', lambda: G.AGENT.upload(upload_path)) reactor.connect(conn, result['host'], result['port'], result['secure']) except Exception as e: msg.error(str(e)) tb = traceback.format_exc() msg.debug(tb) if not G.TIMERS: start_event_loop()
def _upload(self, path, text=None): size = 0 try: if text is None: with open(path, 'rb') as buf_fd: buf = buf_fd.read() else: try: # work around python 3 encoding issue buf = text.encode('utf8') except Exception as e: msg.debug('Error encoding buf ', path, ': ', str_e(e)) # We're probably in python 2 so it's ok to do this buf = text size = len(buf) encoding = 'utf8' rel_path = utils.to_rel_path(path) existing_buf = self.get_buf_by_path(path) if existing_buf: if text is None: buf_md5 = hashlib.md5(buf).hexdigest() if existing_buf['md5'] == buf_md5: msg.log( path, ' already exists and has the same md5. Skipping.') return size existing_buf['md5'] = buf_md5 msg.log('Setting buffer ', rel_path) try: buf = buf.decode('utf-8') except Exception: buf = base64.b64encode(buf).decode('utf-8') encoding = 'base64' existing_buf['buf'] = buf existing_buf['encoding'] = encoding self.send({ 'name': 'set_buf', 'id': existing_buf['id'], 'buf': buf, 'md5': existing_buf['md5'], 'encoding': encoding, }) self.send({'name': 'saved', 'id': existing_buf['id']}) return size try: buf = buf.decode('utf-8') except Exception: buf = base64.b64encode(buf).decode('utf-8') encoding = 'base64' msg.log('Creating buffer ', rel_path, ' (', len(buf), ' bytes)') event = { 'name': 'create_buf', 'buf': buf, 'path': rel_path, 'encoding': encoding, } def done(d): if d.get('id'): self.bufs[d['id']] = buf self.paths_to_ids[rel_path] = d['id'] self.send(event, done) except (IOError, OSError): msg.error('Failed to open ', path) except Exception as e: msg.error('Failed to create buffer ', path, ': ', str_e(e)) return size
def _on_room_info(self, data): self.joined_workspace = True self.workspace_info = data G.PERMS = data['perms'] self.proto.reset_retries() if G.OUTBOUND_FILTERING: msg.error( 'Detected outbound port blocking! See https://floobits.com/help/network for more info.' ) read_only = False if 'patch' not in data['perms']: read_only = True no_perms_msg = '''You don't have permission to edit this workspace. All files will be read-only.''' msg.log('No patch permission. Setting buffers to read-only') if 'request_perm' in data['perms']: should_send = yield self.ok_cancel_dialog, no_perms_msg + '\nDo you want to request edit permission?' # TODO: wait for perms to be OK'd/denied before uploading or bailing if should_send: self.send({ 'name': 'request_perms', 'perms': ['edit_room'] }) else: if G.EXPERT_MODE: editor.status_message(no_perms_msg) else: editor.error_message(no_perms_msg) floo_json = { 'url': utils.to_workspace_url({ 'owner': self.owner, 'workspace': self.workspace, 'host': self.proto.host, 'port': self.proto.port, 'secure': self.proto.secure, }) } utils.update_floo_file(os.path.join(G.PROJECT_PATH, '.floo'), floo_json) utils.update_recent_workspaces(self.workspace_url) changed_bufs = [] missing_bufs = [] new_files = set() ig = ignore.create_ignore_tree(G.PROJECT_PATH) G.IGNORE = ig if not read_only: new_files = set([utils.to_rel_path(x) for x in ig.list_paths()]) for buf_id, buf in data['bufs'].items(): buf_id = int(buf_id) # json keys must be strings buf_path = utils.get_full_path(buf['path']) new_dir = os.path.dirname(buf_path) utils.mkdir(new_dir) self.bufs[buf_id] = buf self.paths_to_ids[buf['path']] = buf_id view = self.get_view(buf_id) if view and not view.is_loading() and buf['encoding'] == 'utf8': view_text = view.get_text() view_md5 = hashlib.md5(view_text.encode('utf-8')).hexdigest() buf['buf'] = view_text buf['view'] = view G.VIEW_TO_HASH[view.native_id] = view_md5 if view_md5 == buf['md5']: msg.debug('md5 sum matches view. not getting buffer ', buf['path']) else: changed_bufs.append(buf) buf['md5'] = view_md5 continue try: if buf['encoding'] == 'utf8': if io: buf_fd = io.open(buf_path, 'Urt', encoding='utf8') buf_buf = buf_fd.read() else: buf_fd = open(buf_path, 'rb') buf_buf = buf_fd.read().decode('utf-8').replace( '\r\n', '\n') md5 = hashlib.md5(buf_buf.encode('utf-8')).hexdigest() else: buf_fd = open(buf_path, 'rb') buf_buf = buf_fd.read() md5 = hashlib.md5(buf_buf).hexdigest() buf_fd.close() buf['buf'] = buf_buf if md5 == buf['md5']: msg.debug('md5 sum matches. not getting buffer ', buf['path']) else: msg.debug('md5 differs. possibly getting buffer later ', buf['path']) changed_bufs.append(buf) buf['md5'] = md5 except Exception as e: msg.debug('Error calculating md5 for ', buf['path'], ', ', str_e(e)) missing_bufs.append(buf) ignored = [] for p, buf_id in self.paths_to_ids.items(): if p not in new_files: ignored.append(p) new_files.discard(p) if self.action == utils.JOIN_ACTION.UPLOAD: yield self._initial_upload, ig, missing_bufs, changed_bufs # TODO: maybe use org name here who = 'Your friends' anon_perms = G.AGENT.workspace_info.get('anon_perms') if 'get_buf' in anon_perms: who = 'Anyone' _msg = 'You are sharing:\n\n%s\n\n%s can join your workspace at:\n\n%s' % ( G.PROJECT_PATH, who, G.AGENT.workspace_url) # Workaround for horrible Sublime Text bug utils.set_timeout(editor.message_dialog, 0, _msg) elif changed_bufs or missing_bufs or new_files: # TODO: handle readonly here if self.action == utils.JOIN_ACTION.PROMPT: stomp_local = yield self.stomp_prompt, changed_bufs, missing_bufs, list( new_files), ignored if stomp_local not in [0, 1]: self.stop() return elif self.action == utils.JOIN_ACTION.DOWNLOAD: stomp_local = True else: # This should never happen assert False return if stomp_local: for buf in changed_bufs: self.get_buf(buf['id'], buf.get('view')) self.save_on_get_bufs.add(buf['id']) for buf in missing_bufs: self.get_buf(buf['id'], buf.get('view')) self.save_on_get_bufs.add(buf['id']) else: yield self._initial_upload, ig, missing_bufs, changed_bufs success_msg = '%s@%s/%s: Joined!' % (self.username, self.owner, self.workspace) msg.log(success_msg) editor.status_message(success_msg) data = utils.get_persistent_data() data['recent_workspaces'].insert(0, {"url": self.workspace_url}) utils.update_persistent_data(data) utils.add_workspace_to_persistent_json(self.owner, self.workspace, self.workspace_url, G.PROJECT_PATH) temp_data = data.get('temp_data', {}) hangout = temp_data.get('hangout', {}) hangout_url = hangout.get('url') if hangout_url: self.prompt_join_hangout(hangout_url) self.emit("room_info")
def share_dir(self, context, dir_to_share, api_args): utils.reload_settings() if not utils.can_auth(): success = yield self.create_or_link_account, context, G.DEFAULT_HOST, False if not success: return utils.reload_settings() dir_to_share = os.path.expanduser(dir_to_share) dir_to_share = os.path.realpath(dir_to_share) dir_to_share = utils.unfuck_path(dir_to_share) if os.path.isfile(dir_to_share): dir_to_share = os.path.dirname(dir_to_share) workspace_name = os.path.basename(dir_to_share) msg.debug('', workspace_name, dir_to_share) if os.path.isfile(dir_to_share): dir_to_share = os.path.dirname(dir_to_share) try: utils.mkdir(dir_to_share) except Exception: msg.error("The directory", dir_to_share, "doesn't exist and I can't create it.") return info = utils.read_floo_file(dir_to_share) def prejoin(workspace_url): try: return self.prejoin_workspace(workspace_url, dir_to_share, api_args) except ValueError: pass workspace_url = info.get('url') if workspace_url: parsed_url = prejoin(workspace_url) if parsed_url: self.remote_connect(context, parsed_url['host'], parsed_url['owner'], parsed_url['workspace'], dir_to_share) return parsed_url = utils.get_workspace_by_path(dir_to_share, prejoin) if parsed_url: self.remote_connect(context, parsed_url['host'], parsed_url['owner'], parsed_url['workspace'], dir_to_share) return host = yield self._get_host, context if not host: return try: r = api.get_orgs_can_admin(host) except IOError as e: editor.error_message('Error getting org list: %s' % str_e(e)) return choices = [G.AUTH[host]['username']] if r.code >= 400: editor.error_message('Error getting org list: %s' % r.body) elif r.body: choices += [org['name'] for org in r.body] if len(choices) == 1: owner = choices[0] else: little = ['Create workspace owned by %s' % s for s in choices] ( owner, index ) = yield self.user_select, context, 'Create workspace owned by', choices, little if not owner: return self.create_workspace(context, host, owner, workspace_name, api_args, dir_to_share)
def create_workspace(self, context, host, owner, name, api_args, dir_to_share): prompt = 'Workspace name: ' api_args['name'] = name api_args['owner'] = owner while True: new_name = yield self.user_charfield, context, prompt, name name = new_name or name try: api_args['name'] = name r = api.create_workspace(host, api_args) except Exception as e: msg.error('Unable to create workspace ', str_e(e)) editor.error_message('Unable to create workspace: %s' % str_e(e)) return if r.code < 400: workspace_url = 'https://%s/%s/%s' % (host, owner, name) msg.log('Created workspace ', workspace_url) self.remote_connect(context, host, owner, name, dir_to_share, utils.JOIN_ACTION.UPLOAD) return msg.error('Unable to create workspace: ', r.body) if r.code not in (400, 402, 409): try: r.body = r.body['detail'] except Exception: pass editor.error_message('Unable to create workspace: %s' % r.body) return if r.code == 402: try: r.body = r.body['detail'] except Exception: pass yes = yield self.user_y_or_n, context, '%s Open billing settings?' % r.body, "Yes" if yes: webbrowser.open('https://%s/%s/settings#billing' % (host, owner)) return if r.code == 400: # TODO: strip leading dots/dashes/etc name = re.sub('[^A-Za-z0-9_\-\.]', '_', name) prompt = 'Workspace names may only contain [A-Za-z0-9_\-\.]. Choose another name: ' continue yes = yield self.user_y_or_n, context, 'Workspace %s/%s already exists. Overwrite?' % ( owner, name), 'Yes' if yes: # TODO: this doesn't set permissions on the workspace correctly self.remote_connect(context, host, owner, name, dir_to_share, utils.JOIN_ACTION.PROMPT) return prompt = 'Workspace %s/%s already exists. Choose new name: ' % ( owner, name)
def on_input(self, workspace_name, dir_to_share=None): if dir_to_share: self.dir_to_share = dir_to_share if workspace_name == '': return self.run(dir_to_share=self.dir_to_share) try: self.api_args['name'] = workspace_name self.api_args['owner'] = self.owner msg.debug(str(self.api_args)) api.create_workspace(self.api_args) workspace_url = 'https://%s/r/%s/%s' % (G.DEFAULT_HOST, self.owner, workspace_name) print('Created workspace %s' % workspace_url) except HTTPError as e: err_body = e.read() msg.error('Unable to create workspace: %s %s' % (unicode(e), err_body)) if e.code not in [400, 402, 409]: return sublime.error_message( 'Unable to create workspace: %s %s' % (unicode(e), err_body)) kwargs = { 'dir_to_share': self.dir_to_share, 'workspace_name': workspace_name, 'api_args': self.api_args, 'owner': self.owner, } if e.code == 400: kwargs['workspace_name'] = re.sub('[^A-Za-z0-9_\-]', '-', workspace_name) kwargs[ 'prompt'] = 'Invalid name. Workspace names must match the regex [A-Za-z0-9_\-]. Choose another name:' elif e.code == 402: try: err_body = json.loads(err_body) err_body = err_body['detail'] except Exception: pass return sublime.error_message('%s' % err_body) else: kwargs[ 'prompt'] = 'Workspace %s/%s already exists. Choose another name:' % ( self.owner, workspace_name) return self.window.run_command('floobits_create_workspace', kwargs) except Exception as e: return sublime.error_message('Unable to create workspace: %s' % unicode(e)) add_workspace_to_persistent_json(self.owner, workspace_name, workspace_url, self.dir_to_share) on_room_info_waterfall.add(on_room_info_msg) self.window.run_command( 'floobits_join_workspace', { 'workspace_url': workspace_url, 'agent_conn_kwargs': { 'get_bufs': False } })
def _on_patch(self, data): buf_id = data['id'] buf = self.bufs[buf_id] if 'buf' not in buf: msg.debug('buf ', buf['path'], ' not populated yet. not patching') return if buf['encoding'] == 'base64': # TODO apply binary patches return self.get_buf(buf_id, None) if len(data['patch']) == 0: msg.debug('wtf? no patches to apply. server is being stupid') return msg.debug('patch is', data['patch']) dmp_patches = DMP.patch_fromText(data['patch']) # TODO: run this in a separate thread old_text = buf['buf'] view = self.get_view(buf_id) if view and not view.is_loading(): view_text = view.get_text() if old_text == view_text: buf['forced_patch'] = False elif not buf.get('forced_patch'): patch = utils.FlooPatch(view_text, buf) # Update the current copy of the buffer buf['buf'] = patch.current buf['md5'] = hashlib.md5( patch.current.encode('utf-8')).hexdigest() buf['forced_patch'] = True msg.debug('forcing patch for ', buf['path']) self.send(patch.to_json()) old_text = view_text else: msg.debug( 'forced patch is true. not sending another force patch for buf ', buf['path']) md5_before = hashlib.md5(old_text.encode('utf-8')).hexdigest() if md5_before != data['md5_before']: msg.warn('starting md5s don\'t match for ', buf['path'], '. this is dangerous!') 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: try: msg.debug('OMG EMPTY!') msg.debug('Starting data:', buf['buf']) msg.debug('Patch:', data['patch']) except Exception as e: msg.error(e) if '\x01' in t[0]: msg.debug('FOUND CRAZY BYTE IN BUFFER') msg.debug('Starting data:', buf['buf']) msg.debug('Patch:', data['patch']) timeout_id = buf.get('timeout_id') if timeout_id: utils.cancel_timeout(timeout_id) del buf['timeout_id'] if not clean_patch: msg.log('Couldn\'t patch ', buf['path'], ' cleanly.') return self.get_buf(buf_id, view) cur_hash = hashlib.md5(t[0].encode('utf-8')).hexdigest() if cur_hash != data['md5_after']: msg.debug('Ending md5s don\'t match for ', buf['path'], ' Setting get_buf timeout.') buf['timeout_id'] = utils.set_timeout(self.get_buf, 2000, buf_id, view) buf['buf'] = t[0] buf['md5'] = cur_hash if not view: msg.debug('No view. Not saving buffer ', buf_id) def _on_load(): v = self.get_view(buf_id) if v and 'buf' in buf: v.update(buf, message=False) self.on_load[buf_id]['patch'] = _on_load return view.apply_patches(buf, t, data['username'])
def _on_room_info(self, data): self.joined_workspace = True self.workspace_info = data G.PERMS = data['perms'] self.proto.reset_retries() if G.OUTBOUND_FILTERING: msg.error( 'Detected outbound port blocking! See https://floobits.com/help/network for more info.' ) read_only = False if 'patch' not in data['perms']: read_only = True no_perms_msg = '''You don't have permission to edit this workspace. All files will be read-only.''' msg.log('No patch permission. Setting buffers to read-only') if 'request_perm' in data['perms']: should_send = yield self.ok_cancel_dialog, no_perms_msg + '\nDo you want to request edit permission?' # TODO: wait for perms to be OK'd/denied before uploading or bailing if should_send: self.send({ 'name': 'request_perms', 'perms': ['edit_room'] }) else: if G.EXPERT_MODE: editor.status_message(no_perms_msg) else: editor.error_message(no_perms_msg) floo_json = { 'url': utils.to_workspace_url({ 'owner': self.owner, 'workspace': self.workspace, 'host': self.proto.host, 'port': self.proto.port, 'secure': self.proto.secure, }) } utils.update_floo_file(os.path.join(G.PROJECT_PATH, '.floo'), floo_json) utils.update_recent_workspaces(self.workspace_url) ig = ignore.create_ignore_tree(G.PROJECT_PATH) G.IGNORE = ig for buf_id, buf in data['bufs'].items(): buf_id = int(buf_id) # json keys must be strings self.bufs[buf_id] = buf self.paths_to_ids[buf['path']] = buf_id changed_bufs, missing_bufs, new_files = self._scan_dir( data['bufs'], ig, read_only) ignored = [] for p, buf_id in self.paths_to_ids.items(): if p not in new_files: ignored.append(p) new_files.discard(p) if self.action == utils.JOIN_ACTION.UPLOAD: yield self._initial_upload, ig, missing_bufs, changed_bufs # TODO: maybe use org name here who = 'Your friends' anon_perms = G.AGENT.workspace_info.get('anon_perms') if 'get_buf' in anon_perms: who = 'Anyone' _msg = 'You are sharing:\n\n%s\n\n%s can join your workspace at:\n\n%s' % ( G.PROJECT_PATH, who, G.AGENT.workspace_url) # Workaround for horrible Sublime Text bug utils.set_timeout(editor.message_dialog, 0, _msg) # Don't auto-upload again on reconnect self.action = utils.JOIN_ACTION.PROMPT elif changed_bufs or missing_bufs or new_files: # TODO: handle readonly here if self.action == utils.JOIN_ACTION.PROMPT: stomp_local = yield self.stomp_prompt, changed_bufs, missing_bufs, list( new_files), ignored if stomp_local not in [0, 1]: self.stop() return elif self.action == utils.JOIN_ACTION.DOWNLOAD: stomp_local = True else: # This should never happen assert False return if stomp_local: for buf in changed_bufs: self.get_buf(buf['id'], buf.get('view')) self.save_on_get_bufs.add(buf['id']) for buf in missing_bufs: self.get_buf(buf['id'], buf.get('view')) self.save_on_get_bufs.add(buf['id']) else: yield self._initial_upload, ig, missing_bufs, changed_bufs success_msg = '%s@%s/%s: Joined!' % (self.username, self.owner, self.workspace) msg.log(success_msg) editor.status_message(success_msg) data = utils.get_persistent_data() data['recent_workspaces'].insert(0, {"url": self.workspace_url}) utils.update_persistent_data(data) utils.add_workspace_to_persistent_json(self.owner, self.workspace, self.workspace_url, G.PROJECT_PATH) temp_data = data.get('temp_data', {}) hangout = temp_data.get('hangout', {}) hangout_url = hangout.get('url') if hangout_url: self.prompt_join_hangout(hangout_url) if data.get('repo_info'): msg.log('Repo info:', data.get('repo_info')) # TODO: check local repo info and update remote (or prompt?) else: repo_info = repo.get_info(self.workspace_url, G.PROJECT_PATH) if repo_info and 'repo' in G.PERMS: self.send({ 'name': 'repo', 'action': 'set', 'data': repo_info, }) self.emit("room_info")
def floobits_share_dir(dir_to_share, perms): utils.reload_settings() workspace_name = os.path.basename(dir_to_share) G.PROJECT_PATH = os.path.realpath(dir_to_share) msg.debug('%s %s %s' % (G.USERNAME, workspace_name, G.PROJECT_PATH)) file_to_share = None dir_to_share = os.path.expanduser(dir_to_share) dir_to_share = utils.unfuck_path(dir_to_share) dir_to_share = os.path.abspath(dir_to_share) dir_to_share = os.path.realpath(dir_to_share) workspace_name = os.path.basename(dir_to_share) if os.path.isfile(dir_to_share): file_to_share = dir_to_share dir_to_share = os.path.dirname(dir_to_share) try: utils.mkdir(dir_to_share) except Exception: return msg.error( "The directory %s doesn't exist and I can't create it." % dir_to_share) if not os.path.isdir(dir_to_share): return msg.error('The directory %s doesn\'t appear to exist' % dir_to_share) floo_file = os.path.join(dir_to_share, '.floo') # look for the .floo file for hints about previous behavior info = {} try: floo_info = open(floo_file, 'rb').read().decode('utf-8') info = json.loads(floo_info) except (IOError, OSError): pass except Exception: msg.warn('couldn\'t read the floo_info file: %s' % floo_file) workspace_url = info.get('url') if workspace_url: parsed_url = api.prejoin_workspace(workspace_url, dir_to_share, {'perms': perms}) if parsed_url: return floobits_join_workspace(workspace_url, dir_to_share, upload_path=file_to_share or dir_to_share) filter_func = lambda workspace_url: api.prejoin_workspace( workspace_url, dir_to_share, {'perms': perms}) parsed_url = utils.get_workspace_by_path(dir_to_share, filter_func) if parsed_url: return floobits_join_workspace(workspace_url, dir_to_share, upload_path=file_to_share or dir_to_share) try: r = api.get_orgs_can_admin() except IOError as e: return editor.error_message('Error getting org list: %s' % str(e)) if r.code >= 400 or len(r.body) == 0: workspace_name = vim_input('Workspace name:', workspace_name, "file") return create_workspace(workspace_name, dir_to_share, G.USERNAME, perms, upload_path=file_to_share or dir_to_share) orgs = r.body if len(orgs) == 0: return create_workspace(workspace_name, dir_to_share, G.USERNAME, perms, upload_path=file_to_share or dir_to_share) choices = [] choices.append(G.USERNAME) for o in orgs: choices.append(o['name']) owner = vim_choice('Create workspace for:', G.USERNAME, choices) if owner: return create_workspace(workspace_name, dir_to_share, owner, perms, upload_path=file_to_share or dir_to_share)
def share_dir(dir_to_share): dir_to_share = os.path.expanduser(dir_to_share) dir_to_share = utils.unfuck_path(dir_to_share) dir_to_share = os.path.abspath(dir_to_share) workspace_name = os.path.basename(dir_to_share) floo_workspace_dir = os.path.join(G.COLAB_DIR, G.USERNAME, workspace_name) if os.path.isfile(dir_to_share): return msg.error('give me a directory please') if not os.path.isdir(dir_to_share): return msg.error('The directory %s doesn\'t appear to exist' % dir_to_share) floo_file = os.path.join(dir_to_share, '.floo') # look for the .floo file for hints about previous behavior info = {} try: floo_info = open(floo_file, 'rb').read().decode('utf-8') info = json.loads(floo_info) except (IOError, OSError): pass except Exception: msg.warn("couldn't read the floo_info file: %s" % floo_file) workspace_url = info.get('url') if workspace_url: try: result = utils.parse_url(workspace_url) except Exception as e: msg.error(str(e)) else: workspace_name = result['workspace'] floo_workspace_dir = os.path.join(G.COLAB_DIR, result['owner'], result['workspace']) # they have previously joined the workspace if os.path.realpath(floo_workspace_dir) == os.path.realpath( dir_to_share): # it could have been deleted, try to recreate it if possible # TODO: org or something here? if result['owner'] == G.USERNAME: try: api.create_workspace({'name': workspace_name}) msg.debug('Created workspace %s' % workspace_url) except Exception as e: msg.debug('Tried to create workspace' + str(e)) # they wanted to share teh dir, so always share it return join_workspace( workspace_url, lambda x: agent.protocol.create_buf(dir_to_share, force=True)) # link to what they want to share try: utils.mkdir(os.path.dirname(floo_workspace_dir)) os.symlink(dir_to_share, floo_workspace_dir) except OSError as e: if e.errno != 17: raise except Exception as e: return msg.error("Couldn't create symlink from %s to %s: %s" % (dir_to_share, floo_workspace_dir, str(e))) # make & join workspace create_workspace(workspace_name, floo_workspace_dir, dir_to_share)