StorageLocationManager
Manages user-selected storage location with security-scoped bookmark persistence.
Overview
Located in Minuta/Sources/Services/StorageLocationManager.swift.
This is a @MainActor ObservableObject that handles:
- User folder selection via system picker
- Security-scoped bookmark creation and restoration
- Persistence across app launches
- Fallback to default location
Properties
@Published private(set) var currentURL: URL? // Selected folder URL (nil = default)
@Published private(set) var hasSelectedLocation: Bool // Whether user has made a selection
var storageURL: URL // Resolved URL (selected or default) Key Methods
saveBookmark(for:)
Saves a security-scoped bookmark for the selected URL:
func saveBookmark(for url: URL) throws - Starts accessing security-scoped resource
- Creates bookmark with appropriate options (Mac Catalyst vs iOS)
- Stores bookmark data in UserDefaults
- Updates published properties
restoreBookmark()
Restores URL from saved bookmark:
func restoreBookmark() throws -> URL - Called on init if user has previously selected location
- Handles stale bookmarks by attempting refresh
- Starts accessing security-scoped resource
useDefaultLocation()
Uses the default ~/Documents/Minuta location:
func useDefaultLocation() - Clears stored bookmark
- Marks location as selected (to skip first-launch prompt)
Default Location
storageURL falls back to <Documents>/Minuta when no folder is selected. What <Documents> means differs per platform:
Mac Catalyst (Homebrew build, not sandboxed): the real
~/Documents, so the folder sits next to the user’s other documents and is picked up by iCloud Drive “Desktop & Documents” sync if that is on.Mac App Store build (sandboxed): the container’s
Documents, invisible in Finder. Planned, see 917-plan-mac-testflight.iOS: the app container’s
Documents. It is exposed to the user through twoInfo.plistkeys (Minuta/Sources/Info.plist):UIFileSharingEnabledpublishes the container’sDocumentsin the Files app under On My iPhone > Minuta (the data lives in theMinutasubfolder there) and in Finder’s file-sharing pane when the device is connected.LSSupportsOpeningDocumentsInPlacelets Files and other apps open those files where they are instead of copying them.
Without both keys the default folder is invisible on the phone, and users cannot find, back up, or move their data. The folder is still local-only: it does not sync to iCloud Drive. Users who want cloud sync pick an iCloud Drive / Dropbox folder in Settings, or wait for the iCloud default described in 918-plan-icloud-default.
Files edited from the Files app while Minuta is open are picked up by
ExternalChangeMonitor(mtime polling), same as a sync-service drop.
resetToDefault()
Resets to show first-launch folder selection:
func resetToDefault() - Clears stored bookmark
- Marks location as not selected
isCurrentLocationAccessible()
Checks if current storage location is writable:
func isCurrentLocationAccessible() -> Bool Security-Scoped Bookmarks
Different bookmark options for Mac Catalyst vs iOS:
Mac Catalyst
url.bookmarkData(options: .withSecurityScope, ...)
URL(resolvingBookmarkData: data, options: .withSecurityScope, ...) iOS
url.bookmarkData(options: .minimalBookmark, ...)
URL(resolvingBookmarkData: data, options: [], ...) Error Handling
enum StorageLocationError: Error {
case noBookmarkFound
case bookmarkCreationFailed(Error)
case bookmarkResolutionFailed(Error)
case accessDenied
case staleBookmark
} Usage Flow
First Launch
- App checks
hasSelectedLocation - If false, shows
StorageSetupView - User picks folder or uses default
saveBookmark()oruseDefaultLocation()called
Subsequent Launches
- Init calls
restoreBookmark()ifhasSelectedLocation - If restoration fails, resets to show folder picker
storageURLproperty provides current location
Changing Folder
- User opens Settings, taps “Data Folder”
FolderPickerViewshown- On selection,
saveBookmark(for:)called AppState.reinitializeStorage()called to reload data
Integration with AppState
AppState uses StorageLocationManager’s storageURL:
let url = storageLocationManager.storageURL
storage = LocalFileStorageService(baseURL: url) Related
- 301-file-storage - File storage service
- 101-overview - Architecture overview
- 402-ios - UI (SettingsSheet, FolderPickerView, StorageSetupView)