前言
一直以来我都苦于一些文档在家和公司来回拷贝上传下载的苦恼中,因为日常使用的都是Linux系统,主要还是不想花钱长期租个云主机装nfs服务,所以一直在等百度网盘出linux版本客户端,结果百度刚没多久发布的Linux版本客户端Bug也很多,登录一次之后再也无法登录,必须删除配置文件重新打开才行。所以我干脆就以百度的网盘接口实现虚拟文件系统,这样也就相当于本地多了块好几个T的云硬盘。在实现文件系统的各种功能中对逻辑和速度有一些取舍,毕竟接口是HTTP的,频繁读写确实很浪费字节,所以对速度我也没有太多的幻想,小文件的读取还是很方便的。经过一番考究,我决定基于fuse来实现,网上对fuse的文献都写得不多,我看了不少,很多也都是互相抄,版本也不一,非常凌乱且没有重点。在实现的过程中确实也遇到了很多问题,最后也只能扒看源码,大部分问题也终于得以解决。本文内容作为一个总结,也作为一个分享,共同学习。
演示截图
目标
首先要确认的是需要实现的功能,为了满足基本的文件操作:文件列表、删除、复制、重命名、读写、创建,这些都是必须的, 至于权限部分我看暂时可以不考虑。为了实现这些功能我先确认了百度云盘的接口,找到各个功能可以对应实现的接口进行了相应的测试之后开始了实现部分。
实现
应用层代码全部使用python3编写,引用了pyfuse3库,
class BDfs(pyfuse3.Operations):
在继承pyfuse3.Operations
之后你可以看到要实现的所有方法,我们按照功能点来分析。
一、文件列表
async def readdir(self, fh, start_id, token):
readdir被用作读取目录信息,这个方法会被频繁调用,方法最后需要通过readdir_reply
方法将要显示的信息返回
pyfuse3.readdir_reply(token,filename, await self.getattr(inode, None), next_start_id)
fh
是 filehandle的缩写,可以认为是文件唯一标识inode(index-node)。
当然fh可以是一个文件的标识,也可以是一个文件夹的标识, start_id
是上层要读取的列表区间。
经过调试发现,上层不会一次调用readdir读取整个目录,而是会分多次调用readdir
,传入start_id 取不同的区间,我们在实现上需要从start_id开始读取到目录最大下标,所以这里要注意pyfuse3.readdir_reply的最后一个参数,会作为未来的readdir调用参数start_id传入,你要以此控制readdir_reply的返回区间,稍有不慎容易文件显示少了,或者说死循环的问题,这个参数我调试了很久才发现正确的使用方法。其次因为这个方法会被多次调用,为了避免请求过于频繁被百度封了接口使用权限,我们最好缓存查询过的路径列表,同时也提高了响应速度。至于token
参数不用理会,正常传递就可以了,源码里的注释写的是:
'''*token* must be the token received by the `~Operations.readdir` handler.'''
最终这个方法会类似这样:
async def readdir(self, fh, start_id, token):
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)
得到列表之后,我们会需要显示文件信息,比如权限、文件类型、创建时间等等,所以我们需要实现获取文件信息的方法
async def getattr(self, inode, ctx):
这个方法需要返回文件或文件夹的基本信息,inode为文件或文件夹的索引节点,因为实现的是基于网盘文件系统,所以此处虽说物理上不一样,但逻辑上是一样的,在这里我直接使用百度的文件唯一标识fs_id
作为inode,根目录没有inode,默认为1,所以此处判断一下如果inode等于根inode的话,直接将节点设置为目录信息,ctx源码里的注释是:
'''
*ctx* will be a `RequestContext` instance.
'''
没说明特定用处,所以我们正常传递就好了。
最终这个方法会类似这样:
async def getattr(self, inode, ctx):
entry = pyfuse3.EntryAttributes()
entry