Skip to content

Migrate to ZIPFoundation #518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ All notable changes to this project will be documented in this file. Take a look

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

<!-- ## [Unreleased] -->
## [Unreleased]

### Changed

#### Shared

* The default `ZIPArchiveOpener` is now using ZIPFoundation instead of Minizip, with improved performances when reading ranges of `stored` ZIP entries.


## [3.0.0-beta.1]

Expand Down
5 changes: 2 additions & 3 deletions Cartfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
github "dexman/Minizip" ~> 1.4.0
github "krzyzanowskim/CryptoSwift" ~> 1.8.0
github "ra1028/DifferenceKit" ~> 1.3.0
github "readium/Fuzi" ~> 3.1.4
github "readium/Fuzi" ~> 4.0.0
github "readium/GCDWebServer" ~> 4.0.0
github "readium/ZIPFoundation" ~> 1.0.0
# There's a regression with 2.7.4 in SwiftSoup, because they used iOS 13 APIs without bumping the deployment target.
github "scinfu/SwiftSoup" == 2.7.1
github "stephencelis/SQLite.swift" ~> 0.15.0
github "weichsel/ZIPFoundation" ~> 0.9.0
24 changes: 12 additions & 12 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.3
// swift-tools-version:5.10
//
// Copyright 2021 Readium Foundation. All rights reserved.
// Use of this source code is governed by the BSD-style license
Expand All @@ -24,23 +24,23 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.8.0"),
.package(url: "https://github.com/marmelroy/Zip.git", from: "2.1.0"),
.package(url: "https://github.com/ra1028/DifferenceKit.git", from: "1.3.0"),
.package(url: "https://github.com/readium/Fuzi.git", from: "3.1.4"),
.package(url: "https://github.com/readium/Fuzi.git", from: "4.0.0"),
.package(url: "https://github.com/readium/GCDWebServer.git", from: "4.0.0"),
.package(url: "https://github.com/readium/ZIPFoundation.git", from: "1.0.0"),
.package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.7.0"),
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0"),
.package(url: "https://github.com/weichsel/ZIPFoundation.git", from: "0.9.0"),
],
targets: [
.target(
name: "ReadiumShared",
dependencies: ["ReadiumInternal", "Fuzi", "SwiftSoup", "Zip"],
path: "Sources/Shared",
exclude: [
// Support for ZIPFoundation is not yet achieved.
"Toolkit/ZIP/ZIPFoundation.swift",
dependencies: [
"ReadiumInternal",
"SwiftSoup",
.product(name: "ReadiumFuzi", package: "Fuzi"),
.product(name: "ReadiumZIPFoundation", package: "ZIPFoundation"),
],
path: "Sources/Shared",
linkerSettings: [
.linkedFramework("CoreServices"),
.linkedFramework("UIKit"),
Expand All @@ -59,8 +59,8 @@ let package = Package(
name: "ReadiumStreamer",
dependencies: [
"CryptoSwift",
"Fuzi",
"ReadiumShared",
.product(name: "ReadiumFuzi", package: "Fuzi"),
],
path: "Sources/Streamer",
resources: [
Expand Down Expand Up @@ -102,8 +102,8 @@ let package = Package(
.target(
name: "ReadiumOPDS",
dependencies: [
"Fuzi",
"ReadiumShared",
.product(name: "ReadiumFuzi", package: "Fuzi"),
],
path: "Sources/OPDS"
),
Expand All @@ -120,8 +120,8 @@ let package = Package(
name: "ReadiumLCP",
dependencies: [
"CryptoSwift",
"ZIPFoundation",
"ReadiumShared",
.product(name: "ReadiumZIPFoundation", package: "ZIPFoundation"),
],
path: "Sources/LCP",
resources: [
Expand Down
31 changes: 14 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,34 +54,31 @@ Note that Carthage will build all Readium modules and their dependencies, but yo

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

| | `ReadiumShared` | `ReadiumStreamer` | `ReadiumNavigator` | `ReadiumOPDS` | `ReadiumLCP` | `ReadiumAdapterGCDWebServer` | `ReadiumAdapterLCPSQLite` |
|-----------------------|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:|------------------------------|---------------------------|
| **`ReadiumShared`** | | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| **`ReadiumInternal`** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
| `CryptoSwift` | | :heavy_check_mark: | | | :heavy_check_mark: | | |
| `DifferenceKit` | | | :heavy_check_mark: | | | | |
| `Fuzi` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
| `Minizip` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
| `ReadiumGCDWebServer` | | | | | | :heavy_check_mark: | |
| `SQLite.swift` | | | | | | | :heavy_check_mark: |
| `SwiftSoup` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
| `ZIPFoundation` | | | | | :heavy_check_mark: | | |
| | `ReadiumShared` | `ReadiumStreamer` | `ReadiumNavigator` | `ReadiumOPDS` | `ReadiumLCP` | `ReadiumAdapterGCDWebServer` | `ReadiumAdapterLCPSQLite` |
|------------------------|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:|------------------------------|---------------------------|
| **`ReadiumShared`** | | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| **`ReadiumInternal`** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
| `CryptoSwift` | | :heavy_check_mark: | | | :heavy_check_mark: | | |
| `DifferenceKit` | | | :heavy_check_mark: | | | | |
| `ReadiumFuzi` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
| `ReadiumGCDWebServer` | | | | | | :heavy_check_mark: | |
| `ReadiumZIPFoundation` | :heavy_check_mark: | | | | :heavy_check_mark: | | |
| `SQLite.swift` | | | | | | | :heavy_check_mark: |
| `SwiftSoup` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |

### CocoaPods

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

```
pod 'ReadiumShared', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumShared.podspec'
pod 'ReadiumStreamer', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumStreamer.podspec'
pod 'ReadiumNavigator', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumNavigator.podspec'
pod 'ReadiumOPDS', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumOPDS.podspec'
pod 'ReadiumLCP', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumLCP.podspec'
pod 'ReadiumInternal', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumInternal.podspec'
pod 'Fuzi', podspec: 'https://raw.githubusercontent.com/readium/Fuzi/refs/heads/master/Fuzi.podspec'

# Required if you use ReadiumAdapterGCDWebServer.
pod 'ReadiumGCDWebServer', podspec: 'https://raw.githubusercontent.com/readium/GCDWebServer/4.0.0/GCDWebServer.podspec'
# Required by all the other libraries
pod 'ReadiumShared', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumShared.podspec'
pod 'ReadiumInternal', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.0.0-beta.1/Support/CocoaPods/ReadiumInternal.podspec'
```

Take a look at [CocoaPods's documentation](https://guides.cocoapods.org/using/using-cocoapods.html) for more information.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Internal/Extensions/Sequence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Foundation

public extension Sequence {
/// Asynchronous variant of `map`.
@inlinable func asyncmap<NewElement>(
@inlinable func asyncMap<NewElement>(
_ transform: (Element) async throws -> NewElement
) async rethrows -> [NewElement] {
var result: [NewElement] = []
Expand Down
28 changes: 18 additions & 10 deletions Sources/LCP/License/Container/ZIPLicenseContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import Foundation
import ReadiumShared
import ZIPFoundation
import ReadiumZIPFoundation

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

func containsLicense() async throws -> Bool {
guard let archive = Archive(url: zip.url, accessMode: .read) else {
return false
do {
let archive = try Archive(url: zip.url, accessMode: .read)
return archive[pathInZIP] != nil
} catch {
throw LCPError.licenseContainer(.openFailed(error))
}
return archive[pathInZIP] != nil
}

func read() async throws -> Data {
guard let archive = Archive(url: zip.url, accessMode: .read) else {
throw LCPError.licenseContainer(.openFailed(nil))
let archive: Archive
do {
archive = try Archive(url: zip.url, accessMode: .read)
} catch {
throw LCPError.licenseContainer(.openFailed(error))
}
guard let entry = archive[pathInZIP] else {
throw LCPError.licenseContainer(.fileNotFound(pathInZIP))
Expand All @@ -47,8 +52,11 @@ class ZIPLicenseContainer: LicenseContainer {
}

func write(_ license: LicenseDocument) async throws {
guard let archive = Archive(url: zip.url, accessMode: .update) else {
throw LCPError.licenseContainer(.openFailed(nil))
let archive: Archive
do {
archive = try Archive(url: zip.url, accessMode: .update)
} catch {
throw LCPError.licenseContainer(.openFailed(error))
}

do {
Expand All @@ -59,8 +67,8 @@ class ZIPLicenseContainer: LicenseContainer {

// Stores the License into the ZIP file
let data = license.jsonData
try archive.addEntry(with: pathInZIP, type: .file, uncompressedSize: UInt32(data.count), provider: { position, size -> Data in
data[position ..< size]
try archive.addEntry(with: pathInZIP, type: .file, uncompressedSize: Int64(data.count), provider: { position, size -> Data in
data[position ..< Int64(size)]
})
} catch {
throw LCPError.licenseContainer(.writeFailed(path: pathInZIP))
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/License/License.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import Foundation
import ReadiumShared
import ZIPFoundation
import ReadiumZIPFoundation

final class License: Loggable {
// Last Documents which passed the integrity checks.
Expand Down
16 changes: 8 additions & 8 deletions Sources/OPDS/OPDS1Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//

import Foundation
import Fuzi
import ReadiumFuzi
import ReadiumShared

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

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

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

static func parsePrice(link: Fuzi.XMLElement) -> OPDSPrice? {
static func parsePrice(link: ReadiumFuzi.XMLElement) -> OPDSPrice? {
guard let price = link.firstChild(tag: "price")?.stringValue,
let value = Double(price),
let currency = link.firstChild(tag: "price")?.attr("currencycode")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public class HTMLResourceContentIterator: ContentIterator {
}

var elements = elements
elements.elements = await elements.elements.enumerated().asyncmap { index, element in
elements.elements = await elements.elements.enumerated().asyncMap { index, element in
let progression = Double(index) / count
return await element.copy(
progression: progression,
Expand Down
2 changes: 1 addition & 1 deletion Sources/Shared/Toolkit/Format/MediaTypeSniffer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import CoreServices
import Foundation
import Fuzi
import ReadiumFuzi

public extension MediaType {
@available(*, unavailable, message: "Use an `AssetRetriever` to sniff a `Format` instead")
Expand Down
18 changes: 9 additions & 9 deletions Sources/Shared/Toolkit/XML/Fuzi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@
//

import Foundation
import Fuzi
import ReadiumFuzi

final class FuziXMLDocument: XMLDocument, Loggable {
enum ParseError: Error {
case notAnXML
}

fileprivate let document: Fuzi.XMLDocument
fileprivate let document: ReadiumFuzi.XMLDocument

convenience init(data: Data, namespaces: [XMLNamespace]) throws {
try self.init(document: Fuzi.XMLDocument(data: data), namespaces: namespaces)
try self.init(document: ReadiumFuzi.XMLDocument(data: data), namespaces: namespaces)
}

convenience init(string: String, namespaces: [XMLNamespace]) throws {
try self.init(document: Fuzi.XMLDocument(string: string), namespaces: namespaces)
try self.init(document: ReadiumFuzi.XMLDocument(string: string), namespaces: namespaces)
}

init(document: Fuzi.XMLDocument, namespaces: [XMLNamespace]) throws {
init(document: ReadiumFuzi.XMLDocument, namespaces: [XMLNamespace]) throws {
guard document.root != nil else {
throw ParseError.notAnXML
}
Expand Down Expand Up @@ -50,10 +50,10 @@ final class FuziXMLDocument: XMLDocument, Loggable {
}

final class FuziXMLElement: XMLElement, Loggable {
fileprivate let document: Fuzi.XMLDocument
fileprivate let element: Fuzi.XMLElement
fileprivate let document: ReadiumFuzi.XMLDocument
fileprivate let element: ReadiumFuzi.XMLElement

fileprivate init(document: Fuzi.XMLDocument, element: Fuzi.XMLElement) {
fileprivate init(document: ReadiumFuzi.XMLDocument, element: ReadiumFuzi.XMLElement) {
self.document = document
self.element = element
}
Expand Down Expand Up @@ -85,7 +85,7 @@ final class FuziXMLElement: XMLElement, Loggable {
}
}

private extension Fuzi.XMLDocument {
private extension ReadiumFuzi.XMLDocument {
func definePrefixes(_ namespaces: [XMLNamespace]) {
for namespace in namespaces {
definePrefix(namespace.prefix, forNamespace: namespace.uri)
Expand Down
Loading