| 1 | import Foundation |
| 2 | |
| 3 | /// Why a store operation failed. The three cases are kept apart because each |
| 4 | /// one asks for a different reaction, and folding them together is how a |
| 5 | /// locked phone ends up deleting the changes it was keeping. |
| 6 | public enum BlobStoreError: Error, Sendable, Equatable { |
| 7 | /// The device is locked, file protection refused the read, or the Keychain |
| 8 | /// would not answer. Retry later (on `.active` or unlock). Never read this |
| 9 | /// as "empty", and never delete anything because of it. |
| 10 | case unavailable |
| 11 | /// The account's key is gone (`errSecItemNotFound`) while its directory |
| 12 | /// still held sealed files. The store has already removed that directory, |
| 13 | /// because nothing in it can ever be read again; the caller reports the |
| 14 | /// loss once and carries on with an empty store. |
| 15 | case keyLost |
| 16 | /// One file failed its authentication tag. The store has removed that |
| 17 | /// file, and only that file; the path is given so it can be reported once. |
| 18 | case corrupt(String) |
| 19 | } |
| 20 | |
| 21 | /// Durable bytes by relative path, for one account. `NativeSealedStore` is the |
| 22 | /// real one; tests use `MemoryBlobStore` and `FaultingBlobStore`. |
| 23 | /// |
| 24 | /// Paths are relative, `/`-separated, and never renamed once written: a |
| 25 | /// sealed file is bound to its path. |
| 26 | public protocol BlobStore: Sendable { |
| 27 | /// Atomic: after a crash the path holds either the old bytes or the new |
| 28 | /// ones. Durable on return means it survives a process kill, not a power |
| 29 | /// loss. |
| 30 | func write(_ data: Data, to path: String) throws |
| 31 | /// nil when there is no such file. |
| 32 | func read(_ path: String) throws -> Data? |
| 33 | /// The names (not paths) of the files directly in `directory`, sorted. An |
| 34 | /// absent directory lists as empty. |
| 35 | func list(_ directory: String) throws -> [String] |
| 36 | /// Removes a file or a directory. Removing what is not there succeeds. |
| 37 | func remove(_ path: String) throws |
| 38 | } |
| 39 | |