def get_info(workspace_url, project_dir): repo_type = detect_type(project_dir) if not repo_type: return msg.debug('Detected ', repo_type, ' repo in ', project_dir) data = { 'type': repo_type, } cmd = REPO_MAPPING[repo_type]['cmd'] try: p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=project_dir) result = p.communicate() repo_url = result[0].decode('utf-8').strip() if repo_type == 'svn': repo_url = parse_svn_xml(repo_url) msg.log(repo_type, ' url is ', repo_url) if not repo_url: msg.error('Error getting ', repo_type, ' url:', result[1]) return except Exception as e: msg.error('Error getting ', repo_type, ' url:', str_e(e)) return data['url'] = repo_url return data
def put(self, item): if not item: return self.sock_q.put(json.dumps(item) + "\n") qsize = self.sock_q.qsize() if qsize > 0: msg.debug("%s items in q" % qsize)
def set_cursor_position(self, offset): line_num, col = self._offset_to_vim(offset) command = 'setpos(".", [%s, %s, %s, %s])' % (self.native_id, line_num, col, 0) msg.debug("setting pos: %s" % command) rv = int(vim.eval(command)) if rv != 0: msg.debug("SHIIIIIIIIT %s" % rv)
def on_rename_buf(self, data): buf = self.FLOO_BUFS[int(data['id'])] # This can screw up if someone else renames the buffer around the same time as us. Oh well. buf = self.get_buf_by_path(utils.get_full_path(data['path'])) if not buf: return super(Protocol, self).on_rename_buf(data) msg.debug('We already renamed %s. Skipping' % buf['path'])
def on_emacs_rename_buf(self, req): buf = self.get_buf_by_path(req['old_path']) if not buf: msg.debug('No buffer for path %s' % req['old_path']) return self.rename_buf(buf['id'], req['path']) self.FLOO_BUFS[buf['id']]['path'] = utils.to_rel_path(req['path'])
def __init__(self, r): self.body = None if isinstance(r, bytes): r = r.decode('utf-8') if isinstance(r, str_instances): lines = r.split('\n') self.code = int(lines[0]) if self.code != 204: self.body = json.loads('\n'.join(lines[1:])) elif hasattr(r, 'code'): # Hopefully this is an HTTPError self.code = r.code if self.code != 204: self.body = json.loads(r.read().decode("utf-8")) elif hasattr(r, 'reason'): # Hopefully this is a URLError # horrible hack, but lots of other stuff checks the response code :/ self.code = 500 self.body = r.reason else: # WFIO self.code = 500 self.body = r msg.debug('code: %s' % self.code)
def __init__(self, parent, path, recurse=True): self.parent = parent self.size = 0 self.children = [] self.files = [] self.ignores = { '/TOO_BIG/': [] } self.path = utils.unfuck_path(path) try: paths = os.listdir(self.path) except OSError as e: if e.errno != errno.ENOTDIR: msg.error('Error listing path %s: %s' % (path, unicode(e))) return self.path = os.path.dirname(self.path) self.add_file(os.path.basename(path)) return except Exception as e: msg.error('Error listing path %s: %s' % (path, unicode(e))) return msg.debug('Initializing ignores for %s' % path) for ignore_file in IGNORE_FILES: try: self.load(ignore_file) except Exception: pass if recurse: for p in paths: self.add_file(p)
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"])
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 = G.INITIAL_RECONNECT_DELAY self.send_auth() if cb: cb()
def put(self, item): if not item: return self.sock_q.put(json.dumps(item) + '\n') qsize = self.sock_q.qsize() if qsize > 0: msg.debug('%s items in q' % qsize)
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'])
def __init__(self, parent, path, recurse=True): self.parent = parent self.size = 0 self.children = [] self.files = [] self.ignores = { '/TOO_BIG/': [] } self.path = utils.unfuck_path(path) try: paths = os.listdir(self.path) except OSError as e: if e.errno != errno.ENOTDIR: msg.error('Error listing path %s: %s' % (path, unicode(e))) return self.path = os.path.dirname(self.path) self.add_file(os.path.basename(path)) return except Exception as e: msg.error('Error listing path %s: %s' % (path, unicode(e))) return msg.debug('Initializing ignores for %s' % path) for ignore_file in IGNORE_FILES: try: self.load(ignore_file) except: pass if recurse: for p in paths: self.add_file(p)
def put(item): if not item: return SOCKET_Q.put(json.dumps(item) + '\n') qsize = SOCKET_Q.qsize() if qsize > 0: msg.debug('%s items in q' % qsize)
def set_cursor_position(self, offset): line_num, col = self._offset_to_vim(offset) command = 'setpos(".", [%s, %s, %s, %s])' % (self.native_id, line_num, col, 0) msg.debug("setting pos: %s" % command) rv = int(vim.eval(command)) if rv != 0: msg.debug('SHIIIIIIIIT %s' % rv)
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"])
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'])
def maybe_selection_changed(self, vim_buf, is_ping): buf = self.get_buf(vim_buf) if not buf: msg.debug("no buffer found for view %s" % vim_buf.number) return view = self.get_view(buf["id"]) msg.debug("selection changed: %s %s %s" % (vim_buf.number, buf["id"], view)) self.SELECTION_CHANGED.append([view, is_ping])
def set_text(self, text): msg.debug("\n\nabout to patch %s %s" % (str(self), self.vim_buf.name)) try: msg.debug("now buf is loadedish? %s" % vim.eval("bufloaded(%s)" % self.native_id)) self.vim_buf[:] = text.encode("utf-8").split("\n") except Exception as e: msg.error("couldn't apply patches because: %s!\nThe unencoded text was: %s" % (str(e), text)) raise
def clear_highlights(view): if not Listener.agent: return buf = get_buf(view) if not buf: return msg.debug('clearing highlights in %s, buf id %s' % (buf['path'], buf['id'])) for user_id, username in Listener.agent.room_info['users'].items(): view.erase_regions('floobits-highlight-%s' % user_id)
def maybe_selection_changed(self, vim_buf, is_ping): buf = self.get_buf(vim_buf) if not buf: msg.debug('no buffer found for view %s' % vim_buf.number) return view = self.get_view(buf['id']) msg.debug("selection changed: %s %s %s" % (vim_buf.number, buf['id'], view)) self.SELECTION_CHANGED.append([view, is_ping])
def wrapped(self, data): if data.get("id") is None: msg.debug("no buf id in data") return buf = self.FLOO_BUFS.get(data["id"]) if buf is None or "buf" not in buf: msg.debug("buf is not populated yet") return func(self, data)
def is_shared(self, p): if not self.agent.is_ready(): msg.debug('agent is not ready. %s is not shared' % p) return False p = utils.unfuck_path(p) # TODO: tokenize on path seps and then look for .. if utils.to_rel_path(p).find("../") == 0: return False return True
def emacs_handle(self, data): msg.debug(data) name = data.get('name') if not name: return msg.error('no name in data?!?') func = getattr(self, "on_emacs_%s" % (name)) if not func: return msg.debug('unknown name', name, 'data:', data) func(data)
def wrapped(self, data): if data.get('id') is None: msg.debug('no buf id in data') return buf = self.FLOO_BUFS.get(data['id']) if buf is None or 'buf' not in buf: msg.debug('buf is not populated yet') return func(self, data)
def recurse(self, root): try: paths = os.listdir(self.path) except OSError as e: if e.errno != errno.ENOTDIR: msg.error('Error listing path ', self.path, ': ', str_e(e)) return except Exception as e: msg.error('Error listing path ', self.path, ': ', str_e(e)) return msg.debug('Initializing ignores for ', self.path) for ignore_file in IGNORE_FILES: try: self.load(ignore_file) except Exception: pass for p in paths: if p == '.' or p == '..': continue if p in BLACKLIST: msg.log('Ignoring blacklisted file ', p) continue p_path = os.path.join(self.path, p) try: s = os.stat(p_path) except Exception as e: msg.error('Error stat()ing path ', p_path, ': ', str_e(e)) continue if stat.S_ISREG(s.st_mode) and p in HIDDEN_WHITELIST: # Don't count these whitelisted files in size self.files.append(p_path) continue is_dir = stat.S_ISDIR(s.st_mode) if root.is_ignored(p_path, is_dir, True): continue if is_dir: ig = Ignore(p_path, self) self.children[p] = ig ig.recurse(root) self.total_size += ig.total_size continue if stat.S_ISREG(s.st_mode): if s.st_size > (MAX_FILE_SIZE): self.ignores['/TOO_BIG/'].append(p) msg.log( self.is_ignored_message(p_path, p, '/TOO_BIG/', False)) else: self.size += s.st_size self.total_size += s.st_size self.files.append(p_path)
def __init__(self, parent, path): self.parent = parent self.ignores = {} self.path = utils.unfuck_path(path) msg.debug('Initializing ignores for %s' % path) for ignore_file in IGNORE_FILES: try: self.load(ignore_file) except: pass
def _offset_to_vim(self, offset): current_offset = 0 for line_num, line in enumerate(self.vim_buf): next_offset = len(line) + 1 if current_offset + next_offset > offset: break current_offset += next_offset col = offset - current_offset msg.debug("offset %s is line %s column %s" % (offset, line_num + 1, col + 1)) return line_num + 1, col + 1
def on_selection_modified(self, view, buf=None): try: SELECTED_EVENTS.get_nowait() except queue.Empty: buf = buf or get_buf(view) if buf: msg.debug('selection in view %s, buf id %s' % (buf['path'], buf['id'])) self.selection_changed.append((view, buf, False)) else: SELECTED_EVENTS.task_done()
def recurse(self, root): try: paths = os.listdir(self.path) except OSError as e: if e.errno != errno.ENOTDIR: msg.error('Error listing path ', self.path, ': ', str_e(e)) return except Exception as e: msg.error('Error listing path ', self.path, ': ', str_e(e)) return msg.debug('Initializing ignores for ', self.path) for ignore_file in IGNORE_FILES: try: self.load(ignore_file) except Exception: pass for p in paths: if p == '.' or p == '..': continue if p in BLACKLIST: msg.log('Ignoring blacklisted file ', p) continue p_path = os.path.join(self.path, p) try: s = os.stat(p_path) except Exception as e: msg.error('Error stat()ing path ', p_path, ': ', str_e(e)) continue if stat.S_ISREG(s.st_mode) and p in HIDDEN_WHITELIST: # Don't count these whitelisted files in size self.files.append(p_path) continue is_dir = stat.S_ISDIR(s.st_mode) if root.is_ignored(p_path, is_dir, True): continue if is_dir: ig = Ignore(p_path, self) self.children[p] = ig ig.recurse(root) self.total_size += ig.total_size continue if stat.S_ISREG(s.st_mode): if s.st_size > (MAX_FILE_SIZE): self.ignores['/TOO_BIG/'].append(p) msg.log(self.is_ignored_message(p_path, p, '/TOO_BIG/', False)) else: self.size += s.st_size self.total_size += s.st_size self.files.append(p_path)
def set_text(self, text): msg.debug('\n\nabout to patch %s %s' % (str(self), self.vim_buf.name)) try: msg.debug("now buf is loadedish? %s" % vim.eval('bufloaded(%s)' % self.native_id)) self.vim_buf[:] = text.encode('utf-8').split('\n') except Exception as e: msg.error( "couldn't apply patches because: %s!\nThe unencoded text was: %s" % (str(e), text)) raise
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 create_buf(path, always_add=False): if not utils.is_shared(path): msg.error( 'Skipping adding %s because it is not in shared path %s.' % (path, G.PROJECT_PATH)) return if os.path.isdir(path): command = 'git ls-files %s' % path try: p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, cwd=path) stdoutdata, stderrdata = p.communicate() if p.returncode == 0: for git_path in stdoutdata.split('\n'): git_path = git_path.strip() if not git_path: continue add_path = os.path.join(path, git_path) msg.debug('adding %s' % add_path) utils.set_timeout(Listener.create_buf, 0, add_path) return except Exception as e: msg.debug("Couldn't run %s. This is probably OK. Error: %s" % (command, str(e))) for dirpath, dirnames, filenames in os.walk(path): # Don't care about hidden stuff dirnames[:] = [d for d in dirnames if d[0] != '.'] for f in filenames: f_path = os.path.join(dirpath, f) if f[0] == '.': msg.log('Not creating buf for hidden file %s' % f_path) else: utils.set_timeout(Listener.create_buf, 0, f_path) return try: buf_fd = open(path, 'rb') buf = buf_fd.read().decode('utf-8') rel_path = utils.to_rel_path(path) msg.log('creating buffer ', rel_path) event = { 'name': 'create_buf', 'buf': buf, 'path': rel_path, } Listener.agent.put(event) except (IOError, OSError): msg.error('Failed to open %s.' % path) except Exception as e: msg.error('Failed to create buffer %s: %s' % (path, str(e)))
def create_view(self, buf): path = self.save_buf(buf) vb = self.get_vim_buf_by_path(buf["path"]) if vb: return View(vb, buf) vim.command(":edit! %s" % path) vb = self.get_vim_buf_by_path(buf["path"]) if vb is None: msg.debug("vim buffer is none even though we tried to open it: %s" % path) return return View(vb, buf)
def load(self, ignore_file): with open(os.path.join(self.path, ignore_file), 'rb') as fd: ignores = fd.read().decode('utf-8') self.ignores[ignore_file] = [] for ignore in ignores.split('\n'): ignore = ignore.strip() if len(ignore) == 0: continue if ignore[0] == '#': continue msg.debug('Adding %s to ignore patterns' % ignore) self.ignores[ignore_file].append(ignore)
def select(self): if not self.sock: msg.error('select(): No socket.') return self.reconnect() try: # this blocks until the socket is readable or writeable _in, _out, _except = select.select([self.sock], [self.sock], [self.sock]) except (select.error, socket.error, Exception) as e: msg.error('Error in select(): %s' % str(e)) return self.reconnect() if _except: msg.error('Socket error') return self.reconnect() if _in: buf = ''.encode('utf-8') while True: try: d = self.sock.recv(4096) if not d: break buf += d except (socket.error, TypeError): break if buf: self.empty_selects = 0 self.protocol(buf) else: self.empty_selects += 1 if self.empty_selects > 10: msg.error('No data from sock.recv() {0} times.'.format( self.empty_selects)) return self.reconnect() if _out: for p in self.get_patches(): if p is None: SOCKET_Q.task_done() continue try: msg.debug('writing patch: %s' % p) self.sock.sendall(p.encode('utf-8')) SOCKET_Q.task_done() except Exception as e: msg.error('Couldn\'t write to socket: %s' % str(e)) return self.reconnect() utils.set_timeout(self.select, 100)
def load(self, ignore_file): with open(os.path.join(self.path, ignore_file), 'r') as fd: ignores = fd.read() rules = [] for ignore in ignores.split('\n'): ignore = ignore.strip() if len(ignore) == 0: continue if ignore[0] == '#': continue msg.debug('Adding ', ignore, ' to ignore patterns') rules.insert(0, ignore) self.ignores[ignore_file] = rules
def load(self, ignore_file): with open(os.path.join(self.path, ignore_file), 'r') as fd: ignores = fd.read() rules = [] for ignore in ignores.split('\n'): ignore = ignore.strip() if len(ignore) == 0: continue if ignore[0] == '#': continue msg.debug('Adding %s to ignore patterns' % ignore) rules.insert(0, ignore) self.ignores[ignore_file] = rules
def hit_url(host, url, data, method): if data: data = json.dumps(data).encode("utf-8") msg.debug("url: ", url, " method: ", method, " data: ", data) r = Request(url, data=data) r.method = method r.get_method = lambda: method auth = get_basic_auth(host) if auth: r.add_header("Authorization", "Basic %s" % auth) r.add_header("Accept", "application/json") r.add_header("Content-type", "application/json") r.add_header("User-Agent", user_agent()) return urlopen(r, timeout=5)
def select(self): if not self.sock: msg.error('select(): No socket.') return self.reconnect() try: # this blocks until the socket is readable or writeable _in, _out, _except = select.select([self.sock], [self.sock], [self.sock]) except (select.error, socket.error, Exception) as e: msg.error('Error in select(): %s' % str(e)) return self.reconnect() if _except: msg.error('Socket error') return self.reconnect() if _in: buf = ''.encode('utf-8') while True: try: d = self.sock.recv(4096) if not d: break buf += d except (socket.error, TypeError): break if buf: self.empty_selects = 0 self.protocol(buf) else: self.empty_selects += 1 if self.empty_selects > 10: msg.error('No data from sock.recv() {0} times.'.format(self.empty_selects)) return self.reconnect() if _out: for p in self.get_patches(): if p is None: SOCKET_Q.task_done() continue try: msg.debug('writing patch: %s' % p) self.sock.sendall(p.encode('utf-8')) SOCKET_Q.task_done() except Exception as e: msg.error('Couldn\'t write to socket: %s' % str(e)) return self.reconnect() utils.set_timeout(self.select, 100)
def create_view(self, buf): path = self.save_buf(buf) vb = self.get_vim_buf_by_path(buf['path']) if vb: return View(vb, buf) vim.command(':edit! %s' % path) vb = self.get_vim_buf_by_path(buf['path']) if vb is None: msg.debug( 'vim buffer is none even though we tried to open it: %s' % path) return return View(vb, buf)
def hit_url(host, url, data, method): if data: data = json.dumps(data).encode('utf-8') msg.debug('url: ', url, ' method: ', method, ' data: ', data) r = Request(url, data=data) r.method = method r.get_method = lambda: method auth = get_basic_auth(host) if auth: r.add_header('Authorization', 'Basic %s' % auth) r.add_header('Accept', 'application/json') r.add_header('Content-type', 'application/json') r.add_header('User-Agent', user_agent()) return urlopen(r, timeout=5)
def apply_patches(self, buf, patches): cursor_offset = self.get_cursor_offset() msg.debug('cursor offset is %s bytes' % cursor_offset) self.set_text(patches[0]) for patch in patches[2]: offset = patch[0] length = patch[1] patch_text = patch[2] if cursor_offset > offset: new_offset = len(patch_text) - length cursor_offset += new_offset self.set_cursor_position(cursor_offset)
def load(self, ignore_file): with open(os.path.join(self.path, ignore_file), 'r') as fd: ignores = fd.read() rules = [] for ignore in ignores.split('\n'): ignore = ignore.strip() if len(ignore) == 0: continue if ignore[0] == '#': continue if ignore in NEGATE_PREFIXES: # Just an exclamation mark or caret? This is some messed up pattern. Skip it. continue msg.debug('Adding ', ignore, ' to ignore patterns') rules.insert(0, ignore) self.ignores[ignore_file] = rules
def rename(self, name): msg.debug('renaming %s to %s' % (self.vim_buf.name, name)) current = vim.current.buffer text = self.get_text() old_name = self.vim_buf.name old_number = self.native_id with open(name, 'wb') as fd: fd.write(text.encode('utf-8')) vim.command('edit! %s' % name) self.vim_buf = vim.current.buffer vim.command('edit! %s' % current.name) vim.command('bdelete! %s' % old_number) try: utils.rm(old_name) except Exception as e: msg.debug("couldn't delete %s... maybe thats OK?" % str(e))
def update_persistent_data(data): seen = set() recent_workspaces = [] for x in data['recent_workspaces']: try: if x['url'] in seen: continue seen.add(x['url']) recent_workspaces.append(x) except Exception as e: msg.debug(str_e(e)) data['recent_workspaces'] = recent_workspaces per_path = os.path.join(G.BASE_DIR, 'persistent.json') with open(per_path, 'wb') as per: per.write(json.dumps(data, indent=2).encode('utf-8'))
def to_json(self): patches = self.patches() if len(patches) == 0: return None msg.debug('sending %s patches' % len(patches)) patch_str = '' for patch in patches: patch_str += str(patch) return { 'id': self.buf['id'], 'md5_after': hashlib.md5(self.current.encode('utf-8')).hexdigest(), 'md5_before': self.md5_before, 'path': self.buf['path'], 'patch': patch_str, 'name': 'patch' }
def hit_url(host, url, data, method): if data: data = json.dumps(data).encode('utf-8') msg.debug('url: ', url, ' method: ', method, ' data: ', data) r = Request(url, data=data) r.method = method r.get_method = lambda: method auth = get_basic_auth(host) if auth: r.add_header('Authorization', 'Basic %s' % auth) r.add_header('Accept', 'application/json') r.add_header('Content-type', 'application/json') r.add_header('User-Agent', user_agent()) cafile = os.path.join(G.BASE_DIR, 'floobits.pem') with open(cafile, 'wb') as cert_fd: cert_fd.write(cert.CA_CERT.encode('utf-8')) return urlopen(r, timeout=10, cafile=cafile)
def handle(self, data): name = data.get('name') if not name: return msg.error('no name in data?!?') func = getattr(self, "on_%s" % (name)) if not func: return msg.debug('unknown name', name, 'data:', data) func(data)
def prejoin_workspace(workspace_url, dir_to_share, api_args): try: result = utils.parse_url(workspace_url) except Exception as e: msg.error(unicode(e)) return False try: w = get_workspace_by_url(workspace_url) except Exception as e: editor.error_message('Error opening url %s: %s' % (workspace_url, str(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(unicode(e)) return False msg.debug('workspace: %s', json.dumps(w.body)) anon_perms = w.body.get('perms', {}).get('AnonymousUser', []) msg.debug('api args: %s' % 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 = update_workspace(w.body['owner'], w.body['name'], 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