1. 引言
本节为非规范性内容。
本文件定义了文件系统 API 的基础架构。此外,还定义了一个
API,使网站可以在不先请求用户授权的情况下,访问文件系统目录。这使得网站能够在用户选择保存位置之前,将数据保存到磁盘,而无需为此类文件使用完全不同的存储机制和 API。入口为 navigator.storage.getDirectory()
方法。
2. 文件与目录
2.1. 基本概念
文件系统条目是指文件条目或目录条目。
每个文件系统条目都关联有一个查询访问算法,接收“read
”或“readwrite
”模式,返回一个文件系统访问结果。除非另有说明,否则该算法返回的文件系统访问结果的权限状态为“denied
”,错误名称为空字符串。
每个文件系统条目还关联有一个请求访问算法,参数与查询访问相同,返回的文件系统访问结果类似,除非另有说明,否则权限状态为“denied”,错误名称为空字符串。
文件系统访问结果是一个结构体,封装了查询或请求文件系统访问的结果。它有如下字段:
- 权限状态
- 错误名称
-
一个字符串。如果权限状态为“
granted
”,则必须为空字符串;否则应为DOMException
错误名称表中的名称。大多数情况下,权限未“granted”时,名称应为“NotAllowedError
”。
相关规范可将此 API 视为强功能。然而,与其他强功能的权限请求算法可能抛出异常不同,文件系统条目的“查询访问”和“请求访问”算法必须在文件系统队列中并行运行,不允许抛出异常。如果算法返回的错误名称非空,调用者应通过队列存储任务来拒绝(reject)。
注意: 仅实现本规范而未实现其依赖规范的实现,无需实现文件系统条目的“查询访问”和“请求访问”算法。
应考虑将访问检查算法关联到 FileSystemHandle。
每个文件系统条目有一个关联的名称(字符串)。
有效文件名指非空字符串,不等于“.”或“..”,且不包含“/”或底层平台作为路径分隔符的任何其他字符。
注意: 这意味着在 Windows 上名称中不能包含“\”,但在其他操作系统可能允许。此外,底层文件系统可能对名称有进一步限制,仅为有效文件名并不保证能成功创建该文件或目录。
应考虑对文件名施加更严格的规范性限制,而不是完全交由底层文件系统决定。
文件条目还包括二进制数据(字节序列)、修改时间戳(自 Unix 纪元以来的毫秒数)、锁(仅允许为“open”“taken-exclusive”“taken-shared”之一的字符串)和共享锁数量(当前持有的共享锁数量)。
用户代理拥有一个关联的文件系统队列,即新建的并行队列。所有文件系统操作都应使用此队列。
- 设 lock 为该文件的锁值。
- 设 count 为该文件的共享锁数量。
- 若 value 为“exclusive”:
- 若 lock 为“open”:
- 将 lock 设为“taken-exclusive”。
- 返回“success”。
- 若 lock 为“open”:
- 若 value 为“shared”:
- 若 lock 为“open”:
- 将 lock 设为“taken-shared”。
- 将 count 设为 1。
- 返回“success”。
- 否则,若 lock 为“taken-shared”:
- 将 count 加 1。
- 返回“success”。
- 若 lock 为“open”:
- 返回“failure”。
注意:这些步骤必须在文件系统队列中执行。
- 设 lock 为该文件的锁值。
- 设 count 为该文件的共享锁数量。
- 若 lock 为“taken-shared”:
- 将 count 减 1。
- 若 count 为 0,将 lock 设为“open”。
- 否则,将 lock 设为“open”。
注意:这些步骤必须在文件系统队列中执行。
注意:锁用于防止文件的并发修改。FileSystemWritableFileStream
需要共享锁,FileSystemSyncAccessHandle
需要独占锁。
目录条目还包含一个子集(子条目),每个子项都是文件系统条目,可以是文件条目或目录条目。
文件系统条目 entry 应至多被一个目录条目的子条目包含,该目录条目被称为 entry 的父项。若不存在这样的目录条目,则其父项为 null。
注意:不同的文件系统条目可表示磁盘上的同一文件或目录,因此两者可拥有不同父项,或一者有父项而另一者无。
文件系统条目可以(但不必须)由宿主操作系统的本地文件系统上的实际文件支持,因此条目的二进制数据、修改时间戳、子条目等可能被此规范之外的应用修改。外部变化如何反映到此规范的数据结构,以及反之,留给具体实现决定。
文件系统条目 a若等于 b,或二者由本地文件系统上的同一文件或目录支持,则为同一条目。
要解析给定 child 相对于 root 的路径:
- 设 result 为新建的 promise。
- 将以下步骤加入文件系统队列:
- 若 child 的 locator 的 root 不等于 root 的 locator 的 root,则 resolve result 为 null,终止。
- 设 childPath 为 child 的 locator 的 path。
- 设 rootPath 为 root 的 locator 的 path。
- 若 childPath 与 rootPath 为同一路径,则 resolve result 为空列表,终止。
- 若 rootPath 的长度大于 childPath,则 resolve result 为 null,终止。
- 对于 rootPath 的每个索引 index:若 rootPath[index] 不等于 childPath[index],则 resolve result 为 null,终止。
- 设 relativePath 为空列表。
- 对于 index 从 rootPath 长度到 childPath 长度(不含),将 childPath[index] 添加到 relativePath。
- resolve result 为 relativePath。
- 返回 result。
文件系统定位器表示文件系统条目的一个可能位置。文件系统定位器可以是文件定位器或目录定位器。
每个文件系统定位器有一个关联的路径(文件系统路径),类型(FileSystemHandleKind),以及根(文件系统根)。
可考虑为每个定位器分配存储桶。
文件定位器是类型为“file”的文件系统定位器。目录定位器是类型为“directory”的文件系统定位器。
文件系统根是一个实现自定义的字符串。
假设一个文件系统定位器
locator 定位到存储桶文件系统根目录下路径为 data/drafts/example.txt
的文件条目,locator
的类型为“file”,路径为 [ "data", "drafts", "example.txt" ],根可包含例如存储桶、磁盘驱动器等相关信息。
如果 a 和 b 的类型、根和路径均相同,则为同一个定位器。
定位条目算法,给定一个文件系统定位器 locator,执行如下实现自定义步骤,但需满足:
- 若 locator 是文件定位器,返回文件条目或 null。
- 若 locator 是目录定位器,返回目录条目或 null。
- 如果返回非 null 条目,则:
- 获取 locator 得到 entry,且期间未有中间文件系统操作。
- entry 的名称为 locator 路径的最后一个元素。
获取定位器算法,给定文件系统条目 entry,执行如下实现自定义步骤,但需满足:
- 若 entry 是文件条目,返回文件定位器。
- 若 entry 是目录条目,返回目录定位器。
- 若返回 locator,则定位 locator 得到 entry,且未有中间操作。
- entry 的名称为 locator 路径的最后一个元素。
文件系统路径是由一个或多个字符串组成的列表。可以是映射到磁盘实际位置或内存的虚拟路径,也可以直接对应本地文件系统路径,或根本不对应任何磁盘文件。其实际物理位置由实现决定。
令 path 为 [ "data", "drafts", "example.txt" ]。不要求磁盘上真的存在名为 example.txt 的文件。
若 a 和 b 长度一致,且每个索引的元素均相等,则为同一路径。
文件系统定位器的内容(包括路径)不应全部暴露给网站进程。只有当定位器后续被相对于父目录定位器“解析”时,网站才可能知晓路径组件。
2.2. FileSystemHandle
接口
enum {
FileSystemHandleKind ,
"file" , }; [
"directory" Exposed =(Window ,Worker ),SecureContext ,Serializable ]interface {
FileSystemHandle readonly attribute FileSystemHandleKind kind ;readonly attribute USVString name ;Promise <boolean >isSameEntry (FileSystemHandle ); };
other
FileSystemHandle
对象关联有一个 定位器(即文件系统定位器)。
注意:多个 FileSystemHandle
对象可以具有相同的文件系统定位器。
当 FileSystemHandle
的定位器的路径第一个元素为空字符串时,该对象位于 bucket 文件系统中。
注意: 这是一个特殊约定,仅有 bucket 文件系统的根目录路径才会包含空字符串,见 getDirectory()
。路径的其它元素必须是有效文件名。
可考虑为每个定位器分配存储桶以优化此设计。
FileSystemHandle
对象是可序列化对象。
它们的序列化步骤如下,给定 value、serialized 和 forStorage:
- 将 serialized.[[Origin]] 设为 value 的相关设置对象的源(origin)。
- 将 serialized.[[Locator]] 设为 value 的定位器。
它们的反序列化步骤如下,给定 serialized 和 value:
- 如果 serialized.[[Origin]] 与 value 的相关设置对象的源(origin)不同源,则抛出 "
DataCloneError
" 异常。 - 将 value 的定位器设为 serialized.[[Locator]]。
- handle .
kind
-
如果 handle 是
FileSystemFileHandle
,则返回 "file";如果是FileSystemDirectoryHandle
,则返回 "directory"。可用于区分目录内容遍历时的文件与目录。 - handle .
name
-
返回 handle 定位器路径的最后一个部分。
kind
的获取步骤:返回 this
的定位器的类型。
name
的获取步骤:返回 this
的定位器路径的最后一个元素(字符串)。
2.2.1.isSameEntry()
方法
- same = await handle1 .
isSameEntry
(handle2) -
如果 handle1 和 handle2 表示同一个文件或目录,则返回 true。
isSameEntry(other)
方法的实现步骤如下:
- 设 realm 为
this
的相关 Realm。 - 设 p 为在 realm 中新建的 promise。
- 将以下步骤加入文件系统队列:
- 如果
this
的定位器与 other 的定位器为同一个定位器,resolve p 为 true。 - 否则,resolve p 为 false。
- 如果
- 返回 p。
2.3. FileSystemFileHandle
接口
dictionary {
FileSystemCreateWritableOptions boolean =
keepExistingData false ; }; [Exposed =(Window ,Worker ),SecureContext ,Serializable ]interface :
FileSystemFileHandle FileSystemHandle {Promise <File >getFile ();Promise <FileSystemWritableFileStream >createWritable (optional FileSystemCreateWritableOptions = {}); [
options Exposed =DedicatedWorker ]Promise <FileSystemSyncAccessHandle >createSyncAccessHandle (); };
注意: FileSystemFileHandle
的定位器类型为 "file"。
FileSystemFileHandle
,给定目录定位器
parentLocator 和字符串 name(在 Realm realm 中):
- 创建 handle,为 realm 中新的
FileSystemFileHandle
。 - 设 childType 为 "file"。
- 设 childRoot 为 parentLocator 的根的副本。
- 设 childPath 为 parentLocator 路径的克隆,并追加 name。
- 将 handle 的定位器设为:类型为 childType,根为 childRoot,路径为 childPath 的文件系统定位器。
- 返回 handle。
FileSystemFileHandle
,给定文件系统根
root 和文件系统路径 path(在 Realm realm 中):
- 创建 handle,为 realm 中新的
FileSystemFileHandle
。 - 将 handle 的定位器设为:类型为 "file",根为 root,路径为 path 的文件系统定位器。
- 返回 handle。
FileSystemFileHandle
对象是可序列化对象,其序列化和反序列化步骤与 FileSystemHandle
相同。
2.3.1.getFile()
方法
getFile()
方法的实现步骤如下:
- 设 result 为新的 promise。
- 设 locator 为
this
的定位器。 - 设 global 为
this
的相关全局对象。 - 将以下步骤加入文件系统队列:
- 设 entry 为用 locator 定位的条目。
- 设 accessResult 为 entry 的“查询访问”算法结果,模式为 "read"。
- 队列一个存储任务(绑定 global)以执行以下步骤:
- 如果 accessResult 的权限状态不是 "granted",则以其错误名为参数 reject result,类型为
DOMException
,并终止。 - 如果 entry 为 null,则以 "
NotFoundError
" DOMException reject result,并终止。 - 断言 entry 是文件条目。
- 创建新的
File
对象 f。 - 将 f 的快照状态设为 entry 当前状态。
- 将 f 的底层字节序列设为 entry 的二进制数据的副本。
- 将 f 的 name 设为 entry 的名称。
- 将 f 的 lastModified 设为 entry 的修改时间戳。
- 将 f 的 type 设为实现自定义的值(例如基于 entry 的名称或扩展名)。
- resolve result 为 f。
- 如果 accessResult 的权限状态不是 "granted",则以其错误名为参数 reject result,类型为
- 返回 result。
2.3.2. createWritable()
方法
- stream = await fileHandle .
createWritable()
- stream = await fileHandle .
createWritable
({keepExistingData
: true/false }) - stream = await fileHandle .
-
返回一个
FileSystemWritableFileStream
,可用于写入文件。通过 stream 所做的更改,只有在流关闭后才会反映到 fileHandle 的定位器所定位的文件条目中。用户代理会尽量确保不会发生部分写入,即文件要么保持原内容,要么包含流关闭前写入的所有数据。通常实现方式为先将数据写入临时文件,只有在写入流关闭时才用临时文件替换原文件条目。
如果
keepExistingData
为 false 或未指定,则临时文件从空开始,否则会先复制原文件内容到临时文件。创建
FileSystemWritableFileStream
时会对目标文件条目获取共享锁,这会阻止在流关闭前为该条目创建FileSystemSyncAccessHandle
。
关于 createWritable "inPlace" 模式的讨论,见 WICG/file-system-access issue #67。目前
Chrome 尚未实现该功能,因与恶意软件检测等机制冲突。对于 bucket 文件系统,可用 FileSystemSyncAccessHandle
接口实现原地写入。
createWritable(options)
方法实现步骤如下:
- 设 result 为新的 promise。
- 设 locator 为
this
的定位器。 - 设 realm 为
this
的相关 Realm。 - 设 global 为
this
的相关全局对象。 - 将以下步骤加入文件系统队列:
- 设 entry 为用 locator 定位的条目。
- 设 accessResult 为 entry 的“请求访问”算法结果,模式为 "readwrite"。
- 如果 accessResult 权限状态不是 "granted",则队列存储任务(global)reject result,异常类型为 accessResult 的错误名。
- 如果 entry 为 null,则队列存储任务(global)reject result,异常类型为 "
NotFoundError
"。 - 断言 entry 是文件条目。
- 设 lockResult 为对 entry 获取“共享”锁的结果。
- 队列存储任务(global)执行:
- 如果 lockResult 为 "failure",则 reject result,异常类型为 "
NoModificationAllowedError
"。 - 创建 stream,即为 entry 创建新的
FileSystemWritableFileStream
(在 realm 中)。 - 如果 options["keepExistingData"] 为 true,则将 stream 的 [[buffer]] 设为 entry 二进制数据的副本。
- resolve result 为 stream。
- 如果 lockResult 为 "failure",则 reject result,异常类型为 "
- 返回 result。
2.3.3. createSyncAccessHandle()
方法
- handle = await fileHandle .
createSyncAccessHandle
() -
返回一个
FileSystemSyncAccessHandle
,可用于同步读写文件。通过 handle 所做的更改可能会立即反映到 fileHandle 的定位器所定位的文件条目中。可调用 flush 方法确保更改被写入文件。创建
FileSystemSyncAccessHandle
时会对目标文件条目获取独占锁,在句柄关闭前,无法再为该条目创建FileSystemSyncAccessHandle
或FileSystemWritableFileStream
。返回的
FileSystemSyncAccessHandle
提供同步方法,适合如 WebAssembly 这类异步开销较大的场景。目前,仅当 fileHandle 位于 bucket 文件系统时,该方法才会成功。
createSyncAccessHandle()
方法的实现步骤如下:
- 设 result 为新的 promise。
- 设 locator 为
this
的定位器。 - 设 realm 为
this
的相关 Realm。 - 设 global 为
this
的相关全局对象。 - 设 isInABucketFileSystem,若
this
位于 bucket 文件系统,则为 true,否则为 false。 - 将以下步骤加入文件系统队列:
- 设 entry 为用 locator 定位的条目。
- 设 accessResult 为 entry 的“请求访问”算法结果,模式为 "readwrite"。
- 如果 accessResult 权限状态不是 "granted",则队列存储任务(global)reject result,异常类型为 accessResult 的错误名。
- 如果 isInABucketFileSystem 为 false,则队列存储任务(global)reject
result,异常类型为 "
InvalidStateError
"。 - 如果 entry 为 null,则队列存储任务(global)reject result,异常类型为 "
NotFoundError
"。 - 断言 entry 是文件条目。
- 设 lockResult 为对 entry 获取“独占”锁的结果。
- 队列存储任务(global)执行:
- 如果 lockResult 为 "failure",则 reject result,异常类型为 "
NoModificationAllowedError
"。 - 创建 handle,即为 entry 创建新的
FileSystemSyncAccessHandle
(在 realm 中)。 - resolve result 为 handle。
- 如果 lockResult 为 "failure",则 reject result,异常类型为 "
- 返回 result。
2.4.
FileSystemDirectoryHandle
接口
dictionary {
FileSystemGetFileOptions boolean =
create false ; };dictionary {
FileSystemGetDirectoryOptions boolean =
create false ; };dictionary {
FileSystemRemoveOptions boolean =
recursive false ; }; [Exposed =(Window ,Worker ),SecureContext ,Serializable ]interface :
FileSystemDirectoryHandle FileSystemHandle {async iterable <USVString ,FileSystemHandle >;Promise <FileSystemFileHandle >getFileHandle (USVString ,
name optional FileSystemGetFileOptions = {});
options Promise <FileSystemDirectoryHandle >getDirectoryHandle (USVString ,
name optional FileSystemGetDirectoryOptions = {});
options Promise <undefined >removeEntry (USVString ,
name optional FileSystemRemoveOptions = {});
options Promise <sequence <USVString >?>resolve (FileSystemHandle ); };
possibleDescendant
注意: FileSystemDirectoryHandle
的定位器类型为 "directory"。
FileSystemDirectoryHandle
,给定目录定位器 parentLocator 和字符串 name(在
Realm realm 中):
- 创建 handle,为 realm 中新的
FileSystemDirectoryHandle
。 - 设 childType 为 "directory"。
- 设 childRoot 为 parentLocator 的根的副本。
- 设 childPath 为 parentLocator 路径的克隆,并追加 name。
- 将 handle 的定位器设为:类型为 childType,根为 childRoot,路径为 childPath 的文件系统定位器。
- 返回 handle。
FileSystemDirectoryHandle
,给定文件系统根
root 和文件系统路径 path(在 Realm realm 中):
- 创建 handle,为 realm 中新的
FileSystemDirectoryHandle
。 - 将 handle 的定位器设为:类型为 "directory",根为 root,路径为 path 的文件系统定位器。
- 返回 handle。
FileSystemDirectoryHandle
对象是可序列化对象,其序列化和反序列化步骤与 FileSystemHandle
相同。
2.4.1. 目录遍历
- for await (let [name, handle] of directoryHandle) {}
- for await (let [name, handle] of directoryHandle . entries()) {}
- for await (let handle of directoryHandle . values()) {}
- for await (let name of directoryHandle . keys()) {}
- for await (let [name, handle] of directoryHandle . entries()) {}
-
遍历所有父项为 directoryHandle 的定位器所定位的目录条目的子项。遍历过程中创建或删除的条目可能会被包含或不被包含,不做保证。
未来可能会为异步可迭代器声明添加参数,例如递归遍历。
FileSystemDirectoryHandle
handle 及其异步迭代器 iterator 获取下一个迭代结果,步骤如下:
-
令 promise 为新建的 promise。
-
-
令 accessResult 为 directory 的 查询访问(参数为 "
read
")的结果。 -
入队存储任务(在 handle 的 相关全局对象 上)以执行以下步骤:
-
如果 accessResult 的权限状态不是 "
granted
",则拒绝 promise,错误为 accessResult 的error name 的DOMException
,终止这些步骤。 -
如果 directory 为
null
,拒绝 promise,错误为 "NotFoundError
" 的DOMException
,终止这些步骤。 -
令 child 为 directory 的 children 中不在 iterator 的 past results 中的、名字为 name 的文件系统条目;如果不存在则为
null
。注意: 此处有意未指定遍历顺序,不同平台文件系统实现和返回顺序不同,规范不保证遍历顺序。
-
如果 child 为
null
,则 resolve promise,值为undefined
,终止这些步骤。 -
追加 child 的 name 到 iterator 的 past results。
-
如果 child 是文件条目:
-
令 result 为创建子
FileSystemFileHandle
的结果,参数为 handle 的 locator 和 child 的 name,作用域为 handle 的 相关 Realm。
-
-
否则:
-
令 result 为创建子
FileSystemDirectoryHandle
的结果,参数为 handle 的 locator 和 child 的 name,作用域为 handle 的 相关 Realm。
-
-
-
返回 promise。
2.4.2. getFileHandle()
方法
- fileHandle = await directoryHandle .
getFileHandle
(name)- fileHandle = await directoryHandle .
getFileHandle
(name, {create
: false }) - fileHandle = await directoryHandle .
-
返回 directoryHandle 的定位器所定位的目录条目下名为 name 的文件句柄。如果文件不存在,则 promise 拒绝。
- fileHandle = await directoryHandle .
getFileHandle
(name, {create
: true }) -
返回 directoryHandle 的定位器所定位的目录条目下名为 name 的文件句柄。如果文件不存在则新建文件,如果无法创建则 promise 拒绝(如同名目录已存在、文件名非法,或出于安全原因禁止创建)。
此操作需要写权限,即使目标文件已存在。如果当前句柄无写权限,可能会弹出权限请求提示。若只需只读访问且无需写权限,可调用
{ create: false }
。
getFileHandle(name, options)
方法实现步骤如下:
- 设 result 为新的 promise。
- 设 realm 为
this
的相关 Realm。 - 设 locator 为
this
的定位器。 - 设 global 为
this
的相关全局对象。 - 将以下步骤加入文件系统队列:
- 若 name 不是有效文件名,则队列存储任务(global)reject result,异常类型为 TypeError,并终止。
- 设 entry 为用 locator 定位的条目。
- 若 options["create"] 为 true,则:
- 设 accessResult 为 entry 的“请求访问”算法结果(模式 "readwrite")。
- 否则:
- 设 accessResult 为 entry 的“查询访问”算法结果(模式 "read")。
- 队列一个存储任务(global)以执行:
- 如果 accessResult 权限状态不是 "granted",则 reject result,异常类型为 accessResult 的错误名,并终止。
- 如果 entry 为 null,则 reject result,异常类型为 NotFoundError,并终止。
- 断言 entry 是目录条目。
- 遍历 entry 的子项:
- 若 child 的 name 等于 name:
- 若 child 是目录条目,则 reject result,异常类型为 TypeMismatchError,并终止。
- resolve result 为用 locator 和 child 的
name 在
realm 下创建的子
FileSystemFileHandle
,并终止。
- 若 child 的 name 等于 name:
- 若 options["create"] 为 false,则 reject result,异常类型为 NotFoundError,并终止。
- 新建 child,类型为文件条目,其“查询访问”与“请求访问”算法继承自 entry。
- 将 child 的 name 设为 name。
- 将 child 的二进制数据设为空字节序列。
- 将 child 的修改时间戳设为当前时间。
- 将 child 添加到 entry 的子项集合中。
- 如果底层文件系统创建 child 时抛出异常,则 reject result 并终止。
- resolve result 为用 locator 和 child 的 name 在
realm
下创建的子
FileSystemFileHandle
。
- 返回 result。
2.4.3. getDirectoryHandle()
方法
- subdirHandle = await directoryHandle .
getDirectoryHandle
(name)- subdirHandle = await directoryHandle .
getDirectoryHandle
(name, {create
: false }) - subdirHandle = await directoryHandle .
-
返回 directoryHandle 的定位器所定位的目录条目下名为 name 的子目录句柄。如果该目录不存在,则 promise 拒绝。
- subdirHandle = await directoryHandle .
getDirectoryHandle
(name, {create
: true }) -
返回 directoryHandle 的定位器所定位的目录条目下名为 name 的子目录句柄。如果该目录不存在则新建目录,若无法创建(如同名文件已存在或名称非法)则 promise 拒绝。
此操作需要写权限,即使目标目录已存在。如果当前句柄无写权限,可能会弹出权限请求提示。若仅需只读访问可调用
{ create: false }
。
getDirectoryHandle(name, options)
方法实现步骤如下:
- 设 result 为新的 promise。
- 设 realm 为
this
的相关 Realm。 - 设 locator 为
this
的定位器。 - 设 global 为
this
的相关全局对象。 - 将以下步骤加入文件系统队列:
- 若 name 不是有效文件名,则队列存储任务(global)reject result,异常类型为 TypeError,并终止。
- 设 entry 为用 locator 定位的条目。
- 若 options["create"] 为 true,则:
- 设 accessResult 为 entry 的“请求访问”算法结果(模式 "readwrite")。
- 否则:
- 设 accessResult 为 entry 的“查询访问”算法结果(模式 "read")。
- 队列一个存储任务(global)以执行:
- 如果 accessResult 权限状态不是 "granted",则 reject result,异常类型为 accessResult 的错误名,并终止。
- 如果 entry 为 null,则 reject result,异常类型为 NotFoundError,并终止。
- 断言 entry 是目录条目。
- 遍历 entry 的子项:
- 若 child 的 name 等于 name:
- 若 child 是文件条目,则 reject result,异常类型为 TypeMismatchError,并终止。
- resolve result 为用 locator 和 child 的
name 在
realm 下创建的子
FileSystemDirectoryHandle
,并终止。
- 若 child 的 name 等于 name:
- 若 options["create"] 为 false,则 reject result,异常类型为 NotFoundError,并终止。
- 新建 child,类型为目录条目,其“查询访问”与“请求访问”算法继承自 entry。
- 将 child 的 name 设为 name。
- 将 child 的 children 设为空集合。
- 将 child 添加到 entry 的子项集合中。
- 如果底层文件系统创建 child 时抛出异常,则 reject result 并终止。
- resolve result 为用 locator 和 child 的 name 在
realm
下创建的子
FileSystemDirectoryHandle
。
- 返回 result。
2.4.4. removeEntry()
方法
- await directoryHandle .
removeEntry
(name)- await directoryHandle .
removeEntry
(name, {recursive
: false }) - await directoryHandle .
-
如果 directoryHandle 的定位器所定位的目录条目包含名为 name 的文件,或空目录,则尝试删除该文件或目录。
删除不存在的文件或目录视为成功;尝试删除非空目录会导致 promise 拒绝。
- await directoryHandle .
removeEntry
(name, {recursive
: true }) -
删除 directoryHandle 的定位器所定位的目录条目下名为 name 的文件系统条目。如果该条目为目录,将递归删除其所有内容。
删除不存在的文件或目录视为成功。
removeEntry(name, options)
方法实现步骤如下:
- 设 result 为新的 promise。
- 设 locator 为
this
的定位器。 - 设 global 为
this
的相关全局对象。 - 将以下步骤加入文件系统队列:
- 若 name 不是有效文件名,则队列存储任务(global)reject result,异常类型为 TypeError,并终止。
- 设 entry 为用 locator 定位的条目。
- 设 accessResult 为 entry 的“请求访问”算法结果(模式 "readwrite")。
- 队列一个存储任务(global)以执行:
- 如果 accessResult 权限状态不是 "granted",则 reject result,异常类型为 accessResult 的错误名,并终止。
- 如果 entry 为 null,则 reject result,异常类型为 NotFoundError,并终止。
- 断言 entry 是目录条目。
- 遍历 entry 的子项:
- 若 child 的 name 等于 name:
- 如果 child 是目录条目:
- 如果 child 的 children 不为空且 options["recursive"] 为 false,则 reject result,异常类型为 InvalidModificationError,并终止。
- 从 entry 的 children 中移除 child。
- 如果底层文件系统移除 child 时抛出异常,则 reject result 并终止。
- resolve result 为 undefined。
- 如果 child 是目录条目:
- 若 child 的 name 等于 name:
- 若未找到 name,则 reject result,异常类型为 NotFoundError。
- 返回 result。
2.4.5. resolve()
方法
- path = await directory .
resolve
(child) -
如果 child 等于 directory,则 path 是空数组。
如果 child 是 directory 的直接子项,则 path 是包含 child 名称的数组。
如果 child 是 directory 的后代,则 path 是包含所有中间目录及 child 名称(作为最后一个元素)的数组。例如,若 directory 表示
/home/user/project
,child 表示/home/user/project/foo/bar
,则返回['foo', 'bar']
。否则(directory 与 child 无父子关系),path 为 null。
// 假设已获得有效目录句柄。 const dir_ref= current_project_dir; if ( ! dir_ref) return ; // 获取文件句柄: const file_ref= await dir_ref. getFileHandle( filename, { create: true }); // 检查 file_ref 是否位于 dir_ref 内: const relative_path= await dir_ref. resolve( file_ref); if ( relative_path=== null ) { // 不在 dir_ref 内。 } else { // relative_path 是名称数组,表示从 dir_ref 到 file_ref 的相对路径: assert relative_path. pop() === file_ref. name; let entry= dir_ref; for ( const nameof relative_path) { entry= await entry. getDirectory( name); } entry= await entry. getFile( file_ref. name); // 此时 entry 与 file_ref 对应同一磁盘文件。 assertawait entry. isSameEntry( file_ref) === true ; }
2.5.
FileSystemWritableFileStream
接口
enum {
WriteCommandType ,
"write" ,
"seek" , };
"truncate" dictionary {
WriteParams required WriteCommandType ;
type unsigned long long ?;
size unsigned long long ?; (
position BufferSource or Blob or USVString )?; };
data typedef (BufferSource or Blob or USVString or WriteParams ); [
FileSystemWriteChunkType Exposed =(Window ,Worker ),SecureContext ]interface :
FileSystemWritableFileStream WritableStream {Promise <undefined >write (FileSystemWriteChunkType );
data Promise <undefined >seek (unsigned long long );
position Promise <undefined >truncate (unsigned long long ); };
size
FileSystemWritableFileStream
具有关联的 [[file]](一个 文件条目)。
FileSystemWritableFileStream
具有关联的 [[buffer]](一个 字节序列),初始为空。
注意:该缓冲区可能变得非常大,因此实现时通常不会将其全部保存在内存中,而是使用临时文件。所有对 [[buffer]] 的访问都通过返回 Promise 的方法和算法完成,因此尽管这些操作看起来是同步的,实际实现可为异步。
FileSystemWritableFileStream
具有关联的 [[seekOffset]](一个数字),初始值为 0。
FileSystemWritableFileStream
对象是 WritableStream
对象的扩展,带有便捷方法并操作单个磁盘文件。
创建后,会有一个底层 sink 可用,流对象立即可用。所有操作都是可排队的,生产者能够响应背压。
底层 sink 的 write 方法(即 WritableStreamDefaultWriter’s write()
)可接受字节类型数据或
WriteParams
作为输入。
FileSystemWritableFileStream
有一个文件位置游标,初始为文件头部偏移 0 字节。
使用 write()
或通过 WritableStream 能力(即 WritableStreamDefaultWriter’s write()
),该位置会根据写入的字节数推进。
同样,将 ReadableStream
管道到 FileSystemWritableFileStream
时,该位置也会根据流经的字节数更新。
FileSystemWritableFileStream
,给定 文件条目 file 和 Realm realm,步骤如下:
- 创建 stream,为 realm 内新的
FileSystemWritableFileStream
。 - 将 stream 的 [[file]] 设为 file。
- 定义 writeAlgorithm,其参数为 chunk,返回以 stream 和 chunk 运行“写入块”算法的结果。
- 定义 closeAlgorithm,其步骤如下:
- 设 closeResult 为新的 promise。
- 将以下步骤加入文件系统队列:
- 设 accessResult 为 file 的“查询访问”算法结果,模式为 "readwrite"。
- 队列一个存储任务(file 的全局对象)执行:
- 如果 accessResult 权限状态不是 "granted",则 reject closeResult,异常类型为 accessResult 的错误名,并终止。
- 执行实现自定义的恶意软件和安全检查。若检查失败,reject closeResult,异常类型为 AbortError,并终止。
- 将 stream 的 [[file]] 的二进制数据设为 stream 的 [[buffer]]。如抛异常则 reject closeResult 并终止。注:预期原子性更新磁盘文件内容。
- 将以下步骤加入文件系统队列:
- 释放 stream 的 [[file]] 锁。
- 队列存储任务(file 的全局对象),resolve closeResult 为 undefined。
- 返回 closeResult。
- 定义 abortAlgorithm,其步骤如下:
- 将以下步骤加入文件系统队列:
- 释放 stream 的 [[file]] 锁。
- 将以下步骤加入文件系统队列:
- 设 highWaterMark 为 1。
- 定义 sizeAlgorithm,返回 1。
- 按如下方式设置 stream: writeAlgorithm 设为 writeAlgorithm,closeAlgorithm 设为 closeAlgorithm,abortAlgorithm 设为 abortAlgorithm,highWaterMark 设为 highWaterMark,sizeAlgorithm 设为 sizeAlgorithm。
- 返回 stream。
FileSystemWritableFileStream
stream 和 chunk,执行如下步骤:
- 将 input 设为将 chunk 转换为
FileSystemWriteChunkType
的结果。如果抛出异常,则返回带该异常的 rejected promise。 - 设 p 为新的 promise。
- 将以下步骤加入文件系统队列:
- 设 accessResult 为 stream 的 [[file]] 的“查询访问”算法结果(模式 "readwrite")。
- 队列存储任务(stream 的全局对象)以执行:
- 如果 accessResult 权限状态不是 "granted",则 reject p,异常类型为 accessResult 的错误名,并终止。
- 若 input 为字典,则 command 为 input["type"],否则为 "write"。
- 如果 command 为 "write":
- 如果 input 是 undefined,或为字典但无 "data" 字段,则 reject p,异常为 TypeError,并终止。
- 设 data 为 input["data"](若为字典)或 input 本身。
- 设 writePosition 为 stream 的 [[seekOffset]]。
- 若 input 为字典且含 "position",则 writePosition 设为该值。
- 设 oldSize 为 stream 的 [[buffer]] 长度。
- 若 data 是 BufferSource,则 dataBytes 为其副本。
- 否则若 data 是 Blob:
- 将 dataBytes 设为读取 data 的结果。若抛异常则 reject p 并终止。
- 否则:
- 断言 data 是 USVString。
- dataBytes 设为 UTF-8 编码 data 的结果。
- 如果 writePosition 大于 oldSize,在 [[buffer]] 尾部追加 writePosition - oldSize 个 0x00 字节(NUL)。实现需表现为内容被 NUL 填充但可用稀疏文件。
- 设 head 为 [[buffer]] 的前 writePosition 字节。
- 设 tail 为空字节序列。
- 若 writePosition + dataBytes 长度小于 oldSize:
- tail 设为 [[buffer]] 的后 oldSize - (writePosition + dataBytes 长度) 字节。
- 将 [[buffer]] 设为 head + dataBytes + tail。
- 若上一步操作超过存储配额,则 reject p,异常为 QuotaExceededError,并终止,且 [[buffer]] 保持原状。存储配额仅适用于 bucket 文件系统,但其他磁盘空间不足也可能导致失败。
- 将 [[seekOffset]] 设为 writePosition + dataBytes 长度。
- resolve p。
- 否则,如果 command 为 "seek":
- 断言 chunk 是字典。
- 若无 "position",则 reject p,异常为 TypeError,并终止。
- 将 [[seekOffset]] 设为 chunk["position"]。
- resolve p。
- 否则,如果 command 为 "truncate":
- 断言 chunk 是字典。
- 若无 "size" 字段,则 reject p,异常为 TypeError,并终止。
- 设 newSize 为 chunk["size"]。
- 设 oldSize 为 [[buffer]] 长度。
- 若 newSize 大于 oldSize:
- 将 [[buffer]] 设为 [[buffer]] 加上 newSize - oldSize 个 0x00 字节。
- 若超出存储配额,则 reject p,异常为 QuotaExceededError,并终止,且 [[buffer]] 保持原状。存储配额仅限 bucket 文件系统,但其他磁盘空间不足也可能失败。
- 否则如果 newSize 小于 oldSize:
- 将 [[buffer]] 设为前 newSize 字节。
- 若 [[seekOffset]] 大于 newSize,则设其为 newSize。
- resolve p。
- 返回 p。
2.5.1. write()
方法
- await stream .
write
(data)- await stream .
write
({type
: "write
",data
: data }) - await stream .
-
将 data 的内容写入与 stream 关联的文件(从当前文件游标偏移处)。
只有在关闭流后,变更才会真正写入磁盘文件。更改通常会先写入临时文件。
- await stream .
write
({type
: "write
",position
: position,data
: data }) -
将 data 的内容写入与 stream 关联的文件(从文件头部 position 字节处),并将文件游标更新至写入末尾位置。
只有在关闭流后,变更才会真正写入磁盘文件。更改通常会先写入临时文件。
- await stream .
write
({type
: "seek
",position
: position }) -
将文件游标偏移更新为距文件头部 position 字节处。
- await stream .
write
({type
: "truncate
",size
: size }) -
将与 stream 关联的文件尺寸调整为 size 字节。如果 size 大于当前文件大小,则用 null 字节补齐,否则截断文件。
当调用
truncate
时,文件游标也会被更新:若游标小于 size,保持不变;若大于 size,则设为 size。这样保证后续写入不会出错。只有在关闭流后,变更才会真正写入磁盘文件。更改通常会先写入临时文件。
write(data)
方法的实现步骤如下:
- 设 writer 为为
this
获取的 writer。 - 设 result 为向 writer 写入 data 的结果。
- 释放 writer。
- 返回 result。
2.5.2. seek()
方法
- await stream .
seek
(position) -
将文件游标偏移更新为距文件头部 position 字节处。
seek(position)
方法的实现步骤如下:
- 设 writer 为为
this
获取的 writer。 - 设 result 为向 writer 写入 «[ "type" → "seek", "position" → position ]» 的结果。
- 释放 writer。
- 返回 result。
2.5.3. truncate()
方法
truncate(size)
方法的实现步骤如下:
- 设 writer 为为
this
获取的 writer。 - 设 result 为向 writer 写入 «[ "type" → "truncate", "size" → size ]» 的结果。
- 释放 writer。
- 返回 result。
2.6.
FileSystemSyncAccessHandle
接口
dictionary { [
FileSystemReadWriteOptions EnforceRange ]unsigned long long ; }; [
at Exposed =DedicatedWorker ,SecureContext ]interface {
FileSystemSyncAccessHandle unsigned long long (
read AllowSharedBufferSource ,
buffer optional FileSystemReadWriteOptions = {});
options unsigned long long (
write AllowSharedBufferSource ,
buffer optional FileSystemReadWriteOptions = {});
options undefined truncate ([EnforceRange ]unsigned long long );
newSize unsigned long long getSize ();undefined flush ();undefined close (); };
FileSystemSyncAccessHandle
具有关联的 [[file]](一个 文件条目)。
FileSystemSyncAccessHandle
具有关联的 [[state]],一个字符串,只能为 "open
" 或
"closed
"。
FileSystemSyncAccessHandle
是能够对单个文件进行读写、获取和更改其大小的对象。
FileSystemSyncAccessHandle
提供同步方法。这在异步操作开销较大的场景(例如 WebAssembly)中带来更高性能。
FileSystemSyncAccessHandle
有一个 文件位置游标,初始为文件头部偏移 0 字节。
FileSystemSyncAccessHandle
,给定 文件条目 file 和 Realm realm,步骤如下:
- 创建 handle,为 realm 内新的
FileSystemSyncAccessHandle
。 - 将 handle 的 [[file]] 设为 file。
- 将 handle 的 [[state]] 设为 "
open
"。 - 返回 handle。
2.6.1. read()
方法
应指定当外部修改文件时 Access Handle 的行为。
read(buffer, FileSystemReadWriteOptions
:
options)
方法的实现步骤如下:
- 如果
this
的 [[state]] 为 "closed",则抛出 InvalidStateError。 - 设 bufferSize 为 buffer 的字节长度。
- 设 fileContents 为
this
的 [[file]] 的二进制数据。 - 设 fileSize 为 fileContents 的字节长度。
- 设 readStart 为 options["at"](如果存在),否则为
this
的文件位置游标。 - 如果底层文件系统不支持从 readStart 读取,则抛出 TypeError。
- 如果 readStart 大于 fileSize:
- 将
this
的文件位置游标设为 fileSize。 - 返回 0。
- 将
- 设 readEnd 为 readStart + (bufferSize − 1)。
- 如果 readEnd 大于 fileSize,则将 readEnd 设为 fileSize。
- 设 bytes 为 fileContents 的 readStart 到 readEnd 字节序列。
- 设 result 为 bytes 的字节长度。
- 如果前述步骤的读取操作失败:
- 如果部分读取并已知读取字节数,则设 result 为该字节数。
- 否则设 result 为 0。
- 设 arrayBuffer 为 buffer 的底层 buffer。
- 将 bytes 写入 arrayBuffer。
- 将
this
的文件位置游标设为 readStart + result。 - 返回 result。
2.6.2. write()
方法
write(buffer, FileSystemReadWriteOptions
:
options)
方法的实现步骤如下:
- 如果
this
的 [[state]] 为 "closed",抛出 InvalidStateError。 - 设 writePosition 为 options["at"](如果存在),否则为
this
的文件位置游标。 - 如果底层文件系统不支持从 writePosition 写入,则抛出 TypeError。
- 设 fileContents 为
this
的 [[file]] 的二进制数据的副本。 - 设 oldSize 为 fileContents 的字节长度。
- 设 bufferSize 为 buffer 的字节长度。
- 如果 writePosition 大于 oldSize,则在 fileContents 尾部追加 writePosition − oldSize 个 0x00 字节(NUL)。实现应表现为跳过的内容被 NUL 填充,可使用稀疏文件。
- 设 head 为 fileContents 的前 writePosition 字节。
- 设 tail 为空字节序列。
- 如果 writePosition + bufferSize 小于 oldSize:
- 设 tail 为 fileContents 的后 oldSize − (writePosition + bufferSize) 字节。
- 设 newSize 为 head 长度 + bufferSize + tail 长度。
- 如果 newSize − oldSize 超过可用存储配额,则抛出 QuotaExceededError。
- 将
this
的 [[file]] 的二进制数据设为 head + buffer 的内容 + tail。实现可直接写入操作系统,允许部分写入。 - 如果前述步骤修改二进制数据时失败:
- 如有部分写入且已知写入字节数:
- 设 bytesWritten 为写入 buffer 的字节数。
- 将
this
的文件位置游标设为 writePosition + bytesWritten。 - 返回 bytesWritten。
- 否则抛出 InvalidStateError。
- 如有部分写入且已知写入字节数:
- 将
this
的文件位置游标设为 writePosition + bufferSize。 - 返回 bufferSize。
2.6.3. truncate()
方法
truncate(newSize)
方法的实现步骤如下:
- 如果
this
的 [[state]] 为 "closed",则抛出 InvalidStateError。 - 设 fileContents 为
this
的 [[file]] 的二进制数据的副本。 - 设 oldSize 为
this
的 [[file]] 的二进制数据的长度。 - 如果底层文件系统不支持将文件大小设置为 newSize,则抛出 TypeError。
- 如果 newSize 大于 oldSize:
- 如果 newSize − oldSize 超过可用存储配额,则抛出 QuotaExceededError。
- 将
this
的 [[file]] 设为 fileContents 拼接 newSize − oldSize 个 0x00 字节的新字节序列。 - 如果上述步骤修改 [[file]] 的二进制数据失败,则抛出 InvalidStateError。
- 否则,如果 newSize 小于 oldSize:
- 将
this
的 [[file]] 设为 fileContents 的前 newSize 字节。 - 如果上述步骤修改 [[file]] 的二进制数据失败,则抛出 InvalidStateError。
- 将
- 如果
this
的文件位置游标大于 newSize,则将其设为 newSize。
2.6.4. getSize()
方法
- handle .
getSize()
-
返回与 handle 关联的文件大小(字节数)。
getSize()
方法的实现步骤如下:
- 如果
this
的 [[state]] 为 "closed",则抛出 InvalidStateError。 - 返回
this
的 [[file]] 的二进制数据的字节长度。
2.6.5. flush()
方法
flush()
方法的实现步骤如下:
- 如果
this
的 [[state]] 为 "closed",则抛出 InvalidStateError。 - 尝试将所有已缓存的文件内容修改同步到底层存储设备。
注意: 这也叫做文件刷新。部分文件系统(如内存型文件系统)无“磁盘”可刷写,此操作可能无实际效果。
2.6.6. close()
方法
close()
方法的实现步骤如下:
- 如果
this
的 [[state]] 为 "closed",直接返回。 - 将
this
的 [[state]] 设为 "closed"。 - 设 lockReleased 为 false。
- 设 file 为
this
的 [[file]]。 - 将以下步骤加入文件系统队列:
- 释放 file 的锁。
- 将 lockReleased 设为 true。
- 暂停,直到 lockReleased 为 true。
注意: 该方法不保证所有文件修改会立即写入底层存储。如需此保证,应先调用 flush()
。
3. 访问 Bucket 文件系统
bucket 文件系统
是一种 存储端点,其
标识符为 "fileSystem"
,类型为 « "local" »
,配额为 null。
存储端点应在 [storage] 标准中定义,而不是在此处定义。应将其合并到那里的表格中。
注意: 虽然用户代理通常会通过将 bucket 文件系统 的内容持久化到磁盘来实现,但并不意味着用户可以轻易访问其内容。同样,也不要求磁盘上实际存在与 bucket 文件系统 子项同名的文件或目录。
[SecureContext ]partial interface StorageManager {Promise <FileSystemDirectoryHandle >getDirectory (); };
- directoryHandle = await navigator . storage .
getDirectory()
-
返回 bucket 文件系统 的根目录。
getDirectory()
方法的实现步骤如下:
-
令 environment 为当前设置对象。
-
令 map 为以 environment 和
"fileSystem"
运行 获取本地存储 bottle 映射 的结果。如果返回失败,则返回带拒绝的 promise,错误为 "SecurityError
" 的DOMException
。 -
如果 map["root"] 不存在:
-
令 path 为 « 空字符串 »。
-
令 handle 为 创建新的
FileSystemDirectoryHandle
,参数为 root 和 path,作用域为当前 Realm 的结果。注意: root 可能包含如存储桶等相关标识信息。
-
返回带解析的 promise,值为 handle。
致谢
特别感谢 Alex Danilo, Anne van Kesteren, Anoesj Sadraee, Austin Sullivan, Chase Phillips, Daseul Lee, Dru Knox, Edgar Chen, Emanuel Krivoy, Hazim Mohamed, Ingvar Stepanyan, Jari Jalkanen, Joshua Bell, Kagami Sascha Rosylight, Marcos Cáceres, Martin Thomson, Olivier Yiptong, Philip Jägenstedt, Randell Jesup, Richard Stotz, Ruth John, Sid Vishnoi, Sihui Liu, Stefan Sauer, Thomas Steiner, Victor Costan,以及 Youenn Fablet 的大力支持!
本标准由 Marijn Kruisselbrink(Google,mek@chromium.org)编写。
知识产权声明
版权所有 © WHATWG(Apple,Google,Mozilla,Microsoft)。本作品采用 知识共享署名 4.0 国际许可协议 许可。若部分内容被纳入源代码,则该部分源代码采用 BSD 3-Clause 许可证。
本规范为 Living Standard。如需专利审查版本,请参见 Living Standard Review Draft。