資料傳輸 - lolly-backup 打包檔

Lolly 使用者累積的一切都存放在自己的裝置上 - 沒有帳號,沒有雲端。資料傳輸打包檔就是搬移這些資料的方式:在一台裝置上匯出,透過任何方式攜帶該檔案(USB、AirDrop、寄給自己的電子郵件、網路共用),再匯入另一台裝置。檔案本身就是傳輸方式。目標裝置可以離線或連線,兩者沒有差別,因為整個過程完全不會與任何伺服器通訊。

搬移整個安裝環境的兩個按鈕:匯出我的資料寫入一個 zip,匯入資料再讀回signed by Lollyvector SVG自己驗證Get the signed file12 paths~5.1k nodes12 groups60 KB搬移整個安裝環境的兩個按鈕:匯出我的資料寫入一個 zip,匯入資料再讀回signed by Lollyvector SVG自己驗證Get the signed file12 paths~5.1k nodes12 groups60 KB

本頁面是格式規格說明。若需終端使用者的操作說明,請見 Using Lolly → Moving to another device。實作程式碼位於 shells/web/src/data-transfer.ts,而 tests/data-transfer.test.ts 則固定了往返(round-trip)的契約。

範圍。 打包檔攜帶的是使用者資料,而非工具。工具與目錄資產是另外同步的,並假設目標裝置上已經存在(最壞情況下版本較新)。匯入絕不會安裝或升級任何工具。

目標

封裝格式

打包檔是一個單純的 .zip 檔。下載檔案會以其所屬的人命名 - LollyTools-<First>-<Last>-<YYYY-MM-DD>-<n>.zip(例如 LollyTools-Ada-Lovelace-2026-06-26-1.zip) - 讓下載資料夾中的備份檔保持可辨識。名字與姓氏這兩部分來自個人檔案,未設定時則省略。沒有個人檔案時會產生 LollyTools-2026-06-26-1.zip,只有名字時則產生 LollyTools-Ada-2026-06-26-1.zip。每個部分都會被清理為檔名安全的字串(保留 Unicode 文字/數字,移除空格與標點,上限 32 個字元)。<n> 是同一天、同一裝置下的序號,因此同一天重複匯出不會互相衝突,並維持順序。shells/web/src/data-transfer.ts 中的 backupFilename() 負責建立此名稱。無論檔名為何,zip 的內容都是相同的。內容如下:

路徑是否必要內容
manifest.json格式 id、版本、數量與各部分的完整性資訊。是讀取端最先查看的內容。
profile.json有設定時使用者的 me 記錄(姓名、聯絡方式、大頭照參照、旗標)。透過 host.profile 讀取。
sessions.json每一個已儲存的工作階段:欄位、工具 id/版本、標籤、縮圖(data-URL)與完整輸入資料。透過 host.state 讀取。
assets.json每個已上傳資產(圖片、字型、品牌 token)的中繼資料,各自指向 assets/blobs/ 下的位元組。
assets/blobs/<n>.<ext>依資產而定原始資產位元組(圖片與字型檔案)。以未壓縮方式儲存(本身已是壓縮格式)。副檔名僅供辨識參考,assets.json 中的 MIME 才是權威依據。
prefs.json使用者自有的本機偏好設定:themesidebarWidth,以及 ct-metrics 活動統計。
lolly.txt打包檔的人類可讀摘要(數量、個人檔案、檔名),供未使用 Lolly 開啟 zip 的人參考。每次匯出都會重新產生,且匯入時會被辨識,因此絕不會被算作跳過的部分。它是在完整性對照表之後才寫入的,因此不包含在其中。

打包檔刻意採用單純的 zip 格式:無論透過何種方式傳輸都能保持完整,且任何解壓縮工具都能檢視其內容。

profile.json 是最小的一部分,也是在應用程式中最先被讀取端看到的部分:由製作者填寫一次的詳細資料,以及讓工具得以使用這些資料的選擇性開啟設定。

會轉換成 profile.json 的個人檔案詳細資料表單 - 姓名、聯絡方式、大頭照,以及旁邊的選擇性開啟設定signed by Lollyvector SVG自己驗證Get the signed file18 paths~2.0k nodes41 groups30 KB會轉換成 profile.json 的個人檔案詳細資料表單 - 姓名、聯絡方式、大頭照,以及旁邊的選擇性開啟設定signed by Lollyvector SVG自己驗證Get the signed file18 paths~2.0k nodes41 groups30 KB

manifest.json

{
  "format": "lolly-backup",
  "formatVersion": 1,
  "minReader": 1,
  "app": "lolly",
  "exportedAt": "2026-06-22T09:30:00.000Z",
  "counts": { "profile": true, "sessions": 2, "userAssets": 4, "prefs": 3 },
  "integrity": {
    "profile.json": "sha256-…",
    "sessions.json": "sha256-…",
    "assets.json": "sha256-…",
    "assets/blobs/0.webp": "sha256-…",
    "prefs.json": "sha256-…"
  }
}
欄位意義
format恆為 lolly-backup。沒有此欄位的檔案會被拒絕,回報「不是 Lolly 備份檔」。
formatVersion此打包檔寫入時所採用的版面配置。只要部分項目集合或結構有任何變動就會遞增。讀取端不會以此欄位作為判斷依據。
minReader安全匯入此打包檔所需的最低讀取端版本。讀取端就是以此欄位作為判斷依據。
app產生此打包檔的應用程式 id,供診斷用途。
exportedAt打包檔建立時的 ISO 時間戳記。
counts寫入端放入了哪些內容,供顯示與合理性檢查之用。
integrity選填。將除 manifest.json 以外的每個部分,對應到其未壓縮位元組的 SRI 風格 sha256-<base64> 摘要值。

版本政策(向前相容性)

formatVersionminReader 之間的區分,正是讓此格式得以演進、同時不會拋棄較舊安裝環境的關鍵:

給作者的經驗法則: 如果每個現有的讀取端,即使忽略你新增的內容,仍能表現正確,那就是新增式變動 - 遞增 formatVersionminReader 保持不變。否則就要提高 minReader

完整性

manifest.integrity 存在時,讀取端會在寫入任何內容之前,驗證所列出每個部分的 SHA-256。若不相符(「未通過完整性檢查」)或缺少某個部分(「不完整」),整個匯入程序就會中止 - 不會有部分還原的情況。這能捕捉檔案傳輸過程中可能造成的損毀(例如中斷的 AirDrop、重新編碼附件的電子郵件閘道、損壞的 USB 磁區)。

完整性檢查在設計上是盡力而為:只有在 Web Crypto 可用時(所有安全的瀏覽器情境與現代 Node)才會寫入,也只有在對照表與 Web Crypto 皆存在時才會進行驗證。沒有對照表的打包檔 - 例如完整性機制出現之前建立的打包檔 - 會照常匯入,不受影響。「無法驗證」絕不會被視為「已損毀」。

資訊清單既不會列出自己,也不會列出重新產生的 lolly.txt 說明檔。摘要值涵蓋的是資訊清單所擔保的各個部分。

匯入語意

匯入採合併覆寫方式,絕不會全部取代:

已儲存的工作階段會自動重新連結到其圖片:資產參照是以 id 保留的,橋接層會在已上傳圖片還原之後重新解析它們(無論如何都必須這麼做,因為 blob: URL 無法在重新載入後保留)。

匯入摘要會回報 { profile, sessions, userAssets, prefs, skipped, failedAssets }failedAssets 計算的是無法還原的已上傳資產(例如裝置儲存空間已滿)。這與 skipped 不同,後者計算的是來自向前相容的較新寫入端、但此版本無法辨識的部分。使用者介面會呈現 skipped(「…‧N 個較新的項目已跳過」),讓還原結果誠實地呈現遺漏的內容。

不會被傳輸的內容

儲存空間計量表會列出相同的分類。已儲存的工作階段與「我的圖片」會被納入打包檔中;資產快取、工具預覽以及下方的離線固定項目,皆可重新產生,因此不會被納入。

儲存空間計量表將此裝置的資料分成具名類別,其中「已儲存的工作階段」與「我的圖片」與「資產快取」分開追蹤,此處為全新安裝、每個類別皆尚未有內容的畫面signed by Lollyvector SVG自己驗證Get the signed file25 paths~4.8k nodes46 groups59 KB儲存空間計量表將此裝置的資料分成具名類別,其中「已儲存的工作階段」與「我的圖片」與「資產快取」分開追蹤,此處為全新安裝、每個類別皆尚未有內容的畫面signed by Lollyvector SVG自己驗證Get the signed file25 paths~4.8k nodes46 groups59 KB

跨殼層保證

data-transfer.ts 只透過能力橋接(host.profilehost.statehost.assets)與共用的 localStorage 偏好設定來讀寫。因為橋接是唯一的接縫,即使底層儲存不同 - 網頁版是 IndexedDB,Tauri 是檔案系統 - 同一個模組仍會在每個殼層產生位元組完全相同的輸出。Tauri 殼層原封不動地重用這個模組,只有它們的 host.state 實作不同。無頭測試針對記憶體內的橋接執行完整的往返測試,因此它可以代表所有殼層。

有兩個殼層基於不同原因不在此保證範圍內:

保留的擴充點

信封格式設計上是一份 manifest 加上一組具名的部分,讓日後新型態的可攜資料能不需破壞性變更就搭上這個格式。它們會以附加部分的形式加入(新的 formatVersion,相同的 minReader),而目前的讀取器會跳過它不認得的內容。這些項目在路線圖上,尚未實作。之所以在此保留這些名稱,是為了讓格式在它們到位時仍保持一致。

對讀取器而言,任何不在這些保留名稱與上述部分之列的內容,都是未知部分:原封不動地保留,並計入 skipped

參考