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.