async def mknod(self, parent_inode, name, mode, rdev, ctx): _inode = BDFile.get_from_fs_id(parent_inode) if not _inode: path = '/' full_path = path else: path = _inode.path full_path = path + '/' name_bytes = name name = name.decode('utf-8') tmp = is_tmp(name) if Env.CLOUD_HOME not in path: raise pyfuse3.FUSEError(errno.EACCES) if not os.path.isdir(Env.PHYSICS_DIR + path): os.makedirs(Env.PHYSICS_DIR + path) file_path = Env.PHYSICS_DIR + full_path + name if tmp: with open(file_path, 'wb') as f: f.write(b'') inode = random.randint(1000000000000000000, 9999999999999999999) ns = (datetime.now().timestamp() * 1e9) _cache = self.fs.cache.get(path, None) _file = BDFile(isdir=False, server_ctime=ns, server_mtime=ns, local_ctime=ns, local_mtime=ns, fs_id=inode, path=full_path + name, filename=name, filename_bytes=name_bytes, size=0, p_inode=parent_inode) BDFile.set_inode_name_pool(parent_inode, name, _file) if not _cache: self.fs.cache[path] = { 'items': [_file], 'expire': datetime.now().timestamp() + BaiDu.DIR_EXPIRE_THRESHOLD } else: self.fs.cache[path]['items'].append(_file) return await self.getattr(inode, ctx) with open(file_path, 'wb') as f: f.write(Env.EMPTY_FILE_FLAG) upload_path = full_path + name # 因为百度不允许创建空文件,这里使用占位符上传临时文件 fs_id = self.fs.upload(parent_inode, file_path, upload_path) if fs_id: UploadInfo.add(parent_inode, fs_id, file_path, upload_path) # 上传成功后将本地临时文件删除,在写入时重新创建,避免文件写入内容包含占位符 # os.remove(file_path) return await self.getattr(fs_id, ctx) raise pyfuse3.FUSEError(errno.EAGAIN)
def rm(self, p_inode, name): f = BDFile.get_from_inode_name(p_inode, name) if not f: return self.__rm_with_path(f.path) BDFile.clear_f_cache(p_inode, f) rdx = f.path.rindex('/') path = '/' if rdx == 0 else f.path[:rdx] self.dir_cache(path, p_inode, force=True)
def __rm(self, p_inode, name): if is_tmp(name): f = BDFile.get_from_inode_name(p_inode, name) if not f: return try: os.remove(Env.PHYSICS_DIR + f.path) self.fs.dir_cache(f.path[:f.path.rindex('/')], p_inode, force=True) BDFile.del_inode_name_pool(p_inode, name) except FileNotFoundError as _: return self.fs.rm(p_inode, name)
async def getattr(self, inode, ctx): """ 这个方法需要返回文件或文件夹的基本信息,实际上inode为文件或文件夹的索引节点 因为实现的是网盘文件系统,所以此处虽说物理上不一样,但逻辑上是一样的,在这里我用文件的独立标识fs_id作为inode 根目录没有inode,默认为1,所以此处判断一下如果inode等于根inode的话,直接将返回设置为目录 :param inode: :param ctx: :return: """ entry = pyfuse3.EntryAttributes() entry.generation = 0 entry.entry_timeout = 300 entry.attr_timeout = 300 if inode == pyfuse3.ROOT_INODE: entry.st_mode = (stat.S_IFDIR | 0o755) entry.st_nlink = 0 entry.st_size = 0 else: f = BDFile.get_from_fs_id(inode) entry.st_mode = (stat.S_IFDIR | 0o755) if f.isdir else (stat.S_IFREG | 0o755) entry.st_size = f.size entry.st_nlink = 1 entry.st_atime_ns = int(f.server_mtime * 1e9) entry.st_ctime_ns = int(f.server_ctime * 1e9) entry.st_mtime_ns = int(f.server_mtime * 1e9) inode = f.fs_id entry.st_rdev = 0 entry.st_blksize = 512 entry.st_blocks = 1 entry.st_gid = os.getgid() entry.st_uid = os.getuid() entry.st_ino = inode return entry
async def fsync(self, fh, datasync): f = BDFile.get_from_fs_id(fh) if not f: return p_path = f.path[:f.path.rindex('/')] self.fs.info_cache(f.path, f.fs_id, force=True) self.fs.dir_cache(p_path, f.p_inode, force=True)
def dir(self, d='/', inode=None): """ 从百度云盘获取文件列表 :param inode: :param d: 路径 :return: """ res = self.__request(BaiDu.LIST, {'dir': d}, 'GET') return BDFile.from_json_list(res.get('list', []), inode)
async def mkdir(self, parent_inode, name, mode, ctx): if parent_inode == pyfuse3.ROOT_INODE: path = '/' else: f = BDFile.get_from_fs_id(parent_inode) path = f.path + '/' inode = self.fs.mkdir(parent_inode, path, name.decode('utf-8')) if not inode: raise pyfuse3.FUSEError(errno.EEXIST) return await self.getattr(inode, ctx)
async def read(self, fh, off, size): f = BDFile.get_from_fs_id(fh) if not f: return b'' else: if is_tmp(f.filename): try: with open(Env.PHYSICS_DIR + f.path, 'rb') as f: f.seek(off) res = f.read(size) except FileNotFoundError as _: return b'' else: res = self.fs.download(f, off, size) return res
async def setattr(self, inode, attr, fields, fh, ctx): f = BDFile.get_from_fs_id(inode) if fields.update_size: f.size = attr.st_size if fields.update_mode: pass if fields.update_uid: pass if fields.update_gid: pass if fields.update_atime: f.server_atime = datetime.now().timestamp() if fields.update_mtime: f.server_mtime = datetime.now().timestamp() if fields.update_ctime: f.server_ctime = datetime.now().timestamp() return await self.getattr(inode, ctx)
def mv(self, p_inode_old, name_old, p_inode_new, name_new): old_f = BDFile.get_from_inode_name(p_inode_old, name_old) new_path = '/' if p_inode_new == 1: self.__mv_with_path(old_f.path, new_path, name_new) else: new_f = BDFile.get_from_fs_id(p_inode_new) if new_f is not None: new_path = new_f.path self.__mv_with_path(old_f.path, new_path, name_new) BDFile.clear_f_cache(p_inode_new, new_f) BDFile.clear_f_cache(p_inode_old, old_f) rdx = old_f.path.rindex('/') path = '/' if rdx == 0 else old_f.path[:rdx] self.dir_cache(path, p_inode_old, force=True) self.dir_cache(new_path, p_inode_new, force=True)
async def readdir(self, fh, start_id, token): """ 读取目录信息,这个方法会被频繁调用 所以为了防止百度因为频繁请求封了账号这里加缓存默认1个小时更新保险一点,默认值在entity.Env里可以调整 缺点就是网页版或者客户端中的更新就不会那么实时了 因为这个方法会不断的被执行, 所以这里需要加文件列表数量校验 调试中看来start_id会一直更新,除非切换了inode也就是参数上的fh,(fh逻辑上算是唯一标识,相当于切换了目录的话,start_id就会从0开始) 需要注意的是这里的inode实际上并不一定是文件系统理解中的inode,但逻辑上是一样的,每个目录和文件都需要有inode。 此处根目录没有,所以做判断 如果fh(inode) = 根inode 则默认获取根目录列表 :param fh: fh(inode) 逻辑上的inode,用作文件或文件夹的唯一标识 :param start_id: start_id 是 pyfuse3.readdir_reply 的最后一个参数,会作为未来的readdir调用参数传入, 应当用作读取区间的标识, 上层不会一次性读取所有的值, 而是会分多次调用readdir,传入start_id 取不同的区间,实现上需要从start_id开始读取到目录最大下标, 需要注意这点 :param token: :return: """ f = BDFile.get_from_fs_id(fh) files = self.fs.dir_cache('/' if not f else f.path, pyfuse3.ROOT_INODE if not f else f.fs_id) max_len = len(files) for i in range(start_id, max_len): pyfuse3.readdir_reply(token, files[i].filename_bytes, await self.getattr(files[i].fs_id, None), i + 1)
def rename(self, p_inode_old, name_old, name_new): f = BDFile.get_from_inode_name(p_inode_old, name_old) path = '/' if p_inode_old == 1 else f.path + '/' self.__rename(path + name_old, name_new) BDFile.clear_f_cache(p_inode_old, f) self.dir_cache(path, p_inode_old, force=True)
async def lookup(self, parent_inode, name, ctx): f = BDFile.get_from_inode_name(parent_inode, name) if not f: raise pyfuse3.FUSEError(errno.ENOENT) return await self.getattr(f.fs_id, ctx)
async def write(self, fh, off, buf): node = BDFile.get_from_fs_id(fh) file_path = Env.PHYSICS_DIR + node.path while buf: return await self.__do_write(file_path, off, buf)