Skip to content

Commit f85e81d

Browse files
committed
Refactoring
1 parent 52bb03e commit f85e81d

File tree

13 files changed

+122
-147
lines changed

13 files changed

+122
-147
lines changed

Sources/Shared/Toolkit/Extensions/URL.swift

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ extension URL: Loggable {
6363
/// Returns the first available URL by appending the given `pathComponent`.
6464
///
6565
/// If `pathComponent` is already taken, then it appends a number to it.
66+
// FIXME: Move to Internal
6667
public func appendingUniquePathComponent(_ pathComponent: String? = nil) -> URL {
6768
/// Returns the first path component matching the given `validation` closure.
6869
/// Numbers are appended to the path component until a valid candidate is found.

TestApp/Sources/App/AppModule.swift

+6-4
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ final class AppModule {
2929
init() throws {
3030
let httpClient = DefaultHTTPClient()
3131

32-
let file = Paths.library.appendingPathComponent("database.db")
33-
let db = try Database(file: file)
32+
let file = Paths.library.appendingPath("database.db", isDirectory: false)
33+
let db = try Database(file: file.url)
3434
print("Created database at \(file.path)")
3535

3636
let books = BookRepository(db: db)
@@ -86,7 +86,9 @@ extension AppModule: ReaderModuleDelegate {}
8686

8787
extension AppModule: OPDSModuleDelegate {
8888
func opdsDownloadPublication(_ publication: Publication?, at link: Link, sender: UIViewController) async throws -> Book {
89-
let url = try link.url(relativeTo: publication?.baseURL)
90-
return try await library.importPublication(from: url.url, sender: sender)
89+
guard let url = try link.url(relativeTo: publication?.baseURL).absoluteURL else {
90+
throw OPDSError.invalidURL(link.href)
91+
}
92+
return try await library.importPublication(from: url, sender: sender)
9193
}
9294
}

TestApp/Sources/AppDelegate.swift

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//
66

77
import Combine
8+
import ReadiumShared
89
import UIKit
910

1011
@UIApplicationMain
@@ -52,9 +53,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
5253
}
5354

5455
func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
56+
guard let url = url.absoluteURL else {
57+
return false
58+
}
59+
5560
Task {
5661
try! await app.library.importPublication(from: url, sender: window!.rootViewController!)
5762
}
63+
5864
return true
5965
}
6066
}

TestApp/Sources/Common/Paths.swift

+46-16
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,27 @@ import ReadiumShared
1111
final class Paths {
1212
private init() {}
1313

14-
static let home: URL =
15-
.init(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
14+
static let home: FileURL =
15+
URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true).fileURL!
1616

17-
static let temporary: URL =
18-
.init(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
17+
static let temporary: FileURL =
18+
URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).fileURL!
1919

20-
static let documents: URL =
21-
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
20+
static let documents: FileURL =
21+
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.fileURL!
2222

23-
static let library: URL =
24-
FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
23+
static let library: FileURL =
24+
FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!.fileURL!
2525

26-
static let covers: URL = {
27-
let url = library.appendingPathComponent("Covers")
28-
try! FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
26+
static let covers: FileURL = {
27+
let url = library.appendingPath("Covers", isDirectory: true)
28+
try! FileManager.default.createDirectory(at: url.url, withIntermediateDirectories: true)
2929
return url
3030
}()
3131

32-
static func makeDocumentURL(for source: URL? = nil, title: String?, mediaType: MediaType) -> URL {
32+
static func makeDocumentURL(for source: FileURL? = nil, title: String?, mediaType: MediaType) -> FileURL {
3333
// Is the file already in Documents/?
34-
if let source = source, source.standardizedFileURL.deletingLastPathComponent() == documents.standardizedFileURL {
34+
if let source = source, documents.isParent(of: source) {
3535
return source
3636
} else {
3737
let title = title.takeIf { !$0.isEmpty } ?? UUID().uuidString
@@ -41,12 +41,42 @@ final class Paths {
4141
}
4242
}
4343

44-
static func makeTemporaryURL() -> URL {
44+
static func makeTemporaryURL() -> FileURL {
4545
temporary.appendingUniquePathComponent()
4646
}
4747

4848
/// Returns whether the given `url` locates a file that is under the app's home directory.
49-
static func isAppFile(at url: URL) -> Bool {
50-
home.isParentOf(url)
49+
static func isAppFile(at url: FileURL) -> Bool {
50+
home.isParent(of: url)
51+
}
52+
}
53+
54+
extension FileURL {
55+
func appendingUniquePathComponent(_ pathComponent: String? = nil) -> FileURL {
56+
/// Returns the first path component matching the given `validation` closure.
57+
/// Numbers are appended to the path component until a valid candidate is found.
58+
func uniquify(_ pathComponent: String?, validation: (String) -> Bool) -> String {
59+
let pathComponent = pathComponent ?? UUID().uuidString
60+
var ext = (pathComponent as NSString).pathExtension
61+
if !ext.isEmpty {
62+
ext = ".\(ext)"
63+
}
64+
let pathComponentWithoutExtension = (pathComponent as NSString).deletingPathExtension
65+
66+
var candidate = pathComponent
67+
var i = 0
68+
while !validation(candidate) {
69+
i += 1
70+
candidate = "\(pathComponentWithoutExtension) \(i)\(ext)"
71+
}
72+
return candidate
73+
}
74+
75+
let pathComponent = uniquify(pathComponent) { candidate in
76+
let destination = appendingPath(candidate, isDirectory: false)
77+
return !((try? destination.url.checkResourceIsReachable()) ?? false)
78+
}
79+
80+
return appendingPath(pathComponent, isDirectory: false)
5181
}
5282
}

TestApp/Sources/Common/Toolkit/Extensions/HTTPClient.swift

-71
This file was deleted.

TestApp/Sources/Data/Book.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ struct Book: Codable {
6565
self.preferencesJSON = preferencesJSON
6666
}
6767

68-
var cover: URL? {
69-
coverPath.map { Paths.covers.appendingPathComponent($0) }
68+
var cover: FileURL? {
69+
coverPath.map { Paths.covers.appendingPath($0, isDirectory: false) }
7070
}
7171

7272
func preferences<P: Decodable>() throws -> P? {

TestApp/Sources/Library/DRM/DRMLibraryService.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Foundation
99
import ReadiumShared
1010

1111
struct DRMFulfilledPublication {
12-
let localURL: URL
12+
let localURL: FileURL
1313
let suggestedFilename: String
1414
}
1515

@@ -19,8 +19,8 @@ protocol DRMLibraryService {
1919
var contentProtection: ContentProtection? { get }
2020

2121
/// Returns whether this DRM can fulfill the given file into a protected publication.
22-
func canFulfill(_ file: URL) -> Bool
22+
func canFulfill(_ file: FileURL) -> Bool
2323

2424
/// Fulfills the given file to the fully protected publication.
25-
func fulfill(_ file: URL) async throws -> DRMFulfilledPublication?
25+
func fulfill(_ file: FileURL) async throws -> DRMFulfilledPublication?
2626
}

TestApp/Sources/Library/DRM/LCPLibraryService.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,20 @@
2323

2424
lazy var contentProtection: ContentProtection? = lcpService.contentProtection()
2525

26-
func canFulfill(_ file: URL) -> Bool {
27-
file.pathExtension.lowercased() == "lcpl"
26+
func canFulfill(_ file: FileURL) -> Bool {
27+
file.pathExtension?.lowercased() == "lcpl"
2828
}
2929

30-
func fulfill(_ file: URL) async throws -> DRMFulfilledPublication? {
31-
let pub = try await lcpService.acquirePublication(from: FileURL(url: file)!).get()
30+
func fulfill(_ file: FileURL) async throws -> DRMFulfilledPublication? {
31+
let pub = try await lcpService.acquirePublication(from: file).get()
3232
// Removes the license file, but only if it's in the App directory (e.g. Inbox/).
3333
// Otherwise we might delete something from a shared location (e.g. iCloud).
3434
if Paths.isAppFile(at: file) {
35-
try? FileManager.default.removeItem(at: file)
35+
try? FileManager.default.removeItem(at: file.url)
3636
}
3737

3838
return DRMFulfilledPublication(
39-
localURL: pub.localURL.url,
39+
localURL: pub.localURL,
4040
suggestedFilename: pub.suggestedFilename
4141
)
4242
}

TestApp/Sources/Library/LibraryError.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ enum LibraryError: LocalizedError {
1313
case bookDeletionFailed(Error?)
1414
case importFailed(Error)
1515
case openFailed(Error)
16-
case downloadFailed(Error)
16+
case downloadFailed(Error?)
1717
case cancelled
1818

1919
var errorDescription: String? {
@@ -27,7 +27,7 @@ enum LibraryError: LocalizedError {
2727
case let .openFailed(error):
2828
return String(format: NSLocalizedString("library_error_openFailed", comment: "Error message used when a low-level error occured while opening a publication"), error.localizedDescription)
2929
case let .downloadFailed(error):
30-
return String(format: NSLocalizedString("library_error_downloadFailed", comment: "Error message when the download of a publication failed"), error.localizedDescription)
30+
return String(format: NSLocalizedString("library_error_downloadFailed", comment: "Error message when the download of a publication failed"), error?.localizedDescription ?? "None")
3131
default:
3232
return nil
3333
}

TestApp/Sources/Library/LibraryModule.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ protocol LibraryModuleAPI {
2121
/// Imports a new publication to the library, either from:
2222
/// - a local file URL
2323
/// - a remote URL which will be downloaded
24-
func importPublication(from url: URL, sender: UIViewController) async throws -> Book
24+
func importPublication(from url: AbsoluteURL, sender: UIViewController) async throws -> Book
2525
}
2626

2727
protocol LibraryModuleDelegate: ModuleDelegate {
@@ -50,7 +50,7 @@ final class LibraryModule: LibraryModuleAPI {
5050
return library
5151
}()
5252

53-
func importPublication(from url: URL, sender: UIViewController) async throws -> Book {
53+
func importPublication(from url: AbsoluteURL, sender: UIViewController) async throws -> Book {
5454
try await library.importPublication(from: url, sender: sender)
5555
}
5656
}

0 commit comments

Comments
 (0)