def _handle(self, data): self._buf_in += data while True: before, sep, after = self._buf_in.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(e)) msg.error('Data: %s' % before) # XXXX: THIS LOSES DATA self._buf_in = after continue name = data.get('name') 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._buf_in = after
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 _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 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, 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 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 remote_connect(self, context, host, owner, workspace, d, join_action=utils.JOIN_ACTION.PROMPT): G.PROJECT_PATH = os.path.realpath(d) try: utils.mkdir(os.path.dirname(G.PROJECT_PATH)) except Exception as e: msg.error("Couldn't create directory", G.PROJECT_PATH, str_e(e)) return auth = G.AUTH.get(host) if not auth: success = yield self.link_account, context, host if not success: return auth = G.AUTH.get(host) if not auth: msg.error("Something went really wrong.") return try: res = api.get_workspace(host, owner, workspace) if res.code == 404: msg.error("The workspace https://%s/%s/%s does not exist" % (host, owner, workspace)) return except Exception as e: message = 'Error getting workspace https://%s/%s/%s: %s' % ( host, owner, workspace, str_e(e)) msg.error(message) editor.error_message(message) return if self.agent: try: self.agent.stop() except Exception: pass G.WORKSPACE_WINDOW = yield self.get_a_window, d self.agent = self._make_agent(context, owner, workspace, auth, join_action) self.emit("agent", self.agent) reactor.reactor.connect(self.agent, host, G.DEFAULT_PORT, True) url = self.agent.workspace_url utils.add_workspace_to_persistent_json(owner, workspace, url, d) utils.update_recent_workspaces(url)
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 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(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(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(e)) fd.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 plugin_loaded(): global called_plugin_loaded if called_plugin_loaded: return called_plugin_loaded = True print('Floobits: Called plugin_loaded.') if not os.path.exists(G.FLOORC_JSON_PATH): migrations.migrate_floorc() utils.reload_settings() # TODO: one day this can be removed (once all our users have updated) old_colab_dir = os.path.realpath(os.path.expanduser(os.path.join('~', '.floobits'))) if os.path.isdir(old_colab_dir) and not os.path.exists(G.BASE_DIR): print('Renaming %s to %s' % (old_colab_dir, G.BASE_DIR)) os.rename(old_colab_dir, G.BASE_DIR) os.symlink(G.BASE_DIR, old_colab_dir) try: utils.normalize_persistent_data() except Exception as e: print('Floobits: Error normalizing persistent data:', str_e(e)) # Keep on truckin' I guess d = utils.get_persistent_data() G.AUTO_GENERATED_ACCOUNT = d.get('auto_generated_account', False) # Sublime plugin API stuff can't be called right off the bat if not utils.can_auth(): utils.set_timeout(create_or_link_account, 1) utils.set_timeout(global_tick, 1)
def make_dir(d): d = os.path.realpath(os.path.expanduser(d)) if not os.path.isdir(d): make_dir = sublime.ok_cancel_dialog( '%s is not a directory. Create it?' % d) if not make_dir: return self.window.show_input_panel( '%s is not a directory. Enter an existing path:' % d, d, None, None, None) try: utils.mkdir(d) except Exception as e: return sublime.error_message( 'Could not create directory %s: %s' % (d, str_e(e))) G.PROJECT_PATH = d if self.upload: result['upload'] = d else: result['upload'] = "" utils.add_workspace_to_persistent_json(result['owner'], result['workspace'], workspace_url, d) open_workspace_window(lambda: run_agent(**result))
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
def plugin_loaded(): global called_plugin_loaded if called_plugin_loaded: return called_plugin_loaded = True print('Floobits: Called plugin_loaded.') if not os.path.exists(G.FLOORC_JSON_PATH): migrations.migrate_floorc() utils.reload_settings() # TODO: one day this can be removed (once all our users have updated) old_colab_dir = os.path.realpath( os.path.expanduser(os.path.join('~', '.floobits'))) if os.path.isdir(old_colab_dir) and not os.path.exists(G.BASE_DIR): print('Renaming %s to %s' % (old_colab_dir, G.BASE_DIR)) os.rename(old_colab_dir, G.BASE_DIR) os.symlink(G.BASE_DIR, old_colab_dir) try: utils.normalize_persistent_data() except Exception as e: print('Floobits: Error normalizing persistent data:', str_e(e)) # Keep on truckin' I guess d = utils.get_persistent_data() G.AUTO_GENERATED_ACCOUNT = d.get('auto_generated_account', False) # Sublime plugin API stuff can't be called right off the bat if not utils.can_auth(): utils.set_timeout(create_or_link_account, 1) utils.set_timeout(global_tick, 1)
def _setup(): # stupid yielding loop until we get a window from st2 w = sublime.active_window() if w is None: return sublime.set_timeout(_setup, 50) try: settings = sublime.load_settings('Floobits.sublime-settings') now = time.time() old_time = settings.get('floobits-id') settings.set('floobits-id', now) interval = utils.set_interval(reactor.tick, G.TICK_TIME) def shutdown(): print('Floobits plugin updated. Shutting down old instance.', old_time) try: utils.cancel_timeout(interval) except Exception: pass try: settings.clear_on_change('floobits-id') except Exception: pass settings.add_on_change('floobits-id', shutdown) w.run_command('floobits_setup') except Exception as e: print(str_e(e))
def join_workspace_by_url(self, context, workspace_url, possible_dirs=None): try: d = utils.parse_url(workspace_url) except Exception as e: return editor.error_message(str_e(e)) return self.join_workspace(context, d['host'], d['workspace'], d['owner'], possible_dirs)
def run(self): try: agent = G.AGENT d = { 'port': agent.proto.port, 'secure': agent.proto.secure, 'owner': agent.owner, 'workspace': agent.workspace, 'host': agent.proto.host, } view = self.window.active_view() if view: path = view.file_name() if path and utils.is_shared(path): d['path'] = utils.to_rel_path(path) try: d['line'] = view.rowcol(view.sel()[0].a)[0] except Exception: pass url = utils.to_workspace_url(d) webbrowser.open(url) except Exception as e: sublime.error_message( 'Unable to open workspace in web editor: %s' % str_e(e))
def on_data(self, name, data): if name == 'create_user': del data['name'] try: floorc = self.BASE_FLOORC + '\n'.join( ['%s %s' % (k, v) for k, v in data.items()]) + '\n' with open(G.FLOORC_PATH, 'w') as floorc_fd: floorc_fd.write(floorc) utils.reload_settings() if utils.can_auth(): p = os.path.join(G.BASE_DIR, 'welcome.md') with open(p, 'w') as fd: text = editor.welcome_text % (G.AUTH.get( self.proto.host, {}).get('username'), self.proto.host) fd.write(text) d = utils.get_persistent_data() d['auto_generated_account'] = True utils.update_persistent_data(d) G.AUTO_GENERATED_ACCOUNT = True editor.open_file(p) else: editor.error_message( 'Something went wrong. You will need to sign up for an account to use Floobits.' ) api.send_error('No username or secret') except Exception as e: msg.debug(traceback.format_exc()) msg.error(str_e(e)) try: d = utils.get_persistent_data() d['disable_account_creation'] = True utils.update_persistent_data(d) finally: self.proto.stop()
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 plugin_loaded(): global called_plugin_loaded if called_plugin_loaded: return called_plugin_loaded = True print('Floobits: Called plugin_loaded.') utils.reload_settings() G.SOCK_SINGLE_READ = SOCK_SINGLE_READ # TODO: one day this can be removed (once all our users have updated) old_colab_dir = os.path.realpath( os.path.expanduser(os.path.join('~', '.floobits'))) if os.path.isdir(old_colab_dir) and not os.path.exists(G.BASE_DIR): print('Renaming %s to %s' % (old_colab_dir, G.BASE_DIR)) os.rename(old_colab_dir, G.BASE_DIR) os.symlink(G.BASE_DIR, old_colab_dir) try: utils.normalize_persistent_data() except Exception as e: print('Floobits: Error normalizing persistent data:', str_e(e)) # Keep on truckin' I guess d = utils.get_persistent_data() G.AUTO_GENERATED_ACCOUNT = d.get('auto_generated_account', False) setup()
def _on_create_user(self, data): try: del data['name'] floorc_json = {'auth': {}} floorc_json['auth'][G.DEFAULT_HOST] = data utils.save_floorc_json(floorc_json) utils.reload_settings() if utils.can_auth(): p = os.path.join(G.BASE_DIR, 'welcome.md') with open(p, 'w') as fd: username = G.AUTH.get(self.proto.host, {}).get('username') text = editor.NEW_ACCOUNT_TXT.format(username=username, host=self.proto.host) fd.write(text) d = utils.get_persistent_data() d['auto_generated_account'] = True utils.update_persistent_data(d) G.AUTO_GENERATED_ACCOUNT = True editor.open_file(p) else: editor.error_message( 'Something went wrong. You will need to sign up for an account to use Floobits.' ) api.send_error('No username or secret') except Exception as e: msg.debug(traceback.format_exc()) msg.error(str_e(e)) finally: try: d = utils.get_persistent_data() d['disable_account_creation'] = True utils.update_persistent_data(d) finally: self.stop()
def _scan_dir(self, bufs, ig, read_only): changed_bufs = [] missing_bufs = [] new_files = set() if not read_only: new_files = set([utils.to_rel_path(x) for x in ig.list_paths()]) for buf_id, buf in 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) return changed_bufs, missing_bufs, new_files
def open_workspace(self): if not self.agent: return try: webbrowser.open(self.agent.workspace_url, new=2, autoraise=True) except Exception as e: msg.error("Couldn't open a browser: %s" % (str_e(e)))
def _on_create_user(self, data): try: del data['name'] floorc_json = { 'auth': {} } floorc_json['auth'][G.DEFAULT_HOST] = data utils.save_floorc_json(floorc_json) utils.reload_settings() if utils.can_auth(): p = os.path.join(G.BASE_DIR, 'welcome.md') with open(p, 'w') as fd: username = G.AUTH.get(self.proto.host, {}).get('username') text = editor.NEW_ACCOUNT_TXT.format(username=username, host=self.proto.host) fd.write(text) d = utils.get_persistent_data() d['auto_generated_account'] = True utils.update_persistent_data(d) G.AUTO_GENERATED_ACCOUNT = True editor.open_file(p) else: editor.error_message('Something went wrong. You will need to sign up for an account to use Floobits.') api.send_error('No username or secret') except Exception as e: msg.debug(traceback.format_exc()) msg.error(str_e(e)) finally: try: d = utils.get_persistent_data() d['disable_account_creation'] = True utils.update_persistent_data(d) finally: self.stop()
def plugin_loaded(): global called_plugin_loaded if called_plugin_loaded: return called_plugin_loaded = True print('Floobits: Called plugin_loaded.') utils.reload_settings() G.SOCK_SINGLE_READ = SOCK_SINGLE_READ # TODO: one day this can be removed (once all our users have updated) old_colab_dir = os.path.realpath(os.path.expanduser(os.path.join('~', '.floobits'))) if os.path.isdir(old_colab_dir) and not os.path.exists(G.BASE_DIR): print('Renaming %s to %s' % (old_colab_dir, G.BASE_DIR)) os.rename(old_colab_dir, G.BASE_DIR) os.symlink(G.BASE_DIR, old_colab_dir) try: utils.normalize_persistent_data() except Exception as e: print('Floobits: Error normalizing persistent data:', str_e(e)) # Keep on truckin' I guess d = utils.get_persistent_data() G.AUTO_GENERATED_ACCOUNT = d.get('auto_generated_account', False) setup()
def delete_workspace(self, context, cb): host = yield self._get_host, context if not host: cb() return api_url = 'https://%s/api/workspaces/can/admin' % (host) try: r = api.api_request(host, api_url) except IOError as e: editor.error_message('Error getting workspaces can admin %s' % str_e(e)) cb() return if r.code >= 400: editor.error_message('Error getting workspace list: %s' % r.body) cb() return choices = [ '%s/%s' % (workspace['owner'], workspace['name']) for workspace in r.body ] (workspace, index) = yield self.user_select, context, 'Select workpace to delete', choices, [] if not workspace: cb() return if G.EXPERT_MODE: yes = True else: yes = yield self.user_y_or_n, context, 'Really delete %s?' % workspace, 'Yes' if not yes: cb() return workspace = r.body[index] try: api.delete_workspace(host, workspace['owner'], workspace['name']) except IOError as e: editor.error_message('Error deleting workspace' % str_e(e)) cb()
def reconnect(self): try: api.get_workspace(self.host, 'Floobits', 'doesnotexist') except Exception as e: print(str_e(e)) editor.error_message('Something went wrong. See https://%s/help/floorc to complete the installation.' % self.host) else: editor.error_message(PORT_BLOCK_MSG % self.host) self.stop()
def delete_workspace(self, context, cb): host = yield self._get_host, context if not host: cb() return api_url = "https://%s/api/workspaces/can/admin" % (host) try: r = api.api_request(host, api_url) except IOError as e: editor.error_message("Error getting workspaces can admin %s" % str_e(e)) cb() return if r.code >= 400: editor.error_message("Error getting workspace list: %s" % r.body) cb() return choices = ["%s/%s" % (workspace["owner"], workspace["name"]) for workspace in r.body] (workspace, index) = yield self.user_select, context, "Select workpace to delete", choices, [] if not workspace: cb() return if G.EXPERT_MODE: yes = True else: yes = yield self.user_y_or_n, context, "Really delete %s?" % workspace, "Yes" if not yes: cb() return workspace = r.body[index] try: api.delete_workspace(host, workspace["owner"], workspace["name"]) except IOError as e: editor.error_message("Error deleting workspace" % str_e(e)) cb()
def delete_workspace(self, context, cb): host = yield self._get_host, context if not host: cb() return api_url = 'https://%s/api/workspaces/can/admin' % (host) try: r = api.api_request(host, api_url) except IOError as e: editor.error_message('Error getting workspaces can admin %s' % str_e(e)) cb() return if r.code >= 400: editor.error_message('Error getting workspace list: %s' % r.body) cb() return choices = ['%s/%s' % (workspace['owner'], workspace['name']) for workspace in r.body] (workspace, index) = yield self.user_select, context, 'Select workpace to delete', choices, [] if not workspace: cb() return if G.EXPERT_MODE: yes = True else: yes = yield self.user_y_or_n, context, 'Really delete %s?' % workspace, 'Yes' if not yes: cb() return workspace = r.body[index] try: api.delete_workspace(host, workspace['owner'], workspace['name']) except IOError as e: editor.error_message('Error deleting workspace' % str_e(e)) cb()
def get_empty_window(): for w in sublime.windows(): project_data = w.project_data() try: folders = project_data.get('folders', []) if len(folders) == 0 or not folders[0].get('path'): # no project data. co-opt this window return w except Exception as e: print(str_e(e))
def _scan_dir(self, bufs, ig, read_only): changed_bufs = [] missing_bufs = [] new_files = set() if not read_only: new_files = set([utils.to_rel_path(x) for x in ig.list_paths()]) for buf_id, buf in 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) return changed_bufs, missing_bufs, new_files
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 stop_handler(self, handler): try: handler.proto.stop() except Exception as e: msg.warn('Error stopping connection: %s' % str_e(e)) self._handlers.remove(handler) self._protos.remove(handler.proto) if hasattr(handler, 'listener_factory'): return handler.listener_factory.stop() if not self._handlers and not self._protos: msg.log('All handlers stopped. Stopping reactor.') self.stop()
def run(self): try: agent = G.AGENT url = utils.to_workspace_url({ 'port': agent.proto.port, 'secure': agent.proto.secure, 'owner': agent.owner, 'workspace': agent.workspace, 'host': agent.proto.host, }) webbrowser.open(url) except Exception as e: sublime.error_message('Unable to open workspace in web editor: %s' % str_e(e))
def create_or_link_account(self, context, host, force, cb): if host != "floobits.com": self.link_account(context, host, cb) return disable_account_creation = utils.get_persistent_data().get( 'disable_account_creation') if disable_account_creation and not force: print( 'We could not automatically create or link your floobits account. Please go to floobits.com and sign up to use this plugin.' ) return if not G.EXPERT_MODE: editor.message_dialog( 'Thank you for installing the Floobits plugin!\n\nLet\'s set up your editor to work with Floobits.' ) choices = [ 'Sign in to Floobits', 'Automatically create a Floobits account', 'Cancel (see https://floobits.com/help/floorc)' ] ( choice, index ) = yield self.user_select, context, 'You need an account to use Floobits! Do you want to:', choices, None if index == -1 or index == 2: d = utils.get_persistent_data() if not d.get('disable_account_creation'): d['disable_account_creation'] = True utils.update_persistent_data(d) # TODO: this instruction is only useful for Sublime Text editor.message_dialog( '''You can set up a Floobits account at any time under:\n\nTools -> Floobits -> Set up''' ) cb(None) return agent = None if index == 0: agent = credentials.RequestCredentialsHandler() else: agent = account.CreateAccountHandler() agent.once('end', cb) try: reactor.reactor.connect(agent, host, G.DEFAULT_PORT, True) except Exception as e: print(str_e(e))
def create_or_link_account(self, context, host, force, cb): if host != "floobits.com": self.link_account(context, host, cb) return disable_account_creation = utils.get_persistent_data().get("disable_account_creation") if disable_account_creation and not force: print( "We could not automatically create or link your floobits account. Please go to floobits.com and sign up to use this plugin." ) return if not G.EXPERT_MODE: editor.message_dialog( "Thank you for installing the Floobits plugin!\n\nLet's set up your editor to work with Floobits." ) choices = ["Sign in to Floobits", "Create a Floobits account", "Cancel (see https://floobits.com/help/floorc)"] (choice, index) = ( yield self.user_select, context, "You need an account to use Floobits! Do you want to:", choices, None, ) if index == -1 or index == 2: d = utils.get_persistent_data() if not d.get("disable_account_creation"): d["disable_account_creation"] = True utils.update_persistent_data(d) # TODO: this instruction is only useful for Sublime Text editor.message_dialog( """You can set up a Floobits account at any time under\n\nTools -> Floobits -> Set up""" ) cb(None) return agent = None if index == 0: agent = credentials.RequestCredentialsHandler() else: agent = account.CreateAccountHandler() agent.once("end", cb) try: reactor.reactor.connect(agent, host, G.DEFAULT_PORT, True) except Exception as e: print(str_e(e))
def reconnect(self): try: api.get_workspace(self.host, 'Floobits', 'doesnotexist') except Exception as e: print(str_e(e)) editor.error_message('Something went wrong. See https://%s/help/floorc to complete the installation.' % self.host) else: if G.OUTBOUND_FILTERING: editor.error_message('Something went wrong. See https://%s/help/floorc to complete the installation.' % self.host) return self.stop() if self.host == 'floobits.com': G.OUTBOUND_FILTERING = True return self.connect() editor.error_message(PORT_BLOCK_MSG % self.host) self.stop()
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 cb(index): if index == 0: token = binascii.b2a_hex(uuid.uuid4().bytes).decode('utf-8') agent = RequestCredentialsHandler(token) elif index == 1: agent = CreateAccountHandler() else: d = utils.get_persistent_data() if d.get('disable_account_creation'): return d['disable_account_creation'] = True utils.update_persistent_data(d) sublime.message_dialog('''You can set up a Floobits account at any time under\n\nTools -> Floobits -> Setup''') try: reactor.connect(agent, G.DEFAULT_HOST, G.DEFAULT_PORT, True) except Exception as e: print(str_e(e))
def _on_rename_buf(self, data): del self.paths_to_ids[data['old_path']] self.paths_to_ids[data['path']] = data['id'] new = utils.get_full_path(data['path']) old = utils.get_full_path(data['old_path']) new_dir = os.path.split(new)[0] if new_dir: utils.mkdir(new_dir) view = self.get_view(data['id']) if view: view.rename(new) else: try: os.rename(old, new) except Exception as e: msg.debug('Error moving ', old, 'to', new, str_e(e)) utils.save_buf(self.bufs[data.id]) self.bufs[data['id']]['path'] = data['path']
def link_account(self, context, host, cb): prompt = 'No credentials found in ~/.floorc.json for %s. Would you like to sign in? (opens a browser)' % host yes = yield self.user_y_or_n, context, prompt, 'Sign in' if not yes: return agent = credentials.RequestCredentialsHandler() if not agent: self.error_message('''A configuration error occured earlier. Please go to %s and sign up to use this plugin. We're really sorry. This should never happen.''' % host) return agent.once('end', cb) try: reactor.reactor.connect(agent, host, G.DEFAULT_PORT, True) except Exception as e: print(str_e(e))
def link_account(self, context, host, cb): prompt = 'No credentials found in ~/.floorc.json for %s. Would you like to sign in? (opens a browser)' % host yes = yield self.user_y_or_n, context, prompt, 'Sign in' if not yes: return agent = credentials.RequestCredentialsHandler() if not agent: self.error_message('''A configuration error occured earlier. Please go to %s and sign up to use this plugin.\n We're really sorry. This should never happen.''' % host) return agent.once('end', cb) try: reactor.reactor.connect(agent, host, G.DEFAULT_PORT, True) except Exception as e: print(str_e(e))
def stop_handler(self, handler): try: handler.proto.stop() except Exception as e: msg.warn('Error stopping connection: ', str_e(e)) try: self._handlers.remove(handler) except Exception: pass try: self._protos.remove(handler.proto) except Exception: pass if hasattr(handler, 'listener_factory'): return handler.listener_factory.stop() if not self._handlers and not self._protos: msg.log('All handlers stopped. Stopping reactor.') self.stop()
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 link_account(host, cb): account = sublime.ok_cancel_dialog('No credentials found in ~/.floorc.json for %s.\n\n' 'Click "Link Account" to open a web browser and add credentials.' % host, 'Link Account') if not account: return token = binascii.b2a_hex(uuid.uuid4().bytes).decode('utf-8') agent = RequestCredentialsHandler(token) if not agent: sublime.error_message('''A configuration error occured earlier. Please go to %s and sign up to use this plugin.\n We're really sorry. This should never happen.''' % host) return agent.once('end', cb) try: reactor.connect(agent, host, G.DEFAULT_PORT, True) except Exception as e: print(str_e(e))
def cb(index): if index == 0: token = binascii.b2a_hex(uuid.uuid4().bytes).decode('utf-8') agent = RequestCredentialsHandler(token) elif index == 1: agent = CreateAccountHandler() else: d = utils.get_persistent_data() if d.get('disable_account_creation'): return d['disable_account_creation'] = True utils.update_persistent_data(d) sublime.message_dialog( '''You can set up a Floobits account at any time under\n\nTools -> Floobits -> Setup''' ) try: reactor.connect(agent, G.DEFAULT_HOST, G.DEFAULT_PORT, True) except Exception as e: print(str_e(e))
def reconnect(self): try: api.get_workspace(self.host, 'Floobits', 'doesnotexist') except Exception as e: print(str_e(e)) editor.error_message( 'Something went wrong. See https://%s/help/floorc to complete the installation.' % self.host) else: if G.OUTBOUND_FILTERING: editor.error_message( 'Something went wrong. See https://%s/help/floorc to complete the installation.' % self.host) return self.stop() if self.host == 'floobits.com': G.OUTBOUND_FILTERING = True return self.connect() editor.error_message(PORT_BLOCK_MSG % self.host) self.stop()
def remote_connect(self, context, host, owner, workspace, d, join_action=utils.JOIN_ACTION.PROMPT): G.PROJECT_PATH = os.path.realpath(d) try: utils.mkdir(os.path.dirname(G.PROJECT_PATH)) except Exception as e: msg.error("Couldn't create directory", G.PROJECT_PATH, str_e(e)) return auth = G.AUTH.get(host) if not auth: success = yield self.link_account, context, host if not success: return auth = G.AUTH.get(host) if not auth: msg.error("Something went really wrong.") return try: res = api.get_workspace(host, owner, workspace) if res.code == 404: msg.error("The workspace https://%s/%s/%s does not exist" % (host, owner, workspace)) return except Exception as e: message = 'Error getting workspace https://%s/%s/%s: %s' % (host, owner, workspace, str_e(e)) msg.error(message) editor.error_message(message) return if self.agent: try: self.agent.stop() except Exception: pass G.WORKSPACE_WINDOW = yield self.get_a_window, d self.agent = self._make_agent(context, owner, workspace, auth, join_action) self.emit("agent", self.agent) reactor.reactor.connect(self.agent, host, G.DEFAULT_PORT, True) url = self.agent.workspace_url utils.add_workspace_to_persistent_json(owner, workspace, url, d) utils.update_recent_workspaces(url)