def stop(self):
     self.retries = -1
     utils.cancel_timeout(self.reconnect_timeout)
     self.reconnect_timeout = None
     self.cleanup()
     msg.log('Disconnected.')
     sublime.status_message('Disconnected.')
 def connect(self, cb=None):
     self.stop(False)
     self.empty_selects = 0
     self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     if self.secure:
         if ssl:
             cert_path = os.path.join(G.COLAB_DIR, "startssl-ca.pem")
             with open(cert_path, "wb") as cert_fd:
                 cert_fd.write(cert.CA_CERT.encode("utf-8"))
             self.sock = ssl.wrap_socket(self.sock, ca_certs=cert_path, cert_reqs=ssl.CERT_REQUIRED)
         else:
             msg.log("No SSL module found. Connection will not be encrypted.")
             if self.port == G.DEFAULT_PORT:
                 self.port = 3148  # plaintext port
     msg.debug("Connecting to %s:%s" % (self.host, self.port))
     try:
         self.sock.connect((self.host, self.port))
         if self.secure and ssl:
             self.sock.do_handshake()
     except socket.error as e:
         msg.error("Error connecting:", e)
         self.reconnect()
         return
     self.sock.setblocking(0)
     msg.debug("Connected!")
     self.reconnect_delay = self.INITIAL_RECONNECT_DELAY
     self.send_auth()
     if cb:
         cb()
 def delete_buf(self, path, unlink=False):
     if not utils.is_shared(path):
         msg.error('Skipping deleting ', path, ' because it is not in shared path ', G.PROJECT_PATH, '.')
         return
     if os.path.isdir(path):
         for dirpath, dirnames, filenames in os.walk(path):
             # TODO: rexamine this assumption
             # Don't care about hidden stuff
             dirnames[:] = [d for d in dirnames if d[0] != '.']
             for f in filenames:
                 f_path = os.path.join(dirpath, f)
                 if f[0] == '.':
                     msg.log('Not deleting buf for hidden file ', f_path)
                 else:
                     self.delete_buf(f_path, unlink)
         return
     buf_to_delete = self.get_buf_by_path(path)
     if buf_to_delete is None:
         msg.error(path, ' is not in this workspace')
         return
     msg.log('deleting buffer ', utils.to_rel_path(path))
     event = {
         'name': 'delete_buf',
         'id': buf_to_delete['id'],
         'unlink': unlink,
     }
     self.send(event)
示例#4
0
 def delete_buf(path):
     if not utils.is_shared(path):
         msg.error('Skipping deleting %s because it is not in shared path %s.' % (path, G.PROJECT_PATH))
         return
     if os.path.isdir(path):
         for dirpath, dirnames, filenames in os.walk(path):
             # TODO: rexamine this assumption
             # Don't care about hidden stuff
             dirnames[:] = [d for d in dirnames if d[0] != '.']
             for f in filenames:
                 f_path = os.path.join(dirpath, f)
                 if f[0] == '.':
                     msg.log('Not deleting buf for hidden file %s' % f_path)
                 else:
                     Listener.delete_buf(f_path)
         return
     buf_to_delete = get_buf_by_path(path)
     if buf_to_delete is None:
         msg.error('%s is not in this workspace' % path)
         return
     msg.log('deleting buffer ', utils.to_rel_path(path))
     event = {
         'name': 'delete_buf',
         'id': buf_to_delete['id'],
     }
     G.AGENT.put(event)
def conn_log(action, item):
    try:
        item = item.decode('utf-8')
    except Exception:
        pass
    msg.log('%s: %s' % (action, item))
    sys.stdout.flush()
示例#6
0
 def on_highlight(self, data):
     #     floobits.highlight(data['id'], region_key, data['username'], data['ranges'], data.get('ping', False))
     #buf_id, region_key, username, ranges, ping=False):
     ping = data.get('ping', False)
     if self.follow_mode:
         ping = True
     buf = self.FLOO_BUFS[data['id']]
     view = self.get_view(data['id'])
     if not view:
         if not ping:
             return
         view = self.create_view(buf)
         if not view:
             return
     if ping:
         try:
             offset = data['ranges'][0][0]
         except IndexError as e:
             msg.debug('could not get offset from range %s' % e)
         else:
             msg.log('You have been summoned by %s' % (data.get('username', 'an unknown user')))
             view.focus()
             view.set_cursor_position(offset)
     if G.SHOW_HIGHLIGHTS:
         view.highlight(data['ranges'], data['user_id'])
示例#7
0
    def delete_buf(self, path):
        """deletes a path"""

        if not path:
            return

        path = utils.get_full_path(path)

        if not self.is_shared(path):
            msg.error('Skipping deleting %s because it is not in shared path %s.' % (path, G.PROJECT_PATH))
            return

        if os.path.isdir(path):
            for dirpath, dirnames, filenames in os.walk(path):
                # Don't care about hidden stuff
                dirnames[:] = [d for d in dirnames if d[0] != '.']
                for f in filenames:
                    f_path = os.path.join(dirpath, f)
                    if f[0] == '.':
                        msg.log('Not deleting buf for hidden file %s' % f_path)
                    else:
                        self.delete_buf(f_path)
            return
        buf_to_delete = None
        rel_path = utils.to_rel_path(path)
        buf_to_delete = self.get_buf_by_path(rel_path)
        if buf_to_delete is None:
            msg.error('%s is not in this workspace' % path)
            return
        msg.log('deleting buffer ', rel_path)
        event = {
            'name': 'delete_buf',
            'id': buf_to_delete['id'],
        }
        self.agent.put(event)
示例#8
0
 def _on_delete_buf(self, req):
     buf = self.get_buf_by_path(req["path"])
     if not buf:
         msg.debug("No buffer for path %s" % req["path"])
         return
     msg.log("deleting buffer ", buf["path"])
     self.send_to_floobits({"name": "delete_buf", "id": buf["id"]})
示例#9
0
 def _on_highlight(self, data):
     buf_id = data['id']
     user_id = data['user_id']
     username = data.get('username', 'an unknown user')
     ping = G.STALKER_MODE or data.get('ping', False)
     previous_highlight = self.user_highlights.get(user_id)
     buf = self.bufs[buf_id]
     view = self.get_view(buf_id)
     if not view:
         if not ping:
             return
         view = self.create_view(buf)
         if not view:
             return
     data['path'] = buf['path']
     self.user_highlights[user_id] = data
     if ping:
         try:
             offset = data['ranges'][0][0]
         except IndexError as e:
             msg.debug('could not get offset from range %s' % e)
         else:
             if data.get('ping'):
                 msg.log('You have been summoned by %s' % (username))
             view.focus()
             view.set_cursor_position(offset)
     if G.SHOW_HIGHLIGHTS:
         if previous_highlight and previous_highlight['id'] == data['id']:
             view.clear_highlight(user_id)
         view.highlight(data['ranges'], user_id)
示例#10
0
def conn_log(action, item):
    try:
        item = item.decode('utf-8')
    except Exception:
        pass
    if G.SOCK_DEBUG:
        msg.log(action, ': ', item)
    sys.stdout.flush()
示例#11
0
 def toggle_highlights(self):
     G.SHOW_HIGHLIGHTS = not G.SHOW_HIGHLIGHTS
     if G.SHOW_HIGHLIGHTS:
         self.buf_enter()
         msg.log('Highlights enabled')
         return
     self.clear()
     msg.log('Highlights disabled')
示例#12
0
 def complete_signup(self):
     if not self.start_ticker():
         return
     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
     VUI.pinocchio()
示例#13
0
 def on_auth(self):
     self.authed = True
     G.JOINED_WORKSPACE = True
     self.retries = self.MAX_RETRIES
     msg.log('Successfully joined workspace %s/%s' % (self.owner, self.workspace))
     if self._on_auth:
         self._on_auth(self)
         self._on_auth = None
示例#14
0
def check_credentials():
    msg.debug('Print checking credentials.')
    if utils.can_auth():
        return
    if not utils.has_browser():
        msg.log('You need a Floobits account to use the Floobits plugin. Go to https://floobits.com to sign up.')
        return
    yield VUI.create_or_link_account, None, G.DEFAULT_HOST, False
示例#15
0
 def update_view(self, buf, view=None):
     msg.debug('updating view for buf %s' % buf['id'])
     view = view or self.get_view(buf['id'])
     if not view:
         msg.log('view for buf %s not found. not updating' % buf['id'])
         return
     self.MODIFIED_EVENTS.put(1)
     view.set_text(buf['buf'])
示例#16
0
    def save(self):
        # TODO: switch to the correct buffer, then save, then switch back (or use writefile)
        if vim.current.buffer.name != self.vim_buf.name:
            return

        try:
            vim.command('silent w!')
        except Exception as e:
            msg.log('Error saving %s: %s' % (self.vim_buf.name, str(e)))
示例#17
0
 def on_delete_buf(self, data):
     # TODO: somehow tell the user about this. maybe delete on disk too?
     del self.FLOO_BUFS[data['id']]
     path = utils.get_full_path(data['path'])
     if not G.DELETE_LOCAL_FILES:
         msg.log('Not deleting %s because delete_local_files is disabled' % path)
         return
     utils.rm(path)
     msg.warn('deleted %s because %s told me to.' % (path, data.get('username', 'the internet')))
示例#18
0
    def info(self):
        kwargs = {
            'cs': bool(int(vim.eval('has("clientserver")'))),
            'servername': vim.eval('v:servername'),
            'updatetime': vim.eval('&l:updatetime'),
            'version': G.__PLUGIN_VERSION__,
        }

        msg.log(FLOOBITS_INFO.format(**kwargs))
示例#19
0
 def _on_delete_buf(self, req):
     buf = self.get_buf_by_path(req['path'])
     if not buf:
         msg.debug('No buffer for path %s' % req['path'])
         return
     msg.log('deleting buffer ', buf['path'])
     self.send_to_floobits({
         'name': 'delete_buf',
         'id': buf['id'],
     })
示例#20
0
 def on_window_command(self, window, command, *args, **kwargs):
     if command == 'rename_path':
         # User is about to rename something
         msg.debug('rename')
     if window == G.WORKSPACE_WINDOW and command == 'close_window':
         msg.log('Workspace window closed, disconnecting.')
         try:
             window.run_command('floobits_leave_workspace')
         except Exception as e:
             msg.error(e)
示例#21
0
 def log_users(self):
     clients = []
     try:
         clients = ['%s on %s' % (x.get('username'), x.get('client')) for x in self.workspace_info['users'].values()]
     except Exception as e:
         print(e)
     msg.log(len(clients), ' connected clients:')
     clients.sort()
     for client in clients:
         msg.log(client)
示例#22
0
    def highlight(self, data=None, user=None):
        if user:
            data = self.last_highlight_by_user.get(user)
        elif not data:
            data = data or self.last_highlight

        if not data:
            msg.log('No recent highlight to replay.')
            return

        self._on_highlight(data)
示例#23
0
 def on_emacs_delete_buf(self, req):
     buf = self.get_buf_by_path(req['path'])
     if not buf:
         msg.debug('No buffer for path %s' % req['path'])
         return
     msg.log('deleting buffer ', buf['path'])
     event = {
         'name': 'delete_buf',
         'id': buf['id'],
     }
     self.agent.put(event)
    def reconnect(self):
        if self.reconnect_timeout:
            return
        self.cleanup()
        self.reconnect_delay = min(10000, int(1.5 * self.reconnect_delay))

        if self.retries > 0:
            msg.log('Floobits: Reconnecting in %sms' % self.reconnect_delay)
            self.reconnect_timeout = utils.set_timeout(self.connect, self.reconnect_delay)
        elif self.retries == 0:
            sublime.error_message('Floobits Error! Too many reconnect failures. Giving up.')
        self.retries -= 1
示例#25
0
    def upload(path):
        try:
            with open(path, 'rb') as buf_fd:
                buf = buf_fd.read()
            encoding = 'utf8'
            rel_path = utils.to_rel_path(path)
            existing_buf = get_buf_by_path(path)
            if existing_buf:
                buf_md5 = hashlib.md5(buf).hexdigest()
                if existing_buf['md5'] == buf_md5:
                    msg.debug('%s already exists and has the same md5. Skipping.' % path)
                    return
                msg.log('setting buffer ', rel_path)

                existing_buf['buf'] = buf
                existing_buf['md5'] = buf_md5

                try:
                    buf = buf.decode('utf-8')
                except Exception:
                    buf = base64.b64encode(buf).decode('utf-8')
                    encoding = 'base64'

                existing_buf['encoding'] = encoding

                G.AGENT.put({
                    'name': 'set_buf',
                    'id': existing_buf['id'],
                    'buf': buf,
                    'md5': buf_md5,
                    'encoding': encoding,
                })
                return

            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,
            }
            G.AGENT.put(event)
        except (IOError, OSError):
            msg.error('Failed to open %s.' % path)
        except Exception as e:
            msg.error('Failed to create buffer %s: %s' % (path, unicode(e)))
示例#26
0
 def update_view(buf, view):
     msg.log('Floobits synced data for consistency: %s' % buf['path'])
     G.VIEW_TO_HASH[view.buffer_id()] = buf['md5']
     view.set_read_only(False)
     try:
         view.run_command('floo_view_replace_region', {'r': [0, view.size()], 'data': buf['buf']})
         view.set_status('Floobits', 'Floobits synced data for consistency.')
         utils.set_timeout(lambda: view.set_status('Floobits', ''), 5000)
     except Exception as e:
         msg.error('Exception updating view: %s' % e)
     if 'patch' not in G.PERMS:
         view.set_status('Floobits', 'You don\'t have write permission. Buffer is read-only.')
         view.set_read_only(True)
示例#27
0
 def rename_buf(self, buf_id, new_path):
     new_path = utils.to_rel_path(new_path)
     if not utils.is_shared(new_path):
         msg.log('New path %s is not shared. Discarding rename event.' % new_path)
         return
     self.agent.put({
         'name': 'rename_buf',
         'id': buf_id,
         'path': new_path,
     })
     old_path = self.FLOO_BUFS[buf_id]['path']
     del self.FLOO_PATHS_TO_BUFS[old_path]
     self.FLOO_PATHS_TO_BUFS[new_path] = buf_id
     self.FLOO_BUFS[buf_id]['path'] = new_path
示例#28
0
 def stop(self, log=True):
     if log:
         msg.log('Disconnecting from workspace %s/%s' % (self.owner, self.workspace))
     utils.cancel_timeout(self.reconnect_timeout)
     self.reconnect_timeout = None
     try:
         self.retries = -1
         self.sock.shutdown(2)
         self.sock.close()
     except Exception:
         return False
     if log:
         msg.log('Disconnected.')
     return True
示例#29
0
 def update(self, data):
     buf = self.buf = data
     msg.log('Floobits synced data for consistency: %s' % buf['path'])
     G.VIEW_TO_HASH[self.view.buffer_id()] = buf['md5']
     self.view.set_read_only(False)
     try:
         self.view.run_command('floo_view_replace_region', {'r': [0, self.view.size()], 'data': buf['buf']})
         self.set_status('Floobits synced data for consistency.')
         utils.set_timeout(self.set_status, 5000, '')
     except Exception as e:
         msg.error('Exception updating view: %s' % e)
     if 'patch' not in G.PERMS:
         self.set_status('You don\'t have write permission. Buffer is read-only.')
         self.view.set_read_only(True)
示例#30
0
    def _on_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)

        try:
            api_args = {
                'name': workspace_name,
                'owner': owner,
            }
            if perms:
                api_args['perms'] = perms
            msg.debug(str(api_args))
            r = api.create_workspace(api_args)
        except Exception as e:
            msg.error('Unable to create workspace: %s' % unicode(e))
            return editor.error_message('Unable to create workspace: %s' % unicode(e))

        workspace_url = 'https://%s/%s/%s' % (G.DEFAULT_HOST, owner, workspace_name)

        if r.code < 400:
            msg.log('Created workspace %s' % workspace_url)
            utils.add_workspace_to_persistent_json(owner, workspace_name, workspace_url, dir_to_share)
            G.PROJECT_PATH = dir_to_share
            agent = self.remote_connect(owner, workspace_name, False)
            return agent.once("room_info", lambda: agent.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 editor.error_message('Unable to create workspace: %s' % r.body)

        if r.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 r.code == 402:
            try:
                r.body = r.body['detail']
            except Exception:
                pass
            cb = lambda data: data['response'] and webbrowser.open('https://%s/%s/settings#billing' % (G.DEFAULT_HOST, owner))
            self.get_input('%s Open billing settings?' % r.body, '', cb, y_or_n=True)
            return
        else:
            prompt = 'Workspace %s/%s already exists. Choose another name:' % (owner, workspace_name)

        return self.get_input(prompt, workspace_name, self._on_create_workspace, workspace_name, dir_to_share, owner, perms)
示例#31
0
 def send_msg(self, text):
     self.send({'name': 'msg', 'data': text})
     timestamp = time.time()
     msgText = self.format_msg(text, self.username, timestamp)
     msg.log(msgText)
     self.chat_deck.appendleft(msgText)
示例#32
0
 def tick(self):
     try:
         self.ticker()
     except Exception as e:
         msg.log("Event loop tick error: %s" % e)
示例#33
0
 def on_part(self, data):
     msg.log('%s left the workspace' % data['username'])
     region_key = 'floobits-highlight-%s' % (data['user_id'])
     for window in sublime.windows():
         for view in window.views():
             view.erase_regions(region_key)
示例#34
0
 def stop(self):
     self.retries = -1
     utils.cancel_timeout(self.reconnect_timeout)
     self.reconnect_timeout = None
     self.cleanup()
     msg.log('Disconnected.')
示例#35
0
    def stomp_prompt(self, changed_bufs, missing_bufs, new_files, ignored, cb):
        if not G.EXPERT_MODE:
            editor.message_dialog(
                'Your copy of %s/%s is out of sync. '
                'You will be prompted after you close this dialog.' %
                (self.owner, self.workspace))

        def pluralize(arg):
            return arg != 1 and 's' or ''

        overwrite_local = ''
        overwrite_remote = ''
        missing = [buf['path'] for buf in missing_bufs]
        changed = [buf['path'] for buf in changed_bufs]

        to_upload = set(new_files + changed).difference(set(ignored))
        to_remove = missing + ignored
        to_fetch = changed + missing
        to_upload_len = len(to_upload)
        to_remove_len = len(to_remove)
        remote_len = to_remove_len + to_upload_len
        to_fetch_len = len(to_fetch)

        msg.log('To fetch: %s' % ', '.join(to_fetch))
        msg.log('To upload: %s' % ', '.join(to_upload))
        msg.log('To remove: %s' % ', '.join(to_remove))

        if not to_fetch:
            overwrite_local = 'Fetch nothing'
        elif to_fetch_len < 5:
            overwrite_local = 'Fetch %s' % ', '.join(to_fetch)
        else:
            overwrite_local = 'Fetch %s file%s' % (to_fetch_len,
                                                   pluralize(to_fetch_len))

        if to_upload_len < 5:
            to_upload_str = 'upload %s' % ', '.join(to_upload)
        else:
            to_upload_str = 'upload %s' % to_upload_len

        if to_remove_len < 5:
            to_remove_str = 'remove %s' % ', '.join(to_remove)
        else:
            to_remove_str = 'remove %s' % to_remove_len

        if to_upload:
            overwrite_remote += to_upload_str
            if to_remove:
                overwrite_remote += ' and '
        if to_remove:
            overwrite_remote += to_remove_str

        if remote_len >= 5 and overwrite_remote:
            overwrite_remote += ' files'

        overwrite_remote = overwrite_remote.capitalize()

        action = 'Overwrite'
        # TODO: change action based on numbers of stuff
        opts = [
            [
                '%s %s remote file%s.' %
                (action, remote_len, pluralize(remote_len)), overwrite_remote
            ],
            [
                '%s %s local file%s.' %
                (action, to_fetch_len, pluralize(to_fetch_len)),
                overwrite_local
            ],
            ['Cancel', 'Disconnect and resolve conflict manually.'],
        ]
        # TODO: sublime text doesn't let us focus a window. so use the active window. super lame
        # G.WORKSPACE_WINDOW.show_quick_panel(opts, cb)
        w = sublime.active_window() or G.WORKSPACE_WINDOW
        w.show_quick_panel(opts, cb)
    def _on_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)

        try:
            api_args = {
                'name': workspace_name,
                'owner': owner,
            }
            if perms:
                api_args['perms'] = perms
            msg.debug(str(api_args))
            r = api.create_workspace(api_args)
        except Exception as e:
            msg.error('Unable to create workspace: %s' % unicode(e))
            return editor.error_message('Unable to create workspace: %s' %
                                        unicode(e))

        workspace_url = 'https://%s/%s/%s' % (G.DEFAULT_HOST, owner,
                                              workspace_name)

        if r.code < 400:
            msg.log('Created workspace %s' % workspace_url)
            utils.add_workspace_to_persistent_json(owner, workspace_name,
                                                   workspace_url, dir_to_share)
            G.PROJECT_PATH = dir_to_share
            agent = self.remote_connect(owner, workspace_name, False)
            return agent.once("room_info", lambda: agent.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 editor.error_message('Unable to create workspace: %s' %
                                        r.body)

        if r.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 r.code == 402:
            try:
                r.body = r.body['detail']
            except Exception:
                pass
            cb = lambda data: data['response'] and webbrowser.open(
                'https://%s/%s/settings#billing' % (G.DEFAULT_HOST, owner))
            self.get_input('%s Open billing settings?' % r.body,
                           '',
                           cb,
                           y_or_n=True)
            return
        else:
            prompt = 'Workspace %s/%s already exists. Choose another name:' % (
                owner, workspace_name)

        return self.get_input(prompt, workspace_name,
                              self._on_create_workspace, workspace_name,
                              dir_to_share, owner, perms)
 def _on_set_follow_mode(self, req):
     msg.log('follow mode is %s' %
             ((req.get('follow_mode') and 'enabled') or 'disabled'))
 def on_connect(self):
     msg.log("have an emacs!")
示例#39
0
    def stomp_prompt(self, changed_bufs, missing_bufs, new_files, ignored, cb):
        if not (G.EXPERT_MODE or hasattr(sublime, 'KEEP_OPEN_ON_FOCUS_LOST')):
            editor.message_dialog(
                'Your copy of %s/%s is out of sync. '
                'You will be prompted after you close this dialog.' %
                (self.owner, self.workspace))

        def pluralize(arg):
            return arg != 1 and 's' or ''

        overwrite_local = ''
        overwrite_remote = ''
        missing = [buf['path'] for buf in missing_bufs]
        changed = [buf['path'] for buf in changed_bufs]

        to_remove = set(missing + ignored)
        to_upload = set(new_files + changed).difference(to_remove)
        to_fetch = changed + missing
        to_upload_len = len(to_upload)
        to_remove_len = len(to_remove)
        remote_len = to_remove_len + to_upload_len
        to_fetch_len = len(to_fetch)

        msg.log('To fetch: ', ', '.join(to_fetch))
        msg.log('To upload: ', ', '.join(to_upload))
        msg.log('To remove: ', ', '.join(to_remove))

        if not to_fetch:
            overwrite_local = 'Fetch nothing'
        elif to_fetch_len < 5:
            overwrite_local = 'Fetch %s' % ', '.join(to_fetch)
        else:
            overwrite_local = 'Fetch %s file%s' % (to_fetch_len,
                                                   pluralize(to_fetch_len))

        if to_upload_len < 5:
            to_upload_str = 'Upload %s' % ', '.join(to_upload)
        else:
            to_upload_str = 'Upload %s' % to_upload_len

        if to_remove_len < 5:
            to_remove_str = 'remove %s' % ', '.join(to_remove)
        else:
            to_remove_str = 'remove %s' % to_remove_len

        if to_upload:
            overwrite_remote += to_upload_str
            if to_remove:
                overwrite_remote += ' and '
        if to_remove:
            overwrite_remote += to_remove_str

        if remote_len >= 5 and overwrite_remote:
            overwrite_remote += ' files'

        # Be fancy and capitalize "remove" if it's the first thing in the string
        if len(overwrite_remote) > 0:
            overwrite_remote = overwrite_remote[0].upper(
            ) + overwrite_remote[1:]

        connected_users_msg = ''

        def filter_user(u):
            if u.get('is_anon'):
                return False
            if 'patch' not in u.get('perms'):
                return False
            if u.get('username') == self.username:
                return False
            return True

        users = set([
            v['username'] for k, v in self.workspace_info['users'].items()
            if filter_user(v)
        ])
        if users:
            if len(users) < 4:
                connected_users_msg = ' Connected: ' + ', '.join(users)
            else:
                connected_users_msg = ' %s users connected' % len(users)

        # TODO: change action based on numbers of stuff
        action = 'Overwrite'
        opts = [
            [
                '%s %s remote file%s.' %
                (action, remote_len, pluralize(remote_len)), overwrite_remote
            ],
            [
                '%s %s local file%s.' %
                (action, to_fetch_len, pluralize(to_fetch_len)),
                overwrite_local
            ],
            ['Cancel', 'Disconnect.' + connected_users_msg],
        ]

        w = sublime.active_window() or G.WORKSPACE_WINDOW
        flags = 0
        if hasattr(sublime, 'KEEP_OPEN_ON_FOCUS_LOST'):
            flags |= sublime.KEEP_OPEN_ON_FOCUS_LOST
        w.show_quick_panel(opts, cb, flags)
示例#40
0
 def on_msg(self, data):
     timestamp = data.get('time') or time.time()
     msg.log('[%s] <%s> %s' %
             (time.ctime(timestamp), data.get('username',
                                              ''), data.get('data', '')))
示例#41
0
 def on_msg(self, data):
     timestamp = data.get('time') or time.time()
     msgText = self.format_msg(data.get('data', ''),
                               data.get('username', ''), timestamp)
     msg.log(msgText)
     self.chat_deck.appendleft(msgText)
示例#42
0
 def stop(self):
     stop_msg = 'Disconnecting from workspace %s/%s' % (self.owner,
                                                        self.workspace)
     msg.log(stop_msg)
     sublime.status_message(stop_msg)
     super(AgentConnection, self).stop()
示例#43
0
 def on_connect(self):
     msg.log('Remote connection estabished.')
     eventStream.emit('remote_conn')
示例#44
0
 def run(self):
     msg.log("Starting event loop.")
     while True:
         sleep(0.1)
         self.vim.session.threadsafe_call(self.tick)
示例#45
0
    def apply_patch(patch_data):
        if not G.AGENT:
            msg.debug('Not connected. Discarding view change.')
            return
        buf_id = patch_data['id']
        buf = BUFS[buf_id]
        if 'buf' not in buf:
            msg.debug('buf %s not populated yet. not patching' % buf['path'])
            return
        if buf['encoding'] == 'base64':
            # TODO apply binary patches
            return Listener.get_buf(buf_id, None)

        view = get_view(buf_id)
        if len(patch_data['patch']) == 0:
            msg.error('wtf? no patches to apply. server is being stupid')
            return
        msg.debug('patch is', patch_data['patch'])
        dmp_patches = DMP.patch_fromText(patch_data['patch'])
        # TODO: run this in a separate thread
        old_text = buf['buf']

        if view and not view.is_loading():
            view_text = get_text(view)
            if old_text == view_text:
                buf['forced_patch'] = False
            elif not buf.get('forced_patch'):
                patch = utils.FlooPatch(get_text(view), 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 %s' % buf['path'])
                G.AGENT.put(patch.to_json())
                old_text = view_text
            else:
                msg.debug(
                    'forced patch is true. not sending another patch for buf %s'
                    % buf['path'])
        md5_before = hashlib.md5(old_text.encode('utf-8')).hexdigest()
        if md5_before != patch_data['md5_before']:
            msg.warn('starting md5s don\'t match for %s. this is dangerous!' %
                     buf['path'])

        t = DMP.patch_apply(dmp_patches, old_text)

        clean_patch = True
        for applied_patch in t[1]:
            if not applied_patch:
                clean_patch = False
                break

        if G.DEBUG:
            if len(t[0]) == 0:
                try:
                    msg.debug('OMG EMPTY!')
                    msg.debug('Starting data:', buf['buf'])
                    msg.debug('Patch:', patch_data['patch'])
                except Exception as e:
                    print(e)

            if '\x01' in t[0]:
                msg.debug('FOUND CRAZY BYTE IN BUFFER')
                msg.debug('Starting data:', buf['buf'])
                msg.debug('Patch:', patch_data['patch'])

        timeout_id = buf.get('timeout_id')
        if timeout_id:
            utils.cancel_timeout(timeout_id)

        if not clean_patch:
            msg.log('Couldn\'t patch %s cleanly.' % buf['path'])
            return Listener.get_buf(buf_id, view)

        cur_hash = hashlib.md5(t[0].encode('utf-8')).hexdigest()
        if cur_hash != patch_data['md5_after']:
            buf['timeout_id'] = utils.set_timeout(Listener.get_buf, 2000,
                                                  buf_id, view)

        buf['buf'] = t[0]
        buf['md5'] = cur_hash

        if not view:
            save_buf(buf)
            return

        regions = []
        commands = []
        for patch in t[2]:
            offset = patch[0]
            length = patch[1]
            patch_text = patch[2]
            region = sublime.Region(offset, offset + length)
            regions.append(region)
            commands.append({
                'r': [offset, offset + length],
                'data': patch_text
            })

        view.run_command('floo_view_replace_regions', {'commands': commands})
        region_key = 'floobits-patch-' + patch_data['username']
        view.add_regions(region_key, regions, 'floobits.patch', 'circle',
                         sublime.DRAW_OUTLINED)
        utils.set_timeout(view.erase_regions, 2000, region_key)

        view.set_status(
            'Floobits', 'Changed by %s at %s' %
            (patch_data['username'], datetime.now().strftime('%H:%M')))
示例#46
0
 def highlight(self, data=None):
     data = data or self.last_highlight
     if not data:
         msg.log('No recent highlight to replay.')
         return
     self._on_highlight(data)
示例#47
0
 def follow(self, follow_mode=None):
     if follow_mode is None:
         follow_mode = not self.follow_mode
     self.follow_mode = follow_mode
     msg.log('follow mode is %s' % {True: 'enabled', False: 'disabled'}[self.follow_mode])
示例#48
0
 def stop(self):
     msg.log('Disconnecting from workspace %s/%s' %
             (self.owner, self.workspace))
     super(AgentConnection, self).stop()
示例#49
0
    def on_post_save(self, view, agent):
        view_buf_id = view.buffer_id()

        def cleanup():
            i = self.between_save_events[view_buf_id]
            i[0] -= 1

        if view.is_scratch():
            return

        i = self.between_save_events[view_buf_id]
        if agent.ignored_saves[view_buf_id] > 0:
            agent.ignored_saves[view_buf_id] -= 1
            return cleanup()
        old_name = i[1]

        i = self.between_save_events[view_buf_id]
        if i[0] > 1:
            return cleanup()
        old_name = i[1]

        event = None
        buf = get_buf(view)
        try:
            name = utils.to_rel_path(view.file_name())
        except ValueError:
            name = view.file_name()
        is_shared = utils.is_shared(view.file_name())

        if buf is None:
            if not is_shared:
                return cleanup()
            if G.IGNORE and G.IGNORE.is_ignored(view.file_name(), log=True):
                msg.log(view.file_name(), ' is ignored. Not creating buffer.')
                return cleanup()
            msg.log('Creating new buffer ', name, view.file_name())
            event = {
                'name': 'create_buf',
                'buf': get_text(view),
                'path': name
            }
        elif name != old_name:
            if is_shared:
                msg.log('renamed buffer ', old_name, ' to ', name)
                event = {
                    'name': 'rename_buf',
                    'id': buf['id'],
                    'path': name
                }
            else:
                msg.log('deleting buffer from shared: ', name)
                event = {
                    'name': 'delete_buf',
                    'id': buf['id'],
                }

        if event:
            agent.send(event)
        if is_shared and buf:
            agent.views_changed.append(('saved', view, buf))

        cleanup()
示例#50
0
def sock_debug(*args, **kwargs):
    if G.SOCK_DEBUG:
        msg.log(*args, **kwargs)
示例#51
0
 def _on_set_follow_mode(self, req):
     G.FOLLOW_USERS = set()
     msg.log('follow mode is %s' % ((req.get('follow_mode') and 'enabled') or 'disabled'))
示例#52
0
 def create_buf(self, path, ig=None, force=False):
     if G.SPARSE_MODE and not force:
         msg.debug("Skipping %s because user enabled sparse mode." % path)
         return
     if not utils.is_shared(path):
         msg.error('Skipping adding %s because it is not in shared path %s.' % (path, G.PROJECT_PATH))
         return
     if os.path.islink(path):
         msg.error('Skipping adding %s because it is a symlink.' % path)
         return
     ignored = ig and ig.is_ignored(path)
     if ignored:
         msg.log('Not creating buf: %s' % (ignored))
         return
     msg.debug('create_buf: path is %s' % path)
     if os.path.isdir(path):
         if ig is None:
             try:
                 ig = ignore.build_ignores(path)
             except Exception as e:
                 msg.error('Error adding %s: %s' % (path, unicode(e)))
                 return
         try:
             paths = os.listdir(path)
         except Exception as e:
             msg.error('Error listing path %s: %s' % (path, unicode(e)))
             return
         for p in paths:
             p_path = os.path.join(path, p)
             if p[0] == '.':
                 if p not in ignore.HIDDEN_WHITELIST:
                     msg.log('Not creating buf for hidden path %s' % p_path)
                     continue
             ignored = ig.is_ignored(p_path)
             if ignored:
                 msg.log('Not creating buf: %s' % (ignored))
                 continue
             try:
                 s = os.lstat(p_path)
             except Exception as e:
                 msg.error('Error lstat()ing path %s: %s' % (path, unicode(e)))
                 continue
             if stat.S_ISDIR(s.st_mode):
                 child_ig = ignore.Ignore(ig, p_path)
                 utils.set_timeout(self.create_buf, 0, p_path, child_ig)
             elif stat.S_ISREG(s.st_mode):
                 utils.set_timeout(self.create_buf, 0, p_path, ig)
         return
     try:
         buf_fd = open(path, 'rb')
         buf = buf_fd.read()
         encoding = 'utf8'
         rel_path = utils.to_rel_path(path)
         existing_buf = self.get_buf_by_path(path)
         if existing_buf and existing_buf['md5'] == hashlib.md5(buf).hexdigest():
             msg.debug('%s already exists and has the same md5. Skipping creating.' % path)
             return
         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.agent.put(event)
     except (IOError, OSError):
         msg.error('Failed to open %s.' % path)
     except Exception as e:
         msg.error('Failed to create buffer %s: %s' % (path, unicode(e)))
示例#53
0
    def handler(self, name, data):
        if name == 'patch':
            Listener.apply_patch(data)
        elif name == 'get_buf':
            buf_id = data['id']
            buf = listener.BUFS.get(buf_id)
            if not buf:
                return msg.warn(
                    'no buf found: %s.  Hopefully you didn\'t need that' %
                    data)
            timeout_id = buf.get('timeout_id')
            if timeout_id:
                utils.cancel_timeout(timeout_id)

            if data['encoding'] == 'base64':
                data['buf'] = base64.b64decode(data['buf'])
            # forced_patch doesn't exist in data, so this is equivalent to buf['forced_patch'] = False
            listener.BUFS[buf_id] = data
            view = listener.get_view(buf_id)
            if view:
                Listener.update_view(data, view)
            else:
                listener.save_buf(data)
        elif name == 'create_buf':
            if data['encoding'] == 'base64':
                data['buf'] = base64.b64decode(data['buf'])
            listener.BUFS[data['id']] = data
            listener.PATHS_TO_IDS[data['path']] = data['id']
            listener.save_buf(data)
            cb = listener.CREATE_BUF_CBS.get(data['path'])
            if cb:
                del listener.CREATE_BUF_CBS[data['path']]
                try:
                    cb(data['id'])
                except Exception as e:
                    print(e)
        elif name == 'rename_buf':
            del listener.PATHS_TO_IDS[data['old_path']]
            listener.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)
            os.rename(old, new)
            view = listener.get_view(data['id'])
            if view:
                view.retarget(new)
            listener.BUFS[data['id']]['path'] = data['path']
        elif name == 'delete_buf':
            path = utils.get_full_path(data['path'])
            listener.delete_buf(data['id'])
            try:
                utils.rm(path)
            except Exception:
                pass
            user_id = data.get('user_id')
            username = self.get_username_by_id(user_id)
            msg.log('%s deleted %s' % (username, path))
        elif name == 'room_info':
            Listener.reset()
            G.JOINED_WORKSPACE = True
            # Success! Reset counter
            self.reconnect_delay = self.INITIAL_RECONNECT_DELAY
            self.retries = self.MAX_RETRIES

            self.workspace_info = data
            G.PERMS = data['perms']

            if 'patch' not in data['perms']:
                msg.log('No patch permission. Setting buffers to read-only')
                if sublime.ok_cancel_dialog(
                        'You don\'t have permission to edit this workspace. All files will be read-only.\n\nDo you want to request edit permission?'
                ):
                    self.put({'name': 'request_perms', 'perms': ['edit_room']})

            project_json = {'folders': [{'path': G.PROJECT_PATH}]}

            utils.mkdir(G.PROJECT_PATH)
            with open(os.path.join(G.PROJECT_PATH, '.sublime-project'),
                      'wb') as project_fd:
                project_fd.write(
                    json.dumps(project_json, indent=4,
                               sort_keys=True).encode('utf-8'))

            floo_json = {
                'url':
                utils.to_workspace_url({
                    'host': self.host,
                    'owner': self.owner,
                    'port': self.port,
                    'workspace': self.workspace,
                    'secure': self.secure,
                })
            }
            with open(os.path.join(G.PROJECT_PATH, '.floo'), 'w') as floo_fd:
                floo_fd.write(json.dumps(floo_json, indent=4, sort_keys=True))

            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)
                listener.BUFS[buf_id] = buf
                listener.PATHS_TO_IDS[buf['path']] = buf_id
                # TODO: stupidly inefficient
                view = listener.get_view(buf_id)
                if view and not view.is_loading(
                ) and buf['encoding'] == 'utf8':
                    view_text = listener.get_text(view)
                    view_md5 = hashlib.md5(
                        view_text.encode('utf-8')).hexdigest()
                    if view_md5 == buf['md5']:
                        msg.debug(
                            'md5 sum matches view. not getting buffer %s' %
                            buf['path'])
                        buf['buf'] = view_text
                        G.VIEW_TO_HASH[view.buffer_id()] = view_md5
                    elif self.get_bufs:
                        Listener.get_buf(buf_id)
                    #TODO: maybe send patch here?
                else:
                    try:
                        buf_fd = open(buf_path, 'rb')
                        buf_buf = buf_fd.read()
                        md5 = hashlib.md5(buf_buf).hexdigest()
                        if md5 == buf['md5']:
                            msg.debug(
                                'md5 sum matches. not getting buffer %s' %
                                buf['path'])
                            if buf['encoding'] == 'utf8':
                                buf_buf = buf_buf.decode('utf-8')
                            buf['buf'] = buf_buf
                        elif self.get_bufs:
                            Listener.get_buf(buf_id)
                    except Exception as e:
                        msg.debug('Error calculating md5:', e)
                        Listener.get_buf(buf_id)

            msg.log('Successfully joined workspace %s/%s' %
                    (self.owner, self.workspace))

            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 self.on_room_info:
                self.on_room_info()
                self.on_room_info = None
        elif name == 'user_info':
            user_id = str(data['user_id'])
            user_info = data['user_info']
            self.workspace_info['users'][user_id] = user_info
            if user_id == str(self.workspace_info['user_id']):
                G.PERMS = user_info['perms']
        elif name == 'join':
            msg.log('%s joined the workspace' % data['username'])
            user_id = str(data['user_id'])
            self.workspace_info['users'][user_id] = data
        elif name == 'part':
            msg.log('%s left the workspace' % data['username'])
            user_id = str(data['user_id'])
            try:
                del self.workspace_info['users'][user_id]
            except Exception as e:
                print('Unable to delete user %s from user list' % (data))
            region_key = 'floobits-highlight-%s' % (user_id)
            for window in sublime.windows():
                for view in window.views():
                    view.erase_regions(region_key)
        elif name == 'highlight':
            region_key = 'floobits-highlight-%s' % (data['user_id'])
            Listener.highlight(data['id'], region_key, data['username'],
                               data['ranges'], data.get('ping', False))
        elif name == 'set_temp_data':
            hangout_data = data.get('data', {})
            hangout = hangout_data.get('hangout', {})
            hangout_url = hangout.get('url')
            if hangout_url:
                self.prompt_join_hangout(hangout_url)
        elif name == 'saved':
            try:
                buf = listener.BUFS[data['id']]
                username = self.get_username_by_id(data['user_id'])
                msg.log('%s saved buffer %s' % (username, buf['path']))
            except Exception as e:
                msg.error(str(e))
        elif name == 'request_perms':
            print(data)
            user_id = str(data.get('user_id'))
            username = self.get_username_by_id(user_id)
            if not username:
                return msg.debug(
                    'Unknown user for id %s. Not handling request_perms event.'
                    % user_id)
            perm_mapping = {
                'edit_room': 'edit',
                'admin_room': 'admin',
            }
            perms = data.get('perms')
            perms_str = ''.join([perm_mapping.get(p) for p in perms])
            prompt = 'User %s is requesting %s permission for this room.' % (
                username, perms_str)
            message = data.get('message')
            if message:
                prompt += '\n\n%s says: %s' % (username, message)
            prompt += '\n\nDo you want to grant them permission?'
            confirm = bool(sublime.ok_cancel_dialog(prompt))
            if confirm:
                action = 'add'
            else:
                action = 'reject'
            self.put({
                'name': 'perms',
                'action': action,
                'user_id': user_id,
                'perms': perms
            })
        elif name == 'perms':
            action = data['action']
            user_id = str(data['user_id'])
            user = self.workspace_info['users'].get(user_id)
            if user is None:
                msg.log('No user for id %s. Not handling perms event' %
                        user_id)
                return
            perms = set(user['perms'])
            if action == 'add':
                perms |= set(data['perms'])
            elif action == 'remove':
                perms -= set(data['perms'])
            else:
                return
            user['perms'] = list(perms)
            if user_id == self.workspace_info['user_id']:
                G.PERMS = perms
        elif name == 'msg':
            self.on_msg(data)
        else:
            msg.debug('unknown name!', name, 'data:', data)
示例#54
0
 def on_join(self, data):
     msg.log('%s joined the workspace' % data['username'])
示例#55
0
def message_dialog(message):
    msg.log(message)