首页
/ Puter FSItem 对象开发指南:文件系统条目(文件/目录)的完整属性与操作方法

Puter FSItem 对象开发指南:文件系统条目(文件/目录)的完整属性与操作方法

2026-09-08 09:39:57作者:何将鹤

FSItem 是 Puter 文件系统中一切“条目”的统一对象模型:无论你通过 puter.fs.stat() 查询一个文件,还是通过 puter.fs.readdir() 列出一个目录,返回的都是 FSItem 实例。它把文件与目录共有的元数据(标识、路径、时间戳、大小、共享状态)和常用的内容操作(读、写、改名、移动、复制、删除、建目录)封装成了可直接调用、可链式串联的对象方法。读完本文你将能独立实现“读取/覆写文件内容、在目录内移动/复制/重命名条目、递归创建子目录并遍历目录树、处理重名冲突与目录专属操作限制”等典型开发场景,并理解 FSItem 在 puter.js 源码层是如何把方法转发给底层 FS 操作的真实机制。

FSItem 在 Puter 文件系统 API 中的定位

在 Puter 的文件系统抽象中,文件与目录被统一建模为“条目(entry / item)”。官方对象文档 fsitem.md 开宗明义地定义:

一个 FSItem 对象代表 Puter 文件系统中的一个文件或一个目录。

它在 puter.js(浏览器侧 JavaScript SDK)中的落地实现在 FSItem.js,其类注释进一步说明了构造来源:

A file or a directory in the Puter file system. Accepts an entry in any of the shapes the API returns it in (is_dir, fsentry_is_dir, …)。

也就是说,无论后端 API 返回的是旧版命名(is_dirfsentry_*)还是 camelCase 命名,FSItem 的构造函数都会统一规整为文档化的属性。通常你在拿到 FSItem 之后,就能直接对条目本身发起后续操作,而无须再拼路径去调用全局的 puter.fs.*

需要特别澄清一个常见误解:文档中 FSItem 对象带有一系列属性名(如 is_shared)由 stat()readdir() 设置,这一点已在原文档标注——并非所有途径拿到的条目都带有全部字段,详见后文“属性”章节。

FSItem 的属性(Attributes)

原文档为 FSItem 定义了 9 个公开属性。下表汇总了名称、类型与含义,随后逐条展开并补充源码层面的细节。

属性 类型 含义
id String 条目唯一标识,由 Puter 在创建条目时生成
name String 条目的名称
path String 条目相对文件系统根目录的路径
isDir Boolean 是否为目录:true 为目录,false 为文件
created Integer 创建时间的 Unix 时间戳
modified Integer 最后修改时间的 Unix 时间戳
accessed Integer 最后访问时间的 Unix 时间戳
size Integer 条目大小(字节);目录该项为 null
is_shared Boolean | null 该条目是否被共享给了他人

id(String)

包含条目唯一标识符的字符串,由 Puter 在条目创建时生成。在源码层,FSItem 构造函数同时维护了 uididuuid 三个同值字段(见 FSItem.js),取值优先级依次为 options.uid ?? options.uuid ?? options.fsentry_uid ?? options.fsentry_id ?? options.fsentry_uuid ?? options.idid 之所以重要,在于条目的 uid 永不改变——即使条目被移动或重命名,用 uid 仍能稳定寻址(这是后续 rename() 优先按 uid 调用的依据)。

name(String)

条目的名称字符串,来自 API 的 namefsentry_name

path(String)

条目相对文件系统根目录的路径。在 puter.js 的操作实现中,传给后端的路径会经过 getAbsolutePathForApp() 解析,相对路径会相对于应用自己的根目录解析(参见 stat.jsreaddir.js)。

isDir(Boolean)

标识条目是否为目录的布尔值。若为 true,表示该条目是目录;若为 false,则表示该条目是文件。这是 FSItem 最常用的判别字段,因为它决定了能否安全调用 read()/write()(仅文件)或 mkdir()/readdir()(仅目录)。

从源码看,这一字段还带着向后兼容的历史包袱:构造函数中保留了更早的别名 isDirectory,并且读取时兼容 options.isDirectory || options.isDir || options.is_dir || options.fsentry_is_dir 四种拼写(FSItem.js)。对应测试 FSItem.test.js 明确断言 isDirisDirectory 在文档化拼写和旧拼写下均保持一致。也就是说,老代码里的 item.isDirectory 依然可用,但新代码应以文档为准使用 isDir

created / modified / accessed(Integer)

三个整数类型的 Unix 时间戳(毫秒级,从后端直接透传),分别表示条目的创建时间、最后修改时间与最后访问时间。源码中它们同样兼容 fsentry_created / fsentry_modified / fsentry_accessed 拼写。

size(Integer)

以字节为单位的条目大小。当条目是目录时,sizenull。这条规则在原文档与源码(FSItem.js 的 JSDoc:null if the item is a directory)中都有明确表述。若需要目录的累计体积,可使用 stat()returnSize 选项做服务器端汇总(参见 stat.js),而不是读 size 字段。

is_shared(Boolean | null)

该条目是否已与他人共享:

  • true:已被共享;
  • false:未被共享;
  • null:条目不属于你(无法获知共享状态)。

原文档同时给出了三条重要的语义边界,理解它们能避免误用:

  1. 统计口径:它统计的是持有该条目 manage 权限的任何人授予的共享;
  2. 只看条目本身:只反映条目自身上的共享,不包含从被共享的父目录继承来的访问权限
  3. 来源受限:该字段仅由 stat()readdir() 设置;通过其他任何方式(例如 write()move() 返回的条目)获取的 FSItem 不携带此字段。

这一点与 puter.js 中 stat()returnShares 选项存在关联:当传入 returnShares: true 时,/stat 响应会附带 shares 数组并转换为共享对象(见 stat.js)。而 is_shared 本身是由后端在普通(非 returnShares)场景下随条目元数据返回的汇总标志。

源码中的非文档化属性(内部表面)

除文档列出的公开属性外,FSItem 实例还带有若干辅助字段,开发时值得了解但不应过度依赖(源码将其设计为“可能变化或消失”的非公开表面):

  • readURL / writeURL / metadataURL:可读/可写/元数据的签名 URL;
  • _internalProperties:不可枚举的内部属性包(enumerable: false),其中 signatureexpires 会从签名 URL 的查询参数中推断(FSItem.js),还包含一个 file_signature getter,产出与 /sign 端点一致的形状,可传给 launch_app 让其他应用打开该文件;
  • watch / open / setAsWallpaper / versions / trash / metadata:源码中已预留但标注 // todo - implement 的占位方法(见 FSItem.js 与 #L294-L337),当前调用为空操作,不应在正式代码中依赖

FSItem 的方法(Methods)

FSItem 的公开方法本质上是把“对条目自身的操作”转换为对 puter.fs.* 对应能力的调用。单元测试 FSItem.test.js 的注释点明了设计意图:这些方法是 puter.fs.* 的薄包装,测试锁定了它们转发参数的精确形状。

read()

读取文件的内容。

语法

fsitem.read()

参数

无。

返回值

一个 Promise,解析为包含文件内容的 Blob(浏览器标准 Web API 对象)。拿到 Blob 后,既可以直接作为资源使用,也可以用 await blob.text() 取字符串。

示例

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            await puter.fs.write('hello.txt', 'Hello, world!');
            const item = await puter.fs.stat('hello.txt');
            const blob = await item.read();
            puter.print(await blob.text());
        })();
    </script>
</body>
</html>

源码层面,read() 直接转发为 puter.fs.read(this.path)FSItem.js),测试断言其按 path 发起读取(FSItem.test.js)。更完整的参数能力(如范围读取)参见 read 操作文档

write()

向文件写入数据,覆盖其原有内容

语法

fsitem.write(data)

参数

  • data(String | File | Blob,必填):要写入文件的数据。

返回值

一个 Promise,解析为写入后文件的 FSItem 对象。

示例

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            await puter.fs.write('hello.txt', 'Hello, world!');
            const item = await puter.fs.stat('hello.txt');
            await item.write('Updated contents!');
            puter.print('File updated');
        })();
    </script>
</body>
</html>

注意这里的语义与全局 puter.fs.write() 不同:FSItem 的 write() 固定以 overwrite: truededupeName: false 转发(FSItem.js),即“就地覆写、不做自动去重改名”。测试用例对参数形状做了精确锁定(FSItem.test.js),并验证二进制 Blob 数据会被原样透传。若需要“写入时若重名则自动改名”,应改走全局 write 操作文档 的参数化形式。

rename()

重命名条目。

语法

fsitem.rename(newName)

参数

  • newName(String,必填):条目的新名称。

返回值

一个 Promise,解析为重命名后条目的 FSItem 对象。

示例

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            await puter.fs.write('hello.txt', 'Hello, world!');
            const item = await puter.fs.stat('hello.txt');
            await item.rename('renamed.txt');
            puter.print('File renamed');
        })();
    </script>
</body>
</html>

rename() 的转发逻辑在源码中最为讲究:优先以 uid 寻址,仅当对象没有 uid 时才退回按 path 寻址(FSItem.js)。代码注释解释了原因——this object was handed out 之后条目可能已被移动,但 uid 永不改变。对应测试覆盖了两种分支(FSItem.test.js)。这保证了即使 FSItem 是通过较早的 stat 结果获得的,改名的目标依然精确。

move()

将条目移动到另一个位置。

语法

fsitem.move(destination)
fsitem.move(destination, overwrite)
fsitem.move(destination, overwrite, newName)

参数

  • destination(String,必填):要移入的目录,或条目的新路径。
  • overwrite(Boolean,可选):目标处已存在条目时是否覆盖。默认 false
  • newName(String,可选):新位置处条目的名称。默认沿用当前名称。

返回值

一个 Promise,解析为移动后条目的 FSItem 对象。

示例

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            const dirname = puter.randName();
            await puter.fs.mkdir(dirname);
            await puter.fs.write('hello.txt', 'Hello, world!');
            const item = await puter.fs.stat('hello.txt');
            await item.move(dirname);
            puter.print(`Moved hello.txt into ${dirname}`);
        })();
    </script>
</body>
</html>

源码实现将 overwritenewName 作为选项对象转发(FSItem.js),overwrite 默认 falsenewName 缺省不传。测试断言了两种签名组合下的精确转发(FSItem.test.js)。更复杂的移动能力(如跨目录与重名处理策略的完整选项)参见 move 操作文档

copy()

将条目复制到另一个目录。

语法

fsitem.copy(destinationDirectory)
fsitem.copy(destinationDirectory, autoRename)
fsitem.copy(destinationDirectory, autoRename, overwrite)

参数

  • destinationDirectory(String,必填):要把条目复制进去的目录。
  • autoRename(Boolean,可选):目标处已存在同名条目时,是否自动挑选一个空闲名称。默认 false,即重名冲突时复制失败。
  • overwrite(Boolean,可选):是否覆盖目标处已存在的条目。默认 false

返回值

一个 Promise,解析为复制后条目的 FSItem 对象。

示例

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            const dirname = puter.randName();
            await puter.fs.mkdir(dirname);
            await puter.fs.write('hello.txt', 'Hello, world!');
            const item = await puter.fs.stat('hello.txt');
            await item.copy(dirname);
            puter.print(`Copied hello.txt into ${dirname}`);
        })();
    </script>
</body>
</html>

源码层面 copy()autoRename 映射为底层选项 dedupeName,并把 overwrite 一并转发(FSItem.js)。这里有一个值得注意的细节:当调用者没有autoRename 时,dedupeName 保持 undefined 而非 false——测试注释解释了原因:后端 copy 的默认行为就是“复制且重名时自动改名”,如果强制发送 dedupeName: false,反而会把后端的默认宽松行为变成冲突报错(FSItem.test.js)。也就是说,在 FSItem.copy 的语境下,三参签名里的第二个参数 autoRename 一旦显式传 true 才会去重,传 false 或省略则交给后端默认逻辑;这与 move()overwrite 的默认语义并不相同,使用时务必区分。完整选项参见 copy 操作文档

delete()

删除条目。

语法

fsitem.delete()

参数

无。

返回值

一个 Promise,在条目被删除后解析。

示例

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            await puter.fs.write('hello.txt', 'Hello, world!');
            const item = await puter.fs.stat('hello.txt');
            await item.delete();
            puter.print('File deleted');
        })();
    </script>
</body>
</html>

源码直接转发 puter.fs.delete(this.path)FSItem.js)。注意目前删除是物理删除而非进回收站——源码中 trash() 方法仍是 // todo implement trashing 的占位状态(FSItem.js),正式代码请勿假设存在可恢复的“回收站”语义。

mkdir()

在该条目内部创建新的子目录。该条目必须是目录,否则抛出错误。

语法

fsitem.mkdir(name)
fsitem.mkdir(name, autoRename)

参数

  • name(String,必填):要创建的子目录名称。
  • autoRename(Boolean,可选):当同名目录已存在时,是否自动挑选空闲名称。默认 false

返回值

一个 Promise,解析为所创建目录的 FSItem 对象。

示例

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            const dirname = puter.randName();
            await puter.fs.mkdir(dirname);
            const dir = await puter.fs.stat(dirname);
            await dir.mkdir('subdir');
            puter.print(`Created subdir inside ${dirname}`);
        })();
    </script>
</body>
</html>

源码在调用前先做了目录校验:若 this.isDirectory 为假,直接抛出 Error('mkdir() can only be called on a directory'),并且不会发起任何后端请求FSItem.js)。测试对此分支做了断言——非目录对象调用 mkdir 抛错且 fs.mkdir 从未被调用(FSItem.test.js)。同时注意子目录路径是使用 path.join(this.path, name) 拼出来的。全局能力参见 mkdir 操作文档

readdir()

列出该条目的内容。该条目必须是目录,否则抛出错误。

语法

fsitem.readdir()

参数

无。

返回值

一个 Promise,解析为 FSItem 对象数组,目录中的每一项对应一个 FSItem。

示例

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            const dirname = puter.randName();
            await puter.fs.mkdir(dirname);
            await puter.fs.write(dirname + '/hello.txt', 'Hello, world!');
            const dir = await puter.fs.stat(dirname);
            const children = await dir.readdir();
            children.forEach((child) => puter.print(child.name + '<br>'));
        })();
    </script>
</body>
</html>

mkdir() 一致,readdir() 在非目录对象上调用会抛出 Error('readdir() can only be called on a directory') 且不发起请求(FSItem.js),测试覆盖了该约束(FSItem.test.js)。实际的后端列出逻辑则由 readdir.js 承载:默认返回完整数组(内部逐页抓取并合并),传入 cursor/includeTotal 则返回 {items, cursor?, total?} 分页封套,stream: true 时可获得异步迭代器。每次成功读取后,目录内的各条目还会被写入 puter.js 客户端缓存(键为 item:<path>),从而让随后的按 path 查询可以命中 consistency: 'eventual' 的缓存。带参数分页的完整用法参见 readdir 操作文档

把 FSItem 与 Puter FS API 结合使用

FSItem 的方法覆盖了绝大多数“拿到条目之后做什么”的需求,但理解它与全局 FS API 的分工,能让代码更清晰:

  • 获得 FSItem 的入口:主要是 puter.fs.stat()(单条目元数据)与 puter.fs.readdir()(目录条目数组);此外 puter.fs.write()mkdir() 等操作也会在成功时返回对应条目的 FSItem。
  • 获得 is_shared 等共享信息:必须经由 stat()/readdir() 取得条目,其相关能力可结合 共享相关文档share 文档 交叉使用。
  • FSItem 方法无法覆盖的参数化能力(如 write 的自动去重、copy 的精细冲突策略、readdir 的分页与流式遍历、statreturnSize/returnPermissions/returnVersions 等)应回退到全局 FS 模块的操作函数。FSItem 的薄包装设计(见 FSItem.js 各方法实现)也印证了这一点:它保证的是常用路径的简洁,而不是对后端能力的穷尽封装。

典型组合用法:遍历目录树并清理

综合本文各方法,可以写出一个“递归遍历 + 条件删除”的典型流程:对根目录 readdir() 得到子项数组,依据 isDir 决定进入子树还是处理文件;需要递归时对目录 FSItem 再次调用 readdir(),清理时则调用 delete()。配合 mkdir() 的目录前置校验,整个流程无需关心“目标到底是不是目录”——FSItem 自己会在语义不匹配时及时抛出可读的错误信息。这样一个 20 行左右的函数,就完整覆盖了 Puter 文件系统中最常用的“对象式”操作方式。

小结

FSItem 把 Puter 文件系统面向用户的编程模型统一成了“条目即对象”:id/name/path/isDir 描述身份与形态,created/modified/accessed/size 描述元数据,is_shared 描述共享边界(仅来自 stat()/readdir()),而 read/write/rename/move/copy/delete/mkdir/readdir 八个方法则覆盖了文件与目录上的核心操作。源码实现(FSItem.js 及其测试 FSItem.test.js)表明,这些方法是对 puter.fs.* 的精确转发:rename 优先按 uid 寻址以抵御移动,write 固定就地覆写,copyautoRename 与后端 dedupeName 存在微妙的映射规则,mkdir/readdir 对非目录调用会抛错拒发请求。把握这些边界,你的 Puter 应用代码就能在“简洁”与“可控”之间取得平衡。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
899
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
525