几个文件文件夹数据交互途径及技术方案及各自的弊端
1、点击按钮或菜单项的“选择文件”发送
技术原理
使用浏览器原生的文件输入框 HTMLInputElement,用户选择文件后可以再 onChange 检测输入框内容变化、从 HTMLInputElement.files 获取FileList。
文档可参考: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input/file
技术实现
支持多选(multiple="true")、未限制类型。
在onChange 获取并校验后得到合法 FileList 后,使用 sendFiles 组件打开文件发送框或直接执行发送(用户选择了“选择文件直接发送”的)。
特殊情况
按浏览器和操作系统不同,选择的内容会有特殊问题,比如:
- Mac端选择 “xxx.app”会实际往输入框输入一个“xxx.app.zip”
- 文件和文件夹混选时
- 先选文件还是先选文件夹、windows还是Mac都会有差异、不能读到文件夹对象或指针。
- 综上,业务逻辑中会忽略文件夹,防止旧版里"误把文件夹当文件发送,最终消息发送失败"的问题发生。
典型弊端
- FileList 是一维数组,不知道文件夹包含关系,文件量大浏览器Native层面可能就会响应很长时间。
- “xxx.app”特殊文件变“xxx.app.zip”的情形,浏览器Native层面会响应很长时间(处理时长取决于.app包大小)。
- 用户选择文件的量不可控,比如用户有个 /Logs 目录,里面的 “*.log”很多并且体量很大,时间空间复杂度一定会很高。
2、点击按钮或菜单项的“选择文件夹”发送
技术原理
使用浏览器原生的文件输入框 HTMLInputElement,并设置实验性的“webkitdirectory”属性,用户选择文件夹后可以再 onChange 检测输入框内容变化、从 HTMLInputElement.files 获取 FileList(一个被递归扁平化的一维文件对象数组),通过 File.webkitRelativePath 判定是否在同一个文件夹下。
文档可参考: https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLInputElement/webkitdirectory
技术实现
支持多选(multiple="true")、未限制类型。
在onChange 获取并校验后得到合法 FileList 后,转换成“文件夹对象”、使用 sendFiles 组件打开文件发送框、执行文件夹压缩、或直接执行发送(用户选择了“选择文件直接发送”的)。
特殊情况
虽然设置了多选,但目前看并不能多选。
典型弊端
- 不能多选文件夹(浏览器API层面设置了多选没用)、不能文件文件夹混选。
- FileList 是扁平化的文件一维数组:
- 文件量大浏览器Native层面可能就会响应很长时间。
- 要得到树状文件夹包含关系,就必须枚举遍历,根据 File.webkitRelativePath 做出判定,文件量大循环响应的时长也自然会长。
- “xxx.app”特殊文件变“xxx.app.zip”的情形,浏览器Native层面会响应很长时间(处理时长取决于.app包大小)。
- 用户选择文件夹里的内容量不可控,比如用户直接选/Windows系统安装包目录,时间空间复杂度一定会很高。
3、从磁盘拖拽文件或文件夹到APP
技术原理
使用浏览器原生的 doucument.onDrop 监听拖入事件,从 event.dataTransfer.items 读取拖入的内容(可以是多个文件夹或文件的混合内容),再 DataTransferItem.webkitGetAsEntry() 得到 entry、再 entry.isDirectory 判定是否文件夹,是文件夹的递归得到目录内容(用于判断超限、文件夹压缩);最终得到一个一维的 FileList 供 sendFile 组件走后续交互逻辑。
技术实现
支持多选&混选。
特殊情况
能得到树状目录结构。
典型弊端
- 从 DataTransferItems 到 FileList 流程偏长,存在异步IO读取,耗时长短取决于内容量(参见下图)。
- 用户拖拽内容量不可控,比如用户直接在C盘下全选、拖拽,时间空间复杂度一定会很高。
4、在 APP 中 CtrlOrCmd+V 从 磁盘 CtrlOrCmd+C 复制的文件和文件夹
技术原理
使用浏览器原生的 doucument.onPaste 监听粘贴事件,然后复用上面拖拽的FileList获得逻辑:
- 从 event.dataTransfer.items 读取拖入的内容(可以是多个文件夹或文件的混合内容)。
- 再 DataTransferItem.webkitGetAsEntry() 得到 entry、再 entry.isDirectory 判定是否文件夹,是文件夹的递归得到目录内容(用于判断超限、文件夹压缩)
- 最终得到一个一维的 FileList 供 sendFile 组件走后续交互逻辑。
文档参考: https://developer.mozilla.org/en-US/docs/Web/API/Document/paste_event
技术实现
理论上支持多选&混选,然后 Ctrl+C 写入剪切板,读取时得到。
特殊情况
- 剪切板有可能会是截图、纯文本、HTML、文件、文件夹,所以必须要做各CASE分支处理、并达成各自的需求。
- 这里纯粹靠浏览器onPaste事件的DataTransfer,如果有文件或文件夹读取不到的情况,首先需要确定是否权限问题、被浏览器滤掉等等。
典型弊端
- 从 DataTransferItems 到 FileList 流程偏长,存在异步IO读取,耗时长短取决于内容量。
- 用户复制内容量不可控,比如用户直接在C盘下全选、Ctrl+C,时间空间复杂度一定会很高。
5、在 APP 的消息输入框右键“粘贴” 从 磁盘 CtrlOrCmd+C 复制的文件和文件夹
技术原理
使用 Electron 的 NodeJS 能力,读取剪切板里的文件或文件夹路径、最终转换得到 FileList:
- 先通过NodeJS读取剪切板中的文件或文件夹路径
- 再通过NodeJS按路径递归读取得到 FIle 对象,传递给渲染进程进行UI展现、发送服务端。
文档参考: https://github.com/electron/electron/issues/9035
技术实现
读取剪切板中的文件或文件夹路径:
通过文件路径得到目录关系、文件数据,并进行超限判定:
将Native层的数据,转为渲染进程可用的FileList:
粘贴按钮按下后,插入文件或文本或HTML到编辑器或sendFile 组件:
特殊情况
getClipboardPaths 不可靠,比如windows端,无法读取到多个路径,如果 Ctrl+C 了多个,这里只能拿到其中的一个。
典型弊端
- 从剪切板路径读取 到 FileList 流程更长,也存在异步IO读取,耗时长短取决于内容量,且更突出。
- 用户Ctrl+C复制内容量、或文件夹里的内容量不可控,时间空间复杂度一定会很高。
使用JsZip进行文件夹压缩打包
以上方案的大前提
目前是考虑最快方案:不考虑各种复杂的UI和产品交互设计、减少各场景CASE分支的定制化开发、尽量保证 Web端&Electron端 尽快先跑起来达成基本需求。
后续优化
- 将异步IO,放在UI展示之后,需要UI先展示Loading或“文件夹大小计算中”等中间状态。
- 对交互体验肯定有优化效果,但对交互流程的时长,只可能会消耗更多。
- 将文件读取压缩的能力放 Native NodeJS 层。
- 需要考虑拖拽、Ctrl+V 怎么从渲染进程拿到 NodeJS,去做监听响应。
- 需要考虑清楚怎么解决UI交互、文件发送时在渲染层的问题。
- 会增加跨进程数据通讯或异步数据更新的数据交互频次(耗时增加)。
Comments
可以发邮件 huzunjie@pyzy.net 或移步到 https://github.com/huzunjie/blog.pyzy.net/issues 评论交流。