-
Notifications
You must be signed in to change notification settings - Fork 323
Breaking: 以 OPFS 储存 Code #1316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/v1.4
Are you sure you want to change the base?
Breaking: 以 OPFS 储存 Code #1316
Changes from 3 commits
0833ed8
4fff717
a61d5b5
87ba347
bc9a6e1
69f64e8
4768e7a
bed286b
2fa931b
bb36715
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,7 +1,7 @@ | ||||||
| import { getStorageName } from "@App/pkg/utils/utils"; | ||||||
| import { db } from "./repo/dao"; | ||||||
| import type { Script, ScriptAndCode } from "./repo/scripts"; | ||||||
| import { ScriptCodeDAO, ScriptDAO } from "./repo/scripts"; | ||||||
| import { ScriptCodeDAO, ScriptCodeDAONew, ScriptDAO } from "./repo/scripts"; | ||||||
| import type { Subscribe } from "./repo/subscribe"; | ||||||
| import { SubscribeDAO } from "./repo/subscribe"; | ||||||
| import type { Value } from "./repo/value"; | ||||||
|
|
@@ -19,7 +19,7 @@ export function migrateToChromeStorage() { | |||||
| // 迁移脚本 | ||||||
| const scripts = await db.table("scripts").toArray(); | ||||||
| const scriptDAO = new ScriptDAO(); | ||||||
| const scriptCodeDAO = new ScriptCodeDAO(); | ||||||
| const scriptCodeDAO = new ScriptCodeDAONew(); | ||||||
| console.log("开始迁移脚本数据", scripts.length); | ||||||
| await Promise.all( | ||||||
| // 不处理 Promise.reject ? | ||||||
|
|
@@ -267,25 +267,48 @@ export function migrateChromeStorage() { | |||||
| ); | ||||||
| }, | ||||||
| }, | ||||||
| { | ||||||
| version: 2, | ||||||
| upgrade: async () => { | ||||||
| const scriptCodeDAO = new ScriptCodeDAO(); | ||||||
| const scriptCodeDAONew = new ScriptCodeDAONew(); | ||||||
| const scriptCodes = await scriptCodeDAO.all(); | ||||||
| await Promise.all(scriptCodes.map(async (scriptCode) => scriptCodeDAONew.save(scriptCode))); | ||||||
| // 过渡期间不删除旧代码 | ||||||
| // await scriptCodeDAO.deletes(scriptCodes.map((e) => e.uuid)); | ||||||
| }, | ||||||
| }, | ||||||
| // { | ||||||
| // version: 3, | ||||||
| // upgrade: async () => { | ||||||
| // // const scriptCodeDAO = new ScriptCodeDAO(); | ||||||
| // // 过渡期间后删除旧代码 | ||||||
| // // await scriptCodeDAO.deletes(scriptCodes.map((e) => e.uuid)); | ||||||
| // }, | ||||||
| // }, | ||||||
| ]; | ||||||
| const localstorageDAO = new LocalStorageDAO(); | ||||||
| localstorageDAO.get("migrations").then(async (item) => { | ||||||
| // 旧代码 logic 错误。只好维持 array 形式但只储单一版本号 | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 哪里有错误?如果是顺序,也不应该这样去处理,应该保留完整版本
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 单一版本号升级 1 -> 2 -> 3 -> 4... |
||||||
| const migrations = item?.value || []; | ||||||
| let migrationVersion = migrations[0] || 0; | ||||||
| for (let i = 0; i < migrationList.length; i++) { | ||||||
| const m = migrationList[i]; | ||||||
| if (!migrations.includes(m.version)) { | ||||||
| if (migrationVersion < m.version) { | ||||||
| // 需要升级 | ||||||
| try { | ||||||
| await m.upgrade(); | ||||||
| migrations.push(m.version); | ||||||
| migrationVersion = m.version; | ||||||
| localstorageDAO.save({ | ||||||
|
||||||
| localstorageDAO.save({ | |
| await localstorageDAO.save({ |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -78,7 +78,7 @@ export interface Script { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| checktime: number; // 脚本检查更新时间戳 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lastruntime?: number; // 脚本最后一次运行时间戳 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nextruntime?: number; // 脚本下一次运行时间戳 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ignoreVersion?: string; // 忽略單一版本的更新檢查 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ignoreVersion?: string; // 忽略单一版本的更新检查 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 分开存储脚本代码 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -147,33 +147,18 @@ export type TClientPageLoadInfo = | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| | { ok: false }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class ScriptDAO extends Repo<Script> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scriptCodeDAO: ScriptCodeDAO = new ScriptCodeDAO(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| super("script"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enableCache(): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| super.enableCache(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.scriptCodeDAO.enableCache(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public save(val: Script) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return super._save(val.uuid, val); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| findByUUID(uuid: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this.get(uuid); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async getAndCode(uuid: string): Promise<ScriptAndCode | undefined> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [script, code] = await Promise.all([this.get(uuid), this.scriptCodeDAO.get(uuid)]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!script || !code) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Object.assign(script, code); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public findByName(name: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this.findOne((key, value) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return value.name === name; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -235,22 +220,22 @@ export class ScriptDAO extends Repo<Script> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (val1.length < 2) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return val1[0] === val2[0]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 無視次序 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 无视次序 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const s = new Set([...val1, ...val2]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (s.size !== val1.length) return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return val1 === val2; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isScriptInfoEqual = (script1: Script, script2: Script) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // @author, @copyright, @license 應該不會改 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // @author, @copyright, @license 应该不会改 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!valEqual(script1.metadata.author, script2.metadata.author)) return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!valEqual(script1.metadata.copyright, script2.metadata.copyright)) return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!valEqual(script1.metadata.license, script2.metadata.license)) return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // @grant, @connect 應該不會改 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // @grant, @connect 应该不会改 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!valEqual(script1.metadata.grant, script2.metadata.grant)) return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!valEqual(script1.metadata.connect, script2.metadata.connect)) return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // @match @include 應該不會改 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // @match @include 应该不会改 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!valEqual(script1.metadata.match, script2.metadata.match)) return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!valEqual(script1.metadata.include, script2.metadata.include)) return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -298,11 +283,82 @@ export class ScriptCodeDAO extends Repo<ScriptCode> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| super("scriptCode"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| findByUUID(uuid: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this.get(uuid); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public save(val: ScriptCode) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return super._save(val.uuid, val); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 不能 extends Repo<ScriptCode>. 没有 dao.gets() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 不能 extends Repo<ScriptCode>. 没有 dao.gets() | |
| // 注意:此 DAO 不继承 Repo<ScriptCode>,而是改用 OPFS 单独存储大体积脚本代码, |
Copilot
AI
Mar 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ScriptCodeDAONew.get() 在文件不存在时返回 { uuid, code: "" }(始终为 truthy),会导致调用方用 if (!scriptCode) / if (!code) 判断“代码缺失”永远不会触发,从而把“缺失”当成“空代码”继续执行。建议让 get() 在文件不存在时返回 undefined(并把返回类型改为 Promise<ScriptCode | undefined>),或提供一个明确的 getOrEmpty() 并统一调用方语义。
| public async get(uuid: string): Promise<ScriptCode> { | |
| if (!this._dirHandlePromise) this._dirHandlePromise = ScriptCodeDAONew.getDirHandle(); | |
| const folder = await this._dirHandlePromise; | |
| let code: string = ""; | |
| let handle: FileSystemFileHandle; | |
| try { | |
| handle = await folder.getFileHandle(`${uuid}.user.js`, { create: false }); | |
| } catch { | |
| // no file -> empty code | |
| return { | |
| uuid, | |
| code, | |
| }; | |
| } | |
| code = await handle.getFile().then((f) => f.text()); | |
| public async get(uuid: string): Promise<ScriptCode | undefined> { | |
| if (!this._dirHandlePromise) this._dirHandlePromise = ScriptCodeDAONew.getDirHandle(); | |
| const folder = await this._dirHandlePromise; | |
| let handle: FileSystemFileHandle; | |
| try { | |
| handle = await folder.getFileHandle(`${uuid}.user.js`, { create: false }); | |
| } catch { | |
| // no file -> undefined | |
| return undefined; | |
| } | |
| const code = await handle.getFile().then((f) => f.text()); |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,7 +7,7 @@ import { | |||||||
| SCRIPT_TYPE_BACKGROUND, | ||||||||
| SCRIPT_TYPE_CRONTAB, | ||||||||
| SCRIPT_TYPE_NORMAL, | ||||||||
| ScriptCodeDAO, | ||||||||
| ScriptCodeDAONew, | ||||||||
| ScriptDAO, | ||||||||
| } from "@App/app/repo/scripts"; | ||||||||
| import type { Subscribe } from "@App/app/repo/subscribe"; | ||||||||
|
|
@@ -190,7 +190,7 @@ export async function prepareScriptByCode( | |||||||
| ) { | ||||||||
| throw new Error(i18n_t("error_script_type_mismatch")); | ||||||||
| } | ||||||||
| const scriptCode = await new ScriptCodeDAO().get(old.uuid); | ||||||||
| const scriptCode = await new ScriptCodeDAONew().get(old.uuid); | ||||||||
| if (!scriptCode) { | ||||||||
|
||||||||
| if (!scriptCode) { | |
| // ScriptCodeDAONew.get() 在缺文件时可能返回空字符串对象,此处需同时检查 code 字段 | |
| if (!scriptCode || !scriptCode.code) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
migrate v2 这里用 ScriptCodeDAONew.save() 迁移旧 ScriptCodeDAO 数据到 OPFS,但 ScriptCodeDAONew.save() 内部又会“过渡期间同步保存至 ScriptCodeDAO”,等于迁移过程中把所有 code 再写回一次 chrome.storage.local,仍会触发大量 onChanged/写放大。建议为迁移提供仅写 OPFS 的路径(例如 save({..., legacy: false}) 或独立的 saveToOPFSOnly)。