Skip to content

Commit a014d59

Browse files
committed
Port API changes from the Kotlin toolkit (#463)
1 parent 7f97ee4 commit a014d59

File tree

298 files changed

+22964
-8063
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

298 files changed

+22964
-8063
lines changed

.github/workflows/checks.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ env:
99
platform: ${{ 'iOS Simulator' }}
1010
device: ${{ 'iPhone 15' }}
1111
commit_sha: ${{ github.sha }}
12-
DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer
12+
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
1313

1414
jobs:
1515
build:
1616
name: Build
17-
runs-on: macos-13
17+
runs-on: macos-14
1818
if: ${{ !github.event.pull_request.draft }}
1919
env:
2020
scheme: ${{ 'Readium-Package' }}
@@ -42,7 +42,7 @@ jobs:
4242
4343
lint:
4444
name: Lint
45-
runs-on: macos-13
45+
runs-on: macos-14
4646
if: ${{ !github.event.pull_request.draft }}
4747
env:
4848
scripts: ${{ 'Sources/Navigator/EPUB/Scripts' }}
@@ -76,7 +76,7 @@ jobs:
7676

7777
int-dev:
7878
name: Integration (Local)
79-
runs-on: macos-13
79+
runs-on: macos-14
8080
if: ${{ !github.event.pull_request.draft }}
8181
defaults:
8282
run:
@@ -98,7 +98,7 @@ jobs:
9898
9999
int-spm:
100100
name: Integration (Swift Package Manager)
101-
runs-on: macos-13
101+
runs-on: macos-14
102102
if: ${{ !github.event.pull_request.draft }}
103103
defaults:
104104
run:
@@ -126,7 +126,7 @@ jobs:
126126
127127
int-carthage:
128128
name: Integration (Carthage)
129-
runs-on: macos-13
129+
runs-on: macos-14
130130
if: ${{ !github.event.pull_request.draft }}
131131
defaults:
132132
run:
@@ -157,7 +157,7 @@ jobs:
157157
int-cocoapods:
158158
name: Integration (CocoaPods)
159159
if: github.event_name == 'push'
160-
runs-on: macos-13
160+
runs-on: macos-14
161161
defaults:
162162
run:
163163
working-directory: TestApp

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,17 @@ All notable changes to this project will be documented in this file. Take a look
1919

2020
#### Shared
2121

22+
* A new `Format` type was introduced to augment `MediaType` with more precise information about the format specifications of an `Asset`.
23+
* `Fetcher` was replaced with a simpler `Container` type.
24+
* `PublicationAsset` was replaced by `Asset`, which contains a `Format` and access to the underlying `Container` or `Resource`.
25+
* The `ResourceError` hierarchy was revamped and simplified (see `ReadError`). Now it is your responsibility to provide a localized user message for each error case.
2226
* The `Link` property key for archive-based publication assets (e.g. an EPUB/ZIP) is now `https://readium.org/webpub-manifest/properties#archive` instead of `archive`.
2327
* The API of `HTTPServer` slightly changed to be more future-proof.
2428

29+
#### Streamer
30+
31+
* The `Streamer` object was deprecated in favor of smaller segregated APIs: `AssetRetriever` and `PublicationOpener`.
32+
2533
#### Navigator
2634

2735
* EPUB: The `scroll` preference is now forced to `true` when rendering vertical text (e.g. CJK vertical). [See this discussion for the rationale](https://github.com/readium/swift-toolkit/discussions/370).

Cartfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ github "dexman/Minizip" ~> 1.4.0
33
github "krzyzanowskim/CryptoSwift" ~> 1.8.0
44
github "ra1028/DifferenceKit" ~> 1.3.0
55
github "readium/GCDWebServer" ~> 4.0.0
6-
github "scinfu/SwiftSoup" ~> 2.7.0
6+
# There's a regression with 2.7.4 in SwiftSoup, because they used iOS 13 APIs without bumping the deployment target.
7+
github "scinfu/SwiftSoup" == 2.7.1
78
github "stephencelis/SQLite.swift" ~> 0.15.0
89
github "weichsel/ZIPFoundation" ~> 0.9.0

Package.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ let package = Package(
3939
path: "Sources/Shared",
4040
exclude: [
4141
// Support for ZIPFoundation is not yet achieved.
42-
"Toolkit/Archive/ZIPFoundation.swift",
42+
"Toolkit/ZIP/ZIPFoundation.swift",
4343
],
4444
resources: [
4545
.process("Resources"),
@@ -63,7 +63,6 @@ let package = Package(
6363
dependencies: [
6464
"CryptoSwift",
6565
"Fuzi",
66-
.product(name: "ReadiumGCDWebServer", package: "GCDWebServer"),
6766
"Zip",
6867
"ReadiumShared",
6968
],
@@ -134,7 +133,7 @@ let package = Package(
134133
]
135134
),
136135
// These tests require a R2LCPClient.framework to run.
137-
// FIXME: Find a solution to run the tests with GitHub action.
136+
// TODO: Find a solution to run the tests with GitHub action.
138137
// .testTarget(
139138
// name: "ReadiumLCPTests",
140139
// dependencies: ["ReadiumLCP"],

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ This toolkit is a modular project, which follows the [Readium Architecture](http
1616

1717
<!-- https://swiftversion.net/ -->
1818

19-
| Readium | iOS | Swift compiler | Xcode |
20-
|-----------|------|----------------|--------|
21-
| `develop` | 13.0 | 5.9 | 15.0.1 |
22-
| 3.0.0 | 13.0 | 5.9 | 15.0.1 |
23-
| 2.5.1 | 11.0 | 5.6.1 | 13.4 |
24-
| 2.5.0 | 10.0 | 5.6.1 | 13.4 |
25-
| 2.4.0 | 10.0 | 5.3.2 | 12.4 |
19+
| Readium | iOS | Swift compiler | Xcode |
20+
|-----------|------|----------------|-------|
21+
| `develop` | 13.0 | 5.10 | 15.4 |
22+
| 3.0.0 | 13.0 | 5.10 | 15.4 |
23+
| 2.5.1 | 11.0 | 5.6.1 | 13.4 |
24+
| 2.5.0 | 10.0 | 5.6.1 | 13.4 |
25+
| 2.4.0 | 10.0 | 5.3.2 | 12.4 |
2626

2727
## Using Readium
2828

Sources/Adapters/GCDWebServer/GCDHTTPServer.swift

Lines changed: 94 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import Foundation
88
import ReadiumGCDWebServer
9+
import ReadiumInternal
910
import ReadiumShared
1011
import UIKit
1112

@@ -19,7 +20,8 @@ public enum GCDHTTPServerError: Error {
1920
/// Implementation of `HTTPServer` using ReadiumGCDWebServer under the hood.
2021
public class GCDHTTPServer: HTTPServer, Loggable {
2122
/// Shared instance of the HTTP server.
22-
public static let shared = GCDHTTPServer()
23+
@available(*, unavailable, message: "Create your own shared instance")
24+
public static var shared: GCDHTTPServer { fatalError() }
2325

2426
/// The actual underlying HTTP server instance.
2527
private let server = ReadiumGCDWebServer()
@@ -30,6 +32,8 @@ public class GCDHTTPServer: HTTPServer, Loggable {
3032
/// Mapping between endpoints and resource transformers.
3133
private var transformers: [HTTPURL: [ResourceTransformer]] = [:]
3234

35+
private let assetRetriever: AssetRetriever
36+
3337
private enum State {
3438
case stopped
3539
case started(port: UInt, baseURL: HTTPURL)
@@ -47,7 +51,12 @@ public class GCDHTTPServer: HTTPServer, Loggable {
4751
/// Creates a new instance of the HTTP server.
4852
///
4953
/// - Parameter logLevel: See `ReadiumGCDWebServer.setLogLevel`.
50-
public init(logLevel: Int = 3) {
54+
public init(
55+
assetRetriever: AssetRetriever,
56+
logLevel: Int = 3
57+
) {
58+
self.assetRetriever = assetRetriever
59+
5160
ReadiumGCDWebServer.setLogLevel(Int32(logLevel))
5261

5362
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
@@ -85,31 +94,40 @@ public class GCDHTTPServer: HTTPServer, Loggable {
8594
}
8695

8796
private func handle(request: ReadiumGCDWebServerRequest, completion: @escaping ReadiumGCDWebServerCompletionBlock) {
88-
responseResource(for: request) { httpServerRequest, resource, failureHandler in
89-
let response: ReadiumGCDWebServerResponse
90-
switch resource.length {
91-
case let .success(length):
92-
response = ResourceResponse(
93-
resource: resource,
94-
length: length,
95-
range: request.hasByteRange() ? request.byteRange : nil
96-
)
97-
case let .failure(error):
98-
self.log(.error, error)
99-
failureHandler?(httpServerRequest, error)
100-
response = ReadiumGCDWebServerErrorResponse(
101-
statusCode: error.httpStatusCode,
102-
error: error
103-
)
104-
}
97+
responseResource(for: request) { httpServerRequest, httpServerResponse, failureHandler in
98+
Task {
99+
let response: ReadiumGCDWebServerResponse
100+
let resource = httpServerResponse.resource
101+
102+
func fail(_ error: ReadError) -> ReadiumGCDWebServerResponse {
103+
self.log(.error, error)
104+
failureHandler?(httpServerRequest, error)
105+
return ReadiumGCDWebServerErrorResponse(
106+
statusCode: 500,
107+
error: error
108+
)
109+
}
110+
111+
switch await resource.length() {
112+
case let .success(length):
113+
response = await ResourceResponse(
114+
resource: httpServerResponse.resource,
115+
length: length,
116+
range: request.hasByteRange() ? request.byteRange : nil,
117+
mediaType: httpServerResponse.mediaType(using: self.assetRetriever)
118+
)
119+
case let .failure(error):
120+
response = fail(error)
121+
}
105122

106-
completion(response) // goes back to ReadiumGCDWebServerConnection.m
123+
completion(response) // goes back to ReadiumGCDWebServerConnection.m
124+
}
107125
}
108126
}
109127

110128
private func responseResource(
111129
for request: ReadiumGCDWebServerRequest,
112-
completion: @escaping (HTTPServerRequest, Resource, HTTPRequestHandler.OnFailure?) -> Void
130+
completion: @escaping (HTTPServerRequest, HTTPServerResponse, HTTPRequestHandler.OnFailure?) -> Void
113131
) {
114132
let completion = { request, resource, failureHandler in
115133
// Escape the queue to avoid deadlocks if something is using the
@@ -124,52 +142,40 @@ public class GCDHTTPServer: HTTPServer, Loggable {
124142
fatalError("Expected an HTTP URL")
125143
}
126144

127-
func transform(resource: Resource, at endpoint: HTTPURL) -> Resource {
145+
func transform(resource: Resource, request: HTTPServerRequest, at endpoint: HTTPURL) -> Resource {
128146
guard let transformers = transformers[endpoint], !transformers.isEmpty else {
129147
return resource
130148
}
149+
let href = request.href?.anyURL ?? request.url.anyURL
131150
var resource = resource
132151
for transformer in transformers {
133-
resource = transformer(resource)
152+
resource = transformer(href, resource)
134153
}
135154
return resource
136155
}
137156

138157
let pathWithoutAnchor = url.removingQuery().removingFragment()
139158

140159
for (endpoint, handler) in handlers {
160+
let request: HTTPServerRequest
141161
if endpoint.isEquivalentTo(pathWithoutAnchor) {
142-
let request = HTTPServerRequest(url: url, href: nil)
143-
let resource = handler.onRequest(request)
144-
completion(
145-
request,
146-
transform(resource: resource, at: endpoint),
147-
handler.onFailure
148-
)
149-
return
150-
162+
request = HTTPServerRequest(url: url, href: nil)
151163
} else if let href = endpoint.relativize(url) {
152-
let request = HTTPServerRequest(
153-
url: url,
154-
href: href
155-
)
156-
let resource = handler.onRequest(request)
157-
completion(
158-
request,
159-
transform(resource: resource, at: endpoint),
160-
handler.onFailure
161-
)
162-
return
164+
request = HTTPServerRequest(url: url, href: href)
165+
} else {
166+
continue
163167
}
168+
169+
var response = handler.onRequest(request)
170+
response.resource = transform(resource: response.resource, request: request, at: endpoint)
171+
completion(request, response, handler.onFailure)
172+
return
164173
}
165174

166175
log(.warning, "Resource not found for request \(request)")
167176
completion(
168177
HTTPServerRequest(url: url, href: nil),
169-
FailureResource(
170-
link: Link(href: request.url.absoluteString),
171-
error: .notFound(nil)
172-
),
178+
HTTPServerResponse(error: .notFound),
173179
nil
174180
)
175181
}
@@ -320,3 +326,44 @@ public class GCDHTTPServer: HTTPServer, Loggable {
320326
return true
321327
}
322328
}
329+
330+
private extension Resource {
331+
func length() async -> ReadResult<UInt64> {
332+
await estimatedLength()
333+
.flatMap { length in
334+
if let length = length {
335+
return .success(length)
336+
} else {
337+
return await read().map { UInt64($0.count) }
338+
}
339+
}
340+
}
341+
}
342+
343+
private extension HTTPServerResponse {
344+
func mediaType(using assetRetriever: AssetRetriever) async -> MediaType {
345+
if let mediaType = mediaType {
346+
return mediaType
347+
}
348+
349+
if let properties = try? await resource.properties().get() {
350+
if let mediaType = properties.mediaType {
351+
return mediaType
352+
}
353+
if
354+
let filename = properties.filename,
355+
let uti = UTI.findFrom(mediaTypes: [], fileExtensions: [URL(fileURLWithPath: filename).pathExtension]),
356+
let type = uti.preferredTag(withClass: .mediaType),
357+
let mediaType = MediaType(type)
358+
{
359+
return mediaType
360+
}
361+
}
362+
363+
if let mediaType = try? await assetRetriever.sniffFormat(of: resource).get().mediaType {
364+
return mediaType
365+
}
366+
367+
return .binary
368+
}
369+
}

0 commit comments

Comments
 (0)