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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Sources/Basics/Concurrency/ConcurrencyHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ extension DispatchQueue {
}

/// Bridges between potentially blocking methods that take a result completion closure and async/await
public func safe_async<T, ErrorType: Error>(_ body: @Sendable @escaping (@Sendable @escaping (Result<T, ErrorType>) -> Void) -> Void) async throws -> T {
public func safe_async<T, ErrorType: Error>(
_ body: @Sendable @escaping (@Sendable @escaping (Result<T, ErrorType>) -> Void) -> Void
) async throws -> T {
try await withCheckedThrowingContinuation { continuation in
// It is possible that body make block indefinitely on a lock, sempahore,
// or similar then synchrously call the completion handler. For full safety
// It is possible that body make block indefinitely on a lock, semaphore,
// or similar then synchronously call the completion handler. For full safety
// it is essential to move the execution off the swift concurrency pool
DispatchQueue.sharedConcurrent.async {
body {
Expand Down
218 changes: 218 additions & 0 deletions Sources/PackageCollections/API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import struct Foundation.URL
import PackageModel
import SourceControl
import Basics

// MARK: - Package collection

Expand All @@ -27,6 +28,7 @@ public protocol PackageCollectionsProtocol {
/// - Parameters:
/// - identifiers: Optional. If specified, only `PackageCollection`s with matching identifiers will be returned.
/// - callback: The closure to invoke when result becomes available
@available(*, noasync, message: "Use the async alternative")
func listCollections(
identifiers: Set<PackageCollectionsModel.CollectionIdentifier>?,
callback: @escaping (Result<[PackageCollectionsModel.Collection], Error>) -> Void
Expand All @@ -36,13 +38,15 @@ public protocol PackageCollectionsProtocol {
///
/// - Parameters:
/// - callback: The closure to invoke after triggering a refresh for the configured package collections.
@available(*, noasync, message: "Use the async alternative")
func refreshCollections(callback: @escaping (Result<[PackageCollectionsModel.CollectionSource], Error>) -> Void)

/// Refreshes a package collection.
///
/// - Parameters:
/// - source: The package collection to be refreshed
/// - callback: The closure to invoke with the refreshed `PackageCollection`
@available(*, noasync, message: "Use the async alternative")
func refreshCollection(
_ source: PackageCollectionsModel.CollectionSource,
callback: @escaping (Result<PackageCollectionsModel.Collection, Error>) -> Void
Expand All @@ -56,6 +60,7 @@ public protocol PackageCollectionsProtocol {
/// By default the new collection is appended to the end (i.e., the least relevant order).
/// - trustConfirmationProvider: The closure to invoke when the collection is not signed and user confirmation is required to proceed
/// - callback: The closure to invoke with the newly added `PackageCollection`
@available(*, noasync, message: "Use the async alternative")
func addCollection(
_ source: PackageCollectionsModel.CollectionSource,
order: Int?,
Expand All @@ -68,6 +73,7 @@ public protocol PackageCollectionsProtocol {
/// - Parameters:
/// - source: The package collection's source
/// - callback: The closure to invoke with the result becomes available
@available(*, noasync, message: "Use the async alternative")
func removeCollection(
_ source: PackageCollectionsModel.CollectionSource,
callback: @escaping (Result<Void, Error>) -> Void
Expand All @@ -79,6 +85,7 @@ public protocol PackageCollectionsProtocol {
/// - source: The source of the `PackageCollection` to be reordered
/// - order: The new order that the `PackageCollection` should be positioned after the move
/// - callback: The closure to invoke with the result becomes available
@available(*, noasync, message: "Use the async alternative")
func moveCollection(
_ source: PackageCollectionsModel.CollectionSource,
to order: Int,
Expand All @@ -90,6 +97,7 @@ public protocol PackageCollectionsProtocol {
/// - Parameters:
/// - source: The `PackageCollection` source to be updated
/// - callback: The closure to invoke when result becomes available
@available(*, noasync, message: "Use the async alternative")
func updateCollection(
_ source: PackageCollectionsModel.CollectionSource,
callback: @escaping (Result<PackageCollectionsModel.Collection, Error>) -> Void
Expand All @@ -101,6 +109,7 @@ public protocol PackageCollectionsProtocol {
/// - Parameters:
/// - source: The package collection's source
/// - callback: The closure to invoke with the `PackageCollection`
@available(*, noasync, message: "Use the async alternative")
func getCollection(
_ source: PackageCollectionsModel.CollectionSource,
callback: @escaping (Result<PackageCollectionsModel.Collection, Error>) -> Void
Expand All @@ -115,6 +124,7 @@ public protocol PackageCollectionsProtocol {
/// - identity: The package identity
/// - location: The package location (optional for deduplication)
/// - callback: The closure to invoke when result becomes available
@available(*, noasync, message: "Use the async alternative")
func getPackageMetadata(
identity: PackageIdentity,
location: String?,
Expand All @@ -132,6 +142,7 @@ public protocol PackageCollectionsProtocol {
/// - collections: Optional. If specified, only look for package in these collections. Data from the most recently
/// processed collection will be used.
/// - callback: The closure to invoke when result becomes available
@available(*, noasync, message: "Use the async alternative")
func getPackageMetadata(
identity: PackageIdentity,
location: String?,
Expand All @@ -144,6 +155,7 @@ public protocol PackageCollectionsProtocol {
/// - Parameters:
/// - collections: Optional. If specified, only packages in these collections are included.
/// - callback: The closure to invoke when result becomes available
@available(*, noasync, message: "Use the async alternative")
func listPackages(
collections: Set<PackageCollectionsModel.CollectionIdentifier>?,
callback: @escaping (Result<PackageCollectionsModel.PackageSearchResult, Error>) -> Void
Expand All @@ -160,6 +172,7 @@ public protocol PackageCollectionsProtocol {
/// - Parameters:
/// - collections: Optional. If specified, only list targets within these collections.
/// - callback: The closure to invoke when result becomes available
@available(*, noasync, message: "Use the async alternative")
func listTargets(
collections: Set<PackageCollectionsModel.CollectionIdentifier>?,
callback: @escaping (Result<PackageCollectionsModel.TargetListResult, Error>) -> Void
Expand All @@ -176,6 +189,7 @@ public protocol PackageCollectionsProtocol {
/// - query: The search query
/// - collections: Optional. If specified, only search within these collections.
/// - callback: The closure to invoke when result becomes available
@available(*, noasync, message: "Use the async alternative")
func findPackages(
_ query: String,
collections: Set<PackageCollectionsModel.CollectionIdentifier>?,
Expand All @@ -193,6 +207,7 @@ public protocol PackageCollectionsProtocol {
/// For more flexibility, use the `findPackages` API instead.
/// - collections: Optional. If specified, only search within these collections.
/// - callback: The closure to invoke when result becomes available
@available(*, noasync, message: "Use the async alternative")
func findTargets(
_ query: String,
searchType: PackageCollectionsModel.TargetSearchType?,
Expand All @@ -201,6 +216,159 @@ public protocol PackageCollectionsProtocol {
)
}

public extension PackageCollectionsProtocol {
func listCollections(
identifiers: Set<PackageCollectionsModel.CollectionIdentifier>? = nil
) async throws -> [PackageCollectionsModel.Collection] {
try await safe_async {
self.listCollections(identifiers: identifiers, callback: $0)
}
}

func refreshCollections() async throws -> [PackageCollectionsModel.CollectionSource] {
try await safe_async {
self.refreshCollections(callback: $0)
}
}

func refreshCollection(
_ source: PackageCollectionsModel.CollectionSource
) async throws -> PackageCollectionsModel.Collection {
try await safe_async {
self.refreshCollection(
source,
callback: $0
)
}
}

func addCollection(
_ source: PackageCollectionsModel.CollectionSource,
order: Int? = nil,
trustConfirmationProvider: ((PackageCollectionsModel.Collection, @escaping (Bool) -> Void) -> Void)? = nil
) async throws -> PackageCollectionsModel.Collection {
try await safe_async {
self.addCollection(
source,
order: order,
trustConfirmationProvider:trustConfirmationProvider,
callback: $0
)
}
}

func removeCollection(
_ source: PackageCollectionsModel.CollectionSource
) async throws {
try await safe_async {
self.removeCollection(
source,
callback: $0
)
}
}

func moveCollection(
_ source: PackageCollectionsModel.CollectionSource,
to order: Int
) async throws {
try await safe_async {
self.moveCollection(
source,
to: order,
callback: $0
)
}
}

func updateCollection(
_ source: PackageCollectionsModel.CollectionSource
) async throws -> PackageCollectionsModel.Collection {
try await safe_async {
self.updateCollection(
source,
callback: $0
)
}
}

func getCollection(
_ source: PackageCollectionsModel.CollectionSource
) async throws -> PackageCollectionsModel.Collection {
try await safe_async {
self.getCollection(
source,
callback: $0
)
}
}

func getPackageMetadata(
identity: PackageIdentity,
location: String? = nil,
collections: Set<PackageCollectionsModel.CollectionIdentifier>? = nil
) async throws -> PackageCollectionsModel.PackageMetadata {
try await safe_async {
self.getPackageMetadata(
identity: identity,
location: location,
collections: collections,
callback: $0
)
}
}

func listPackages(
collections: Set<PackageCollectionsModel.CollectionIdentifier>? = nil
) async throws -> PackageCollectionsModel.PackageSearchResult {
try await safe_async {
self.listPackages(
collections: collections,
callback: $0
)
}
}

func listTargets(
collections: Set<PackageCollectionsModel.CollectionIdentifier>? = nil
) async throws -> PackageCollectionsModel.TargetListResult {
try await safe_async {
self.listTargets(
collections: collections,
callback: $0
)
}
}

func findPackages(
_ query: String,
collections: Set<PackageCollectionsModel.CollectionIdentifier>? = nil
) async throws -> PackageCollectionsModel.PackageSearchResult {
try await safe_async {
self.findPackages(
query,
collections: collections,
callback: $0
)
}
}

func findTargets(
_ query: String,
searchType: PackageCollectionsModel.TargetSearchType? = nil,
collections: Set<PackageCollectionsModel.CollectionIdentifier>? = nil
) async throws -> PackageCollectionsModel.TargetSearchResult {
try await safe_async {
self.findTargets(
query,
searchType: searchType,
collections: collections,
callback: $0
)
}
}
}

public enum PackageCollectionError: Equatable, Error {
/// Package collection is not signed and there is no record of user's trust selection
case trustConfirmationRequired
Expand Down Expand Up @@ -233,6 +401,7 @@ public protocol PackageIndexProtocol {
/// - identity: The package identity
/// - location: The package location (optional for deduplication)
/// - callback: The closure to invoke when result becomes available
@available(*, noasync, message: "Use the async alternative")
func getPackageMetadata(
identity: PackageIdentity,
location: String?,
Expand All @@ -244,6 +413,7 @@ public protocol PackageIndexProtocol {
/// - Parameters:
/// - query: The search query
/// - callback: The closure to invoke when result becomes available
@available(*, noasync, message: "Use the async alternative")
func findPackages(
_ query: String,
callback: @escaping (Result<PackageCollectionsModel.PackageSearchResult, Error>) -> Void
Expand All @@ -255,13 +425,61 @@ public protocol PackageIndexProtocol {
/// - offset: Offset of the first item in the result
/// - limit: Number of items to return in the result. Implementations might impose a threshold for this.
/// - callback: The closure to invoke when result becomes available
@available(*, noasync, message: "Use the async alternative")
func listPackages(
offset: Int,
limit: Int,
callback: @escaping (Result<PackageCollectionsModel.PaginatedPackageList, Error>) -> Void
)
}

public extension PackageIndexProtocol {
func getPackageMetadata(
identity: PackageIdentity,
location: String?
) async throws -> PackageCollectionsModel.PackageMetadata {
try await safe_async {
self.getPackageMetadata(
identity: identity,
location: location,
callback: $0
)
}
}

/// Finds and returns packages that match the query.
///
/// - Parameters:
/// - query: The search query
/// - callback: The closure to invoke when result becomes available
func findPackages(
_ query: String
) async throws -> PackageCollectionsModel.PackageSearchResult {
try await safe_async {
self.findPackages(query, callback: $0)
}
}

/// A paginated list of packages in the index.
///
/// - Parameters:
/// - offset: Offset of the first item in the result
/// - limit: Number of items to return in the result. Implementations might impose a threshold for this.
/// - callback: The closure to invoke when result becomes available
func listPackages(
offset: Int,
limit: Int
) async throws -> PackageCollectionsModel.PaginatedPackageList {
try await safe_async {
self.listPackages(
offset: offset,
limit: limit,
callback: $0
)
}
}
}

public enum PackageIndexError: Equatable, Error {
/// Package index support is disabled
case featureDisabled
Expand Down
8 changes: 5 additions & 3 deletions Sources/PackageCollections/PackageCollections.swift
Original file line number Diff line number Diff line change
Expand Up @@ -489,9 +489,11 @@ public struct PackageCollections: PackageCollectionsProtocol, Closable {

// Fetch the collection from the network and store it in local storage
// This helps avoid network access in normal operations
private func refreshCollectionFromSource(source: PackageCollectionsModel.CollectionSource,
trustConfirmationProvider: ((PackageCollectionsModel.Collection, @escaping (Bool) -> Void) -> Void)?,
callback: @escaping (Result<Model.Collection, Error>) -> Void) {
private func refreshCollectionFromSource(
source: PackageCollectionsModel.CollectionSource,
trustConfirmationProvider: ((PackageCollectionsModel.Collection, @escaping (Bool) -> Void) -> Void)?,
callback: @escaping (Result<Model.Collection, Error>) -> Void
) {
guard let provider = self.collectionProviders[source.type] else {
return callback(.failure(UnknownProvider(source.type)))
}
Expand Down
Loading