Example #1
0
 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()
Example #3
0
    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))
Example #5
0
    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()
Example #6
0
    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
Example #7
0
    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
Example #8
0
 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
Example #10
0
 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
Example #12
0
    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
Example #13
0
    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)
Example #15
0
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)
Example #17
0
    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
            }
        })
Example #19
0
    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()
Example #20
0
    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))
Example #22
0
    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
Example #23
0
    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
Example #25
0
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())
Example #26
0
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
Example #28
0
    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()
Example #29
0
    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))
Example #30
0
 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)))
Example #31
0
    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]))
Example #32
0
    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)
Example #33
0
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()
Example #34
0
    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
Example #35
0
    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")
Example #36
0
    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)
Example #37
0
    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)
Example #38
0
    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
                }
            })
Example #39
0
    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'])
Example #40
0
    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")
Example #41
0
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)
Example #42
0
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)