def getxattr(self, path, *args, **kwargs): if path in self._intermediates: raise fuse.FuseOSError(errno.ENODATA) try: (path, fs) = self._map_path(path) except ValueError: raise fuse.FuseOSError(errno.ENOENT) return getattr(fs, 'getxattr')(path, *args, **kwargs)
def getxattr(self, path, attrname): if path == '/' or path in self._write_buffers: raise fuse.FuseOSError(errno.ENODATA) (g, e) = self._path_to_guildmoji(path) if not g and e: raise fuse.FuseOSError(errno.ENOENT) if attrname == constants.URL_XATTR_NAME: return bytes(self._emoji_url(e), 'utf-8') elif attrname == constants.CREATEDBY_XATTR_NAME: return bytes(self._user_string(e['user']), 'utf-8') else: raise fuse.FuseOSError(errno.ENODATA)
def create(self, path, mode, fi=None): g = self._path_to_guild(path) if not g: raise fuse.FuseOSError(errno.ENOENT) if not self._guild_is_writable(g): raise fuse.FuseOSError(errno.EPERM) try: self._path_to_extension(path) except ValueError: raise fuse.FuseOSError(errno.EINVAL) self._write_buffers[path] = io.BytesIO() return 0
def unlink(self, path): # TODO: what about an unlink on a new file open in _write_buffers ? (g, e) = self._path_to_guildmoji(path) if g and e: logger.info('🗑️ Deleting :%s: (id %s) from "%s" (id %s)', e['name'], e['id'], g['name'], g['id']) self._request('DELETE', f"guilds/{g['id']}/emojis/{e['id']}") self._invalidate_guild(g['id']) elif g and not e: # Sorry, but we won't delete a whole discord. raise fuse.FuseOSError(errno.EPERM) else: raise fuse.FuseOSError(errno.ENOENT)
def getxattr(self, path, attrname): if path == '/' or path in self._write_buffers: raise fuse.FuseOSError(errno.ENODATA) emojis = self._get_all_emoji() name = self._path_to_name(path) e = emojis[name] if name not in emojis: raise fuse.FuseOSError(errno.ENOENT) if attrname == constants.URL_XATTR_NAME: return bytes(e['url'], 'utf-8') elif attrname == constants.CREATEDBY_XATTR_NAME: return bytes(e['user_display_name'], 'utf-8') else: raise fuse.FuseOSError(errno.ENODATA)
def readlink(self, path): emojis = self._get_all_emoji() name = self._path_to_name(path) if name not in emojis: raise fuse.FuseOSError(errno.ENOENT) e = emojis[name] if not e['is_alias']: raise fuse.FuseOSError(errno.EINVAL) # We don't 'deference' it ourselves using our metadata table, because it could be an alias # to a regular Unicode emoji. # This will represent it as a dangling symlink instead of throwing. # TODO: do something smart and/or reasonable about regular Unicode emoji # (possibly, have another mountpoint for them?) return self._emoji_to_filename(e, name=e['alias_for'])
def unlink(self, path): # TODO: what about an unlink on a new file open in _write_buffers ? emojis = self._get_all_emoji() name = self._path_to_name(path) if name not in emojis: raise fuse.FuseOSError(errno.ENOENT) self._delete_emoji(name)
def _request(self, method, url, **kwargs): """Execute a request against our _session, respecting ratelimiting and raising if the response isn't okay. Retry on ratelimiting (after sleeping) but not on other error.""" # TODO: this is very close to, but not quite, Discord._request(). # Respect any ratelimiting on the given URL path time.sleep(max(0, self._retry_after.get(url, 0) - time.time())) resp = self._session.request(method, url, **kwargs) if resp.status_code == 429: self._retry_after[url] = time.time() + resp.headers.get( 'retry-after', 60) logger.warn( 'Got ratelimited by Slack; retrying after %s seconds for %s', self._retry_after[url], url) return self._request(method, url, **kwargs) else: resp.raise_for_status() j = resp.json() logger.debug('resp for %s to %s json: %s', method, url, j) if not j['ok']: logger.error('Got an error on %s to %s: %s', method, url, j['error']) if j['error'] == 'no_permission': raise fuse.FuseOSError(errno.EPERM) assert (j['ok']) return resp
def listxattr(self, path): if path == '/' or path in self._write_buffers: return [] (g, e) = self._path_to_guildmoji(path) if not g and e: raise fuse.FuseOSError(errno.ENOENT) return [constants.URL_XATTR_NAME, constants.CREATEDBY_XATTR_NAME]
def symlink(self, target_path, source_path): emojis = self._get_all_emoji() source = self._path_to_name(source_path) if source not in emojis: raise fuse.FuseOSError(errno.ENOENT) target = self._path_to_name(target_path) self._alias_emoji(source, target)
def listxattr(self, path): if path == '/' or path in self._write_buffers: return [] emojis = self._get_all_emoji() name = self._path_to_name(path) if name not in emojis: raise fuse.FuseOSError(errno.ENOENT) return [constants.URL_XATTR_NAME, constants.CREATEDBY_XATTR_NAME]
def read(self, path, size, offset, fh): if path in self._write_buffers: b = self._write_buffers[path] b.seek(offset) return b.read(size) (g, e) = self._path_to_guildmoji(path) if g and e: b = utils.get_emoji_bytes(self._emoji_url(e)) return b[offset:offset + size] else: raise fuse.FuseOSError(errno.ENOENT)
def read(self, path, size, offset, fh): if path in self._write_buffers: b = self._write_buffers[path] b.seek(offset) return b.read(size) emojis = self._get_all_emoji() name = self._path_to_name(path) if name not in emojis: raise fuse.FuseOSError(errno.ENOENT) e = emojis[name] b = utils.get_emoji_bytes(e['url']) return b[offset:offset + size]
def getattr(self, path, *args, **kwargs): # Serve directory entries for our intermediates. if path in self._intermediates: return dict( st_mode=stat.S_IFDIR | 0o555, st_nlink=2, st_uid=utils.getuid(), st_gid=utils.getgid(), ) # Delegate to mountpoints their / and below. try: (path, fs) = self._map_path(path) except ValueError: raise fuse.FuseOSError(errno.ENOENT) return getattr(fs, 'getattr')(path, *args, **kwargs)
def _path_to_guildmoji(self, path): '''Given a /discord/foo/bar.png path, find and return (guild, emoji) objects.''' g = self._path_to_guild(path) if g: eh = self._path_to_emojiname(path) if eh: emojis = self._get_emojis(g['id']) if eh in emojis: return (g, emojis[eh]) else: raise fuse.FuseOSError(errno.ENOENT) else: return (g, None) # TODO: Better document (& test!) the semantics of this. else: return (None, None)
def getattr(self, path, fh): if path == '/': return dict( st_mode=stat.S_IFDIR | 0o555 | stat.S_IWUSR, st_mtime=time.time(), st_ctime=time.time(), st_atime=time.time(), st_nlink=2, st_uid=utils.getuid(), st_gid=utils.getgid(), ) if path in self._write_buffers: return dict(st_mode=stat.S_IFREG | 0o600, st_atime=time.time(), st_ctime=time.time(), st_mtime=time.time(), st_nlink=1, st_uid=utils.getuid(), st_gid=utils.getgid(), st_size=len(self._write_buffers[path].getbuffer())) # This will raise a Fuse ENOENT if a nonexistent emoji name was specified in the path (g, e) = self._path_to_guildmoji(path) if g is None: raise fuse.FuseOSError(errno.ENOENT) elif e is None: return dict( st_mode=stat.S_IFDIR | 0o555 | (stat.S_IWUSR if self._guild_is_writable(g) else 0), st_mtime=time.time(), st_ctime=time.time(), st_atime=time.time(), st_nlink=2, st_uid=utils.getuid(), st_gid=utils.getgid(), ) else: return dict(st_mode=stat.S_IFREG | 0o444, st_atime=time.time(), st_ctime=time.time(), st_mtime=time.time(), st_nlink=1, st_uid=utils.getuid(), st_gid=utils.getgid(), st_size=utils.get_content_length(self._emoji_url(e)))
def getattr(self, path, fh): emojis = self._get_all_emoji() # TODO: if we don't set allow_others in our fuse_main invocation, # the 0o555 is probably just confusing. if path == '/': return dict( st_mode=stat.S_IFDIR | 0o555 | stat.S_IWUSR, st_mtime=max([e['created'] for e in emojis.values()]), st_ctime=min([e['created'] for e in emojis.values()]), st_atime=time.time(), st_nlink=2, st_uid=utils.getuid(), st_gid=utils.getgid(), ) if path in self._write_buffers: return dict(st_mode=stat.S_IFREG | 0o600, st_atime=time.time(), st_ctime=time.time(), st_mtime=time.time(), st_nlink=1, st_uid=utils.getuid(), st_gid=utils.getgid(), st_size=len(self._write_buffers[path].getbuffer())) name = self._path_to_name(path) if name not in emojis: raise fuse.FuseOSError(errno.ENOENT) e = emojis[name] return dict( st_mode=(stat.S_IFLNK if e['is_alias'] else stat.S_IFREG) | 0o444, st_mtime=e['created'], st_ctime=e['created'], st_atime=time.time(), st_nlink=1, st_uid=utils.getuid(), st_gid=utils.getgid(), st_size=(utils.get_content_length(e['url']) if self._real_sizes else 256 * 1024), )