接收缓存保留期 API
本文扩展集成 API 设置契约。所有示例使用现有监听的实际地址,以及 /api/legnasend/v1/integration 前缀。须先启用 API。调用密钥须明确拥有 settings.read、settings.write 和全局工作区授权 *,匿名访问不会获得这些权限;只读客户端仅需 settings.read 和 *。
值与作用范围
POST /settings/update 每次修改一个字段:
{"version":"VERSION_FROM_READ","field":"receiveCacheRetentionDays","value":7}value 必须是 −2 至 3650 的 JSON 整数。-2 表示 1 小时(3,600,000 毫秒);-1 表示保留已登记原生接收残留直至明确手动清理;0 允许立即自动清理;正整数表示保留天数。原生设置界面提供 −2、−1、0、1、7、30 六档,API 还接受范围内其他整数。字符串 "7"、布尔值 true、浮点 JSON 写法 7.0、null 及越界值均被拒绝,不做类型转换。
此设置作用于意外退出后已登记、不可续传的原生接收暂存;普通取消和不可续传失败仍清理事务缓存。新的持久续传记录单独采用 1 小时绝对租约;已有旧记录保留原先的一天期限,网络失败可保留已确认块,不由本设置延长。启动和API清理保留有效租约,本地明确手动清理可提前回收非活动自有缓存;活动、成品及身份不符内容仍受保护。汇总原因durable_resume表示检查了持久记录,durable_resume_failed表示其存储检查失败,不等于成功释放空间。详情见持久续传。工作区上传、私有工作区导出副本和 Android 文档提供器事务分别管理。保留缓存不会为原始 LocalSend 协议增加分片续传能力。
修改只保存并同步策略,不会执行清理、绕过年龄、停止传输或重启监听。API POST /cache/cleanup 仍须 cache.clean 权限并遵守保留期。只有本地接收缓存管理对话框经过单独确认后才忽略年龄,身份、缓存头和活动写入保护始终保留。到期表示下一次维护时具备清理资格,不是精确时刻触发的删除计时器。
分别读取已保存与实际生效状态
GET /settings 及成功修改返回版本化设置快照,并必须包含顶层 receiveCacheRetention 对象。示例节选:
{
"version": "64_HEX_DIGEST_FROM_THE_HOST",
"settings": {"receiveCacheRetentionDays": 7},
"pendingRestart": [],
"receiveCacheRetention": {
"effectiveDays": 7,
"automaticCleanupPaused": false,
"busy": false,
"error": null
}
}示例省略无关设置。settings.receiveCacheRetentionDays 是已保存的整数偏好,不代表原生配置必定成功。
| 运行状态字段 | 类型与含义 |
|---|---|
effectiveDays | −2…3650 的整数;实际原生策略尚未确认时为 null |
automaticCleanupPaused | 布尔值;当前是否暂停自动原生缓存清理 |
busy | 布尔值;策略设置操作是否进行中 |
error | null、invalid、save、apply 或 restore |
invalid 表示已保存偏好损坏,save 表示持久化失败,apply 表示同步失败,restore 表示偏好恢复失败。宿主在失败后尽可能查询实际原生策略,因此 effectiveDays 可能与已保存值不同。应读取全部字段,不能仅凭错误非空或偏好值判断自动清理是否暂停;实际原生策略未知或偏好恢复不确定时保持暂停。
automaticCleanupPaused 是同步安全开关,不是保留模式。手动保留成功应用后可以返回 effectiveDays: -1 与 automaticCleanupPaused: false:自动维护仍可运行,但手动保留策略使原生残留保留,不会绕过年龄或活动写入者检查。策略未知(effectiveDays: null)及修改进行中(busy: true)会暂停安全开关;save/apply 失败后若旧策略已核对一致,可能保留诊断但不暂停。
乐观并发与结果处理
- 先读取
GET /settings,保留完整version。 - 核对已保存和实际生效状态,再准备一次修改。
- 使用该版本提交一次请求,等待持久化和原生策略确认。
- 遇到
409 settings_changed,重新读取并判断原意图是否仍成立,不直接替换版本后盲目重发。 - 遇到
409 settings_busy,等待已有操作结束再读取状态,不自动排队修改。 - 遇到
503 host_operation_failed,读取实际状态;持久化恢复或原生同步可能失败,错误不证明完全没有发生改变。 - 网络异常、宿主超时或
outcome_unknown后先查询状态,不自动重放修改。
版本包含已保存设置、待重启状态和完整保留期运行快照。它是内容摘要,不是单调递增历史版本;临时的 busy 或错误变化也可能使旧版本失效,相同快照可能产生相同版本。此类操作没有请求 ID 去重。应用内 API 测试台验证整数输入,每次写入均经页内确认,并用响应 JSON 展示实际状态诊断;支持四种界面语言。
cURL
需要 curl 与 jq。BASE 使用实际监听地址;密钥通过进程环境传入,不放入 URL。HTTPS 应配置受信任的设备证书,不关闭证书验证。
BASE='http://HOST:PORT/api/legnasend/v1/integration'
DAYS=7
SNAPSHOT=$(curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $LEGNASEND_API_TOKEN" "$BASE/settings")
VERSION=$(printf '%s' "$SNAPSHOT" | jq -er '.version')
BODY=$(jq -nc --arg version "$VERSION" --argjson days "$DAYS" \
'if ($days|type)=="number" and ($days|floor)==$days and $days>=-2 and $days<=3650
then {version:$version,field:"receiveCacheRetentionDays",value:$days}
else error("Expected integer -1..3650") end')
curl --include --request POST \
-H "Authorization: Bearer $LEGNASEND_API_TOKEN" -H 'Content-Type: application/json' \
--data-binary "$BODY" "$BASE/settings/update"
# 失败或结果不明确后读取状态,不自动重复 POST。
curl --fail-with-body -H "Authorization: Bearer $LEGNASEND_API_TOKEN" "$BASE/settings"使用新版本进行负例测试时,把 JSON 值替换为 true 或 "7",接口应返回 400,不视为整数。有效示例不要用 --arg days 构造字符串。jq 会把整数数值输出为整数 JSON;最终以线上的 JSON 类型而非客户端语言数值表示决定是否接受。
JavaScript
浏览器仍遵循已配置的 CORS 与 TLS 规则。token 来自明确的秘密输入,不嵌入 URL 或提交到源码。
const base = 'http://HOST:PORT/api/legnasend/v1/integration';
const headers = {Authorization: `Bearer ${token}`};
async function readSettings() {
const response = await fetch(`${base}/settings`, {headers, credentials: 'omit'});
if (!response.ok) throw new Error(`Read failed: ${response.status}`);
return response.json();
}
async function setRetention(days) {
// 不使用 Number(days),避免把 true 或数值字符串悄悄转换。
if (!Number.isInteger(days) || days < -2 || days > 3650) {
throw new TypeError('Expected integer -1..3650');
}
const snapshot = await readSettings();
const response = await fetch(`${base}/settings/update`, {
method: 'POST', credentials: 'omit',
headers: {...headers, 'Content-Type': 'application/json'},
body: JSON.stringify({version: snapshot.version, field: 'receiveCacheRetentionDays', value: days})
});
console.log(response.status, await response.json());
if (!response.ok) console.log('Current state:', await readSettings());
// 调用者在网络失败后读取状态,不重复 POST。
}
await setRetention(7);
// setRetention(true) 与 setRetention('7') 在本地拒绝。Python
Python 的 bool 是 int 子类,应使用 type(days) is int,而不是只用 isinstance(days, int)。
import json, os, urllib.request, urllib.error
BASE = 'http://HOST:PORT/api/legnasend/v1/integration'
HEADERS = {'Authorization': 'Bearer ' + os.environ['LEGNASEND_API_TOKEN']}
def call(method, route, payload=None):
data = None if payload is None else json.dumps(payload).encode('utf-8')
headers = {**HEADERS, **({'Content-Type': 'application/json'} if data is not None else {})}
request = urllib.request.Request(BASE + route, data=data, headers=headers, method=method)
try:
response = urllib.request.urlopen(request, timeout=40)
except urllib.error.HTTPError as response_error:
response = response_error
with response:
return response.status, json.loads(response.read(262144))
def set_retention(days):
if type(days) is not int or not -2 <= days <= 3650:
raise TypeError('Expected integer -1..3650')
status, snapshot = call('GET', '/settings')
if status != 200:
raise RuntimeError(('Read failed', status, snapshot))
result = call('POST', '/settings/update', {
'version': snapshot['version'], 'field': 'receiveCacheRetentionDays', 'value': days})
print(result)
if result[0] != 200:
print('Current state:', call('GET', '/settings'))
# 网络异常继续上抛;重新决定操作前先读取当前状态。
set_retention(-1)
# set_retention(True)、set_retention('7')、set_retention(7.0) 在本地拒绝。HTTPS 可用 ssl.create_default_context(cafile='DEVICE_CA.pem') 创建受信任设备 CA 上下文并传给 urlopen;监听要求客户端证书时再提供相应证书。原始传输协议、证书身份验证和文件格式不变。
未设置保留期时默认 1 小时;已有显式设置不覆盖。原字段名 receiveCacheRetentionDays 为兼容保留,−2 是小时策略代码而非负天数。原生桥接为 {mode:"hour",days:null}。网页单文件与原文件批次采用相同的 1 小时默认;Android SAF 新恢复记录也采用 1 小时,旧记录保留原期限。
持久记录的活动时间仅延后本地清理,不延长已经公布的续传授权期限;活动写入与完成文件继续受保护。