rclone OneDrive 元数据实战:系统元数据、权限管理与 Graph API 对接细节
【免费下载链接】rclone"rsync for cloud storage" - Google Drive, S3, Dropbox, Backblaze B2, One Drive, Swift, Hubic, Wasabi, Google Cloud Storage, Azure Blob, Azure Files, Yandex Files项目地址: https://gitcode.com/GitHub_Trending/rc/rclone
本文以 rclone OneDrive 后端的官方元数据说明文档 metadata.md 为主体,系统讲解 OneDrive 在 rclone 中的元数据能力边界:支持哪些系统元数据键、如何配置并读写共享权限(permissions)、Personal 与 Business 两种盘型的差异,以及权限增删改在源码层面如何映射到 Microsoft Graph API 的具体调用。读完本文,你可以直接用 rclone 对 OneDrive 文件/目录做元数据同步与权限治理,并能看懂其底层实现与测试验证方式。
1. OneDrive 元数据模型:只有系统元数据,没有用户元数据
根据 metadata.md 的开篇说明:OneDrive 目前支持 System Metadata(系统元数据),但不支持 User Metadata(用户元数据),且文件(File)和目录(Folder)均适用这一能力。写入元数据时,rclone 只会写入可写的系统属性——任何只读或未识别的键都会被静默忽略。
这一能力边界在源码中有明确的特性声明。onedrive.go 中的 features 定义:
ReadMetadata: true, WriteMetadata: true, UserMetadata: false, // 不支持用户自定义元数据 ReadDirMetadata: true, WriteDirMetadata: true, UserDirMetadata: false,也就是说:你无法像操作 S3 或 B2 那样给文件挂任意foo=bar标签;你能读写的键集合是后端预先定义好的那一批系统属性,以及一个特殊的permissions键(见后文)。
1.1 完整的系统元数据键表
下表完整继承自 metadata.go 中的systemMetadataInfo定义(第 27-126 行),这也是rclone lsjson输出中可见的全部键:
| 键名 | 类型 | 只读 | 说明 |
|---|---|---|---|
content-type | string | 是 | 文件的 MIME 类型 |
mtime | RFC 3339 | 否 | 最后修改时间(Business 精度为秒,Personal 为毫秒) |
btime | RFC 3339 | 否 | 文件创建(birth)时间 |
utime | RFC 3339 | 是 | 上传时间 |
created-by-display-name | string | 是 | 创建者显示名 |
created-by-id | string | 是 | 创建者用户 ID |
description | string | 否(已废弃) | 文件短描述,最多 1024 字符;微软已不再支持 |
id | string | 是 | 该项在 OneDrive 内的唯一标识 |
last-modified-by-display-name | string | 是 | 最后修改者显示名 |
last-modified-by-id | string | 是 | 最后修改者 ID |
malware-detected | boolean | 是 | OneDrive 是否检测到该项含恶意软件 |
package-type | string | 是 | 若存在,表示该项是"包"(如 OneNote),某些场景按文件处理、某些场景按目录处理 |
shared-owner-id | string | 是 | 共享项所有者的 ID(如已共享) |
shared-by-id | string | 是 | 执行共享的用户 ID(如已共享) |
shared-scope | string | 是 | 共享范围:anonymous、organization或users |
shared-time | RFC 3339 | 是 | 共享发生的时间 |
permissions | JSON | 视配置 | OneDrive 格式权限的 JSON 转储,需开启--onedrive-metadata-permissions |
几个值得注意的实现细节:
- 时间格式:metadata.go 第 22-23 行定义了双向格式——读取时输出为
2006-01-02T15:04:05.999Z(Personal 返回毫秒,Business 只有秒),写入时按 RFC 3339 解析。 description已名存实亡:源码中Set方法遇到该键只会打一条 debug 日志metadata description is no longer supported -- skipping并跳过,不再向 API 提交(metadata.go 第 261-263 行)。btime缺失时的回退:toAPIMetadata()中,若只提供了mtime而btime为零值,会把mtime同时用作btime,避免 API 创建时把createdDateTime覆盖掉(metadata.go 第 298-301 行)。
2. 权限支持:--onedrive-metadata-permissions开关与取值
metadata.md 指出:权限(Permissions)在设置--onedrive-metadata-permissions后才可用,该选项在 onedrive.go 第 823 行注册为配置项metadata_permissions:
MetadataPermissions rwChoice `config:"metadata_permissions"`文档声明的取值为read、write、read,write、off(默认)。从源码结构看(rwChoices.Choices(),metadata.go 第 131-138 行),该选项底层是一个按位组合的fs.Bits类型,实际还接受一个文档未列出的取值failok——设置后写权限失败时只记录 ERROR 日志而不使传输失败(第 363-371 行的defer中吞掉错误)。各取值的组合效果:
| 取值 | 读权限 | 写权限 | 行为 |
|---|---|---|---|
off(默认) | 否 | 否 | 不读不写,省 API 调用 |
read | 是 | 否 | 读权限需要额外 API 调用;写元数据请求中携带的 permissions 会被忽略 |
write | 否 | 是 | 只写不读 |
read,write | 是 | 是 | 文档推荐组合:更新/删除权限需要知道 Permission ID,只有先读回来才能做 diff |
failok | 由组合决定 | 由组合决定 | 写入失败仅记日志,不中断传输 |
为什么推荐read,write?权限的"更新"和"删除"操作都以 Permission ID 为准。rclone 的写入流程是先Get现有权限、再与传入的目标状态做 diff(见第 4 节),只开write时拿不到"旧权限",就无法判断哪些该更新、哪些该删除。
性能上文档也给出了明确提醒:读、写权限都需要额外 API 调用,如果不需要权限操作,建议省略该参数。
2.1 权限的 JSON Schema:Personal 与 Business 略有不同
权限以 JSON 数组形式读/写,schema 与 OneDrive Graph API 的 permission 资源一致,但 Personal 与 Business 字段有差异:
- Personal用
grantedTo(单数对象)+invitation字段; - Business用
grantedToIdentities(数组),grantedTo已废弃。
metadata.md 中给出的 OneDrive Personal 示例:
[ { "id": "1234567890ABC!123", "grantedTo": { "user": { "id": "ryan@contoso.com" }, "application": {}, "device": {} }, "invitation": { "email": "ryan@contoso.com" }, "link": { "webUrl": "https://1drv.ms/t/s!1234567890ABC" }, "roles": [ "read" ], "shareId": "s!1234567890ABC" } ]OneDrive Business 示例(注意grantedToIdentities是数组,且可混排"链接型"与"用户型"权限):
[ { "id": "48d31887-5fad-4d73-a9f5-3c356e68a038", "grantedToIdentities": [ { "user": { "displayName": "ryan@contoso.com" }, "application": {}, "device": {} } ], "link": { "type": "view", "scope": "users", "webUrl": "https://contoso.sharepoint.com/:w:/t/design/a577ghg9hgh737613bmbjf839026561fmzhsr85ng9f3hjck2t5s" }, "roles": [ "read" ], "shareId": "u!LKj1lkdlals90j1nlkascl" }, { "id": "5D33DD65C6932946", "grantedTo": { "user": { "displayName": "John Doe", "id": "efee1b77-fb3b-4f65-99d6-274c11914d12" }, "application": {}, "device": {} }, "roles": [ "owner" ], "shareId": "FWxc1lasfdbEAGM5fI7B67aB5ZMPDMmQ11U" } ]这些字段与 api/types.go 中的PermissionsType结构一一对应(第 230-241 行),其中还包含文档未强调的grantedToV2/grantedToIdentitiesV2变体(Business 专用),InheritedFrom(继承权限的祖先引用)也是只读字段。角色常量定义在同文件第 246-255 行:read、write、owner、member。
2.2 用--metadata-mapper写入权限的完整示例
写入权限的方式是:在元数据里传入一个permissions键,值就是上述同格式的 JSON 字符串。rclone 的--metadata-mapper工具对这一步非常有帮助。文档给出的"添加一个 read 权限"请求示例:
{ "Metadata": { "permissions": "[{\"grantedToIdentities\":[{\"user\":{\"id\":\"ryan@contoso.com\"}}],\"roles\":[\"read\"]}]" } }添加权限时的收件人解析规则(来自 metadata.md 及fillRecipients实现):
- 可在
grantedTo或grantedToIdentities的User.ID或DisplayName中提供邮箱地址; - 也可以直接在
User.ID中提供 ObjectID(无@的 ID 会被当作 ObjectID 处理); - 添加用户权限时至少需要一个有效收件人,否则
addPermission直接跳过(metadata.go 第 616-619 行); - 设置
Link.Scope为"anonymous"时支持创建公共链接——此时走的是 rclone 自己的PublicLink实现,且若没有任何收件人则只创建链接后直接返回; - 不能添加
owner角色的邀请(源码第 623-626 行会跳过); - 注意:若目标文件/目录上已存在冲突的权限,添加操作可能失败。
3. 权限更新与删除的语义
metadata.md 对更新/删除的说明,结合sortPermissions(metadata.go 第 433-488 行)的实现,可以精确概括为:
- 更新:传入的权限项同时包含 Permission
id和新的roles。roles是唯一可变更的属性;且源码会做健全性检查——只有在旧权限中存在同 ID、且新旧角色确实不同(旧角色非空、非owner)时才会进入 update 队列,否则记 debug 日志跳过。 - 删除:传入的 JSON 数组是"希望保留的权限集合"。旧权限中 ID 未出现在保留集合里、且角色非
owner的项进入 remove 队列。传空数组即删除全部可删权限。owner角色不可删除,会被忽略。 - 添加:
id为空的新权限项进入 add 队列。
3.1 权限变更如何落到 Graph API
processPermissions(第 491-526 行)按remove → add → update的固定顺序执行,对应三个 API 端点:
| 操作 | 方法/端点 | 说明 |
|---|---|---|
| 获取现有权限 | GET /{id}/permissions | getPermissions,每次读权限都会调用 |
| 添加权限 | POST /{id}/invite | 请求体为AddPermissionsRequest(recipients、roles,retainInheritedPermissions=false) |
| 更新权限 | PATCH /{id}/permissions/{permId} | 请求体仅含roles |
| 删除权限 | DELETE /{id}/permissions/{permId} | 无请求体 |
所有调用都经过f.pacer.Call限速,并用shouldRetry判断可重试错误;失败时错误被fserrors.NoRetryError包装,避免无意义重试。
3.2 两个源码才能看到的"坑"
(1) 用户权限必须排在组权限之前(Graph API 怪癖)
orderPermissions(第 404-430 行)会把含用户身份的权限排到前面。源码注释解释了背景:当同时为一个组和一个用户添加相同权限、且该用户正是组成员时,若先加组权限,Graph 会先返回"已添加用户权限"又立即把它丢掉。这个 workaround 有对应单测 metadata_test.go 的TestOrderPermissions/TestOrderPermissionsJSON覆盖,对 Personal 与 Business 两种盘型分别验证(包括grantedToV2的 Business 变体)。
(2) Business 下"链接型权限"不能更新,只能删了再加
sortPermissions中有一个特判(第 452-460 行):非 Personal 盘型下,若待更新权限带有Link.WebURL(即共享链接型权限),会同时放入 remove 和 add 队列,用"删除+重新添加"绕过 Graph API 无法更新链接型权限的限制。
4. 读写调用链与目录元数据
4.1 对象侧:Get → Set → Write
以文件为例,元数据更新的完整调用链(updateMetadata,metadata.go 第 760-790 行):
Get(ctx):把缓存的系统元数据转成fs.Metadata;若MetadataPermissions含read,此处会额外发起一次GET /permissions调用并序列化为permissions键;Set(ctx, meta):只解析可写键(mtime、btime、permissions),返回"设置了多少个可写属性"——若为 0,直接跳过后续 API 调用;Write(ctx, updatePermissions):toAPIMetadata()组装出只含fileSystemInfo.lastModifiedDateTime/createdDateTime的api.Metadata,通过PATCH提交;随后若需要,刷新 normalizedID 并调用WritePermissions。
上传新文件的路径同理:fetchMetadataForCreate(第 703-733 行)在创建上传请求时就把createdDateTime/lastModifiedDateTime一并带上,mtime无条件写入。
4.2 目录侧:多一次 API 调用的原因
文档提到"在 OneDrive Business 上给 Folder 设置 mtime/btime 需要一次额外 API 调用"。源码印证了这一点:MkdirMetadata(第 799-844 行)在createDir成功后,会再调用一次meta.Write(ctx, false)来设置 modtime,源码注释直言 "for some reason, OneDrive Business and Personal needs this extra step to set modtime. Seems like a bug..."。目录的SetModTime还会尽量保留已有的非零btime(第 945-955 行)。
权限对目录同样生效:createDir中若元数据含permissions且开启了写权限,会先RefreshPermissions再WritePermissions(第 880-892 行),因为权限必须作为独立步骤执行。
5. 测试用例:权限能力是如何被验证的
内部测试 onedrive_internal_test.go 提供了与文档描述一一对应的行为验证(需要真实远端环境运行):
TestWritePermissions(第 75-148 行):完整走一遍"以 read 角色添加 → 更新为 write → 删除"流程,并断言远端读回的权限与预期 JSON 一致。它还先做skipIfSharingRefused预检——如果组织策略拒绝共享邀请(sharingFailed错误),测试整体跳过;TestReadPermissions(第 158-172 行):验证只开read时,携带 permissions 的写入不会改变远端权限;TestReadMetadata(第 175-198 行):断言systemMetadataInfo中所有必选键(package-type、shared-*等可选键除外)都存在且非空;TestDirectoryMetadata(第 200 行起):验证目录的 mtime/btime/权限读写,包括operations.SetDirModTime与DirSetModTime两条改时间路径。
这些测试从源码层面确认了文档中的关键承诺:键集合完整、只读时不产生写副作用、目录与文件同等支持。
6. 速查:常用命令
查看任意文件/目录的元数据与权限(文档给出的 TIP,直接可复制):
rclone lsjson remote:path --stat -M --onedrive-metadata-permissions read结合--metadata-mapper对单个对象写入权限(以同步场景为例):
rclone copyto src:file.txt onedrive:file.txt \ --metadata \ --onedrive-metadata-permissions read,write \ --metadata-mapper '{ "Metadata": { "permissions": "[{\"grantedToIdentities\":[{\"user\":{\"id\":\"ryan@contoso.com\"}}],\"roles\":[\"read\"]}]" } }'要点回顾:
- OneDrive 只有系统元数据,键集合固定为第 1.1 节表格,
description已被微软废弃; - 权限操作必须显式开启
--onedrive-metadata-permissions,读写都更推荐read,write; - 权限 JSON 中 Personal 看
grantedTo,Business 看grantedToIdentities;roles唯一可更新,owner不可删除; permissions的值是"目标保留集合"而非"要删除的集合",语义差异是误删权限的高发点;- 所有权限读写都伴随额外 API 调用,Business 目录时间设置还会多一次 PATCH,批量操作时需注意配额与耗时。
【免费下载链接】rclone"rsync for cloud storage" - Google Drive, S3, Dropbox, Backblaze B2, One Drive, Swift, Hubic, Wasabi, Google Cloud Storage, Azure Blob, Azure Files, Yandex Files项目地址: https://gitcode.com/GitHub_Trending/rc/rclone
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考