Skip to content

Commit cddfa94

Browse files
authored
Bubble up errors while loading Resources (#400)
1 parent 23e22cf commit cddfa94

13 files changed

+216
-67
lines changed

.github/workflows/checks.yml

+12-6
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ jobs:
3333
git diff --exit-code Support/Carthage/Readium.xcodeproj
3434
- name: Build
3535
run: |
36-
xcodebuild build-for-testing -scheme "$scheme" -destination "platform=$platform,name=$device"
36+
set -eo pipefail
37+
xcodebuild build-for-testing -scheme "$scheme" -destination "platform=$platform,name=$device" | if command -v xcpretty &> /dev/null; then xcpretty; else cat; fi
3738
- name: Test
3839
run: |
39-
xcodebuild test-without-building -scheme "$scheme" -destination "platform=$platform,name=$device"
40+
set -eo pipefail
41+
xcodebuild test-without-building -scheme "$scheme" -destination "platform=$platform,name=$device" | if command -v xcpretty &> /dev/null; then xcpretty; else cat; fi
4042
4143
lint:
4244
name: Lint
@@ -91,7 +93,8 @@ jobs:
9193
run: make dev lcp=${{ secrets.LCP_URL_SPM }}
9294
- name: Build
9395
run: |
94-
xcodebuild build -scheme TestApp -destination "platform=$platform,name=$device"
96+
set -eo pipefail
97+
xcodebuild build -scheme TestApp -destination "platform=$platform,name=$device" | if command -v xcpretty &> /dev/null; then xcpretty; else cat; fi
9598
9699
int-spm:
97100
name: Integration (Swift Package Manager)
@@ -118,7 +121,8 @@ jobs:
118121
run: make spm lcp=${{ secrets.LCP_URL_SPM }} commit=$commit_sha
119122
- name: Build
120123
run: |
121-
xcodebuild build -scheme TestApp -destination "platform=$platform,name=$device"
124+
set -eo pipefail
125+
xcodebuild build -scheme TestApp -destination "platform=$platform,name=$device" | if command -v xcpretty &> /dev/null; then xcpretty; else cat; fi
122126
123127
int-carthage:
124128
name: Integration (Carthage)
@@ -145,7 +149,8 @@ jobs:
145149
run: make carthage lcp=${{ secrets.LCP_URL_CARTHAGE }} commit=$commit_sha
146150
- name: Build
147151
run: |
148-
xcodebuild build -scheme TestApp -destination "platform=$platform,name=$device"
152+
set -eo pipefail
153+
xcodebuild build -scheme TestApp -destination "platform=$platform,name=$device" | if command -v xcpretty &> /dev/null; then xcpretty; else cat; fi
149154
150155
# Warning: This job cannot actually test the state of the current commit,
151156
# but will check that the latest branch/tag set in the Podspecs are valid.
@@ -168,4 +173,5 @@ jobs:
168173
run: make cocoapods lcp=${{ secrets.LCP_URL_COCOAPODS }} commit=$commit_sha
169174
- name: Build
170175
run: |
171-
xcodebuild build -workspace TestApp.xcworkspace -scheme TestApp -destination "platform=$platform,name=$device"
176+
set -eo pipefail
177+
xcodebuild build -workspace TestApp.xcworkspace -scheme TestApp -destination "platform=$platform,name=$device" | if command -v xcpretty &> /dev/null; then xcpretty; else cat; fi

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. Take a look
1111
#### Navigator
1212

1313
* The `AudioNavigator` API has been promoted to stable and ships with a new Preferences API.
14+
* The new `NavigatorDelegate.didFailToLoadResourceAt(_:didFailToLoadResourceAt:withError:)` delegate API notifies when an error occurs while loading a publication resource (contributed by [@ettore](https://github.com/readium/swift-toolkit/pull/400)).
1415

1516
### Fixed
1617

Sources/Adapters/GCDWebServer/GCDHTTPServer.swift

+46-14
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,19 @@ public enum GCDHTTPServerError: Error {
1717

1818
/// Implementation of `HTTPServer` using ReadiumGCDWebServer under the hood.
1919
public class GCDHTTPServer: HTTPServer, Loggable {
20+
private struct EndpointHandler {
21+
let resourceHandler: (HTTPServerRequest) -> Resource
22+
let failureHandler: HTTPServer.FailureHandler?
23+
}
24+
2025
/// Shared instance of the HTTP server.
2126
public static let shared = GCDHTTPServer()
2227

2328
/// The actual underlying HTTP server instance.
2429
private let server = ReadiumGCDWebServer()
2530

2631
/// Mapping between endpoints and their handlers.
27-
private var handlers: [HTTPServerEndpoint: (HTTPServerRequest) -> Resource] = [:]
32+
private var handlers: [HTTPServerEndpoint: EndpointHandler] = [:]
2833

2934
/// Mapping between endpoints and resource transformers.
3035
private var transformers: [HTTPServerEndpoint: [ResourceTransformer]] = [:]
@@ -84,7 +89,7 @@ public class GCDHTTPServer: HTTPServer, Loggable {
8489
}
8590

8691
private func handle(request: ReadiumGCDWebServerRequest, completion: @escaping ReadiumGCDWebServerCompletionBlock) {
87-
responseResource(for: request) { resource in
92+
responseResource(for: request) { httpServerRequest, resource, failureHandler in
8893
let response: ReadiumGCDWebServerResponse
8994
switch resource.length {
9095
case let .success(length):
@@ -95,19 +100,26 @@ public class GCDHTTPServer: HTTPServer, Loggable {
95100
)
96101
case let .failure(error):
97102
self.log(.error, error)
98-
response = ReadiumGCDWebServerErrorResponse(statusCode: error.httpStatusCode)
103+
failureHandler?(httpServerRequest, error)
104+
response = ReadiumGCDWebServerErrorResponse(
105+
statusCode: error.httpStatusCode,
106+
error: error
107+
)
99108
}
100109

101-
completion(response)
110+
completion(response) // goes back to ReadiumGCDWebServerConnection.m
102111
}
103112
}
104113

105-
private func responseResource(for request: ReadiumGCDWebServerRequest, completion: @escaping (Resource) -> Void) {
106-
let completion = { resource in
114+
private func responseResource(
115+
for request: ReadiumGCDWebServerRequest,
116+
completion: @escaping (HTTPServerRequest, Resource, FailureHandler?) -> Void
117+
) {
118+
let completion = { request, resource, failureHandler in
107119
// Escape the queue to avoid deadlocks if something is using the
108120
// server in the handler.
109121
DispatchQueue.global().async {
110-
completion(resource)
122+
completion(request, resource, failureHandler)
111123
}
112124
}
113125

@@ -130,36 +142,56 @@ public class GCDHTTPServer: HTTPServer, Loggable {
130142

131143
for (endpoint, handler) in handlers {
132144
if endpoint == pathWithoutAnchor {
133-
let resource = handler(HTTPServerRequest(url: request.url, href: nil))
134-
completion(transform(resource: resource, at: endpoint))
145+
let request = HTTPServerRequest(url: request.url, href: nil)
146+
let resource = handler.resourceHandler(request)
147+
completion(request,
148+
transform(resource: resource, at: endpoint),
149+
handler.failureHandler)
135150
return
136151

137152
} else if path.hasPrefix(endpoint.addingSuffix("/")) {
138-
let resource = handler(HTTPServerRequest(
153+
let request = HTTPServerRequest(
139154
url: request.url,
140155
href: path.removingPrefix(endpoint.removingSuffix("/"))
141-
))
142-
completion(transform(resource: resource, at: endpoint))
156+
)
157+
let resource = handler.resourceHandler(request)
158+
completion(request,
159+
transform(resource: resource, at: endpoint),
160+
handler.failureHandler)
143161
return
144162
}
145163
}
146164

147-
completion(FailureResource(link: Link(href: request.url.absoluteString), error: .notFound(nil)))
165+
log(.warning, "Resource not found for request \(request)")
166+
completion(HTTPServerRequest(url: request.url, href: nil),
167+
FailureResource(link: Link(href: request.url.absoluteString),
168+
error: .notFound(nil)),
169+
nil)
148170
}
149171
}
150172

151173
// MARK: HTTPServer
152174

153175
public func serve(at endpoint: HTTPServerEndpoint, handler: @escaping (HTTPServerRequest) -> Resource) throws -> URL {
176+
try serve(at: endpoint, handler: handler, failureHandler: nil)
177+
}
178+
179+
public func serve(
180+
at endpoint: HTTPServerEndpoint,
181+
handler: @escaping (HTTPServerRequest) -> Resource,
182+
failureHandler: FailureHandler?
183+
) throws -> URL {
154184
try queue.sync(flags: .barrier) {
155185
if case .stopped = state {
156186
try start()
157187
}
188+
158189
guard case let .started(port: _, baseURL: baseURL) = state else {
159190
throw GCDHTTPServerError.serverNotStarted
160191
}
161192

162-
handlers[endpoint] = handler
193+
handlers[endpoint] = EndpointHandler(resourceHandler: handler,
194+
failureHandler: failureHandler)
163195

164196
return baseURL.appendingPathComponent(endpoint)
165197
}

Sources/Navigator/CBZ/CBZNavigatorViewController.swift

+29-15
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ open class CBZNavigatorViewController: UIViewController, VisualNavigator, Loggab
2626

2727
private let server: HTTPServer?
2828
private let publicationEndpoint: HTTPServerEndpoint?
29-
private let publicationBaseURL: URL
29+
private var publicationBaseURL: URL!
3030

3131
public convenience init(
3232
publication: Publication,
@@ -39,52 +39,66 @@ open class CBZNavigatorViewController: UIViewController, VisualNavigator, Loggab
3939
}
4040

4141
let publicationEndpoint: HTTPServerEndpoint?
42-
let baseURL: URL
43-
if let url = publication.baseURL {
42+
let uuidEndpoint = UUID().uuidString
43+
if publication.baseURL != nil {
4444
publicationEndpoint = nil
45-
baseURL = url
4645
} else {
47-
let endpoint = UUID().uuidString
48-
publicationEndpoint = endpoint
49-
baseURL = try httpServer.serve(at: endpoint, publication: publication)
46+
publicationEndpoint = uuidEndpoint
5047
}
5148

5249
self.init(
5350
publication: publication,
5451
initialLocation: initialLocation,
5552
httpServer: httpServer,
56-
publicationEndpoint: publicationEndpoint,
57-
publicationBaseURL: baseURL
53+
publicationEndpoint: publicationEndpoint
5854
)
55+
56+
if let url = publication.baseURL {
57+
publicationBaseURL = url
58+
} else {
59+
publicationBaseURL = try httpServer.serve(
60+
at: uuidEndpoint,
61+
publication: publication,
62+
failureHandler: { [weak self] request, error in
63+
DispatchQueue.main.async { [weak self] in
64+
guard let self = self, let href = request.href else {
65+
return
66+
}
67+
self.delegate?.navigator(self, didFailToLoadResourceAt: href, withError: error)
68+
}
69+
}
70+
)
71+
}
72+
73+
publicationBaseURL = URL(string: publicationBaseURL.absoluteString.addingSuffix("/"))!
5974
}
6075

6176
@available(*, deprecated, message: "See the 2.5.0 migration guide to migrate the HTTP server")
6277
public convenience init(publication: Publication, initialLocation: Locator? = nil) {
6378
precondition(!publication.isRestricted, "The provided publication is restricted. Check that any DRM was properly unlocked using a Content Protection.")
64-
guard let baseURL = publication.baseURL else {
79+
guard publication.baseURL != nil else {
6580
preconditionFailure("No base URL provided for the publication. Add it to the HTTP server.")
6681
}
6782

6883
self.init(
6984
publication: publication,
7085
initialLocation: initialLocation,
7186
httpServer: nil,
72-
publicationEndpoint: nil,
73-
publicationBaseURL: baseURL
87+
publicationEndpoint: nil
7488
)
89+
90+
publicationBaseURL = URL(string: publicationBaseURL.absoluteString.addingSuffix("/"))!
7591
}
7692

7793
private init(
7894
publication: Publication,
7995
initialLocation: Locator?,
8096
httpServer: HTTPServer?,
81-
publicationEndpoint: HTTPServerEndpoint?,
82-
publicationBaseURL: URL
97+
publicationEndpoint: HTTPServerEndpoint?
8398
) {
8499
self.publication = publication
85100
server = httpServer
86101
self.publicationEndpoint = publicationEndpoint
87-
self.publicationBaseURL = URL(string: publicationBaseURL.absoluteString.addingSuffix("/"))!
88102

89103
initialIndex = {
90104
guard let initialLocation = initialLocation, let initialIndex = publication.readingOrder.firstIndex(withHREF: initialLocation.href) else {

Sources/Navigator/EPUB/EPUBNavigatorViewController.swift

+10
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,16 @@ extension EPUBNavigatorViewController: EPUBNavigatorViewModelDelegate {
944944
}
945945
}
946946
}
947+
948+
func epubNavigatorViewModel(
949+
_ viewModel: EPUBNavigatorViewModel,
950+
didFailToLoadResourceAt href: String,
951+
withError error: ResourceError
952+
) {
953+
DispatchQueue.main.async {
954+
self.delegate?.navigator(self, didFailToLoadResourceAt: href, withError: error)
955+
}
956+
}
947957
}
948958

949959
extension EPUBNavigatorViewController: EPUBSpreadViewDelegate {

0 commit comments

Comments
 (0)