08月02, 2023

浏览器与Electron端文件与文件夹打包发送

几个文件文件夹数据交互途径及技术方案及各自的弊端

1、点击按钮或菜单项的“选择文件”发送

技术原理

使用浏览器原生的文件输入框 HTMLInputElement,用户选择文件后可以再 onChange 检测输入框内容变化、从 HTMLInputElement.files 获取FileList。

文档可参考: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input/file

技术实现

支持多选(multiple="true")、未限制类型。 alt

在onChange 获取并校验后得到合法 FileList 后,使用 sendFiles 组件打开文件发送框或直接执行发送(用户选择了“选择文件直接发送”的)。 alt

特殊情况

按浏览器和操作系统不同,选择的内容会有特殊问题,比如:

  • 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")、未限制类型。 alt

在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 组件走后续交互逻辑。

文档和官方示例参考: https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransferItem/webkitGetAsEntry#%E7%A4%BA%E4%BE%8B

技术实现

支持多选&混选。 alt

特殊情况

能得到树状目录结构。

典型弊端

  • 从 DataTransferItems 到 FileList 流程偏长,存在异步IO读取,耗时长短取决于内容量(参见下图)。 alt
  • 用户拖拽内容量不可控,比如用户直接在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 写入剪切板,读取时得到。

alt

alt

特殊情况

  • 剪切板有可能会是截图、纯文本、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

技术实现

读取剪切板中的文件或文件夹路径: alt

通过文件路径得到目录关系、文件数据,并进行超限判定:

alt

将Native层的数据,转为渲染进程可用的FileList:

alt

粘贴按钮按下后,插入文件或文本或HTML到编辑器或sendFile 组件:

alt

特殊情况

getClipboardPaths 不可靠,比如windows端,无法读取到多个路径,如果 Ctrl+C 了多个,这里只能拿到其中的一个。

典型弊端

  • 从剪切板路径读取 到 FileList 流程更长,也存在异步IO读取,耗时长短取决于内容量,且更突出。
  • 用户Ctrl+C复制内容量、或文件夹里的内容量不可控,时间空间复杂度一定会很高。

使用JsZip进行文件夹压缩打包

alt

以上方案的大前提

目前是考虑最快方案:不考虑各种复杂的UI和产品交互设计、减少各场景CASE分支的定制化开发、尽量保证 Web端&Electron端 尽快先跑起来达成基本需求。

后续优化

  • 将异步IO,放在UI展示之后,需要UI先展示Loading或“文件夹大小计算中”等中间状态。
    • 对交互体验肯定有优化效果,但对交互流程的时长,只可能会消耗更多。
  • 将文件读取压缩的能力放 Native NodeJS 层。
    • 需要考虑拖拽、Ctrl+V 怎么从渲染进程拿到 NodeJS,去做监听响应。
    • 需要考虑清楚怎么解决UI交互、文件发送时在渲染层的问题。
    • 会增加跨进程数据通讯或异步数据更新的数据交互频次(耗时增加)。

本文链接:http://blog.pyzy.net/post/jszip.html

-- EOF --

Comments