Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ All notable changes to this project will be documented in this file. Take a look
#### Shared

* All public types that parsed or serialized JSON now use the new type-safe `JSONValue` enum instead of `Any` / `[String: Any]`. See [the migration guide](docs/Migration%20Guide.md) for upgrade instructions.
* OPDS models (`Feed`, `Group`, `Facet`, `OpdsMetadata`) are now structs with value semantics.

#### Navigator

Expand Down
8 changes: 4 additions & 4 deletions Sources/Adapters/GCDWebServer/GCDHTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@
//

import Foundation
import ReadiumGCDWebServer
@preconcurrency import ReadiumGCDWebServer
import ReadiumInternal
import ReadiumShared
import UIKit

public enum GCDHTTPServerError: Error {
case failedToStartServer(cause: Error)
public enum GCDHTTPServerError: Error, Sendable {
case failedToStartServer(cause: any Error)
case serverNotStarted
case invalidEndpoint(HTTPServerEndpoint)
case nullServerURL
}

/// Implementation of `HTTPServer` using ReadiumGCDWebServer under the hood.
public class GCDHTTPServer: HTTPServer, Loggable {
public final class GCDHTTPServer: HTTPServer, Loggable {
/// The actual underlying HTTP server instance.
private let server = ReadiumGCDWebServer()

Expand Down
4 changes: 2 additions & 2 deletions Sources/Adapters/LCPSQLite/SQLiteLCPLicenseRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import Foundation
import ReadiumLCP
import ReadiumShared
import SQLite
@preconcurrency import SQLite

@available(*, deprecated, message: "Use LCPKeychainLicenseRepository from ReadiumLCP instead")
public class LCPSQLiteLicenseRepository: LCPLicenseRepository, Loggable {
public final class LCPSQLiteLicenseRepository: LCPLicenseRepository, Loggable, Sendable {
let licenses = Table("Licenses")
let id = SQLite.Expression<String>("id")
let printsLeft = SQLite.Expression<Int?>("printsLeft")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import Foundation
import ReadiumLCP
import ReadiumShared
import SQLite
@preconcurrency import SQLite

@available(*, deprecated, message: "Use LCPKeychainPassphraseRepository from ReadiumLCP instead")
public class LCPSQLitePassphraseRepository: LCPPassphraseRepository, Loggable {
public final class LCPSQLitePassphraseRepository: LCPPassphraseRepository, Loggable, Sendable {
let transactions = Table("Transactions")
let licenseId = SQLite.Expression<String>("licenseId")
let provider = SQLite.Expression<String>("origin")
Expand Down
2 changes: 1 addition & 1 deletion Sources/Internal/Extensions/Task.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation

@MainActor
public final class CancellableTasks {
public final class CancellableTasks: Sendable {
private var tasks: Set<Task<Void, Never>> = []

public nonisolated init() {}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Internal/Keychain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Foundation
import Security

/// Errors occurring in ``Keychain``.
public enum KeychainError: Error {
public enum KeychainError: Error, Sendable {
/// The item was not found in the Keychain.
case itemNotFound

Expand Down
4 changes: 2 additions & 2 deletions Sources/Internal/UTI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import Foundation
import UniformTypeIdentifiers

/// Uniform Type Identifier.
public struct UTI {
public struct UTI: Sendable {
/// Type tag class, eg. UTTagClass.mimeType.
public enum TagClass {
public enum TagClass: Sendable {
case mediaType, fileExtension
}

Expand Down
6 changes: 3 additions & 3 deletions Sources/LCP/Authentications/LCPAuthenticating.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import Foundation

public protocol LCPAuthenticating {
public protocol LCPAuthenticating: Sendable {
/// Retrieves the passphrase to decrypt the given license.
///
/// If `allowUserInteraction` is true, the reading app can prompt the user to enter the
Expand All @@ -31,14 +31,14 @@ public protocol LCPAuthenticating {
) async -> String?
}

public enum LCPAuthenticationReason {
public enum LCPAuthenticationReason: Sendable {
/// No matching passphrase was found.
case passphraseNotFound
/// The provided passphrase was invalid.
case invalidPassphrase
}

public struct LCPAuthenticatedLicense {
public struct LCPAuthenticatedLicense: Sendable {
/// A hint to be displayed to the User to help them remember the User Passphrase.
public var hint: String {
document.encryption.userKey.textHint
Expand Down
4 changes: 2 additions & 2 deletions Sources/LCP/Authentications/LCPDialog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ import SwiftUI
/// }
/// }
/// ```
public struct LCPDialog: View {
public enum ErrorMessage {
public struct LCPDialog: View, Sendable {
public enum ErrorMessage: Sendable {
case incorrectPassphrase

var string: String {
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/Authentications/LCPDialogAuthentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import UIKit
/// For this authentication to trigger, you must provide a `sender` parameter of type
/// `UIViewController` to `Streamer.open()` or `LCPService.retrieveLicense()`. It will be used
/// as the presenting view controller for the dialog.
public class LCPDialogAuthentication: LCPAuthenticating, Loggable {
public final class LCPDialogAuthentication: LCPAuthenticating, Loggable, Sendable {
private let animated: Bool
private let modalPresentationStyle: UIModalPresentationStyle
private let modalTransitionStyle: UIModalTransitionStyle
Expand Down
4 changes: 2 additions & 2 deletions Sources/LCP/Authentications/LCPObservableAuthentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import SwiftUI
/// Pair an ``LCPObservableAuthentication`` with an ``LCPDialog`` to implement
/// the LCP authentication in SwiftUI.
@MainActor
public final class LCPObservableAuthentication: LCPAuthenticating, ObservableObject {
public final class LCPObservableAuthentication: LCPAuthenticating, ObservableObject, Sendable {
/// Represents an on-going LCP authentication request.
///
/// You must call the `submit()` or `cancel()` API to conclude the request.
@MainActor
public final class Request: Identifiable {
public final class Request: Identifiable, Sendable {
/// LCP License requested to be unlocked.
public let license: LCPAuthenticatedLicense

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
/// passphrase.
///
/// If the provided `passphrase` is incorrect, the given `fallback` authentication is used.
public class LCPPassphraseAuthentication: LCPAuthenticating {
public final class LCPPassphraseAuthentication: LCPAuthenticating, Sendable {
private let passphrase: String
private let fallback: LCPAuthenticating?

Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/LCPAcquiredPublication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ReadiumShared

/// Holds information about an LCP protected publication which was acquired
/// from an LCPL.
public struct LCPAcquiredPublication {
public struct LCPAcquiredPublication: Sendable {
/// Path to the downloaded publication.
///
/// You must move this file to the user library's folder.
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/LCPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public typealias LCPClientContext = Any
/// Copy of the R2LCPClient.LCPClientError enum.
///
/// Order is important, because it is used to match the original enum cases.
public enum LCPClientError: Int, Error {
public enum LCPClientError: Int, Error, Sendable {
case licenseOutOfDate = 0
case certificateRevoked
case certificateSignatureInvalid
Expand Down
12 changes: 6 additions & 6 deletions Sources/LCP/LCPError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public enum LCPError: Error {
/// from the number of "register" events in the status document. If no event is
/// logged in the status document, no such message should appear (certainly not
/// "The license was registered by 0 devices").
public enum StatusError: Error {
public enum StatusError: Error, Sendable {
/// This license was cancelled on the given date.
case cancelled(Date)
/// This license has been returned on the given date.
Expand All @@ -80,7 +80,7 @@ public enum StatusError: Error {
}

/// Errors while renewing a loan.
public enum RenewError: Error {
public enum RenewError: Error, Sendable {
/// Your publication could not be renewed properly.
case renewFailed
/// Incorrect renewal period, your publication could not be renewed.
Expand All @@ -90,7 +90,7 @@ public enum RenewError: Error {
}

/// Errors while returning a loan.
public enum ReturnError: Error {
public enum ReturnError: Error, Sendable {
/// Your publication could not be returned properly.
case returnFailed
/// Your publication has already been returned before or is expired.
Expand All @@ -100,7 +100,7 @@ public enum ReturnError: Error {
}

/// Errors while parsing the License or Status JSON Documents.
public enum ParsingError: Error {
public enum ParsingError: Error, Sendable {
/// The JSON is malformed and can't be parsed.
case malformedJSON
/// The JSON is not representing a valid License Document.
Expand All @@ -118,9 +118,9 @@ public enum ParsingError: Error {
}

/// Errors while reading or writing a LCP container (LCPL, EPUB, LCPDF, etc.)
public enum ContainerError: Error {
public enum ContainerError: Error, Sendable {
/// Can't access the container, it's format is wrong.
case openFailed(Error?)
case openFailed((any Error)?)
/// The file at given relative path is not found in the Container.
case fileNotFound(String)
/// Can't read the file at given relative path in the Container.
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/LCPLicenseRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public protocol LCPLicenseRepository {
}

/// Holds the current state of consumable user rights for a license.
public struct LCPConsumableUserRights {
public struct LCPConsumableUserRights: Sendable {
/// Maximum number of pages left to be printed.
///
/// If `nil`, there is no limit.
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/LCPProgress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation

/// Percent-based progress of the acquisition.
public enum LCPProgress {
public enum LCPProgress: Sendable {
/// Undetermined progress, a spinner should be shown to the user.
case indefinite
/// A finite progress from 0.0 to 1.0, a progress bar should be shown to the user.
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/LCPRenewDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public protocol LCPRenewDelegate {
///
/// No date picker is presented for selecting a preferred end date. If you want to support one, you can subclass or
/// decorate `LCPRenewDelegate`.
public class LCPDefaultRenewDelegate: NSObject, LCPRenewDelegate {
public final class LCPDefaultRenewDelegate: NSObject, LCPRenewDelegate {
private let presentingViewController: UIViewController
private let modalPresentationStyle: UIModalPresentationStyle

Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/LCPService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public final class LCPService: Loggable {
}

/// Source of an LCP License Document (LCPL) file.
public enum LicenseDocumentSource {
public enum LicenseDocumentSource: Sendable {
/// Raw bytes of the LCPL.
case data(Data)

Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/License/Model/Components/LCP/ContentKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ReadiumShared

/// Used to encrypt the Publication Resources.
/// This is encrypted using the User Key.
public struct ContentKey: JSONValueDecodable {
public struct ContentKey: JSONValueDecodable, Sendable {
/// Algorithm used to encrypt the Content Key, identified using the URIs defined in [XML-ENC]. This MUST match the Content Key encryption algorithm named in the Encryption Profile identified in `encryption/profile`.
public let algorithm: String
/// Encrypted Content Key.
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/License/Model/Components/LCP/Encryption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation
import ReadiumShared

public struct Encryption: JSONValueDecodable {
public struct Encryption: JSONValueDecodable, Sendable {
/// Identifies the Encryption Profile used by this LCP-protected Publication.
public let profile: String
/// Used to encrypt the Publication Resources.
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/License/Model/Components/LCP/Rights.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation
import ReadiumShared

public struct Rights: JSONValueDecodable {
public struct Rights: JSONValueDecodable, Sendable {
/// Maximum number of pages that can be printed over the lifetime of the license.
public let print: Int?
/// Maximum number of characters that can be copied to the clipboard over the lifetime of the license.
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/License/Model/Components/LCP/Signature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Foundation
import ReadiumShared

/// Signature allowing to certify the License Document integrity.
public struct Signature: JSONValueDecodable {
public struct Signature: JSONValueDecodable, Sendable {
/// Algorithm used to calculate the signature, identified using the URIs given in [XML-SIG]. This MUST match the signature algorithm named in the Encryption Profile identified in `encryption/profile`.
public let algorithm: String
/// The Provider Certificate: an X509 certificate used by the Content Provider.
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/License/Model/Components/LCP/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation
import ReadiumShared

public struct User: JSONValueDecodable {
public struct User: JSONValueDecodable, Sendable {
public typealias ID = String

/// Unique identifier for the User at a specific Provider.
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/License/Model/Components/LCP/UserKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Foundation
import ReadiumShared

/// Used to encrypt the ContentKey.
public struct UserKey: JSONValueDecodable {
public struct UserKey: JSONValueDecodable, Sendable {
/// A hint to be displayed to the User to help them remember the User Passphrase.
public let textHint: String
/// Algorithm used to generate the User Key from the User Passphrase, identified using the URIs defined in [XML-ENC]. This MUST match the User Key hash algorithm named in the Encryption Profile identified in `encryption/profile`.
Expand Down
4 changes: 2 additions & 2 deletions Sources/LCP/License/Model/Components/LSD/Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import Foundation
import ReadiumShared

/// Event related to the change in status of a License Document.
public struct Event: JSONValueDecodable {
public enum EventType: String {
public struct Event: JSONValueDecodable, Sendable {
public enum EventType: String, Sendable {
/// Signals a successful registration event by a device.
case register
/// Signals a successful renew event.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation
import ReadiumShared

public struct PotentialRights: JSONValueDecodable {
public struct PotentialRights: JSONValueDecodable, Sendable {
/// Time and Date when the license ends.
public let end: Date?

Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/License/Model/Components/Link.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Foundation
import ReadiumShared

/// A Link to a resource.
public struct Link: JSONValueDecodable {
public struct Link: JSONValueDecodable, Sendable {
/// The link destination.
public let href: String
/// Indicates the relationship between the resource and its containing collection.
Expand Down
2 changes: 1 addition & 1 deletion Sources/LCP/License/Model/Components/Links.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation
import ReadiumShared

public struct Links: JSONValueDecodable {
public struct Links: JSONValueDecodable, Sendable {
private let links: [Link]

public init?<T: JSONValueEncodable>(json: T?, warnings: WarningLogger?) throws {
Expand Down
4 changes: 2 additions & 2 deletions Sources/LCP/License/Model/LicenseDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import ReadiumShared

/// Document that contains references to the various keys, links to related external resources, rights and restrictions that are applied to the Protected Publication, and user information.
/// https://github.com/readium/lcp-specs/blob/master/schema/license.schema.json
public struct LicenseDocument {
public struct LicenseDocument: Sendable {
public typealias ID = String
public typealias Provider = String

/// The possible rel of Links.
public enum Rel: String {
public enum Rel: String, Sendable {
/// Location where a Reading System can redirect a User looking for additional information about the User Passphrase.
case hint
/// Location where the Publication associated with the License Document can be downloaded
Expand Down
6 changes: 3 additions & 3 deletions Sources/LCP/License/Model/StatusDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import ReadiumShared

/// Document that contains information about the history of a License Document, along with its current status and available interactions.
/// https://github.com/readium/lcp-specs/blob/master/schema/status.schema.json
public struct StatusDocument {
public enum Status: String {
public struct StatusDocument: Sendable {
public enum Status: String, Sendable {
/// The License Document is available, but the user hasn't accessed the License and/or Status Document yet.
case ready
/// The license is active, and a device has been successfully registered for this license. This is the default value if the License Document does not contain a registration link, or a registration mechanism through the license itself.
Expand All @@ -25,7 +25,7 @@ public struct StatusDocument {
case expired
}

public enum Rel: String {
public enum Rel: String, Sendable {
case register
case license
case `return`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ReadiumInternal
import ReadiumShared

/// Errors occurring in ``LCPKeychainLicenseRepository``.
public enum LCPKeychainLicenseRepositoryError: Error {
public enum LCPKeychainLicenseRepositoryError: Error, Sendable {
/// The license with the given `id` was not found in the repository.
case licenseNotFound(id: LicenseDocument.ID)

Expand Down
Loading
Loading