Skip to content

Commit f23a2e5

Browse files
authored
Migrate to ZIPFoundation (#518)
1 parent 6c58ef4 commit f23a2e5

40 files changed

+375
-748
lines changed

CHANGELOG.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ All notable changes to this project will be documented in this file. Take a look
44

55
**Warning:** Features marked as *alpha* may change or be removed in a future release without notice. Use with caution.
66

7-
<!-- ## [Unreleased] -->
7+
## [Unreleased]
8+
9+
### Changed
10+
11+
#### Shared
12+
13+
* The default `ZIPArchiveOpener` is now using ZIPFoundation instead of Minizip, with improved performances when reading ranges of `stored` ZIP entries.
14+
815

916
## [3.0.0-beta.1]
1017

Cartfile

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
github "dexman/Minizip" ~> 1.4.0
21
github "krzyzanowskim/CryptoSwift" ~> 1.8.0
32
github "ra1028/DifferenceKit" ~> 1.3.0
4-
github "readium/Fuzi" ~> 3.1.4
3+
github "readium/Fuzi" ~> 4.0.0
54
github "readium/GCDWebServer" ~> 4.0.0
5+
github "readium/ZIPFoundation" ~> 1.0.0
66
# There's a regression with 2.7.4 in SwiftSoup, because they used iOS 13 APIs without bumping the deployment target.
77
github "scinfu/SwiftSoup" == 2.7.1
88
github "stephencelis/SQLite.swift" ~> 0.15.0
9-
github "weichsel/ZIPFoundation" ~> 0.9.0

Package.swift

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.3
1+
// swift-tools-version:5.10
22
//
33
// Copyright 2021 Readium Foundation. All rights reserved.
44
// Use of this source code is governed by the BSD-style license
@@ -24,23 +24,23 @@ let package = Package(
2424
],
2525
dependencies: [
2626
.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.8.0"),
27-
.package(url: "https://github.com/marmelroy/Zip.git", from: "2.1.0"),
2827
.package(url: "https://github.com/ra1028/DifferenceKit.git", from: "1.3.0"),
29-
.package(url: "https://github.com/readium/Fuzi.git", from: "3.1.4"),
28+
.package(url: "https://github.com/readium/Fuzi.git", from: "4.0.0"),
3029
.package(url: "https://github.com/readium/GCDWebServer.git", from: "4.0.0"),
30+
.package(url: "https://github.com/readium/ZIPFoundation.git", from: "1.0.0"),
3131
.package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.7.0"),
3232
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0"),
33-
.package(url: "https://github.com/weichsel/ZIPFoundation.git", from: "0.9.0"),
3433
],
3534
targets: [
3635
.target(
3736
name: "ReadiumShared",
38-
dependencies: ["ReadiumInternal", "Fuzi", "SwiftSoup", "Zip"],
39-
path: "Sources/Shared",
40-
exclude: [
41-
// Support for ZIPFoundation is not yet achieved.
42-
"Toolkit/ZIP/ZIPFoundation.swift",
37+
dependencies: [
38+
"ReadiumInternal",
39+
"SwiftSoup",
40+
.product(name: "ReadiumFuzi", package: "Fuzi"),
41+
.product(name: "ReadiumZIPFoundation", package: "ZIPFoundation"),
4342
],
43+
path: "Sources/Shared",
4444
linkerSettings: [
4545
.linkedFramework("CoreServices"),
4646
.linkedFramework("UIKit"),
@@ -59,8 +59,8 @@ let package = Package(
5959
name: "ReadiumStreamer",
6060
dependencies: [
6161
"CryptoSwift",
62-
"Fuzi",
6362
"ReadiumShared",
63+
.product(name: "ReadiumFuzi", package: "Fuzi"),
6464
],
6565
path: "Sources/Streamer",
6666
resources: [
@@ -102,8 +102,8 @@ let package = Package(
102102
.target(
103103
name: "ReadiumOPDS",
104104
dependencies: [
105-
"Fuzi",
106105
"ReadiumShared",
106+
.product(name: "ReadiumFuzi", package: "Fuzi"),
107107
],
108108
path: "Sources/OPDS"
109109
),
@@ -120,8 +120,8 @@ let package = Package(
120120
name: "ReadiumLCP",
121121
dependencies: [
122122
"CryptoSwift",
123-
"ZIPFoundation",
124123
"ReadiumShared",
124+
.product(name: "ReadiumZIPFoundation", package: "ZIPFoundation"),
125125
],
126126
path: "Sources/LCP",
127127
resources: [

README.md

+14-17
Original file line numberDiff line numberDiff line change
@@ -54,34 +54,31 @@ Note that Carthage will build all Readium modules and their dependencies, but yo
5454

5555
Refer to the following table to know which dependencies are required for each Readium library.
5656

57-
| | `ReadiumShared` | `ReadiumStreamer` | `ReadiumNavigator` | `ReadiumOPDS` | `ReadiumLCP` | `ReadiumAdapterGCDWebServer` | `ReadiumAdapterLCPSQLite` |
58-
|-----------------------|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:|------------------------------|---------------------------|
59-
| **`ReadiumShared`** | | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
60-
| **`ReadiumInternal`** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
61-
| `CryptoSwift` | | :heavy_check_mark: | | | :heavy_check_mark: | | |
62-
| `DifferenceKit` | | | :heavy_check_mark: | | | | |
63-
| `Fuzi` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
64-
| `Minizip` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
65-
| `ReadiumGCDWebServer` | | | | | | :heavy_check_mark: | |
66-
| `SQLite.swift` | | | | | | | :heavy_check_mark: |
67-
| `SwiftSoup` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
68-
| `ZIPFoundation` | | | | | :heavy_check_mark: | | |
57+
| | `ReadiumShared` | `ReadiumStreamer` | `ReadiumNavigator` | `ReadiumOPDS` | `ReadiumLCP` | `ReadiumAdapterGCDWebServer` | `ReadiumAdapterLCPSQLite` |
58+
|------------------------|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:|------------------------------|---------------------------|
59+
| **`ReadiumShared`** | | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
60+
| **`ReadiumInternal`** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
61+
| `CryptoSwift` | | :heavy_check_mark: | | | :heavy_check_mark: | | |
62+
| `DifferenceKit` | | | :heavy_check_mark: | | | | |
63+
| `ReadiumFuzi` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
64+
| `ReadiumGCDWebServer` | | | | | | :heavy_check_mark: | |
65+
| `ReadiumZIPFoundation` | :heavy_check_mark: | | | | :heavy_check_mark: | | |
66+
| `SQLite.swift` | | | | | | | :heavy_check_mark: |
67+
| `SwiftSoup` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
6968

7069
### CocoaPods
7170

7271
Add the following `pod` statements to your `Podfile` for the Readium libraries you want to use:
7372

7473
```
75-
pod 'ReadiumShared', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumShared.podspec'
7674
pod 'ReadiumStreamer', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumStreamer.podspec'
7775
pod 'ReadiumNavigator', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumNavigator.podspec'
7876
pod 'ReadiumOPDS', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumOPDS.podspec'
7977
pod 'ReadiumLCP', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumLCP.podspec'
80-
pod 'ReadiumInternal', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumInternal.podspec'
81-
pod 'Fuzi', podspec: 'https://raw.githubusercontent.com/readium/Fuzi/refs/heads/master/Fuzi.podspec'
8278
83-
# Required if you use ReadiumAdapterGCDWebServer.
84-
pod 'ReadiumGCDWebServer', podspec: 'https://raw.githubusercontent.com/readium/GCDWebServer/4.0.0/GCDWebServer.podspec'
79+
# Required by all the other libraries
80+
pod 'ReadiumShared', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumShared.podspec'
81+
pod 'ReadiumInternal', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumInternal.podspec'
8582
```
8683

8784
Take a look at [CocoaPods's documentation](https://guides.cocoapods.org/using/using-cocoapods.html) for more information.

Sources/Internal/Extensions/Sequence.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Foundation
88

99
public extension Sequence {
1010
/// Asynchronous variant of `map`.
11-
@inlinable func asyncmap<NewElement>(
11+
@inlinable func asyncMap<NewElement>(
1212
_ transform: (Element) async throws -> NewElement
1313
) async rethrows -> [NewElement] {
1414
var result: [NewElement] = []

Sources/LCP/License/Container/ZIPLicenseContainer.swift

+18-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import Foundation
88
import ReadiumShared
9-
import ZIPFoundation
9+
import ReadiumZIPFoundation
1010

1111
/// Access to a License Document stored in a ZIP archive.
1212
/// Meant to be subclassed to customize the pathInZIP property, eg. EPUBLicenseContainer.
@@ -20,15 +20,20 @@ class ZIPLicenseContainer: LicenseContainer {
2020
}
2121

2222
func containsLicense() async throws -> Bool {
23-
guard let archive = Archive(url: zip.url, accessMode: .read) else {
24-
return false
23+
do {
24+
let archive = try Archive(url: zip.url, accessMode: .read)
25+
return archive[pathInZIP] != nil
26+
} catch {
27+
throw LCPError.licenseContainer(.openFailed(error))
2528
}
26-
return archive[pathInZIP] != nil
2729
}
2830

2931
func read() async throws -> Data {
30-
guard let archive = Archive(url: zip.url, accessMode: .read) else {
31-
throw LCPError.licenseContainer(.openFailed(nil))
32+
let archive: Archive
33+
do {
34+
archive = try Archive(url: zip.url, accessMode: .read)
35+
} catch {
36+
throw LCPError.licenseContainer(.openFailed(error))
3237
}
3338
guard let entry = archive[pathInZIP] else {
3439
throw LCPError.licenseContainer(.fileNotFound(pathInZIP))
@@ -47,8 +52,11 @@ class ZIPLicenseContainer: LicenseContainer {
4752
}
4853

4954
func write(_ license: LicenseDocument) async throws {
50-
guard let archive = Archive(url: zip.url, accessMode: .update) else {
51-
throw LCPError.licenseContainer(.openFailed(nil))
55+
let archive: Archive
56+
do {
57+
archive = try Archive(url: zip.url, accessMode: .update)
58+
} catch {
59+
throw LCPError.licenseContainer(.openFailed(error))
5260
}
5361

5462
do {
@@ -59,8 +67,8 @@ class ZIPLicenseContainer: LicenseContainer {
5967

6068
// Stores the License into the ZIP file
6169
let data = license.jsonData
62-
try archive.addEntry(with: pathInZIP, type: .file, uncompressedSize: UInt32(data.count), provider: { position, size -> Data in
63-
data[position ..< size]
70+
try archive.addEntry(with: pathInZIP, type: .file, uncompressedSize: Int64(data.count), provider: { position, size -> Data in
71+
data[position ..< Int64(size)]
6472
})
6573
} catch {
6674
throw LCPError.licenseContainer(.writeFailed(path: pathInZIP))

Sources/LCP/License/License.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import Foundation
88
import ReadiumShared
9-
import ZIPFoundation
9+
import ReadiumZIPFoundation
1010

1111
final class License: Loggable {
1212
// Last Documents which passed the integrity checks.

Sources/OPDS/OPDS1Parser.swift

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

77
import Foundation
8-
import Fuzi
8+
import ReadiumFuzi
99
import ReadiumShared
1010

1111
public enum OPDS1ParserError: Error {
@@ -85,7 +85,7 @@ public class OPDS1Parser: Loggable {
8585
/// Feed can only be v1 (XML).
8686
/// - parameter document: The XMLDocument data
8787
/// - Returns: The resulting Feed
88-
public static func parse(document: Fuzi.XMLDocument) throws -> Feed {
88+
public static func parse(document: ReadiumFuzi.XMLDocument) throws -> Feed {
8989
document.definePrefix("thr", forNamespace: "http://purl.org/syndication/thread/1.0")
9090
document.definePrefix("dcterms", forNamespace: "http://purl.org/dc/terms/")
9191
document.definePrefix("opds", forNamespace: "http://opds-spec.org/2010/catalog")
@@ -218,7 +218,7 @@ public class OPDS1Parser: Loggable {
218218
/// Publication can only be v1 (XML).
219219
/// - parameter document: The XMLDocument data
220220
/// - Returns: The resulting Publication
221-
public static func parseEntry(document: Fuzi.XMLDocument) throws -> Publication? {
221+
public static func parseEntry(document: ReadiumFuzi.XMLDocument) throws -> Publication? {
222222
guard let root = document.root else {
223223
throw OPDS1ParserError.rootNotFound
224224
}
@@ -254,8 +254,8 @@ public class OPDS1Parser: Loggable {
254254
}
255255
// The OpenSearch document may contain multiple Urls, and we need to find the closest matching one.
256256
// We match by mimetype and profile; if that fails, by mimetype; and if that fails, the first url is returned
257-
var typeAndProfileMatch: Fuzi.XMLElement? = nil
258-
var typeMatch: Fuzi.XMLElement? = nil
257+
var typeAndProfileMatch: ReadiumFuzi.XMLElement? = nil
258+
var typeMatch: ReadiumFuzi.XMLElement? = nil
259259
if let selfMimeType = feed.links.firstWithRel(.self)?.mediaType {
260260
let selfMimeParams = parseMimeType(mimeTypeString: selfMimeType.string)
261261
for url in urls {
@@ -297,7 +297,7 @@ public class OPDS1Parser: Loggable {
297297
return MimeTypeParameters(type: type, parameters: params)
298298
}
299299

300-
static func parseEntry(entry: Fuzi.XMLElement) -> Publication? {
300+
static func parseEntry(entry: ReadiumFuzi.XMLElement) -> Publication? {
301301
// Shortcuts to get tag(s)' string value.
302302
func tag(_ name: String) -> String? {
303303
entry.firstChild(tag: name)?.stringValue
@@ -456,7 +456,7 @@ public class OPDS1Parser: Loggable {
456456
}
457457
}
458458

459-
static func parseIndirectAcquisition(children: [Fuzi.XMLElement]) -> [OPDSAcquisition] {
459+
static func parseIndirectAcquisition(children: [ReadiumFuzi.XMLElement]) -> [OPDSAcquisition] {
460460
children.compactMap { child in
461461
guard let type = child.attributes["type"] else {
462462
return nil
@@ -470,7 +470,7 @@ public class OPDS1Parser: Loggable {
470470
}
471471
}
472472

473-
static func parsePrice(link: Fuzi.XMLElement) -> OPDSPrice? {
473+
static func parsePrice(link: ReadiumFuzi.XMLElement) -> OPDSPrice? {
474474
guard let price = link.firstChild(tag: "price")?.stringValue,
475475
let value = Double(price),
476476
let currency = link.firstChild(tag: "price")?.attr("currencycode")

Sources/Shared/Publication/Services/Content/Iterators/HTMLResourceContentIterator.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public class HTMLResourceContentIterator: ContentIterator {
131131
}
132132

133133
var elements = elements
134-
elements.elements = await elements.elements.enumerated().asyncmap { index, element in
134+
elements.elements = await elements.elements.enumerated().asyncMap { index, element in
135135
let progression = Double(index) / count
136136
return await element.copy(
137137
progression: progression,

Sources/Shared/Toolkit/Format/MediaTypeSniffer.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import CoreServices
88
import Foundation
9-
import Fuzi
9+
import ReadiumFuzi
1010

1111
public extension MediaType {
1212
@available(*, unavailable, message: "Use an `AssetRetriever` to sniff a `Format` instead")

Sources/Shared/Toolkit/XML/Fuzi.swift

+9-9
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@
55
//
66

77
import Foundation
8-
import Fuzi
8+
import ReadiumFuzi
99

1010
final class FuziXMLDocument: XMLDocument, Loggable {
1111
enum ParseError: Error {
1212
case notAnXML
1313
}
1414

15-
fileprivate let document: Fuzi.XMLDocument
15+
fileprivate let document: ReadiumFuzi.XMLDocument
1616

1717
convenience init(data: Data, namespaces: [XMLNamespace]) throws {
18-
try self.init(document: Fuzi.XMLDocument(data: data), namespaces: namespaces)
18+
try self.init(document: ReadiumFuzi.XMLDocument(data: data), namespaces: namespaces)
1919
}
2020

2121
convenience init(string: String, namespaces: [XMLNamespace]) throws {
22-
try self.init(document: Fuzi.XMLDocument(string: string), namespaces: namespaces)
22+
try self.init(document: ReadiumFuzi.XMLDocument(string: string), namespaces: namespaces)
2323
}
2424

25-
init(document: Fuzi.XMLDocument, namespaces: [XMLNamespace]) throws {
25+
init(document: ReadiumFuzi.XMLDocument, namespaces: [XMLNamespace]) throws {
2626
guard document.root != nil else {
2727
throw ParseError.notAnXML
2828
}
@@ -50,10 +50,10 @@ final class FuziXMLDocument: XMLDocument, Loggable {
5050
}
5151

5252
final class FuziXMLElement: XMLElement, Loggable {
53-
fileprivate let document: Fuzi.XMLDocument
54-
fileprivate let element: Fuzi.XMLElement
53+
fileprivate let document: ReadiumFuzi.XMLDocument
54+
fileprivate let element: ReadiumFuzi.XMLElement
5555

56-
fileprivate init(document: Fuzi.XMLDocument, element: Fuzi.XMLElement) {
56+
fileprivate init(document: ReadiumFuzi.XMLDocument, element: ReadiumFuzi.XMLElement) {
5757
self.document = document
5858
self.element = element
5959
}
@@ -85,7 +85,7 @@ final class FuziXMLElement: XMLElement, Loggable {
8585
}
8686
}
8787

88-
private extension Fuzi.XMLDocument {
88+
private extension ReadiumFuzi.XMLDocument {
8989
func definePrefixes(_ namespaces: [XMLNamespace]) {
9090
for namespace in namespaces {
9191
definePrefix(namespace.prefix, forNamespace: namespace.uri)

0 commit comments

Comments
 (0)