diff --git a/CHANGELOG.md b/CHANGELOG.md index 3677fbd3b..cf7d8c03e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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] + +### 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] diff --git a/Cartfile b/Cartfile index bf2762daa..4597c8310 100644 --- a/Cartfile +++ b/Cartfile @@ -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 diff --git a/Package.swift b/Package.swift index 1ce44f2a7..cc883c4ec 100644 --- a/Package.swift +++ b/Package.swift @@ -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 @@ -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"), @@ -59,8 +59,8 @@ let package = Package( name: "ReadiumStreamer", dependencies: [ "CryptoSwift", - "Fuzi", "ReadiumShared", + .product(name: "ReadiumFuzi", package: "Fuzi"), ], path: "Sources/Streamer", resources: [ @@ -102,8 +102,8 @@ let package = Package( .target( name: "ReadiumOPDS", dependencies: [ - "Fuzi", "ReadiumShared", + .product(name: "ReadiumFuzi", package: "Fuzi"), ], path: "Sources/OPDS" ), @@ -120,8 +120,8 @@ let package = Package( name: "ReadiumLCP", dependencies: [ "CryptoSwift", - "ZIPFoundation", "ReadiumShared", + .product(name: "ReadiumZIPFoundation", package: "ZIPFoundation"), ], path: "Sources/LCP", resources: [ diff --git a/README.md b/README.md index ecef380dc..1ede84b33 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/Sources/Internal/Extensions/Sequence.swift b/Sources/Internal/Extensions/Sequence.swift index fe3f59d6d..645629562 100644 --- a/Sources/Internal/Extensions/Sequence.swift +++ b/Sources/Internal/Extensions/Sequence.swift @@ -8,7 +8,7 @@ import Foundation public extension Sequence { /// Asynchronous variant of `map`. - @inlinable func asyncmap( + @inlinable func asyncMap( _ transform: (Element) async throws -> NewElement ) async rethrows -> [NewElement] { var result: [NewElement] = [] diff --git a/Sources/LCP/License/Container/ZIPLicenseContainer.swift b/Sources/LCP/License/Container/ZIPLicenseContainer.swift index 3b8259247..4fb2e2e2d 100644 --- a/Sources/LCP/License/Container/ZIPLicenseContainer.swift +++ b/Sources/LCP/License/Container/ZIPLicenseContainer.swift @@ -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. @@ -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)) @@ -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 { @@ -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)) diff --git a/Sources/LCP/License/License.swift b/Sources/LCP/License/License.swift index 78ad271e2..9e652553e 100644 --- a/Sources/LCP/License/License.swift +++ b/Sources/LCP/License/License.swift @@ -6,7 +6,7 @@ import Foundation import ReadiumShared -import ZIPFoundation +import ReadiumZIPFoundation final class License: Loggable { // Last Documents which passed the integrity checks. diff --git a/Sources/OPDS/OPDS1Parser.swift b/Sources/OPDS/OPDS1Parser.swift index 35db096e1..5220cec0d 100644 --- a/Sources/OPDS/OPDS1Parser.swift +++ b/Sources/OPDS/OPDS1Parser.swift @@ -5,7 +5,7 @@ // import Foundation -import Fuzi +import ReadiumFuzi import ReadiumShared public enum OPDS1ParserError: Error { @@ -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") @@ -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 } @@ -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 { @@ -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 @@ -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 @@ -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") diff --git a/Sources/Shared/Publication/Services/Content/Iterators/HTMLResourceContentIterator.swift b/Sources/Shared/Publication/Services/Content/Iterators/HTMLResourceContentIterator.swift index 64386f26f..a078da080 100644 --- a/Sources/Shared/Publication/Services/Content/Iterators/HTMLResourceContentIterator.swift +++ b/Sources/Shared/Publication/Services/Content/Iterators/HTMLResourceContentIterator.swift @@ -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, diff --git a/Sources/Shared/Toolkit/Format/MediaTypeSniffer.swift b/Sources/Shared/Toolkit/Format/MediaTypeSniffer.swift index d0e84b6f5..2163d15d8 100644 --- a/Sources/Shared/Toolkit/Format/MediaTypeSniffer.swift +++ b/Sources/Shared/Toolkit/Format/MediaTypeSniffer.swift @@ -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") diff --git a/Sources/Shared/Toolkit/XML/Fuzi.swift b/Sources/Shared/Toolkit/XML/Fuzi.swift index c74284277..d1b04a316 100644 --- a/Sources/Shared/Toolkit/XML/Fuzi.swift +++ b/Sources/Shared/Toolkit/XML/Fuzi.swift @@ -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 } @@ -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 } @@ -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) diff --git a/Sources/Shared/Toolkit/ZIP/Minizip.swift b/Sources/Shared/Toolkit/ZIP/Minizip.swift deleted file mode 100644 index c3a7201b9..000000000 --- a/Sources/Shared/Toolkit/ZIP/Minizip.swift +++ /dev/null @@ -1,373 +0,0 @@ -// -// Copyright 2024 Readium Foundation. All rights reserved. -// Use of this source code is governed by the BSD-style license -// available in the top-level LICENSE file of the project. -// - -import Foundation -import Minizip - -final class MinizipArchiveOpener: ArchiveOpener { - func open(resource: any Resource, format: Format) async -> Result { - guard - format.conformsTo(.zip), - let file = resource.sourceURL?.fileURL - else { - return .failure(.formatNotSupported(format)) - } - - return await MinizipContainer.make(file: file) - .mapError { - switch $0 { - case .notAZIP: - return .formatNotSupported(format) - case let .reading(error): - return .reading(error) - } - } - .map { ContainerAsset(container: $0, format: format) } - } - - func sniffOpen(resource: any Resource) async -> Result { - guard let file = resource.sourceURL?.fileURL else { - return .failure(.formatNotRecognized) - } - - return await MinizipContainer.make(file: file) - .mapError { - switch $0 { - case .notAZIP: - return .formatNotRecognized - case let .reading(error): - return .reading(error) - } - } - .map { - ContainerAsset( - container: $0, - format: Format( - specifications: .zip, - mediaType: .zip, - fileExtension: "zip" - ) - ) - } - } -} - -/// A ZIP ``Container`` using the Minizip library. -final class MinizipContainer: Container, Loggable { - enum MakeError: Error { - case notAZIP - case reading(ReadError) - } - - static func make(file: FileURL) async -> Result { - guard await (try? file.exists()) ?? false else { - return .failure(.reading(.access(.fileSystem(.fileNotFound(nil))))) - } - guard let zipFile = MinizipFile(url: file.url) else { - return .failure(.notAZIP) - } - defer { try? zipFile.close() } - - do { - var entries = [RelativeURL: MinizipEntryMetadata]() - - try zipFile.goToFirstEntry() - repeat { - switch try zipFile.entryMetadataAtCurrentOffset() { - case let .file(path, length: length, compressedLength: compressedLength): - if let url = RelativeURL(path: path) { - entries[url] = MinizipEntryMetadata(length: length, compressedLength: compressedLength) - } - case .directory: - // Directories are ignored - break - } - } while try zipFile.goToNextEntry() - - return .success(Self(file: file, entries: entries)) - - } catch { - return .failure(.reading(.decoding(error))) - } - } - - private let file: FileURL - private let entriesMetadata: [RelativeURL: MinizipEntryMetadata] - - public var sourceURL: AbsoluteURL? { file } - public let entries: Set - - private init(file: FileURL, entries: [RelativeURL: MinizipEntryMetadata]) { - self.file = file - entriesMetadata = entries - self.entries = Set(entries.keys.map(\.anyURL)) - } - - subscript(url: any URLConvertible) -> (any Resource)? { - guard - let url = url.relativeURL, - let metadata = entriesMetadata[url] - else { - return nil - } - return MinizipResource(file: file, entryPath: url.path, metadata: metadata) - } -} - -private struct MinizipEntryMetadata { - let length: UInt64 - let compressedLength: UInt64? -} - -private actor MinizipResource: Resource, Loggable { - private let file: FileURL - private let entryPath: String - private let metadata: MinizipEntryMetadata - - init(file: FileURL, entryPath: String, metadata: MinizipEntryMetadata) { - self.file = file - self.entryPath = entryPath - self.metadata = metadata - } - - nonisolated func close() { - Task { await doClose() } - } - - func doClose() async { - do { - try _zipFile?.getOrNil()?.close() - _zipFile = .failure(.unsupportedOperation(DebugError("The Minizip resource is already closed"))) - } catch { - log(.error, error) - } - } - - public let sourceURL: AbsoluteURL? = nil - - func estimatedLength() async -> ReadResult { - .success(metadata.length) - } - - func properties() async -> ReadResult { - .success(ResourceProperties { - $0.filename = RelativeURL(path: entryPath)?.lastPathSegment - $0.archive = ArchiveProperties( - entryLength: metadata.compressedLength ?? metadata.length, - isEntryCompressed: metadata.compressedLength != nil - ) - }) - } - - func stream(range: Range?, consume: @escaping (Data) -> Void) async -> ReadResult { - let range = range ?? 0 ..< metadata.length - - return await zipFile().flatMap { zipFile in - do { - try zipFile.openEntry(at: entryPath, offset: range.lowerBound) - try consume(zipFile.readFromCurrentOffset(length: UInt64(range.count))) - return .success(()) - } catch { - return .failure(.decoding(error)) - } - } - } - - private var _zipFile: ReadResult? - private func zipFile() async -> ReadResult { - if _zipFile == nil { - if let zipFile = MinizipFile(url: file.url) { - _zipFile = .success(zipFile) - } else { - _zipFile = .failure(.decoding("Failed to open the ZIP file with Minizip")) - } - } - return _zipFile! - } -} - -/// Holds an opened Minizip file and provide a bridge to its C++ API. -private final class MinizipFile { - enum MinizipError: Error { - case status(Int32) - case noEntryOpened - case readFailed - } - - // Holds an entry's metadata. - enum Entry { - case file(String, length: UInt64, compressedLength: UInt64?) - case directory(String) - } - - private let file: unzFile - private var isClosed = false - /// Information about the currently opened entry. - private(set) var openedEntry: (path: String, offset: UInt64)? - /// Length of the buffer used when reading an entry's data. - private var bufferLength: Int { 1024 * 32 } - - init?(url: URL) { - guard let file = unzOpen64(url.path) else { - return nil - } - self.file = file - } - - deinit { - try? close() - } - - func close() throws { - guard !isClosed else { - return - } - try closeEntry() - try execute { unzClose(file) } - isClosed = true - } - - /// Moves offset to the first entry in the archive. - func goToFirstEntry() throws { - try closeEntry() - try execute { unzGoToFirstFile(file) } - } - - /// Moves offset to the next entry in the archive. - /// - /// - Returns: `false` when reaching the end of the archive. - func goToNextEntry() throws -> Bool { - try closeEntry() - - let status = unzGoToNextFile(file) - switch status { - case UNZ_END_OF_LIST_OF_FILE: - return false - case UNZ_OK: - return true - default: - throw MinizipError.status(status) - } - } - - /// Moves the offset to the entry at `path`. - func goToEntry(at path: String) throws { - try closeEntry() - try execute { unzLocateFile(file, path.removingPrefix("/"), nil) } - } - - /// Reads the metadata of the entry at the current offset in the archive. - func entryMetadataAtCurrentOffset() throws -> Entry { - let filenameMaxSize = 1024 - var fileInfo = unz_file_info64() - let filename = UnsafeMutablePointer.allocate(capacity: filenameMaxSize) - defer { - free(filename) - } - memset(&fileInfo, 0, MemoryLayout.size) - try execute { unzGetCurrentFileInfo64(file, &fileInfo, filename, UInt(filenameMaxSize), nil, 0, nil, 0) } - let path = String(cString: filename) - - if path.hasSuffix("/") { - return .directory(path) - } else { - let isCompressed = (fileInfo.compression_method != 0) - return .file(path, - length: UInt64(fileInfo.uncompressed_size), - compressedLength: isCompressed ? UInt64(fileInfo.compressed_size) : nil) - } - } - - /// Opens the entry at the given `path` and file `offset`, to read its content. - func openEntry(at path: String, offset: UInt64 = 0) throws { - if let entry = openedEntry, entry.path == path, entry.offset <= offset { - if entry.offset < offset { - try seek(by: offset - entry.offset) - } - - } else { - try goToEntry(at: path) - try execute { unzOpenCurrentFile(file) } - openedEntry = (path: path, offset: 0) - try seek(by: offset) - } - } - - /// Closes the current entry if it is opened. - func closeEntry() throws { - guard openedEntry != nil else { - return - } - openedEntry = nil - try execute { unzCloseCurrentFile(file) } - } - - /// Advances the current position in the archive by the given `offset`. - func seek(by offset: UInt64) throws { - // Deflate is stream-based, and can't be used for random access. Therefore, if the file - // is compressed we need to read and discard the content from the start until we reach - // the desired offset. - try readFromCurrentOffset(length: offset) { _, _ in } - - // For non-compressed entries, we can seek directly in the content. - // FIXME: https://github.com/readium/r2-shared-swift/issues/98 -// return execute { return unzseek64(archive, offset, SEEK_CUR) } - } - - /// Reads the given `length` of data at the current offset in the archive. - func readFromCurrentOffset(length: UInt64) throws -> Data { - guard length > 0 else { - return Data() - } - - var data = Data(capacity: Int(length)) - try readFromCurrentOffset(length: length) { bytes, length in - data.append(bytes, count: Int(length)) - } - return data - } - - typealias Consumer = (_ bytes: UnsafePointer, _ length: UInt64) -> Void - - /// Consumes the given `length` of data at the current offset in the archive. - func readFromCurrentOffset(length: UInt64, consumer: Consumer) throws { - guard var entry = openedEntry else { - throw MinizipError.noEntryOpened - } - guard length > 0 else { - return - } - - var totalBytesRead: UInt64 = 0 - defer { - entry.offset += totalBytesRead - openedEntry = entry - } - - while totalBytesRead < length { - let bytesToRead = min(UInt64(bufferLength), length - totalBytesRead) - var buffer = [CUnsignedChar](repeating: 0, count: Int(bytesToRead)) - let bytesRead = UInt64(unzReadCurrentFile(file, &buffer, UInt32(bytesToRead))) - if bytesRead == 0 { - break - } - if bytesRead > 0 { - totalBytesRead += bytesRead - consumer(buffer, bytesRead) - } else { - throw MinizipError.readFailed - } - } - } - - /// Executes a Minizip statement and checks its status. - private func execute(_ block: () -> Int32) throws { - let status = block() - guard status == UNZ_OK else { - throw MinizipError.status(status) - } - } -} diff --git a/Sources/Shared/Toolkit/ZIP/ZIPArchiveOpener.swift b/Sources/Shared/Toolkit/ZIP/ZIPArchiveOpener.swift index beb7638f6..d0b5a6cd7 100644 --- a/Sources/Shared/Toolkit/ZIP/ZIPArchiveOpener.swift +++ b/Sources/Shared/Toolkit/ZIP/ZIPArchiveOpener.swift @@ -8,7 +8,7 @@ import Foundation /// An ``ArchiveOpener`` for ZIP resources. public class ZIPArchiveOpener: ArchiveOpener { - private let opener = MinizipArchiveOpener() + private let opener = ZIPFoundationArchiveOpener() public init() {} diff --git a/Sources/Shared/Toolkit/ZIP/ZIPFoundation.swift b/Sources/Shared/Toolkit/ZIP/ZIPFoundation.swift index cfb68a6cb..cc2c0d778 100644 --- a/Sources/Shared/Toolkit/ZIP/ZIPFoundation.swift +++ b/Sources/Shared/Toolkit/ZIP/ZIPFoundation.swift @@ -5,134 +5,185 @@ // import Foundation -import ZIPFoundation - -/// A ZIP `Archive` using the ZIPFoundation library. -/// -/// Note: At the moment, the Minizip version is used. Keeping this in case we migrate to -/// ZIPFoundation. -final class ZIPFoundationArchive: Archive, Loggable { - fileprivate let archive: ZIPFoundation.Archive - - // Note: passwords are not supported with ZIPFoundation - required convenience init(file: URL, password: String?) throws { - try self.init(file: file, accessMode: .read) - } - - fileprivate init(file: URL, accessMode: Archive.AccessMode) throws { - guard let archive = Archive(url: file, accessMode: accessMode) else { - throw ArchiveError.openFailed +import ReadiumZIPFoundation + +/// An ``ArchiveOpener`` able to open ZIP archives using ZIPFoundation. +public final class ZIPFoundationArchiveOpener: ArchiveOpener { + public init() {} + + public func open(resource: any Resource, format: Format) async -> Result { + guard + format.conformsTo(.zip), + let file = resource.sourceURL?.fileURL + else { + return .failure(.formatNotSupported(format)) } - self.archive = archive + + return await ZIPFoundationContainer.make(file: file) + .mapError { + switch $0 { + case .notAZIP: + return .formatNotSupported(format) + case let .reading(error): + return .reading(error) + } + } + .map { ContainerAsset(container: $0, format: format) } } - lazy var entries: [ArchiveEntry] = archive.map(ArchiveEntry.init) + public func sniffOpen(resource: any Resource) async -> Result { + guard let file = resource.sourceURL?.fileURL else { + return .failure(.formatNotRecognized) + } - func entry(at path: String) -> ArchiveEntry? { - archive[path] - .filter { $0.type != .directory } - .map(ArchiveEntry.init) + return await ZIPFoundationContainer.make(file: file) + .mapError { + switch $0 { + case .notAZIP: + return .formatNotRecognized + case let .reading(error): + return .reading(error) + } + } + .map { + ContainerAsset( + container: $0, + format: Format( + specifications: .zip, + mediaType: .zip, + fileExtension: "zip" + ) + ) + } } +} - func read(at path: String) -> Data? { - objc_sync_enter(archive) - defer { objc_sync_exit(archive) } +/// A ZIP ``Container`` using the ZIPFoundation library. +final class ZIPFoundationContainer: Container, Loggable { + enum MakeError: Error { + case notAZIP + case reading(ReadError) + } - guard let entry = archive[path] else { - return nil + static func make(file: FileURL) async -> Result { + guard await (try? file.exists()) ?? false else { + return .failure(.reading(.access(.fileSystem(.fileNotFound(nil))))) } do { - var data = Data() - _ = try archive.extract(entry) { chunk in - data.append(chunk) + let archive = try ReadiumZIPFoundation.Archive(url: file.url, accessMode: .read) + var entries = [RelativeURL: ZIPFoundationEntryMetadata]() + + for entry in archive { + guard + entry.type == .file, + let url = RelativeURL(path: entry.path(using: .utf8)), + !url.path.isEmpty + else { + continue + } + entries[url] = ZIPFoundationEntryMetadata( + length: entry.uncompressedSize, + compressedLength: entry.isCompressed ? entry.compressedSize : nil + ) } - return data + + return .success(Self(file: file, entries: entries)) + } catch { - log(.error, error) - return nil + return .failure(.reading(.decoding(error))) } } - func read(at path: String, range: Range) -> Data? { - objc_sync_enter(archive) - defer { objc_sync_exit(archive) } + private let file: FileURL + private let entriesMetadata: [RelativeURL: ZIPFoundationEntryMetadata] - guard let entry = archive[path] else { + public var sourceURL: AbsoluteURL? { file } + public let entries: Set + + private init(file: FileURL, entries: [RelativeURL: ZIPFoundationEntryMetadata]) { + self.file = file + entriesMetadata = entries + self.entries = Set(entries.keys.map(\.anyURL)) + } + + subscript(url: any URLConvertible) -> (any Resource)? { + guard + let url = url.relativeURL, + let metadata = entriesMetadata[url] + else { return nil } + return ZIPFoundationResource(file: file, entryPath: url.path, metadata: metadata) + } +} - let rangeLength = range.upperBound - range.lowerBound - var data = Data() +private struct ZIPFoundationEntryMetadata { + let length: UInt64 + let compressedLength: UInt64? +} - do { - var offset = 0 - let progress = Progress() - - _ = try archive.extract(entry, progress: progress) { chunk in - let chunkLength = chunk.count - defer { - offset += chunkLength - if offset >= range.upperBound { - progress.cancel() - } - } +private actor ZIPFoundationResource: Resource, Loggable { + private let file: FileURL + private let entryPath: String + private let metadata: ZIPFoundationEntryMetadata - guard offset < range.upperBound, offset + chunkLength >= range.lowerBound else { - return - } + init(file: FileURL, entryPath: String, metadata: ZIPFoundationEntryMetadata) { + self.file = file + self.entryPath = entryPath + self.metadata = metadata + } - let startingIndex = (range.lowerBound > offset) - ? (range.lowerBound - offset) - : 0 - data.append(chunk[startingIndex...]) - } - } catch { - switch error { - case Archive.ArchiveError.cancelledOperation: - break - default: - log(.error, error) - return nil - } - } + public let sourceURL: AbsoluteURL? = nil - return data[0 ..< rangeLength] + func estimatedLength() async -> ReadResult { + .success(metadata.length) } -} -final class MutableZIPFoundationArchive: ZIPFoundationArchive, MutableArchive { - required convenience init(file: URL, password: String?) throws { - try self.init(file: file, accessMode: .update) + func properties() async -> ReadResult { + .success(ResourceProperties { + $0.filename = RelativeURL(path: entryPath)?.lastPathSegment + $0.archive = ArchiveProperties( + entryLength: metadata.compressedLength ?? metadata.length, + isEntryCompressed: metadata.compressedLength != nil + ) + }) } - func replace(at path: String, with data: Data, deflated: Bool) throws { - objc_sync_enter(archive) - defer { objc_sync_exit(archive) } + func stream(range: Range?, consume: @escaping (Data) -> Void) async -> ReadResult { + if range != nil {} - do { - // Removes the old entry if it already exists in the archive, otherwise we get - // duplicated entries - if let entry = archive[path] { - try archive.remove(entry) + return await archive().flatMap { archive in + guard let entry = archive[entryPath] else { + return .failure(.decoding("No entry found in the ZIP at \(entryPath)")) } - try archive.addEntry(with: path, type: .file, uncompressedSize: UInt32(data.count), compressionMethod: deflated ? .deflate : .none, provider: { position, size in - data[position ..< size] - }) - } catch { - log(.error, error) - throw ArchiveError.updateFailed + do { + if let range = range { + try archive.extractRange(range, of: entry) { data in + consume(data) + } + } else { + _ = try archive.extract(entry, skipCRC32: true) { data in + consume(data) + } + } + return .success(()) + } catch { + return .failure(.decoding(error)) + } } } -} -private extension ArchiveEntry { - init(entry: Entry) { - self.init( - path: entry.path, - length: entry.uncompressedSize, - compressedLength: entry.compressedSize - ) + private var _archive: ReadResult? + private func archive() async -> ReadResult { + if _archive == nil { + do { + _archive = try .success(ReadiumZIPFoundation.Archive(url: file.url, accessMode: .read)) + } catch { + _archive = .failure(.decoding(error)) + } + } + return _archive! } } diff --git a/Sources/Streamer/Parser/EPUB/EPUBContainerParser.swift b/Sources/Streamer/Parser/EPUB/EPUBContainerParser.swift index cc4906fa8..00f8ee52c 100644 --- a/Sources/Streamer/Parser/EPUB/EPUBContainerParser.swift +++ b/Sources/Streamer/Parser/EPUB/EPUBContainerParser.swift @@ -5,12 +5,12 @@ // import Foundation -import Fuzi +import ReadiumFuzi import ReadiumShared /// A parser for the META-INF/container.xml file. final class EPUBContainerParser: Loggable { - private let document: Fuzi.XMLDocument + private let document: ReadiumFuzi.XMLDocument init(data: Data) throws { document = try XMLDocument(data: data) diff --git a/Sources/Streamer/Parser/EPUB/EPUBEncryptionParser.swift b/Sources/Streamer/Parser/EPUB/EPUBEncryptionParser.swift index c514394fe..0310ac897 100644 --- a/Sources/Streamer/Parser/EPUB/EPUBEncryptionParser.swift +++ b/Sources/Streamer/Parser/EPUB/EPUBEncryptionParser.swift @@ -5,7 +5,7 @@ // import Foundation -import Fuzi +import ReadiumFuzi import ReadiumShared /// A parser module which provide methods to parse encrypted XML elements. @@ -26,8 +26,8 @@ final class EPUBEncryptionParser: Loggable { self.init(container: container, data: data) } - private lazy var document: Fuzi.XMLDocument? = { - let document = try? Fuzi.XMLDocument(data: data) + private lazy var document: ReadiumFuzi.XMLDocument? = { + let document = try? ReadiumFuzi.XMLDocument(data: data) document?.definePrefix("enc", forNamespace: "http://www.w3.org/2001/04/xmlenc#") document?.definePrefix("ds", forNamespace: "http://www.w3.org/2000/09/xmldsig#") document?.definePrefix("comp", forNamespace: "http://www.idpf.org/2016/encryption#compression") diff --git a/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift b/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift index 255a21a43..cc43f2bd1 100644 --- a/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift +++ b/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift @@ -5,16 +5,16 @@ // import Foundation -import Fuzi +import ReadiumFuzi import ReadiumShared /// Reference: https://github.com/readium/architecture/blob/master/streamer/parser/metadata.md final class EPUBMetadataParser: Loggable { - private let document: Fuzi.XMLDocument - private let displayOptions: Fuzi.XMLDocument? + private let document: ReadiumFuzi.XMLDocument + private let displayOptions: ReadiumFuzi.XMLDocument? private let metas: OPFMetaList - init(document: Fuzi.XMLDocument, displayOptions: Fuzi.XMLDocument?, metas: OPFMetaList) { + init(document: ReadiumFuzi.XMLDocument, displayOptions: ReadiumFuzi.XMLDocument?, metas: OPFMetaList) { self.document = document self.displayOptions = displayOptions self.metas = metas @@ -25,7 +25,7 @@ final class EPUBMetadataParser: Loggable { document.definePrefix("rendition", forNamespace: "http://www.idpf.org/2013/rendition") } - private lazy var metadataElement: Fuzi.XMLElement? = document.firstChild(xpath: "/opf:package/opf:metadata") + private lazy var metadataElement: ReadiumFuzi.XMLElement? = document.firstChild(xpath: "/opf:package/opf:metadata") /// Parses the Metadata in the XML element. func parse() throws -> Metadata { @@ -73,7 +73,7 @@ final class EPUBMetadataParser: Loggable { /// 1. its xml:lang attribute /// 2. the package's xml:lang attribute /// 3. the primary language for the publication - private func language(for element: Fuzi.XMLElement) -> String? { + private func language(for element: ReadiumFuzi.XMLElement) -> String? { element.attr("lang") ?? packageLanguage ?? languages.first } @@ -142,7 +142,7 @@ final class EPUBMetadataParser: Loggable { /// Finds all the ` element matching the given `title-type`. /// The elements are then sorted by the `display-seq` refines, when available. - private func titleElements(ofType titleType: EPUBTitleType) -> [Fuzi.XMLElement] { + private func titleElements(ofType titleType: EPUBTitleType) -> [ReadiumFuzi.XMLElement] { // Finds the XML element corresponding to the specific title type // `titleType` metas["title-type"] @@ -173,7 +173,7 @@ final class EPUBMetadataParser: Loggable { } }() - private lazy var mainTitleElement: Fuzi.XMLElement? = titleElements(ofType: .main).first + private lazy var mainTitleElement: ReadiumFuzi.XMLElement? = titleElements(ofType: .main).first ?? metas["title", in: .dcterms].first?.element private lazy var mainTitle: LocalizedString? = localizedString(for: mainTitleElement) @@ -432,7 +432,7 @@ final class EPUBMetadataParser: Loggable { /// /// - Parameter metadata: The XML metadata element. /// - Returns: The array of XML element representing the contributors. - private func findContributorElements() -> [Fuzi.XMLElement] { + private func findContributorElements() -> [ReadiumFuzi.XMLElement] { let contributors = metas["creator", in: .dcterms] + metas["publisher", in: .dcterms] + metas["contributor", in: .dcterms] @@ -446,7 +446,7 @@ final class EPUBMetadataParser: Loggable { /// - Parameters: /// - element: The XML element reprensenting the contributor. /// - Returns: The newly created Contributor instance. - private func createContributor(from element: Fuzi.XMLElement, roles: [String] = []) -> Contributor? { + private func createContributor(from element: ReadiumFuzi.XMLElement, roles: [String] = []) -> Contributor? { guard let name = localizedString(for: element) else { return nil } @@ -531,7 +531,7 @@ final class EPUBMetadataParser: Loggable { /// /// - Parameters: /// - element: The element to parse (can be a title or a contributor). - private func localizedString(for element: Fuzi.XMLElement?) -> LocalizedString? { + private func localizedString(for element: ReadiumFuzi.XMLElement?) -> LocalizedString? { guard let element = element else { return nil } @@ -568,7 +568,7 @@ final class EPUBMetadataParser: Loggable { /// Returns the given `dc:` tag in the `metadata` element. /// /// This looks under `metadata/dc-metadata` as well, to be compatible with old EPUB 2 files. - private func dcElement(tag: String) -> Fuzi.XMLElement? { + private func dcElement(tag: String) -> ReadiumFuzi.XMLElement? { metadataElement? .firstChild(xpath: "(.|opf:dc-metadata)/dc:\(tag)") } diff --git a/Sources/Streamer/Parser/EPUB/EPUBParser.swift b/Sources/Streamer/Parser/EPUB/EPUBParser.swift index f38020d5e..aa893ba96 100644 --- a/Sources/Streamer/Parser/EPUB/EPUBParser.swift +++ b/Sources/Streamer/Parser/EPUB/EPUBParser.swift @@ -5,7 +5,7 @@ // import Foundation -import Fuzi +import ReadiumFuzi import ReadiumShared /// Epub related constants. diff --git a/Sources/Streamer/Parser/EPUB/NCXParser.swift b/Sources/Streamer/Parser/EPUB/NCXParser.swift index 20533ea24..465f3f805 100644 --- a/Sources/Streamer/Parser/EPUB/NCXParser.swift +++ b/Sources/Streamer/Parser/EPUB/NCXParser.swift @@ -5,7 +5,7 @@ // import Foundation -import Fuzi +import ReadiumFuzi import ReadiumShared /// From IDPF a11y-guidelines content/nav/toc.html : @@ -29,7 +29,7 @@ final class NCXParser { self.url = url } - private lazy var document: Fuzi.XMLDocument? = { + private lazy var document: ReadiumFuzi.XMLDocument? = { let document = try? XMLDocument(data: data) document?.definePrefix("ncx", forNamespace: "http://www.daisy.org/z3986/2005/ncx/") return document @@ -55,13 +55,13 @@ final class NCXParser { } /// Parses recursively a list of nodes as a list of `Link`. - private func links(in element: Fuzi.XMLElement, nodeTagName: String) -> [Link] { + private func links(in element: ReadiumFuzi.XMLElement, nodeTagName: String) -> [Link] { element.xpath("ncx:\(nodeTagName)") .compactMap { self.link(for: $0, nodeTagName: nodeTagName) } } /// Parses a node element as a `Link`. - private func link(for element: Fuzi.XMLElement, nodeTagName: String) -> Link? { + private func link(for element: ReadiumFuzi.XMLElement, nodeTagName: String) -> Link? { NavigationDocumentParser.makeLink( title: element.firstChild(xpath: "ncx:navLabel/ncx:text")?.stringValue, href: element.firstChild(xpath: "ncx:content")?.attr("src").flatMap(RelativeURL.init(epubHREF:)), diff --git a/Sources/Streamer/Parser/EPUB/NavigationDocumentParser.swift b/Sources/Streamer/Parser/EPUB/NavigationDocumentParser.swift index d2d2f2562..2521c841f 100644 --- a/Sources/Streamer/Parser/EPUB/NavigationDocumentParser.swift +++ b/Sources/Streamer/Parser/EPUB/NavigationDocumentParser.swift @@ -5,7 +5,7 @@ // import Foundation -import Fuzi +import ReadiumFuzi import ReadiumShared /// The navigation document if documented here at Navigation @@ -31,9 +31,9 @@ final class NavigationDocumentParser { self.url = url } - private lazy var document: Fuzi.XMLDocument? = { + private lazy var document: ReadiumFuzi.XMLDocument? = { // Warning: Somehow if we use HTMLDocument instead of XMLDocument, then the `epub` prefix doesn't work. - let document = try? Fuzi.XMLDocument(data: data) + let document = try? ReadiumFuzi.XMLDocument(data: data) document?.definePrefix("html", forNamespace: "http://www.w3.org/1999/xhtml") document?.definePrefix("epub", forNamespace: "http://www.idpf.org/2007/ops") return document @@ -52,13 +52,13 @@ final class NavigationDocumentParser { } /// Parses recursively an
    as a list of `Link`. - private func links(in element: Fuzi.XMLElement) -> [Link] { + private func links(in element: ReadiumFuzi.XMLElement) -> [Link] { element.xpath("html:ol[1]/html:li") .compactMap { self.link(for: $0) } } /// Parses a
  1. element as a `Link`. - private func link(for li: Fuzi.XMLElement) -> Link? { + private func link(for li: ReadiumFuzi.XMLElement) -> Link? { guard let label = li.firstChild(xpath: "html:a|html:span") else { return nil } diff --git a/Sources/Streamer/Parser/EPUB/OPFMeta.swift b/Sources/Streamer/Parser/EPUB/OPFMeta.swift index ca0394ca6..2de40bc4a 100644 --- a/Sources/Streamer/Parser/EPUB/OPFMeta.swift +++ b/Sources/Streamer/Parser/EPUB/OPFMeta.swift @@ -5,7 +5,7 @@ // import Foundation -import Fuzi +import ReadiumFuzi import ReadiumShared /// Package vocabularies used for `property`, `properties`, `scheme` and `rel`. @@ -96,7 +96,7 @@ enum OPFVocabulary: String { /// > Reserved prefixes should not be overridden in the prefix attribute, but Reading Systems /// > must use such local overrides when encountered. /// http://www.idpf.org/epub/301/spec/epub-publications.html#sec-metadata-reserved-vocabs - static func prefixes(in document: Fuzi.XMLDocument) -> [String: String] { + static func prefixes(in document: ReadiumFuzi.XMLDocument) -> [String: String] { document.definePrefix("opf", forNamespace: "http://www.idpf.org/2007/opf") guard let prefixAttribute = document.firstChild(xpath: "/opf:package")?.attr("prefix") else { return [:] @@ -128,7 +128,7 @@ struct OPFMeta { let id: String? /// ID of the metadata that is refined by this one, if any. let refines: String? - let element: Fuzi.XMLElement + let element: ReadiumFuzi.XMLElement } /// Represents a `link` tag in an OPF document. @@ -139,15 +139,15 @@ struct OPFLink { let href: String /// ID of the metadata that is refined by this one, if any. let refines: String? - let element: Fuzi.XMLElement + let element: ReadiumFuzi.XMLElement } struct OPFMetaList { - private let document: Fuzi.XMLDocument + private let document: ReadiumFuzi.XMLDocument private let metas: [OPFMeta] private let links: [OPFLink] - init(document: Fuzi.XMLDocument) { + init(document: ReadiumFuzi.XMLDocument) { self.document = document let prefixes = OPFVocabulary.prefixes(in: document) document.definePrefix("opf", forNamespace: "http://www.idpf.org/2007/opf") diff --git a/Sources/Streamer/Parser/EPUB/OPFParser.swift b/Sources/Streamer/Parser/EPUB/OPFParser.swift index a0f2055ab..1157b0e59 100644 --- a/Sources/Streamer/Parser/EPUB/OPFParser.swift +++ b/Sources/Streamer/Parser/EPUB/OPFParser.swift @@ -5,7 +5,7 @@ // import Foundation -import Fuzi +import ReadiumFuzi import ReadiumShared // http://www.idpf.org/epub/30/spec/epub30-publications.html#title-type @@ -33,11 +33,11 @@ final class OPFParser: Loggable { private let baseURL: RelativeURL /// DOM representation of the OPF file. - private let document: Fuzi.XMLDocument + private let document: ReadiumFuzi.XMLDocument /// iBooks Display Options XML file to use as a fallback for metadata. /// See https://github.com/readium/architecture/blob/master/streamer/parser/metadata.md#epub-2x-9 - private let displayOptions: Fuzi.XMLDocument? + private let displayOptions: ReadiumFuzi.XMLDocument? /// List of metadata declared in the package. private let metas: OPFMetaList @@ -47,9 +47,9 @@ final class OPFParser: Loggable { init(baseURL: RelativeURL, data: Data, displayOptionsData: Data? = nil, encryptions: [RelativeURL: Encryption]) throws { self.baseURL = baseURL - document = try Fuzi.XMLDocument(data: data) + document = try ReadiumFuzi.XMLDocument(data: data) document.definePrefix("opf", forNamespace: "http://www.idpf.org/2007/opf") - displayOptions = (displayOptionsData.map { try? Fuzi.XMLDocument(data: $0) }) ?? nil + displayOptions = (displayOptionsData.map { try? ReadiumFuzi.XMLDocument(data: $0) }) ?? nil metas = OPFMetaList(document: document) self.encryptions = encryptions } @@ -159,7 +159,7 @@ final class OPFParser: Loggable { return (resources, readingOrder) } - private func makeLink(manifestItem: Fuzi.XMLElement, spineItem: Fuzi.XMLElement?, isCover: Bool) -> Link? { + private func makeLink(manifestItem: ReadiumFuzi.XMLElement, spineItem: ReadiumFuzi.XMLElement?, isCover: Bool) -> Link? { guard let relativeHref = manifestItem.attr("href").flatMap(RelativeURL.init(epubHREF:)), let href = baseURL.resolve(relativeHref) diff --git a/Sources/Streamer/Parser/EPUB/Services/EPUBPositionsService.swift b/Sources/Streamer/Parser/EPUB/Services/EPUBPositionsService.swift index e4a216461..dc7c7c72d 100644 --- a/Sources/Streamer/Parser/EPUB/Services/EPUBPositionsService.swift +++ b/Sources/Streamer/Parser/EPUB/Services/EPUBPositionsService.swift @@ -86,7 +86,7 @@ public actor EPUBPositionsService: PositionsService { private func computePositionsByReadingOrder() async -> [[Locator]] { var lastPositionOfPreviousResource = 0 - var positions = await readingOrder.asyncmap { link -> [Locator] in + var positions = await readingOrder.asyncMap { link -> [Locator] in let (lastPosition, positions): (Int, [Locator]) = await { if presentation.layout(of: link) == .fixed { return makePositions(ofFixedResource: link, from: lastPositionOfPreviousResource) @@ -99,7 +99,7 @@ public actor EPUBPositionsService: PositionsService { } // Calculates totalProgression - let totalPageCount = await positions.asyncmap(\.count).reduce(0, +) + let totalPageCount = await positions.asyncMap(\.count).reduce(0, +) if totalPageCount > 0 { positions = positions.map { locators in locators.map { locator in diff --git a/Sources/Streamer/Parser/PDF/Services/LCPDFPositionsService.swift b/Sources/Streamer/Parser/PDF/Services/LCPDFPositionsService.swift index 64b3e9465..e39dff254 100644 --- a/Sources/Streamer/Parser/PDF/Services/LCPDFPositionsService.swift +++ b/Sources/Streamer/Parser/PDF/Services/LCPDFPositionsService.swift @@ -27,7 +27,7 @@ final class LCPDFPositionsService: PositionsService, PDFPublicationService, Logg private func computePositionsByReadingOrder() async -> ReadResult<[[Locator]]> { // Calculates the page count of each resource from the reading order. - let resources = await readingOrder.asyncmap { link -> (Int, Link) in + let resources = await readingOrder.asyncMap { link -> (Int, Link) in let href = link.url() guard let resource = container[href], diff --git a/Support/Carthage/.xcodegen b/Support/Carthage/.xcodegen index 7c32cfc16..893a38592 100644 --- a/Support/Carthage/.xcodegen +++ b/Support/Carthage/.xcodegen @@ -139,13 +139,10 @@ "framework" : "..\/..\/Carthage\/Build\/CryptoSwift.xcframework" }, { - "framework" : "..\/..\/Carthage\/Build\/Fuzi.xcframework" + "framework" : "..\/..\/Carthage\/Build\/ReadiumFuzi.xcframework" }, { - "framework" : "..\/..\/Carthage\/Build\/Minizip.xcframework" - }, - { - "framework" : "..\/..\/Carthage\/Build\/ZIPFoundation.xcframework" + "framework" : "..\/..\/Carthage\/Build\/ReadiumZIPFoundation.xcframework" }, { "target" : "ReadiumShared" @@ -173,10 +170,7 @@ "framework" : "..\/..\/Carthage\/Build\/DifferenceKit.xcframework" }, { - "framework" : "..\/..\/Carthage\/Build\/Fuzi.xcframework" - }, - { - "framework" : "..\/..\/Carthage\/Build\/Minizip.xcframework" + "framework" : "..\/..\/Carthage\/Build\/ReadiumFuzi.xcframework" }, { "framework" : "..\/..\/Carthage\/Build\/SwiftSoup.xcframework" @@ -212,10 +206,7 @@ "ReadiumOPDS" : { "dependencies" : [ { - "framework" : "..\/..\/Carthage\/Build\/Fuzi.xcframework" - }, - { - "framework" : "..\/..\/Carthage\/Build\/Minizip.xcframework" + "framework" : "..\/..\/Carthage\/Build\/ReadiumFuzi.xcframework" }, { "target" : "ReadiumShared" @@ -240,10 +231,10 @@ "ReadiumShared" : { "dependencies" : [ { - "framework" : "..\/..\/Carthage\/Build\/Fuzi.xcframework" + "framework" : "..\/..\/Carthage\/Build\/ReadiumFuzi.xcframework" }, { - "framework" : "..\/..\/Carthage\/Build\/Minizip.xcframework" + "framework" : "..\/..\/Carthage\/Build\/ReadiumZIPFoundation.xcframework" }, { "framework" : "..\/..\/Carthage\/Build\/SwiftSoup.xcframework" @@ -263,9 +254,6 @@ }, "sources" : [ { - "excludes" : [ - "Toolkit\/ZIP\/ZIPFoundation.swift" - ], "path" : "..\/..\/Sources\/Shared" } ], @@ -277,10 +265,7 @@ "framework" : "..\/..\/Carthage\/Build\/CryptoSwift.xcframework" }, { - "framework" : "..\/..\/Carthage\/Build\/Fuzi.xcframework" - }, - { - "framework" : "..\/..\/Carthage\/Build\/Minizip.xcframework" + "framework" : "..\/..\/Carthage\/Build\/ReadiumFuzi.xcframework" }, { "target" : "ReadiumShared" @@ -13977,7 +13962,6 @@ ../../Sources/Shared/Toolkit/XML/Fuzi.swift ../../Sources/Shared/Toolkit/XML/XML.swift ../../Sources/Shared/Toolkit/ZIP -../../Sources/Shared/Toolkit/ZIP/Minizip.swift ../../Sources/Shared/Toolkit/ZIP/ZIPArchiveOpener.swift ../../Sources/Shared/Toolkit/ZIP/ZIPFoundation.swift ../../Sources/Streamer diff --git a/Support/Carthage/Readium.xcodeproj/project.pbxproj b/Support/Carthage/Readium.xcodeproj/project.pbxproj index 0915c11fc..0b159fa3e 100644 --- a/Support/Carthage/Readium.xcodeproj/project.pbxproj +++ b/Support/Carthage/Readium.xcodeproj/project.pbxproj @@ -8,23 +8,23 @@ /* Begin PBXBuildFile section */ 0025BE4D568B560277323B95 /* FileResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05502AD8940D6616D6C386F /* FileResource.swift */; }; - 01AC52ADE389D14F5274CEB2 /* Minizip.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFFEBDFE931745C07DACD4A3 /* Minizip.xcframework */; }; 01AD628D6DE82E1C1C4C281D /* NavigationDocumentParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29AD63CD2A41586290547212 /* NavigationDocumentParser.swift */; }; 01E785BEA7F30AD1C8A5F3DE /* SearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5B029CA09EE1F86A19612A /* SearchService.swift */; }; 01F29BAD25D4597903AF253F /* CompositeFormatSniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41E54A18E2983098A6A4DAE0 /* CompositeFormatSniffer.swift */; }; 035807359AFA2EE23E00F8AB /* TransformingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0918DA360AAB646144E435D5 /* TransformingContainer.swift */; }; 03B5862162A21448DC7813C6 /* HTTPServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCDF341872EEFB88B6674DE /* HTTPServer.swift */; }; 0411D7B6ACD75F2D03547787 /* RARFormatSniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEF14E6F136136D43A39897 /* RARFormatSniffer.swift */; }; + 048A8C8447FBF687484CD248 /* ReadiumFuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2828D89EBB52CCA782ED1146 /* ReadiumFuzi.xcframework */; }; 0569A7E5E5B31AF3B4C8C234 /* EPUBContainerParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78033AFDF98C92351785D17F /* EPUBContainerParser.swift */; }; 061C798F8A5DACFACB414B15 /* CGRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48856E9AB402E2907B5230F3 /* CGRect.swift */; }; 06CF9F75A9DB1B6241CA7719 /* LCPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DEBFCD9D71243C4ACC3A49 /* LCPService.swift */; }; 077AD829863BD952DEBFB5A0 /* StatusDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45319A96D79565A2CD31B650 /* StatusDocument.swift */; }; - 0828EA20B47F13D1FC4C364F /* Fuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B421601FB56132514CCD9699 /* Fuzi.xcframework */; }; 0A262D1DF87FBBAA402A1194 /* ContentProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C57EC6B0ADED2B0D395F2AEA /* ContentProtection.swift */; }; 0A6BF62D6FE0C04DA8B8D3CA /* AnyURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE350D88BC82408491D8B516 /* AnyURL.swift */; }; 0AF2BBF12939AFBF6173E333 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC6AE42A31D77B548CB0BB4 /* Observable.swift */; }; 0B9AC6EF44DA518E9F37FB49 /* ContentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E809378D79D09192A0AAE1 /* ContentService.swift */; }; 0BFCDAEC82CFF09AFC53A5D0 /* LCPDFTableOfContentsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94414130EC3731CD9920F27D /* LCPDFTableOfContentsService.swift */; }; + 0C038E3525BB600EF6815EB9 /* ReadiumFuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2828D89EBB52CCA782ED1146 /* ReadiumFuzi.xcframework */; }; 0ECE94F27E005FC454EA9D12 /* DecorableNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626CFFF131E0E840B76428F1 /* DecorableNavigator.swift */; }; 0F1AAB56A6ADEDDE2AD7E41E /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1039900AC78465AD989D7464 /* Content.swift */; }; 1004CE1C72C85CC3702C09C0 /* Asset.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC811653B33761089E270C4A /* Asset.swift */; }; @@ -40,7 +40,6 @@ 1600DB04CEACF97EE8AD9CEE /* URLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 258351CE21165EDED7F87878 /* URLProtocol.swift */; }; 17108D46A0353A254DA193B0 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7D002FDDAD1A21AC5BB84CE /* Container.swift */; }; 1752D756BED37325D6D4ED29 /* ReadiumInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 42FD63C2720614E558522675 /* ReadiumInternal.framework */; }; - 180E78AF6C7507F06AEEB806 /* Minizip.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFFEBDFE931745C07DACD4A3 /* Minizip.xcframework */; }; 18217BC157557A5DDA4BA119 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADDB8B9906FC78C038203BDD /* User.swift */; }; 188D742F80B70DE8A625AD21 /* Facet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 387B19B66C4D91A295B5EFA6 /* Facet.swift */; }; 1AEF63A8471C7676092842D2 /* FileExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D555435E2BADB2B877FD50C7 /* FileExtension.swift */; }; @@ -72,7 +71,6 @@ 2AE6A09759A80A668D08CDEC /* Archive.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90EA81ECD9488CB3CBDAB41 /* Archive.swift */; }; 2B4D0BD8E7A2E53319CD7782 /* TransformingResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ACF6B7BA6B9BDE7DD076BE /* TransformingResource.swift */; }; 2B8BC06B6B366E67C716DDA1 /* OPFMeta.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FC49AFB32B525AAC5BF7612 /* OPFMeta.swift */; }; - 2C1934F6A462DA5F5EE9B13C /* Fuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B421601FB56132514CCD9699 /* Fuzi.xcframework */; }; 2C5436091DD72FDBF6FF136D /* OPFParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61575203A3BEB8E218CAFE38 /* OPFParser.swift */; }; 2CD7B92212F4757C0DF02BD7 /* HTTPResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42BAA4F11B7355F1962FEAD9 /* HTTPResource.swift */; }; 2E518C960D386F13E0A5E9B7 /* EPUBFixedSpreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF99DAF66659A218CEC25EAE /* EPUBFixedSpreadView.swift */; }; @@ -108,11 +106,13 @@ 3E9F244ACDA938D330B9EAEA /* Subject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CD4C99103DC795E44F56AE /* Subject.swift */; }; 3ECB525CEB712CEC5EFCD26D /* WarningLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3510E7E84A5361BCECC90569 /* WarningLogger.swift */; }; 40A44414CC911BF49BB5EE60 /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DA31089FCAD8DFB9AC46E4E /* Tokenizer.swift */; }; + 422FF6643B32F9BCE8043757 /* ReadiumFuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2828D89EBB52CCA782ED1146 /* ReadiumFuzi.xcframework */; }; 44152DBECE34F063AD0E93BC /* Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3E6442F0C7FE2098D71F27 /* Link.swift */; }; 448374F2605586249A6CB4C8 /* FailureResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78FFDF8CF77437EDB41E4547 /* FailureResource.swift */; }; 46D29739FB017C62767CBE63 /* Presentation+EPUB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42EFF9139B59D763CE254F92 /* Presentation+EPUB.swift */; }; 47125BFFEC67DEB2C3D1B48C /* AudioNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE34D74E282834684E1C999 /* AudioNavigator.swift */; }; 47FE7B25B5DB153D406CF0CE /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4937644CB65AE6801CE3295 /* UserSettings.swift */; }; + 4977279900B4BA602D92B5C4 /* ReadiumFuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2828D89EBB52CCA782ED1146 /* ReadiumFuzi.xcframework */; }; 4A5F53CCC083D3E348379963 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF64D7C05A790D9CA5DD442 /* Types.swift */; }; 4AD286114A634A74BE78B1A0 /* LicenseContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15980B67505AAF10642B56C8 /* LicenseContainer.swift */; }; 4AE70F783C07D9938B40E792 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125BAF5FDFA097BA5CC63539 /* StringExtension.swift */; }; @@ -135,7 +135,6 @@ 5730E84475195005D1291672 /* Publication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF03272C07D6951ADC1311E /* Publication.swift */; }; 57583D27AB12063C3D114A47 /* AudioParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9FFEB1FF4B5CD74EB35CD63 /* AudioParser.swift */; }; 58E8C178E0748B7CBEA9D7AC /* CSSLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = C51A36BFDC79EB5377D69582 /* CSSLayout.swift */; }; - 5903F431F5558958EF7CDDA0 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8D187A577EBFCFF738D1CDC7 /* ZIPFoundation.xcframework */; }; 5912EC9BB073282862F325F2 /* DocumentTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A00FF0C84822A134A353BD4 /* DocumentTypes.swift */; }; 594CE84C2B11169AA0B86615 /* HTMLResourceContentIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34AB954525AC159166C96A36 /* HTMLResourceContentIterator.swift */; }; 5A9AEC4A9AE686ED74E9B8BF /* RWPMFormatSniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE4559CE100932572C843E5 /* RWPMFormatSniffer.swift */; }; @@ -156,6 +155,7 @@ 66018235ED40B89D27EE9F33 /* Group.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAAD26EE52713DB9F103610 /* Group.swift */; }; 66A251DA78C53384E94F169F /* PublicationServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6D87AB6FB1B213E6269736B /* PublicationServer.swift */; }; 6719F981514309A65D206A85 /* LCPAcquisition.swift in Sources */ = {isa = PBXBuildFile; fileRef = F622773881411FB8BE686B9F /* LCPAcquisition.swift */; }; + 6724EAA0D931CF252E5FAEDF /* ZIPFoundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59624A8738EB3A791CEF8E4C /* ZIPFoundation.swift */; }; 674BEEF110667C3051296E9B /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3481F848A616A9A825A4BD /* Double.swift */; }; 67F1C7C3D434D2AA542376E3 /* PublicationParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F609C27F073E40D662CFE093 /* PublicationParser.swift */; }; 682DFC1AF2BD7CAE0862B331 /* CryptoSwift.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = E37F94C388A86CB8A34812A5 /* CryptoSwift.xcframework */; }; @@ -184,7 +184,6 @@ 75E5BBD405026A68BF6741F9 /* AbsoluteURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DF154DCC73CFBDB0F919DE /* AbsoluteURL.swift */; }; 762CF84C6FB1FCCF03EA91B6 /* Loggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 067E58BE65BCB4F8D1E8B911 /* Loggable.swift */; }; 76F6EE39F504B6A80837C90D /* MediaOverlayNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DAAE19E8372F6ECF772E0A /* MediaOverlayNode.swift */; }; - 77006A5BF6FCBFFD60B15EBA /* Minizip.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD824370ACE916C9BB9CAF84 /* Minizip.swift */; }; 77C828CCC90DF784B2D75774 /* OpdsMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DDB25FC1693613B72DFDB6E /* OpdsMetadata.swift */; }; 78C52EED635B5F8C38A02298 /* Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B24895126F2A744A8E9E61 /* Metadata.swift */; }; 79763D2B25EC6585792C958D /* FileResourceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD0F310FEAC86A2DD40B8AE /* FileResourceFactory.swift */; }; @@ -205,10 +204,8 @@ 844135596BE2BBA9C570F6C4 /* ProxyPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = A77C8BE0AC489E5FF2DA1FE3 /* ProxyPreference.swift */; }; 84BB48C576F7F1DBE32D9745 /* FormatSniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FB533E84CE563807BDB012 /* FormatSniffer.swift */; }; 86D8ED9188418FCFA290CD1C /* Assets in Resources */ = {isa = PBXBuildFile; fileRef = 251275D0DF87F85158A5FEA9 /* Assets */; }; - 8709012E5CE29CC28E7470C4 /* Fuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B421601FB56132514CCD9699 /* Fuzi.xcframework */; }; 8889176F35267918E2B3577E /* PublicationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667B76C4766DFF58D066D40B /* PublicationService.swift */; }; 89A7CA4173B39673A237EAED /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE7B762C97CFC214997EC677 /* Weak.swift */; }; - 8B9BCCB1D724E18BF2893629 /* Fuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B421601FB56132514CCD9699 /* Fuzi.xcframework */; }; 8BA982B992E0370C7BF94DF6 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55D0EAD1ABB7B829A3891D3A /* UIView.swift */; }; 8BD3DB373A8785BE8E71845D /* EPUBFormatSniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0495085D1A7758A1197F666E /* EPUBFormatSniffer.swift */; }; 8CCDF77696A0F2C7BF3171CC /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 600D8714B762FE37DE405C2E /* LocalizedString.swift */; }; @@ -216,7 +213,6 @@ 8E25FF2EEFA72D9B0F3025C5 /* PDFFormatSniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B72B76AB39E09E4A2E465AF /* PDFFormatSniffer.swift */; }; 8F5B0B5B83BF7F1145556FF8 /* Properties+OPDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD79372361D085CA0500CF4 /* Properties+OPDS.swift */; }; 9065C4C0F40B6A5601541EF7 /* Streamable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622CB8B75A568846FECA44D6 /* Streamable.swift */; }; - 90769CA2ABCBD1F7203697DC /* Minizip.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFFEBDFE931745C07DACD4A3 /* Minizip.xcframework */; }; 90CFD62B993F6759716C0AF0 /* LicensesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56286133DD0AE093F2C5E9FD /* LicensesService.swift */; }; 914DEDFE5594761D3F180491 /* EPUBPositionsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C93236E313B55D8B835D9F /* EPUBPositionsService.swift */; }; 917AD2492BFA4FD8B2FC85B8 /* ResourceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6104651E0A538A99B44B328F /* ResourceResponse.swift */; }; @@ -264,6 +260,7 @@ AD572C6A7AD031FEC40A0BD7 /* LanguageFormatSniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E5D0B9449607B2906901C2 /* LanguageFormatSniffer.swift */; }; AD87094AA40926939955E9F2 /* LCPRenewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 230985A228FA74F24735D6BB /* LCPRenewDelegate.swift */; }; B01E5FC64FD1411DD9899ADC /* HTMLInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A75B535BBE6EE5898327DB52 /* HTMLInjection.swift */; }; + B02109488289D631DF959B8D /* ReadiumFuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2828D89EBB52CCA782ED1146 /* ReadiumFuzi.xcframework */; }; B066F9DDCD00A8917478CB6C /* LCPDialogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BB64178365BFA9ED75C7078 /* LCPDialogViewController.swift */; }; B0B2C38D1A7B36E73C3E3779 /* DifferenceKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F95F3F20D758BE0E7005EA3 /* DifferenceKit.xcframework */; }; B0F62AC136EF3587E147468E /* AssetRetriever.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C054DDC6D1BCF4A420C980C /* AssetRetriever.swift */; }; @@ -293,6 +290,7 @@ BD520A603D8351461485877F /* ComicFormatSniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC6271FA7F282364A4E68C4C /* ComicFormatSniffer.swift */; }; BE754F8C0536C8B7D28A294C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5507BD4012032A7567175B69 /* Localizable.strings */; }; BF0CC6F4EE0F18B52D2D8693 /* Properties+Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E175BF1A1F97687B4119BB1 /* Properties+Encryption.swift */; }; + C0A492BDDCB4A472F408B3B4 /* ReadiumZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69E17C4870C64264819EB227 /* ReadiumZIPFoundation.xcframework */; }; C122D3F719E77AEFA36E3034 /* RPFFormatSniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ACA9031C70DAB05CE0DD463 /* RPFFormatSniffer.swift */; }; C1A94B2A9C446CB03650DC47 /* NCXParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4363E8A92B1EA9AF2561DCE9 /* NCXParser.swift */; }; C1CD3DA1A9EF154667E5B50B /* WebServerResourceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D008F7BB187AE82CBB115D0F /* WebServerResourceResponse.swift */; }; @@ -317,16 +315,15 @@ CC85122A71D3145940827338 /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90C4D94134D9F741D38D8AA /* Comparable.swift */; }; CCAF8FB4DBD81448C99D589A /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB152578CBA091A41A51B25 /* Language.swift */; }; CD0243B5EB8B408E34786214 /* ReadiumInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 42FD63C2720614E558522675 /* ReadiumInternal.framework */; }; + CDE5EE79D3F0D00F9CAED8B8 /* ReadiumZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69E17C4870C64264819EB227 /* ReadiumZIPFoundation.xcframework */; }; CE0720B9C9C3D9C4353F6ED2 /* OPDSFormatSniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA7CEF568A02BA2CB4AAD7F /* OPDSFormatSniffer.swift */; }; CEC42C02BB2B02562785B259 /* Range.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB3E08C8187DCC3099CF9D22 /* Range.swift */; }; D1248D9E9EE269C3245927F7 /* Cancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBD54FD376456C1925316BC /* Cancellable.swift */; }; D25058A427D47ABEA88A3F4B /* PublicationSpeechSynthesizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99DE4955327D8C2DE6F866D3 /* PublicationSpeechSynthesizer.swift */; }; D29E1DBC5BD1B82C996427C4 /* Closeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02A9225D636D845BF24F6AC /* Closeable.swift */; }; - D2FA324CCED60BCF338C46F7 /* Minizip.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFFEBDFE931745C07DACD4A3 /* Minizip.xcframework */; }; D4BBC0AD7652265497B5CD1C /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10894CC9684584098A22D8FA /* URLExtensions.swift */; }; D50FE2B82BB34E2881723BE9 /* ZIPLicenseContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6453ABD6DF237362C6EECD2 /* ZIPLicenseContainer.swift */; }; D525E9E2AFA0ED63151A4FBC /* Assets in Resources */ = {isa = PBXBuildFile; fileRef = DBCE9786DD346E6BDB2E50FF /* Assets */; }; - D5543164860E3B7B8FD10012 /* Minizip.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFFEBDFE931745C07DACD4A3 /* Minizip.xcframework */; }; D589F337B244F5B58E85AEFB /* AudioSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9D0ACCBA7B6E4473A95D5E /* AudioSettings.swift */; }; D5AF26F18E98CEF06AEC0329 /* SQLiteLCPLicenseRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17ACAC3E8F61DA108DCC9F51 /* SQLiteLCPLicenseRepository.swift */; }; D65BEF9DAF1FEB1A5BEED700 /* EPUBParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C234075C7F7573BA54B77D /* EPUBParser.swift */; }; @@ -339,7 +336,6 @@ D8C9C54CC84D1C0810343D9E /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E788FD34BE635B4B80C18A6 /* UIColor.swift */; }; D9499DACC5329F3774CBEDAC /* SwiftSoup.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE09289EB0FEA5FEC8506B1F /* SwiftSoup.xcframework */; }; D94A07E9627214888D37C6DF /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F64FBE3CA5C1B0C73A22E86D /* Bundle.swift */; }; - DAA4EA6A44E288F015A638D0 /* Fuzi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B421601FB56132514CCD9699 /* Fuzi.xcframework */; }; DB423F5860A1C47EF2E18113 /* PDFTapGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF324AD5E3E30687AC5262D /* PDFTapGestureController.swift */; }; DC0487666F03A3FAFE49D0B9 /* EPUBLicenseContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500E55D9CA753D6D6AA76D10 /* EPUBLicenseContainer.swift */; }; DD04CA793E06BBAD6A75329F /* EPUBPreferences+Legacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF31AEFB5FF0E7892C6D903E /* EPUBPreferences+Legacy.swift */; }; @@ -539,6 +535,7 @@ 251275D0DF87F85158A5FEA9 /* Assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Assets; path = ../../Sources/Navigator/EPUB/Assets; sourceTree = SOURCE_ROOT; }; 258351CE21165EDED7F87878 /* URLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLProtocol.swift; sourceTree = ""; }; 2732AFC91AB15FA09C60207A /* Locator+Audio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locator+Audio.swift"; sourceTree = ""; }; + 2828D89EBB52CCA782ED1146 /* ReadiumFuzi.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ReadiumFuzi.xcframework; path = ../../Carthage/Build/ReadiumFuzi.xcframework; sourceTree = ""; }; 294E01A2E6FF25539EBC1082 /* Properties+Archive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Properties+Archive.swift"; sourceTree = ""; }; 29AD63CD2A41586290547212 /* NavigationDocumentParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationDocumentParser.swift; sourceTree = ""; }; 2AF56CF04F94B7BE45631897 /* LCPContentProtection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCPContentProtection.swift; sourceTree = ""; }; @@ -615,6 +612,7 @@ 567C115FF0939F69AD83AE82 /* UserRights.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRights.swift; sourceTree = ""; }; 56C489452239BF85F4D14E95 /* PublicationMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicationMediaLoader.swift; sourceTree = ""; }; 57338C29681D4872D425AB81 /* UInt64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UInt64.swift; sourceTree = ""; }; + 59624A8738EB3A791CEF8E4C /* ZIPFoundation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZIPFoundation.swift; sourceTree = ""; }; 5A85DB4931BA5D965042CC6F /* CompositePublicationParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositePublicationParser.swift; sourceTree = ""; }; 5BC6AE42A31D77B548CB0BB4 /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; 5E788FD34BE635B4B80C18A6 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; @@ -638,6 +636,7 @@ 68FF131876FA3A63025F2662 /* Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = ""; }; 691C96D23D42A0C6AC03B1AE /* FileAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileAsset.swift; sourceTree = ""; }; 69212974E62EF509BC1F0C7A /* UnknownAbsoluteURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnknownAbsoluteURL.swift; sourceTree = ""; }; + 69E17C4870C64264819EB227 /* ReadiumZIPFoundation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ReadiumZIPFoundation.xcframework; path = ../../Carthage/Build/ReadiumZIPFoundation.xcframework; sourceTree = ""; }; 6BC71BAFF7A20D7903E6EE4D /* Properties+EPUB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Properties+EPUB.swift"; sourceTree = ""; }; 6D80848AADD20D4384D9AF59 /* HTMLFontFamilyDeclaration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLFontFamilyDeclaration.swift; sourceTree = ""; }; 6FA3AA272772FCF7D6268A74 /* FileURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileURL.swift; sourceTree = ""; }; @@ -681,7 +680,6 @@ 8B6A5B12925813FB40C41034 /* Presentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presentation.swift; sourceTree = ""; }; 8B72B76AB39E09E4A2E465AF /* PDFFormatSniffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFFormatSniffer.swift; sourceTree = ""; }; 8BF64D7C05A790D9CA5DD442 /* Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; }; - 8D187A577EBFCFF738D1CDC7 /* ZIPFoundation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ZIPFoundation.xcframework; path = ../../Carthage/Build/ZIPFoundation.xcframework; sourceTree = ""; }; 8DA31089FCAD8DFB9AC46E4E /* Tokenizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tokenizer.swift; sourceTree = ""; }; 8EA0008AF1B9B97962824D85 /* FallbackContentProtection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FallbackContentProtection.swift; sourceTree = ""; }; 91F34B9B08BC6FB84CE54A26 /* LCPProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCPProgress.swift; sourceTree = ""; }; @@ -738,7 +736,6 @@ B15C9123EA383ED81DE0393A /* AVTTSEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVTTSEngine.swift; sourceTree = ""; }; B22A0E76866F626D79F0A64C /* SQLiteLCPPassphraseRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SQLiteLCPPassphraseRepository.swift; sourceTree = ""; }; B39BF68DBB28D655023ADB62 /* Fetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fetcher.swift; sourceTree = ""; }; - B421601FB56132514CCD9699 /* Fuzi.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Fuzi.xcframework; path = ../../Carthage/Build/Fuzi.xcframework; sourceTree = ""; }; B53B841C2F5A59BA3B161258 /* Resource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resource.swift; sourceTree = ""; }; B5CE464C519852D38F873ADB /* PotentialRights.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PotentialRights.swift; sourceTree = ""; }; B7C9D54352714641A87F64A0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -775,7 +772,6 @@ CF31AEFB5FF0E7892C6D903E /* EPUBPreferences+Legacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EPUBPreferences+Legacy.swift"; sourceTree = ""; }; CFE1142A6C038A35C527CE84 /* URITemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URITemplate.swift; sourceTree = ""; }; CFE34EA8AF2D815F7169CA45 /* Fuzi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fuzi.swift; sourceTree = ""; }; - CFFEBDFE931745C07DACD4A3 /* Minizip.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Minizip.xcframework; path = ../../Carthage/Build/Minizip.xcframework; sourceTree = ""; }; D008F7BB187AE82CBB115D0F /* WebServerResourceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebServerResourceResponse.swift; sourceTree = ""; }; D0C2A38D366CE8560BCBAC8B /* PDFPositionsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFPositionsService.swift; sourceTree = ""; }; D13272E03B63E96D4246F79D /* PDFParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFParser.swift; sourceTree = ""; }; @@ -798,7 +794,6 @@ DBCE9786DD346E6BDB2E50FF /* Assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Assets; path = ../../Sources/Streamer/Assets; sourceTree = SOURCE_ROOT; }; DCE34D74E282834684E1C999 /* AudioNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioNavigator.swift; sourceTree = ""; }; DCF20C1D3C33365D25704663 /* XMLFormatSniffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLFormatSniffer.swift; sourceTree = ""; }; - DD824370ACE916C9BB9CAF84 /* Minizip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Minizip.swift; sourceTree = ""; }; DE5CE54D209AEA4AE5B07618 /* AudioFormatSniffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioFormatSniffer.swift; sourceTree = ""; }; DF89316F77F23DACA2E04696 /* PDFDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFDocument.swift; sourceTree = ""; }; DF92954C8C8C3EC50C835CBA /* Link.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Link.swift; sourceTree = ""; }; @@ -863,8 +858,7 @@ buildActionMask = 2147483647; files = ( B0B2C38D1A7B36E73C3E3779 /* DifferenceKit.xcframework in Frameworks */, - 8709012E5CE29CC28E7470C4 /* Fuzi.xcframework in Frameworks */, - 180E78AF6C7507F06AEEB806 /* Minizip.xcframework in Frameworks */, + 4977279900B4BA602D92B5C4 /* ReadiumFuzi.xcframework in Frameworks */, A040DE6F2D9A6B8D16B063B9 /* SwiftSoup.xcframework in Frameworks */, 33E3A544AFA1F9CF71771558 /* ReadiumShared.framework in Frameworks */, 70853F23094C2BAFAD422AFD /* ReadiumInternal.framework in Frameworks */, @@ -876,9 +870,8 @@ buildActionMask = 2147483647; files = ( 26CC9CB6CA4D81EB60AE860C /* CryptoSwift.xcframework in Frameworks */, - DAA4EA6A44E288F015A638D0 /* Fuzi.xcframework in Frameworks */, - 90769CA2ABCBD1F7203697DC /* Minizip.xcframework in Frameworks */, - 5903F431F5558958EF7CDDA0 /* ZIPFoundation.xcframework in Frameworks */, + 0C038E3525BB600EF6815EB9 /* ReadiumFuzi.xcframework in Frameworks */, + C0A492BDDCB4A472F408B3B4 /* ReadiumZIPFoundation.xcframework in Frameworks */, 13534618D5095F49627C4409 /* ReadiumShared.framework in Frameworks */, C3F4CBE80D741D4158CA8407 /* ReadiumInternal.framework in Frameworks */, ); @@ -898,8 +891,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8B9BCCB1D724E18BF2893629 /* Fuzi.xcframework in Frameworks */, - 01AC52ADE389D14F5274CEB2 /* Minizip.xcframework in Frameworks */, + B02109488289D631DF959B8D /* ReadiumFuzi.xcframework in Frameworks */, + CDE5EE79D3F0D00F9CAED8B8 /* ReadiumZIPFoundation.xcframework in Frameworks */, D9499DACC5329F3774CBEDAC /* SwiftSoup.xcframework in Frameworks */, EE16BE486539BC9B3C3C6896 /* ReadiumInternal.framework in Frameworks */, 38E81FCDABFBB514685402B8 /* CoreServices.framework in Frameworks */, @@ -911,8 +904,7 @@ buildActionMask = 2147483647; files = ( 682DFC1AF2BD7CAE0862B331 /* CryptoSwift.xcframework in Frameworks */, - 2C1934F6A462DA5F5EE9B13C /* Fuzi.xcframework in Frameworks */, - D2FA324CCED60BCF338C46F7 /* Minizip.xcframework in Frameworks */, + 048A8C8447FBF687484CD248 /* ReadiumFuzi.xcframework in Frameworks */, ACD1914D2D9BB7141148740F /* ReadiumShared.framework in Frameworks */, 1752D756BED37325D6D4ED29 /* ReadiumInternal.framework in Frameworks */, ); @@ -932,8 +924,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 0828EA20B47F13D1FC4C364F /* Fuzi.xcframework in Frameworks */, - D5543164860E3B7B8FD10012 /* Minizip.xcframework in Frameworks */, + 422FF6643B32F9BCE8043757 /* ReadiumFuzi.xcframework in Frameworks */, 39326587EF76BFD5AD68AED2 /* ReadiumShared.framework in Frameworks */, CD0243B5EB8B408E34786214 /* ReadiumInternal.framework in Frameworks */, ); @@ -1115,8 +1106,8 @@ 248500BDC6637BD9EDEEB8A0 /* ZIP */ = { isa = PBXGroup; children = ( - DD824370ACE916C9BB9CAF84 /* Minizip.swift */, C51C74A5990A3BA93B3DC587 /* ZIPArchiveOpener.swift */, + 59624A8738EB3A791CEF8E4C /* ZIPFoundation.swift */, ); path = ZIP; sourceTree = ""; @@ -1749,12 +1740,11 @@ 342D5C0FEE79A2ABEE24A43E /* CoreServices.framework */, E37F94C388A86CB8A34812A5 /* CryptoSwift.xcframework */, 3F95F3F20D758BE0E7005EA3 /* DifferenceKit.xcframework */, - B421601FB56132514CCD9699 /* Fuzi.xcframework */, - CFFEBDFE931745C07DACD4A3 /* Minizip.xcframework */, + 2828D89EBB52CCA782ED1146 /* ReadiumFuzi.xcframework */, 6536C07F5A50F7F25FDBF69C /* ReadiumGCDWebServer.xcframework */, + 69E17C4870C64264819EB227 /* ReadiumZIPFoundation.xcframework */, F07214E263C6589987A561F9 /* SQLite.xcframework */, BE09289EB0FEA5FEC8506B1F /* SwiftSoup.xcframework */, - 8D187A577EBFCFF738D1CDC7 /* ZIPFoundation.xcframework */, ); name = Frameworks; sourceTree = ""; @@ -2601,7 +2591,6 @@ 26CA8F00CA85378CA0F8B3F2 /* MediaTypeSniffer.swift in Sources */, 7E45E10720EA6B4F18196316 /* Metadata+Presentation.swift in Sources */, 78C52EED635B5F8C38A02298 /* Metadata.swift in Sources */, - 77006A5BF6FCBFFD60B15EBA /* Minizip.swift in Sources */, 74E94DF537F0DE19706003DA /* NowPlayingInfo.swift in Sources */, 3079CCA7A341CB0A43A8DA74 /* OPDSAcquisition.swift in Sources */, C35C6AB637D2048D9B0A3C62 /* OPDSAvailability.swift in Sources */, @@ -2678,6 +2667,7 @@ 5591563FD08A956B80C37716 /* XMLFormatSniffer.swift in Sources */, BBF5AAEEE90BD88D58191DA3 /* ZIPArchiveOpener.swift in Sources */, B49522888052E9F41D0DD013 /* ZIPFormatSniffer.swift in Sources */, + 6724EAA0D931CF252E5FAEDF /* ZIPFoundation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Support/Carthage/project.yml b/Support/Carthage/project.yml index 5482b4011..032d25857 100644 --- a/Support/Carthage/project.yml +++ b/Support/Carthage/project.yml @@ -12,11 +12,9 @@ targets: deploymentTarget: "13.0" sources: - path: ../../Sources/Shared - excludes: - - Toolkit/ZIP/ZIPFoundation.swift dependencies: - - framework: ../../Carthage/Build/Fuzi.xcframework - - framework: ../../Carthage/Build/Minizip.xcframework + - framework: ../../Carthage/Build/ReadiumFuzi.xcframework + - framework: ../../Carthage/Build/ReadiumZIPFoundation.xcframework - framework: ../../Carthage/Build/SwiftSoup.xcframework - target: ReadiumInternal - sdk: CoreServices.framework @@ -36,8 +34,7 @@ targets: type: folder dependencies: - framework: ../../Carthage/Build/CryptoSwift.xcframework - - framework: ../../Carthage/Build/Fuzi.xcframework - - framework: ../../Carthage/Build/Minizip.xcframework + - framework: ../../Carthage/Build/ReadiumFuzi.xcframework - target: ReadiumShared - target: ReadiumInternal settings: @@ -57,8 +54,7 @@ targets: type: folder dependencies: - framework: ../../Carthage/Build/DifferenceKit.xcframework - - framework: ../../Carthage/Build/Fuzi.xcframework - - framework: ../../Carthage/Build/Minizip.xcframework + - framework: ../../Carthage/Build/ReadiumFuzi.xcframework - framework: ../../Carthage/Build/SwiftSoup.xcframework - target: ReadiumShared - target: ReadiumInternal @@ -73,8 +69,7 @@ targets: sources: - path: ../../Sources/OPDS dependencies: - - framework: ../../Carthage/Build/Fuzi.xcframework - - framework: ../../Carthage/Build/Minizip.xcframework + - framework: ../../Carthage/Build/ReadiumFuzi.xcframework - target: ReadiumShared - target: ReadiumInternal settings: @@ -89,9 +84,8 @@ targets: - path: ../../Sources/LCP dependencies: - framework: ../../Carthage/Build/CryptoSwift.xcframework - - framework: ../../Carthage/Build/Fuzi.xcframework - - framework: ../../Carthage/Build/Minizip.xcframework - - framework: ../../Carthage/Build/ZIPFoundation.xcframework + - framework: ../../Carthage/Build/ReadiumFuzi.xcframework + - framework: ../../Carthage/Build/ReadiumZIPFoundation.xcframework - target: ReadiumShared - target: ReadiumInternal settings: diff --git a/Support/CocoaPods/ReadiumLCP.podspec b/Support/CocoaPods/ReadiumLCP.podspec index 41f188111..24d218fed 100644 --- a/Support/CocoaPods/ReadiumLCP.podspec +++ b/Support/CocoaPods/ReadiumLCP.podspec @@ -21,6 +21,6 @@ Pod::Spec.new do |s| s.dependency 'ReadiumShared' s.dependency 'ReadiumInternal' - s.dependency 'ZIPFoundation', '~> 0.9.0' + s.dependency 'ReadiumZIPFoundation', '~> 1.0.0' s.dependency 'CryptoSwift', '~> 1.8.0' end diff --git a/Support/CocoaPods/ReadiumNavigator.podspec b/Support/CocoaPods/ReadiumNavigator.podspec index a9b5c21c8..754c111ab 100644 --- a/Support/CocoaPods/ReadiumNavigator.podspec +++ b/Support/CocoaPods/ReadiumNavigator.podspec @@ -17,6 +17,7 @@ Pod::Spec.new do |s| s.source_files = "Sources/Navigator/**/*.{m,h,swift}" s.platform = :ios s.ios.deployment_target = "13.0" + s.dependency 'ReadiumShared' s.dependency 'ReadiumInternal' s.dependency 'DifferenceKit', '~> 1.0' diff --git a/Support/CocoaPods/ReadiumOPDS.podspec b/Support/CocoaPods/ReadiumOPDS.podspec index c57d3f3c3..3483c2cc6 100644 --- a/Support/CocoaPods/ReadiumOPDS.podspec +++ b/Support/CocoaPods/ReadiumOPDS.podspec @@ -18,6 +18,6 @@ Pod::Spec.new do |s| s.dependency 'ReadiumShared' s.dependency 'ReadiumInternal' - s.dependency 'Fuzi', '~> 3.1.0' + s.dependency 'ReadiumFuzi', '~> 4.0.0' end diff --git a/Support/CocoaPods/ReadiumShared.podspec b/Support/CocoaPods/ReadiumShared.podspec index 20065249d..d8cc070f9 100644 --- a/Support/CocoaPods/ReadiumShared.podspec +++ b/Support/CocoaPods/ReadiumShared.podspec @@ -7,7 +7,6 @@ Pod::Spec.new do |s| s.homepage = "http://readium.github.io" s.author = { "Readium" => "contact@readium.org" } s.source = { :git => 'https://github.com/readium/swift-toolkit.git', :branch => "develop" } - s.exclude_files = ["Sources/Shared/Toolkit/ZIP/ZIPFoundation.swift"] s.requires_arc = true s.source_files = "Sources/Shared/**/*.{m,h,swift}" s.platform = :ios @@ -16,9 +15,9 @@ Pod::Spec.new do |s| s.libraries = "xml2" s.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' } - s.dependency 'Fuzi', '~> 3.1.0' - s.dependency 'Minizip', '~> 1.0.0' s.dependency 'SwiftSoup', '~> 2.7.0' + s.dependency 'ReadiumFuzi', '~> 4.0.0' + s.dependency 'ReadiumZIPFoundation', '~> 1.0.0' s.dependency 'ReadiumInternal' end diff --git a/Support/CocoaPods/ReadiumStreamer.podspec b/Support/CocoaPods/ReadiumStreamer.podspec index e20c669f3..515f801d7 100644 --- a/Support/CocoaPods/ReadiumStreamer.podspec +++ b/Support/CocoaPods/ReadiumStreamer.podspec @@ -20,9 +20,9 @@ Pod::Spec.new do |s| s.libraries = 'z', 'xml2' s.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' } + s.dependency 'ReadiumFuzi', '~> 4.0.0' s.dependency 'ReadiumShared' s.dependency 'ReadiumInternal' s.dependency 'CryptoSwift', '~> 1.8.0' - s.dependency 'Fuzi', '~> 3.1.0' end diff --git a/TestApp/Integrations/Carthage/project+lcp.yml b/TestApp/Integrations/Carthage/project+lcp.yml index 71d2ba30f..a51ff0bdd 100644 --- a/TestApp/Integrations/Carthage/project+lcp.yml +++ b/TestApp/Integrations/Carthage/project+lcp.yml @@ -25,21 +25,19 @@ targets: dependencies: - framework: Carthage/Build/CryptoSwift.xcframework - framework: Carthage/Build/DifferenceKit.xcframework - - framework: Carthage/Build/Fuzi.xcframework - - framework: Carthage/Build/Minizip.xcframework - framework: Carthage/Build/R2LCPClient.xcframework - - framework: Carthage/Build/ReadiumNavigator.xcframework - - framework: Carthage/Build/ReadiumShared.xcframework - - framework: Carthage/Build/ReadiumStreamer.xcframework - framework: Carthage/Build/ReadiumAdapterGCDWebServer.xcframework - framework: Carthage/Build/ReadiumAdapterLCPSQLite.xcframework - framework: Carthage/Build/ReadiumGCDWebServer.xcframework - framework: Carthage/Build/ReadiumInternal.xcframework - framework: Carthage/Build/ReadiumLCP.xcframework + - framework: Carthage/Build/ReadiumNavigator.xcframework - framework: Carthage/Build/ReadiumOPDS.xcframework + - framework: Carthage/Build/ReadiumShared.xcframework + - framework: Carthage/Build/ReadiumStreamer.xcframework + - framework: Carthage/Build/ReadiumZIPFoundation.xcframework - framework: Carthage/Build/SQLite.xcframework - framework: Carthage/Build/SwiftSoup.xcframework - - framework: Carthage/Build/ZIPFoundation.xcframework - package: GRDB - package: Kingfisher - package: MBProgressHUD diff --git a/TestApp/Integrations/Carthage/project.yml b/TestApp/Integrations/Carthage/project.yml index a3a264099..6e035222c 100644 --- a/TestApp/Integrations/Carthage/project.yml +++ b/TestApp/Integrations/Carthage/project.yml @@ -25,15 +25,15 @@ targets: dependencies: - framework: Carthage/Build/CryptoSwift.xcframework - framework: Carthage/Build/DifferenceKit.xcframework - - framework: Carthage/Build/Fuzi.xcframework - - framework: Carthage/Build/Minizip.xcframework - - framework: Carthage/Build/ReadiumNavigator.xcframework - - framework: Carthage/Build/ReadiumShared.xcframework - - framework: Carthage/Build/ReadiumStreamer.xcframework - framework: Carthage/Build/ReadiumAdapterGCDWebServer.xcframework + - framework: Carthage/Build/ReadiumFuzi.xcframework - framework: Carthage/Build/ReadiumGCDWebServer.xcframework - framework: Carthage/Build/ReadiumInternal.xcframework + - framework: Carthage/Build/ReadiumNavigator.xcframework - framework: Carthage/Build/ReadiumOPDS.xcframework + - framework: Carthage/Build/ReadiumShared.xcframework + - framework: Carthage/Build/ReadiumStreamer.xcframework + - framework: Carthage/Build/ReadiumZIPFoundation.xcframework - framework: Carthage/Build/SwiftSoup.xcframework - package: GRDB - package: Kingfisher diff --git a/TestApp/Integrations/CocoaPods/Podfile b/TestApp/Integrations/CocoaPods/Podfile index 155e4b7d6..b66f6aaf8 100644 --- a/TestApp/Integrations/CocoaPods/Podfile +++ b/TestApp/Integrations/CocoaPods/Podfile @@ -10,10 +10,6 @@ target 'TestApp' do pod 'ReadiumAdapterGCDWebServer', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/VERSION/Support/CocoaPods/ReadiumAdapterGCDWebServer.podspec' pod 'ReadiumOPDS', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/VERSION/Support/CocoaPods/ReadiumOPDS.podspec' pod 'ReadiumInternal', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/VERSION/Support/CocoaPods/ReadiumInternal.podspec' - pod 'Fuzi', podspec: 'https://raw.githubusercontent.com/readium/Fuzi/refs/heads/master/Fuzi.podspec' - - # Required for R2Streamer and ReadiumAdapterGCDWebServer. - pod 'ReadiumGCDWebServer', podspec: 'https://raw.githubusercontent.com/readium/GCDWebServer/master/GCDWebServer.podspec' pod 'GRDB.swift', '~> 6.0' pod 'Kingfisher', '~> 5.0' diff --git a/TestApp/Integrations/CocoaPods/Podfile+lcp b/TestApp/Integrations/CocoaPods/Podfile+lcp index bd97896cc..394acff97 100644 --- a/TestApp/Integrations/CocoaPods/Podfile+lcp +++ b/TestApp/Integrations/CocoaPods/Podfile+lcp @@ -12,12 +12,8 @@ target 'TestApp' do pod 'ReadiumOPDS', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/VERSION/Support/CocoaPods/ReadiumOPDS.podspec' pod 'ReadiumLCP', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/VERSION/Support/CocoaPods/ReadiumLCP.podspec' pod 'ReadiumInternal', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/VERSION/Support/CocoaPods/ReadiumInternal.podspec' - pod 'Fuzi', podspec: 'https://raw.githubusercontent.com/readium/Fuzi/refs/heads/master/Fuzi.podspec' pod 'R2LCPClient', podspec: 'LCP_URL' - # Required for ReadiumStreamer and ReadiumAdapterGCDWebServer. - pod 'ReadiumGCDWebServer', podspec: 'https://raw.githubusercontent.com/readium/GCDWebServer/master/GCDWebServer.podspec' - pod 'GRDB.swift', '~> 6.0' pod 'Kingfisher', '~> 5.0' pod 'MBProgressHUD', '~> 1.0' diff --git a/Tests/SharedTests/Toolkit/Archive/Container+ZIPTests.swift b/Tests/SharedTests/Toolkit/Archive/ZIPFoundationTests.swift similarity index 55% rename from Tests/SharedTests/Toolkit/Archive/Container+ZIPTests.swift rename to Tests/SharedTests/Toolkit/Archive/ZIPFoundationTests.swift index 1959b6845..057df9a9a 100644 --- a/Tests/SharedTests/Toolkit/Archive/Container+ZIPTests.swift +++ b/Tests/SharedTests/Toolkit/Archive/ZIPFoundationTests.swift @@ -9,34 +9,36 @@ import XCTest private let fixtures = Fixtures(path: "Archive") -struct ZIPTester { - let make: (FileURL) async throws -> Container +class ZIPFoundationTests: XCTestCase { + private func container(for filename: String) async throws -> Container { + try await ZIPFoundationContainer.make(file: fixtures.url(for: filename)).get() + } func testOpenSuccess() async throws { - _ = try await make(fixtures.url(for: "test.zip")) + _ = try await container(for: "test.zip") } func testOpenNotFound() async { do { - _ = try await make(fixtures.url(for: "unknown.zip")) + _ = try await container(for: "unknown.zip") XCTFail("Expected an error") } catch {} } func testOpenNotAZIP() async { do { - _ = try await make(fixtures.url(for: "not-a.zip")) + _ = try await container(for: "not-a.zip") XCTFail("Expected an error") } catch {} } func testGetNonExistingEntry() async throws { - let container = try await make(fixtures.url(for: "test.zip")) + let container = try await container(for: "test.zip") XCTAssertNil(container[AnyURL(path: "unknown")!]) } func testEntries() async throws { - let container = try await make(fixtures.url(for: "test.zip")) + let container = try await container(for: "test.zip") XCTAssertEqual( container.entries, @@ -53,7 +55,7 @@ struct ZIPTester { } func testResources() async throws { - let container = try await make(fixtures.url(for: "test.zip")) + let container = try await container(for: "test.zip") try await AssertEntry(path: ".hidden", in: container, isCompressed: false, length: 0, originalLength: 0) try await AssertEntry(path: "A folder/Sub.folder%/file.txt", in: container, isCompressed: false, length: 20, originalLength: 20) @@ -65,7 +67,7 @@ struct ZIPTester { } func testReadCompressedEntry() async throws { - let container = try await make(fixtures.url(for: "test.zip")) + let container = try await container(for: "test.zip") let entry = try XCTUnwrap(container[AnyURL(path: "A folder/Sub.folder%/file-compressed.txt")!]) let data = try await entry.read().get() let string = String(data: data, encoding: .utf8)! @@ -74,7 +76,7 @@ struct ZIPTester { } func testReadUncompressedEntry() async throws { - let container = try await make(fixtures.url(for: "test.zip")) + let container = try await container(for: "test.zip") let entry = try XCTUnwrap(container[AnyURL(path: "A folder/Sub.folder%/file.txt")!]) let data = try await entry.read().get() XCTAssertNotNil(data) @@ -86,7 +88,7 @@ struct ZIPTester { func testReadUncompressedRange() async throws { // FIXME: It looks like unzseek64 starts from the beginning of the file header, instead of the content. Reading a first byte solves this but then Minizip crashes randomly... Note that this only fails in the test case. I didn't see actual issues in LCPDF or videos embedded in EPUBs. - let container = try await make(fixtures.url(for: "test.zip")) + let container = try await container(for: "test.zip") let entry = try XCTUnwrap(container[AnyURL(path: "A folder/Sub.folder%/file.txt")!]) let data = try await entry.read(range: 14 ..< 20).get() XCTAssertEqual( @@ -96,7 +98,7 @@ struct ZIPTester { } func testReadCompressedRange() async throws { - let container = try await make(fixtures.url(for: "test.zip")) + let container = try await container(for: "test.zip") let entry = try XCTUnwrap(container[AnyURL(path: "A folder/Sub.folder%/file-compressed.txt")!]) let data = try await entry.read(range: 14 ..< 20).get() XCTAssertEqual( @@ -105,6 +107,30 @@ struct ZIPTester { ) } + func testRandomCompressedRead() async throws { + for _ in 0 ..< 100 { + let container = try await container(for: "test.zip") + let entry = try XCTUnwrap(container[AnyURL(path: "A folder/wasteland-cover.jpg")!]) + let length: UInt64 = 103_477 + let lower = UInt64.random(in: 0 ..< length - 100) + let upper = UInt64.random(in: lower ..< length) + let range = lower ..< upper + _ = try await entry.read(range: range).get() + } + } + + func testRandomStoredRead() async throws { + for _ in 0 ..< 100 { + let container = try await container(for: "test.zip") + let entry = try XCTUnwrap(container[AnyURL(path: "uncompressed.jpg")!]) + let length: UInt64 = 279_551 + let lower = UInt64.random(in: 0 ..< length - 100) + let upper = UInt64.random(in: lower ..< length) + let range = lower ..< upper + _ = try await entry.read(range: range).get() + } + } + private func AssertEntry( path: String, in container: Container, @@ -128,67 +154,3 @@ struct ZIPTester { XCTAssertEqual(properties.filename, RelativeURL(path: path)!.lastPathSegment) } } - -class MinizipTests: XCTestCase { - lazy var tester = ZIPTester { - try await MinizipContainer.make(file: $0).get() - } - - func testOpenSuccess() async throws { try await tester.testOpenSuccess() } - func testOpenNotFound() async { await tester.testOpenNotFound() } - func testOpenNotAZIP() async { await tester.testOpenNotAZIP() } - func testGetNonExistingEntry() async throws { try await tester.testGetNonExistingEntry() } - func testEntries() async throws { try await tester.testEntries() } - func testResources() async throws { try await tester.testResources() } - func testReadCompressedEntry() async throws { try await tester.testReadCompressedEntry() } - func testReadUncompressedEntry() async throws { try await tester.testReadUncompressedEntry() } - func testReadCompressedRange() async throws { try await tester.testReadCompressedRange() } - func testReadUncompressedRange() async throws { try await tester.testReadUncompressedRange() } -} - -// class ZIPFoundationTests: XCTestCase { -// -// lazy var tester = ZIPTester { -// url in try ZIPFoundation.make(url: url).get() -// } -// -// func testOpenSuccess() async throws { try await tester.testOpenSuccess() } -// func testOpenNotFound() async { await tester.testOpenNotFound() } -// func testOpenNotAZIP() async { await tester.testOpenNotAZIP() } -// func testGetNonExistingEntry() async throws { try await tester.testGetNonExistingEntry() } -// func testEntries() async throws { try await tester.testEntries() } -// func testResources() async throws { try await tester.testResources() } -// func testReadCompressedEntry() async throws { try await tester.testReadCompressedEntry() } -// func testReadUncompressedEntry() async throws { try await tester.testReadUncompressedEntry() } -// func testReadCompressedRange() async throws { try await tester.testReadCompressedRange() } -// func testReadUncompressedRange() async throws { try await tester.testReadUncompressedRange() } -// -// } - -class ZIPBenchmarkingTests: XCTestCase { - func testCompareRange() async throws { - let containers: [Container] = [ - try! await MinizipContainer.make(file: fixtures.url(for: "test.zip")).get(), -// try! ZIPFoundationArchive(url: fixtures.url(for: "test.zip")) - ] - let path = AnyURL(path: "A folder/wasteland-cover.jpg")! - let length: UInt64 = 103_477 - - let entries = try containers - .map { try XCTUnwrap($0[path]) } - - measure { - let exp = expectation(description: "Finished") - Task { - let lower = UInt64.random(in: 0 ..< length - 100) - let upper = UInt64.random(in: lower ..< length) - let range = lower ..< upper - let datas = await entries.asyncmap { await $0.read(range: range).getOrNil() } - let data = datas[0] - XCTAssertTrue(datas.allSatisfy { $0 == data }) - exp.fulfill() - } - wait(for: [exp], timeout: 200.0) - } - } -} diff --git a/Tests/StreamerTests/Parser/EPUB/EPUBContainerParserTests.swift b/Tests/StreamerTests/Parser/EPUB/EPUBContainerParserTests.swift index aa4f78115..c878f9453 100644 --- a/Tests/StreamerTests/Parser/EPUB/EPUBContainerParserTests.swift +++ b/Tests/StreamerTests/Parser/EPUB/EPUBContainerParserTests.swift @@ -4,7 +4,7 @@ // available in the top-level LICENSE file of the project. // -import Fuzi +import ReadiumFuzi import ReadiumShared @testable import ReadiumStreamer import XCTest diff --git a/Tests/StreamerTests/Parser/EPUB/EPUBMetadataParserTests.swift b/Tests/StreamerTests/Parser/EPUB/EPUBMetadataParserTests.swift index 80da508bc..2caeefea7 100644 --- a/Tests/StreamerTests/Parser/EPUB/EPUBMetadataParserTests.swift +++ b/Tests/StreamerTests/Parser/EPUB/EPUBMetadataParserTests.swift @@ -4,7 +4,7 @@ // available in the top-level LICENSE file of the project. // -import Fuzi +import ReadiumFuzi import ReadiumShared @testable import ReadiumStreamer import XCTest @@ -356,7 +356,7 @@ class EPUBMetadataParserTests: XCTestCase { // MARK: - Toolkit func parseMetadata(_ name: String, displayOptions: String? = nil) throws -> Metadata { - func parseDocument(named name: String, type: String) throws -> Fuzi.XMLDocument { + func parseDocument(named name: String, type: String) throws -> ReadiumFuzi.XMLDocument { try XMLDocument(data: fixtures.data(at: "\(name).\(type)")) } diff --git a/docs/Migration Guide.md b/docs/Migration Guide.md index 78293ba7d..5d744639e 100644 --- a/docs/Migration Guide.md +++ b/docs/Migration Guide.md @@ -2,6 +2,24 @@ All migration steps necessary in reading apps to upgrade to major versions of the Swift Readium toolkit will be documented in this file. +## Unreleased + +### ZIPFoundation replaces Minizip + +The default `ZIPArchiveOpener` is now using ZIPFoundation instead of Minizip, with improved performances when reading ranges of `stored` ZIP entries. + +If you use Carthage, remove `Minizip.xcframework` from your dependencies and add `ReadiumZIPFoundation.xcframework` instead. No changes are needed when using Swift Package Manager or CocoaPods. + +### CocoaPods dependencies + +Some CocoaPods dependencies are now part of the official trunk and handled automatically. You must remove the custom declarations from your `Podfile`: + +```diff +-pod 'Fuzi', podspec: 'https://raw.githubusercontent.com/readium/Fuzi/refs/heads/master/Fuzi.podspec' +-pod 'ReadiumGCDWebServer', podspec: 'https://raw.githubusercontent.com/readium/GCDWebServer/4.0.0/GCDWebServer.podspec' +``` + + ## 3.0.0-alpha.2 ### Error management