Storage behavior
Handle file changes explicitly.
Cabinet’s typed locations describe filesystem paths. Constructing a Cabinet.File or Cabinet.Directory does not create it on disk. Child names passed to file(named:) and subdirectory(named:) must be single path components.
Choose a write policy
File writes and copies default to .failIfExists. Select .replace when an existing file should be overwritten:
try file.write(contents, policy: .replace)
Replacement writes stage the contents in a private sibling directory and publish them with a filesystem rename. The resulting single-file replacement is atomic. This protects against exposing a partially written file during replacement; it does not coordinate multiple writers or make several operations one transaction. Avoid using an exists check as a guarantee that a later operation will succeed: another process can change the filesystem between calls.
Propagate failures
Reading, writing, enumerating, and removing items throw on failure. JSON helpers also propagate encoding or decoding failures. Handle the thrown error according to the operation your application was attempting.
All I/O is synchronous. When operations may take noticeable time, run them in an execution context appropriate for blocking work.
Enumerate and move
let files = try directory.files(.recursive)
let directories = try directory.subdirectories()
let size = try directory.totalSize()
Enumeration is shallow by default; .recursive visits descendants. Use metadata() to inspect available size and dates.
Moves update the value’s location, so declare an item with var before calling move(to:). Directory moves fail when the destination exists. Cabinet does not replace nonempty directories.
Typed paths do not establish a security sandbox. Applications remain responsible for permissions, access to user-selected locations, and coordination between filesystem users.
Create and extract an archive
import CabinetZip
let backups = try Cabinet.Directory.applicationSupport
.subdirectory(named: "MyApplicationBackups")
try backups.create()
let archive = try backups.file(named: "backup.zip")
try directory.zip(to: archive, policy: .replace)
let restored = try backups.subdirectory(named: "Restored")
try archive.unzip(to: restored)
Choose an archive location outside the directory being archived. Directory archives include nested files and empty directories. Pass mode: .shallow to include only immediate files, or call zip(to:policy:) on a collection of files. Extraction accepts an optional Foundation Progress and pendingUnitCount. Extraction requires a destination that does not already exist, rejects unsafe entry paths and symbolic links, and stages extracted contents before publishing the destination.
Migrate from SwiftLibs
Replace the SLFileSystem dependency and import with Cabinet. Qualify locations as Cabinet.File and Cabinet.Directory. Initializers accepting URLs now throw, and callers must handle I/O failures that were previously suppressed.
Use file(named:) and subdirectory(named:) to construct child locations. Rename by constructing a destination and calling the mutating move(to:) method. Choose .replace explicitly for file operations that intentionally overwrite data, and use readJSON and writeJSON for Codable values.
Add the separate CabinetZip product for archive operations. See the archive API reference for the available operations.
The main API changes are:
| SwiftLibs | Cabinet |
|---|---|
FileSystem.File / FileSystem.Directory |
Cabinet.File / Cabinet.Directory |
exist |
exists |
data getter/setter |
Throwing read() / write(_:policy:) |
File create() creating only its parent |
Explicit parent?.create() followed by write |
delete() returning Bool |
Throwing remove() |
renameTo / moveTo |
move(to:) with a typed destination |
files(.deep) / subdirs(.deep) |
files(.recursive) / subdirectories(.recursive) |
| Implicit deep traversal | Explicit .recursive; default is shallow |
Sentinel metadata such as .distantPast |
Optional attributes from throwing metadata() |
| ZIP extraction replacing a directory | Extraction requires an absent destination |
Missing files now throw on reads and removals. Directory clearing can partially succeed before throwing; it is not transactional. File copy currently reads the contents into memory and copies data only, without preserving extended attributes.
If staging cleanup fails, Cabinet.Error.cleanupFailed reports its path and both the cleanup error and any earlier operation error. Replacement does not preserve the old file’s metadata or extended attributes.