import Foundation
import SwiftUI
struct Book: Codable, Identifiable {
let id: UUID
let title: String
let filename: String
let fileExtension: String
}
final class LibraryStore: ObservableObject {
@Published private(set) var books: [Book] = []
@Published var errorMessage: String?
private let fileManager = FileManager.default
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
private var documentsURL: URL {
fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
}
private var indexURL: URL {
documentsURL.appendingPathComponent("library.json")
}
init() {
do {
try fileManager.createDirectory(at: documentsURL, withIntermediateDirectories: true)
if fileManager.fileExists(atPath: indexURL.path) {
books = try decoder.decode([Book].self, from: Data(contentsOf: indexURL))
}
} catch {
errorMessage = "Impossible de charger la bibliothèque : \(error.localizedDescription)"
}
}
func importBooks(from urls: [URL]) {
var imported: [Book] = []
do {
for url in urls {
imported.append(try copyBook(from: url))
}
let updatedBooks = books + imported
try encoder.encode(updatedBooks).write(to: indexURL, options: .atomic)
books = updatedBooks
} catch {
var cleanupErrors: [String] = []
for book in imported {
do {
try fileManager.removeItem(at: documentsURL.appendingPathComponent(book.filename))
} catch {
cleanupErrors.append(error.localizedDescription)
}
}
let cleanupMessage = cleanupErrors.isEmpty
? ""
: " Certains fichiers temporaires n’ont pas pu être supprimés."
errorMessage = "Impossible d’importer le document : \(error.localizedDescription)\(cleanupMessage)"
}
}
func deleteBooks(at offsets: IndexSet) {
let removedBooks = offsets.map { books[$0] }
let updatedBooks = books.enumerated()
.filter { !offsets.contains($0.offset) }
.map(\.element)
do {
try encoder.encode(updatedBooks).write(to: indexURL, options: .atomic)
books = updatedBooks
for book in removedBooks {
try fileManager.removeItem(at: documentsURL.appendingPathComponent(book.filename))
}
} catch {
errorMessage = "Impossible de supprimer le document : \(error.localizedDescription)"
}
}
func fileURL(for book: Book) -> URL {
documentsURL.appendingPathComponent(book.filename)
}
private func copyBook(from sourceURL: URL) throws -> Book {
let hasAccess = sourceURL.startAccessingSecurityScopedResource()
defer {
if hasAccess {
sourceURL.stopAccessingSecurityScopedResource()
}
}
let fileExtension = sourceURL.pathExtension.lowercased()
let id = UUID()
let filename = "\(id.uuidString).\(fileExtension)"
try fileManager.copyItem(at: sourceURL, to: documentsURL.appendingPathComponent(filename))
let title = sourceURL.deletingPathExtension().lastPathComponent
return Book(id: id, title: title, filename: filename, fileExtension: fileExtension)
}
}
To embed this project on your website, copy the following code and paste it into your website's HTML: