Format danych
Wszystko, co SyncPocket App zapamiętuje, jest przechowywane lokalnie w wersjonowanym archiwum JSON zawierającym elementy i definicje Tablic oraz w folderze blobs na zawartość binarną. Ta strona jest referencją formatu.
Stabilność: poniższy format odzwierciedla aktualne źródło prawdy (Packages/MemoryKit). Do wydania 1.0 może ewoluować; aplikacja migruje stare dane automatycznie (już teraz migruje starszy format ClipStack).
Układ na dysku
~/Library/Application Support/MemoryApp/
├── items.json # wersjonowany HistoryEnvelope (elementy + Tablice)
├── items.backup.json # ostatni poprawny, atomowy snapshot
└── blobs/ # zawartość binarna wskazywana z items.json
├── <nazwa>.rtf # zawartość rich-text
└── <nazwa>.png # obrazy
HistoryEnvelope
items.json jest wersjonowanym HistoryEnvelope. Wersja 2 zapisuje savedAt oraz jedno atomowe archive z dwiema tablicami: items i boards. Wspólny zapis zapobiega utrwaleniu członkostwa elementu bez odpowiadającej definicji Tablicy.
Schemat ClipItem
Każdy element archive.items to ClipItem — platformowo neutralna struktura Swift Codable (tylko Foundation + CryptoKit, współdzielona przez macOS i iOS):
| Pole | Typ | Opis |
|---|---|---|
id | UUID string | Unikalny identyfikator elementu, generowany w chwili przechwycenia. |
type | string | Jedna z wartości ClipType: text, link, color, image, file. |
date | date | Ostatni moment przechwycenia/promocji (standardowe kodowanie dat Swift Codable); steruje retencją. |
createdAt | date | Pierwszy moment utworzenia. date jest odświeżane po ponownym przechwyceniu tej samej treści i jest znacznikiem używanym przez retencję. |
plainText | string · opcjonalne | Czysto tekstowa treść dla elementów text, link i color. |
rtfFile | string · opcjonalne | Nazwa pliku w blobs/ z zawartością RTF, gdy skopiowany tekst miał formatowanie. |
imageFile | string · opcjonalne | Nazwa pliku w blobs/ z obrazem dla elementów image. |
fileURLs | string[] | Adresy URL plików dla elementów file (pliki skopiowane w Finderze). Puste dla pozostałych typów. |
sourceAppName | string · opcjonalne | Czytelna nazwa aplikacji, z której skopiowano treść. |
sourceBundleID | string · opcjonalne | Identyfikator bundle aplikacji źródłowej (używany też przez listę wykluczeń). |
pinned | boolean | Trzyma element na górze i chroni przed retencją czasową. Domyślnie false. |
contentHash | string | SHA-256 treści, małe litery hex — klucz deduplikacji. |
boardIDs | UUID string[] | Identyfikatory Tablic opartych na członkostwie. Loved i Saved mają stałe ID; Pinned wynika z osobnego pola pinned. Duplikaty są usuwane z zachowaniem kolejności. |
reminderDate | date · opcjonalne | Zaplanowane lokalne przypomnienie. Dopóki pole istnieje, chroni element przed retencją do chwili jawnego wyczyszczenia. |
modifiedAt | date | Ostatnia zmiana stanu widocznego dla użytkownika. Konflikt między urządzeniami wygrywa nowsza wartość; starsze archiwa przyjmują tutaj date. |
deletedAt | date · opcjonalne | Gdy istnieje, element znajduje się w Usuniętych. Jest kasowany bezpowrotnie 30 dni po tym czasie, chyba że wcześniej zostanie przywrócony. |
Przykład
{
"version": 2,
"savedAt": 804108100.0,
"archive": {
"items": [{
"id": "5B1E9C2A-8F1D-4A5E-9C77-2A1B3C4D5E6F",
"type": "color",
"date": 804108000.0,
"createdAt": 804108000.0,
"modifiedAt": 804108000.0,
"plainText": "#FF6B6B",
"fileURLs": [],
"sourceAppName": "Figma",
"sourceBundleID": "com.figma.Desktop",
"pinned": false,
"boardIDs": ["F0000000-0000-4000-8000-000000000001"],
"reminderDate": 804799200.0,
"contentHash": "a1b2c3…64 znaki hex…d4e5f6"
}],
"boards": [{
"id": "F0000000-0000-4000-8000-000000000001",
"name": "Loved",
"kind": "loved",
"createdAt": -978307200.0
}]
}
}
Schemat MemoryBoard
Każdy element archive.boards jest rekordem MemoryBoard:
| Pole | Typ | Opis |
|---|---|---|
id | UUID string | Stabilny identyfikator Tablicy. |
name | string | Nazwa Tablicy widoczna dla użytkownika. |
kind | string | loved, saved albo custom. |
createdAt | date | Znacznik utworzenia używany do stabilnej kolejności. |
Pinned, Loved i Saved używają stałych ID Tablic (F000…000, F000…001 i F000…002) i są automatycznie przywracane w starszych archiwach. Członkostwo Pinned wynika z pinned, a isLoved i isSaved są wyliczane z boardIDs.
ClipType
| Wartość | Nazwa wyświetlana | SF Symbol |
|---|---|---|
text | Tekst | doc.text |
link | Link | link |
color | Kolor | paintpalette |
image | Obraz | photo |
file | Pliki | folder |
Wykrywanie typu tekstu
Skopiowany tekst jest klasyfikowany przez ClipItem.detectTextType(_:) według tych reguł, w kolejności (wejście jest najpierw przycinane z białych znaków):
- Kolor — pasuje do
^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$, czyli#RGB,#RRGGBBlub#RRGGBBAA. - Link — nie zawiera spacji ani nowych linii, parsuje się jako URL ze schematem
httplubhttpsi niepustym hostem. - Tekst — cała reszta.
Hashowanie treści i deduplikacja
Każdy element ma contentHash — skrót SHA-256 treści zakodowany jako hex małymi literami (dla tekstu: bajty UTF-8 ciągu):
Hashing.hash(of: "https://syncpocket.app")
// "…64 znaki hex małymi literami…"
Gdy pojawia się nowa treść w schowku, magazyn sprawdza hash: jeśli element z takim samym contentHash już istnieje, duplikat nie powstaje — istniejący element wraca na górę, a jego date jest odświeżane. Podpisane buildy iCloud używają CloudKitSyncEngine, a izolowane testy UI — NoopSyncEngine. Wydanie produkcyjne nadal wymaga podpisanego kontenera i testów konfliktów na dwóch urządzeniach.
Retencja czasowa
RetentionPolicy oferuje days30 (domyślne), days60, days90, days120 i forever. Wybrana preferencja jest przechowywana lokalnie poza archiwum. Element wygasa tylko wtedy, gdy jego odświeżone date osiągnie granicę i nie ma żadnej ochrony:
pinned == false;boardIDsjest puste; orazreminderDate == nil.
Przypomnienie chroni również po zaplanowanym terminie, dopóki użytkownik go nie wyczyści, dzięki czemu otwarcie doręczonego powiadomienia nie przegrywa z czyszczeniem przy powrocie aplikacji na pierwszy plan.
Migracja
Przy wczytywaniu surowa starsza tablica [ClipItem] i envelope w wersji 1 są migrowane do wersji 2. Brakujące pola dostają zgodne wstecznie wartości, Pinned, Loved i Saved są przywracane, a dane ze starszego katalogu ClipStack są przenoszone automatycznie.