LocalStorageProvider.swift

LocalStorageProvider Documentation

Overview

LocalStorageProvider.swift implements the StorageProvider protocol using local file storage. It manages entries and clusters by storing them as JSON files in the app's documents directory.

Core Components

Properties

private let fileManager = FileManager.default
private let entriesURL: URL
private let clustersURL: URL
Property
Purpose

fileManager

System file operations

entriesURL

Location for entries file

clustersURL

Location for clusters file

Initialization

init() throws {
    let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
    let storageDirectory = documentsDirectory.appendingPathComponent("Storage")
    // ...
}
  • Creates storage directory if needed

  • Sets up file URLs

  • Throws if setup fails

Primary Operations

Entry Management

Save Entry

func saveEntry(_ entry: Entry) async throws {
    var entries = try await fetchEntries()
    entries.append(entry)
    
    try await Task {
        let data = try JSONEncoder().encode(entries)
        try data.write(to: entriesURL)
    }.value
}

Steps:

  1. Fetch existing entries

  2. Append new entry

  3. Encode to JSON

  4. Write to file

Fetch Entries

func fetchEntries() async throws -> [Entry] {
    guard fileManager.fileExists(atPath: entriesURL.path) else {
        return []
    }
    // ...
}
  • Checks file existence

  • Reads and decodes data

  • Returns empty array if no file

Cluster Management

Update Cluster

func updateCluster(_ cluster: Cluster) async throws {
    var clusters = try await fetchClusters()
    
    if let index = clusters.firstIndex(where: { $0.id == cluster.id }) {
        clusters[index] = cluster
    } else {
        clusters.append(cluster)
    }
    // ...
}
  • Updates existing or adds new

  • Maintains cluster list

  • Persists changes

Fetch Clusters

func fetchClusters() async throws -> [Cluster] {
    guard fileManager.fileExists(atPath: clustersURL.path) else {
        return []
    }
    // ...
}
  • Similar to entry fetching

  • Handles missing file case

  • Decodes cluster data

Helper Methods

JSON Encoding

private func encode<T: Encodable>(_ value: T) throws -> Data {
    let encoder = JSONEncoder()
    encoder.dateEncodingStrategy = .iso8601
    return try encoder.encode(value)
}
  • Generic encoding method

  • Handles date formatting

  • Type-safe operation

JSON Decoding

private func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .iso8601
    return try decoder.decode(type, from: data)
}
  • Generic decoding method

  • Consistent date handling

  • Type inference

Usage Examples

Basic Operations

let storage = try LocalStorageProvider()

// Save entry
let entry = Entry(content: "Test", embeddings: [...])
try await storage.saveEntry(entry)

// Fetch clusters
let clusters = try await storage.fetchClusters()

// Update cluster
let updatedCluster = Cluster(...)
try await storage.updateCluster(updatedCluster)

Error Handling

do {
    try await storage.saveEntry(entry)
} catch {
    print("Storage error: \(error)")
}

File Structure

Directory Layout

Documents/
└── Storage/
    ├── entries.json
    └── clusters.json

File Format

// entries.json example
[
    {
        "id": "UUID",
        "content": "text",
        "embeddings": [...]
    }
]

Best Practices

  1. Data Integrity

    • Atomic write operations

    • Validate JSON structure

    • Handle file corruption

    • Implement backups

  2. Performance

    • Batch similar operations

    • Minimize disk access

    • Cache when appropriate

    • Handle large files

  3. Error Recovery

    • Keep backup copies

    • Validate before writing

    • Handle partial failures

    • Clean up temporary files

  4. Concurrency

    • Use async/await

    • Handle file locks

    • Prevent race conditions

    • Manage shared access

Limitations

  1. Storage Constraints

    • Local storage only

    • File size limits

    • Device space limits

    • No remote sync

  2. Performance Issues

    • Full file reads/writes

    • JSON parsing overhead

    • Memory usage with large files

    • No indexing support

  3. Concurrency Limits

    • Single file access

    • No concurrent writes

    • Potential blocking

    • Lock management

  • StorageProvider: Protocol definition

  • Entry/Cluster Models: Data structures

  • JSONEncoder/Decoder: Data conversion

  • FileManager: File operations

Last updated