1. 引言
本节为非规范性内容。
本文件定义了文件系统 API 的基础架构。此外,还定义了一个
API,使网站可以在不先请求用户授权的情况下,访问文件系统目录。这使得网站能够在用户选择保存位置之前,将数据保存到磁盘,而无需为此类文件使用完全不同的存储机制和 API。入口为 navigator.storage.getDirectory()
方法。
2. 文件与目录
2.1. 基本概念
每个文件系统条目都关联有一个查询访问算法,接收“read”或“readwrite”模式,返回一个文件系统访问结果。除非另有说明,否则该算法返回的文件系统访问结果的权限状态为“denied”,错误名称为空字符串。
每个文件系统条目还关联有一个请求访问算法,接收“read”或“readwrite”模式,返回一个文件系统访问结果。除非另有说明,否则该算法返回的文件系统访问结果的权限状态为“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,如果 a 是 b,或者 如果 a 和 b 由本地文件系统上的同一个文件或目录支持。
要 解析 一个 文件系统 定位器 child,相对于一个 目录定位器 root:
-
令 result 为 一个新的 promise。
-
-
如果 child的 定位器的 根 不是 root的 定位器的 根, 解决 result,值为 null,并中止这些步骤。
-
如果 childPath 被视为 与以下路径相同: rootPath, 解决 result,值为 « »,并中止这些步骤。
-
如果 rootPath的 大小 大于 childPath的 大小, 解决 result,值为 null,并中止这些 步骤。
-
-
如果 rootPath.[[index]] 不是 childPath.[[index]],则 解决 result,值为 null,并中止 这些步骤。
-
-
令 relativePath 为 « »。
-
对于每个 index(取值 范围 从 rootPath的 大小 到 rootPath的 大小, 不包含上界), 追加 childPath.[[index]] 到 relativePath。
-
解决 result,值为 relativePath。
-
-
返回 result。
文件系统定位器表示文件系统条目的一个可能位置。文件系统定位器可以是文件定位器或目录定位器。
每个文件系统定位器有一个关联的路径(文件系统路径),类型(FileSystemHandleKind),以及根(文件系统根)。
可考虑为每个定位器分配存储桶。
文件定位器是类型为“file”的文件系统定位器。目录定位器是类型为“directory”的文件系统定位器。
假设一个文件系统定位器
locator 定位到存储桶文件系统根目录下路径为 data/drafts/example.txt 的文件条目,locator
的类型为“file”,路径为 [ "data", "drafts",
"example.txt" ],根可包含例如存储桶、磁盘驱动器等相关信息。
如果文件系统定位器 a 的类型与文件系统定位器 b 的类型相同,a 的根与 b 的根相同,并且 a 的路径与 b 的路径是同一路径,则这两个定位器为同一个定位器。
一个 文件系统路径 是一个 列表,包含一个或多个 字符串。 这可以是映射到磁盘上或内存中实际位置的虚拟路径, 可以直接对应本地文件系统上的路径,也可能根本不 对应磁盘上的任何文件。实际物理位置对于 相应的 文件系统条目 而言是 由实现定义的。
令 path 为以下 列表
« "data", "drafts", "example.txt" »。
这里并不假定磁盘上的任何位置存在名为 example.txt 的文件。
一个 文件系统路径 a 被视为 与以下路径相同 :一个 文件系统路径 b,如果 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()。路径的其它元素必须是有效文件名。
可考虑为每个定位器分配存储桶以优化此设计。
它们的反序列化步骤如下,给定 serialized 和 value:
- 如果 serialized.[[Origin]] 与 value 的相关设置对象的源(origin)不同源,则抛出 "
DataCloneError"DOMException。 - 将 value 的定位器设为 serialized.[[Locator]]。
- handle .
kind -
如果 handle 是
FileSystemFileHandle,则返回 "file";如果是FileSystemDirectoryHandle,则返回 "directory"。可用于区分目录内容遍历时的文件与目录。 - handle .
name
name 的获取步骤:返回
this
的定位器路径的最后一个元素(字符串)。
2.2.1.isSameEntry()
方法
- same = await handle1 .
isSameEntry(handle2) -
如果 handle1 和 handle2 表示同一个文件或目录,则返回 true。
isSameEntry(other) 方法的实现步骤如下:
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
对象是可序列化对象,其序列化步骤和反序列化步骤与 FileSystemHandle
相同。
2.3.1.getFile()
方法
getFile() 方法的实现步骤如下:
- 设 result 为新的 promise。
- 设 locator 为
this的定位器。 - 设 global 为
this的相关全局对象。 - 将以下步骤入队至文件系统队列:
- 设 entry 为用 locator 定位条目得到的结果。
- 设 accessResult 为 entry 的“查询访问”算法结果,模式为 "read"。
- 将一个存储任务入队(绑定 global)以执行以下步骤:
- 如果 accessResult 的权限状态不是 "granted",则 拒绝
result,原因为一个名称等于 accessResult 的错误名的
DOMException,并终止。 - 如果 entry 为 null,则以 "
NotFoundError" DOMException 拒绝 result,并终止。 - 断言 entry 是文件条目。
- 创建新的
File对象 f。 - 将 f 的快照状态设为 entry 当前状态。
- 将 f 的底层字节序列设为 entry 的二进制数据的副本。
- 将 f 的 name 设为 entry 的名称。
- 将 f 的 lastModified 设为 entry 的修改时间戳。
- 将 f 的 type
设为实现自定义的值(例如基于 entry 的名称或扩展名)。
读取和快照行为仍需在 [FILE-API] 规范中更明确地定义;目前的规定仍较为粗略。
- 解决 result 为 f。
- 如果 accessResult 的权限状态不是 "granted",则 拒绝
result,原因为一个名称等于 accessResult 的错误名的
- 返回 result。
2.3.2. createWritable()
方法
- stream = await fileHandle .
createWritable()- stream = await fileHandle .
createWritable({keepExistingData: true/false }) - stream = await fileHandle .
-
返回一个
FileSystemWritableFileStream,可用于写入文件。通过 stream 所做的更改,只有在流关闭后才会反映到 fileHandle 的定位器所定位的文件条目中。用户代理会尽量确保不会发生部分写入,即文件要么保持原内容,要么包含流关闭前写入的所有数据。通常实现方式为先将数据写入临时文件,只有在写入流关闭时才用临时文件替换由 fileHandle 的定位器所定位的文件条目。
如果
keepExistingData为 false 或未指定,则临时文件从空开始,否则会先复制原文件内容到临时文件。创建
FileSystemWritableFileStream时会对 fileHandle 的定位器所定位的文件条目获取共享锁,这会阻止在流关闭前为该条目创建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)拒绝 result,使用名称为 accessResult 的错误名称的 DOMException,并中止这些步骤。
- 如果 entry 为 null,则将存储任务入队(global)拒绝
result,异常为 "
NotFoundError" DOMException,并中止这些步骤。 - 断言 entry 是文件条目。
- 设 lockResult 为对 entry 获取“共享”锁的结果。
- 将存储任务入队(global)执行:
- 如果 lockResult 为 "failure",则 拒绝
result,异常为 "
NoModificationAllowedError" DOMException,并中止这些步骤。 - 创建 stream,即为 entry 创建新的
FileSystemWritableFileStream(在 realm 中)。 - 如果 options["keepExistingData"] 为 true,则将 stream 的 [[buffer]] 设为 entry 二进制数据的副本。
- 解决 result 为 stream。
- 如果 lockResult 为 "failure",则 拒绝
result,异常为 "
- 返回 result。
2.3.3. createSyncAccessHandle()
方法
- handle = await fileHandle .
createSyncAccessHandle() -
返回一个
FileSystemSyncAccessHandle,可用于同步读写文件。通过 handle 所做的更改可能会立即反映到 fileHandle 的定位器所定位的文件条目中。可调用 flush 方法确保更改被写入文件。创建
FileSystemSyncAccessHandle时会对 fileHandle 的定位器所定位的文件条目获取独占锁,在句柄关闭前,无法再为该条目创建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)拒绝 result,使用名称为 accessResult 的错误名称的 DOMException,并中止这些步骤。
- 如果 isInABucketFileSystem 为 false,则将存储任务入队(global)拒绝
result,异常为 "
InvalidStateError" DOMException,并中止这些步骤。 - 如果 entry 为 null,则将存储任务入队(global)拒绝
result,异常为 "
NotFoundError" DOMException,并中止这些步骤。 - 断言 entry 是文件条目。
- 设 lockResult 为对 entry 获取“独占”锁的结果。
- 将存储任务入队(global)执行:
- 如果 lockResult 为 "failure",则 拒绝
result,异常为 "
NoModificationAllowedError" DOMException,并中止这些步骤。 - 创建 handle,即为 entry 创建新的
FileSystemSyncAccessHandle(在 realm 中)。 - 解决 result 为 handle。
- 如果 lockResult 为 "failure",则 拒绝
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
对象是可序列化对象,其序列化步骤和反序列化步骤与 FileSystemHandle
相同。
2.4.1. 目录遍历
未来可能会为异步可迭代器声明添加参数,例如递归遍历。
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。
-
-
如果 name 不是一个 有效文件名,将存储任务入队,使用 global 来 拒绝 result,原因为一个
TypeError并 中止这些步骤。 -
令 entry 为以下操作的结果:定位条目,给定 locator。
-
如果 options["
create"] 为 true:-
令 accessResult 为运行以下算法的结果:entry的 请求访问,给定 "
readwrite"。
-
-
否则:
-
令 accessResult 为运行以下算法的结果:entry的 查询访问,给定 "
read"。
-
-
将存储任务入队,使用 global 来运行以下步骤:
-
如果 accessResult的 权限 状态 不是 "
granted", 拒绝 result,原因为一个DOMException,其名称为 accessResult的 错误名称,并中止 这些步骤。 -
如果 entry 为
null,拒绝 result,原因为一个 "NotFoundError"DOMException并中止这些步骤。 -
-
如果 child的 名称 等于 name:
-
如果 child 是一个 目录 条目:
-
拒绝 result ,原因为一个 "
TypeMismatchError"DOMException并中止这些步骤。
-
-
解决 result,值为 以下操作的结果: 创建 一个子
FileSystemFileHandle,使用 locator 和 child的 名称,在 realm 中,并 中止这些步骤。
-
-
-
如果 options["
create"] 为 false:-
拒绝 result,原因为一个 "
NotFoundError"DOMException并 中止这些步骤。
-
-
将 child的 名称 设为 name。
-
将 child的 修改时间戳 设为当前时间。
-
如果在底层文件系统中创建 child 时抛出异常, 拒绝 result,原因为该异常,并 中止这些步骤。
应更明确地规定此处可能抛出哪些异常。 [议题 #11]
-
解决 result,值为以下操作的结果: 创建一个子
FileSystemFileHandle,使用 locator 和 child的 名称,在 realm 中。
-
-
-
返回 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。
-
-
如果 name 不是一个 有效文件名,将存储任务入队,使用 global 来 拒绝 result,原因为一个
TypeError并 中止这些步骤。 -
令 entry 为以下操作的结果:定位条目,给定 locator。
-
如果 options["
create"] 为 true:-
令 accessResult 为运行以下算法的结果:entry的 请求访问,给定 "
readwrite"。
-
-
否则:
-
令 accessResult 为运行以下算法的结果:entry的 查询访问,给定 "
read"。
-
-
将存储任务入队,使用 global 来运行以下步骤:
-
如果 accessResult的 权限 状态 不是 "
granted", 拒绝 result,原因为一个DOMException,其名称为 accessResult的 错误名称,并中止 这些步骤。 -
如果 entry 为
null,拒绝 result,原因为一个 "NotFoundError"DOMException并中止这些步骤。 -
-
如果 child的 名称 等于 name:
-
如果 child 是一个 文件条目:
-
拒绝 result ,原因为一个 "
TypeMismatchError"DOMException并中止这些步骤。
-
-
解决 result,值为 以下操作的结果: 创建 一个子
FileSystemDirectoryHandle,使用 locator 和 child的 名称,在 realm 中,并 中止这些步骤。
-
-
-
如果 options["
create"] 为 false:-
拒绝 result,原因为一个 "
NotFoundError"DOMException并 中止这些步骤。
-
-
将 child的 名称 设为 name。
-
如果在底层文件系统中创建 child 时抛出异常, 拒绝 result,原因为该异常,并 中止这些步骤。
应更明确地规定此处可能抛出哪些异常。 [议题 #11]
-
解决 result,值为以下操作的结果: 创建一个子
FileSystemDirectoryHandle,使用 locator 和 child的 名称,在 realm 中。
-
-
-
返回 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。
-
-
如果 name 不是一个 有效文件名,将存储任务入队,使用 global 来 拒绝 result,原因为一个
TypeError并 中止这些步骤。 -
令 entry 为以下操作的结果:定位条目,给定 locator。
-
令 accessResult 为运行以下算法的结果:entry的 请求访问,给定 "
readwrite"。 -
将存储任务入队,使用 global 来运行以下步骤:
-
如果 accessResult的 权限 状态 不是 "
granted", 拒绝 result,原因为一个DOMException,其名称为 accessResult的 错误名称,并中止 这些步骤。 -
如果 entry 为
null,拒绝 result,原因为一个 "NotFoundError"DOMException并中止这些步骤。 -
-
如果 child的 名称 等于 name:
-
如果 child 是一个 目录 条目:
-
如果 child的 子条目 不为 空 且 options["
recursive"] 为 false:-
拒绝 result,原因为一个 "
InvalidModificationError"DOMException并 中止这些步骤。
-
-
-
如果在底层文件系统中移除 child 时抛出 一个 异常,拒绝 result,原因为该 异常,并 中止这些步骤。
注意: 如果
recursive为 true,移除操作可能失败 且不具有原子性。某些文件或目录可能已被 移除,而其他文件 或目录仍然存在。应更明确地规定哪些异常可能在此 抛出。 [议题 #11]
-
解决 result,值为
undefined。
-
-
-
拒绝 result,原因为一个 "
NotFoundError"DOMException。
-
-
-
返回 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",则 拒绝 closeResult,使用以下名称的 DOMException: accessResult 的错误名称,并中止这些步骤。
- 执行实现自定义的恶意软件扫描和安全浏览检查。若检查失败,拒绝 closeResult,异常为 "AbortError" DOMException,并中止这些步骤。
- 将 stream 的 [[file]] 的二进制数据设为 stream 的 [[buffer]]。如抛异常则 以该异常拒绝 closeResult 并终止。注:预期原子性更新磁盘文件内容。
- 将以下步骤入队至文件系统队列:
- 返回 closeResult。
- 定义 abortAlgorithm,其步骤如下:
- 设 highWaterMark 为 1。
- 定义 sizeAlgorithm,返回 1。
- 按如下方式设置 stream: writeAlgorithm 设为 writeAlgorithm,closeAlgorithm 设为 closeAlgorithm,abortAlgorithm 设为 abortAlgorithm,highWaterMark 设为 highWaterMark,sizeAlgorithm 设为 sizeAlgorithm。
- 返回 stream。
FileSystemWritableFileStream
stream 和 chunk,执行以下步骤:
-
令 input 为将 chunk 转换 成
FileSystemWriteChunkType的结果。 如果此步骤抛出异常,则返回 一个已拒绝的 promise,原因为 该异常。 -
令 p 为 一个新的 promise。
-
-
令 accessResult 为运行 stream 的 [[file]] 的 查询访问,给定 "
readwrite" 的结果。 -
将存储任务入队,以 stream 的 相关全局对象 运行以下步骤:
-
如果 accessResult 的 权限 状态 不是 "
granted", 则以一个DOMException(其名称为 accessResult 的 错误名称)拒绝 p,并中止这些步骤。 -
如果 command 是 "
write":-
如果 input 是
undefined,或 input 是一个 字典 且 input["data"] 不 存在, 则以一个TypeError拒绝 p 并中止这些步骤。 -
令 writePosition 为 stream 的 [[seekOffset]]。
-
如果 input 是一个 字典 且 input["
position"] 存在,则将 writePosition 设为 input["position"]。 -
令 oldSize 为 stream 的 [[buffer]] 的 长度。
-
如果 data 是一个
BufferSource, 令 dataBytes 为 以下数据的副本: data。 -
否则,如果 data 是一个
Blob: -
否则:
-
如果 writePosition 大于 oldSize, 则在 stream 的 [[buffer]] 末尾 追加 writePosition - oldSize 个 0x00 (NUL) 字节。
注意: 期望实现表现为被跳过的文件内容确实用 NUL 字节填充。这并不意味着这些字节必须真的写入磁盘并占用空间。多数文件系统支持所谓稀疏文件,即这些 NUL 字节不占用实际磁盘空间。
-
令 head 为一个包含 stream 的 [[buffer]] 前 writePosition 个字节的 字节序列。
-
令 tail 为空的 字节序列。
-
如果 writePosition + data 的 长度 小于 oldSize:
-
令 tail 为一个 字节序列,包含 stream 的 [[buffer]] 最后 oldSize - (writePosition + data 的 长度) 个字节。
-
-
将 stream 的 [[buffer]] 设为 head、data 与 tail 的连接。
-
如果前面修改 stream 的 [[buffer]] 的操作因超出 存储配额 而失败, 则以一个
QuotaExceededError拒绝 p 并中止这些步骤, 且保持 stream 的 [[buffer]] 不变。 -
将 stream 的 [[seekOffset]] 设为 writePosition + data 的 长度。
-
解决 p。
-
-
否则,如果 command 是 "
seek": -
否则,如果 command 是 "
truncate":-
令 newSize 为 chunk["
size"]。 -
令 oldSize 为 stream 的 [[buffer]] 的 长度。
-
如果 newSize 大于 oldSize:
-
将 stream 的 [[buffer]] 设为一个 字节序列, 由 stream 的 [[buffer]] 与包含 newSize-oldSize 个
0x00字节的 字节序列 拼接而成。 -
如果前一步操作因超出 存储配额 而失败, 则以一个
QuotaExceededError拒绝 p 并中止这些步骤, 且保持 stream 的 [[buffer]] 不变。
-
-
否则,如果 newSize 小于 oldSize:
-
将 stream 的 [[buffer]] 设为一个 字节序列,包含 stream 的 [[buffer]] 中前 newSize 个字节。
-
-
如果 stream 的 [[seekOffset]] 大于 newSize, 则将 stream 的 [[seekOffset]] 设为 newSize。
-
解决 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)
方法的实现步骤如下:
2.5.2. seek()
方法
- await stream .
seek(position) -
将文件游标偏移更新为距文件头部 position 字节处。
seek(position)
方法的实现步骤如下:
2.5.3. truncate()
方法
truncate(size)
方法的实现步骤如下:
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 Handles 应当如何响应。 [Issue #35]
read(buffer, FileSystemReadWriteOptions:
options) 方法的步骤如下:
-
如果 this 的 [[state]] 为 "
closed", throw 一个 "InvalidStateError"DOMException。 -
令 bufferSize 为 buffer 的 byte length。
-
令 fileContents 为 this 的 [[file]] 的 binary data。
-
令 fileSize 为 fileContents 的 length。
-
令 readStart 为 options["
at"] (如果 options["at"] exists);否则为 this 的 file position cursor。 -
如果 readStart 大于 fileSize:
-
将 this 的 file position cursor 设为 fileSize。
-
返回 0。
-
-
令 readEnd 为 readStart + (bufferSize − 1)。
-
如果 readEnd 大于 fileSize,则将 readEnd 设为 fileSize。
-
令 bytes 为一个 byte sequence,包含 fileContents 中从 readStart 到 readEnd 的字节。
-
令 result 为 bytes 的 length。
-
如果前面从 fileContents 读取的操作失败:
-
如果发生部分读取且读入 bytes 的字节数已知, 将 result 设为已读取的字节数。
-
否则将 result 设为 0。
-
-
令 arrayBuffer 为 buffer 的 underlying buffer。
-
Write bytes 到 arrayBuffer 中。
-
将 this 的 file position cursor 设为 readStart + result。
-
返回 result。
2.6.2. write()
方法
write(buffer, FileSystemReadWriteOptions:
options) 方法的步骤如下:
-
如果 this 的 [[state]] 为 "
closed", 抛出 一个 "InvalidStateError"DOMException。 -
令 writePosition 为 options["
at"] (如果 options["at"] 存在);否则为 this 的 文件 位置 游标。 -
令 oldSize 为 fileContents 的 长度。
-
令 bufferSize 为 buffer 的 字节 长度。
-
如果 writePosition 大于 oldSize, 则在 fileContents 末尾追加 writePosition − oldSize 个 0x00 (NUL) 字节。
注意: 期望实现表现为被跳过的文件内容确实用 NUL 字节填充。这并不意味着这些字节必须真的写入磁盘并占用空间。多数文件系统支持所谓稀疏文件(sparse files),这些 NUL 字节不占用实际磁盘空间。
-
令 head 为一个 字节 序列,包含 fileContents 的前 writePosition 个字节。
-
令 tail 为空的 字节 序列。
-
如果 writePosition + bufferSize 小于 oldSize:
-
将 tail 设为一个 字节 序列,包含 fileContents 最后 oldSize − (writePosition + bufferSize) 个字节。
-
-
如果 newSize − oldSize 超出可用 存储 配额, 抛出 一个
QuotaExceededError。 -
将 this 的 [[file]] 的 二进制 数据 设为 head、buffer 的内容与 tail 的连接。
注意: 用于访问 buffer 内容的机制被刻意保持模糊。实现很可能为了性能而直接对宿主操作系统发出写调用(而不是创建 buffer 的副本),这使得无法详细规定写入顺序及部分写入的结果。
-
如果前面修改 this 的 [[file]] 的 二进制 数据 的操作失败:
-
如果发生部分写入且从 buffer 写入的字节数已知:
-
否则 抛出 一个 "
InvalidStateError"DOMException。
-
-
返回 bufferSize。
2.6.3. truncate()
方法
truncate(newSize)
方法的步骤为:
-
如果 this 的 [[state]] 为 "
closed", 抛出 一个 "InvalidStateError"DOMException。 -
如果 newSize 大于 oldSize:
-
如果 newSize − oldSize 超过了可用的 存储配额, 抛出 一个
QuotaExceededError。 -
将 this 的 [[file]] 设为一个 字节序列,该字节序列由 fileContents 与一个 包含 newSize − oldSize 个 0x00 字节的 字节序列 连接而成。
-
如果前述步骤中修改 this 的 [[file]] 的 二进制数据 的操作失败, 抛出 一个 "
InvalidStateError"DOMException。
-
-
否则,如果 newSize 小于 oldSize:
-
将 this 的 [[file]] 设为一个 字节序列,该序列包含 fileContents 中 前 newSize 个字节。
-
如果前述步骤中修改 this 的 [[file]] 的 二进制数据 的操作失败, 抛出 一个 "
InvalidStateError"DOMException。
-
2.6.4. getSize()
方法
- handle .
getSize() -
返回与 handle 关联的文件的大小(字节数)。
getSize() 方法的步骤为:
-
如果 this 的 [[state]] 为 "
closed", 抛出 一个 "InvalidStateError"DOMException。
2.6.5. flush()
方法
flush() 方法的步骤为:
-
如果 this 的 [[state]] 为 "
closed", 抛出 一个 "InvalidStateError"DOMException。 -
尝试将该文件内容的所有已缓存修改传输到底层文件系统的存储设备。
注意: 这也被称为刷新 (flushing)。在某些文件系统(例如内存文件系统)上可能是空操作,因为它们没有可刷新到的“磁盘”。
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。