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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import Foundation

/// Replaces all of the app's groups with the given ones
public final class DeveloperServicesAssignAppGroupRequest: DeveloperServicesPlatformRequest {

public struct Response: Decodable, Sendable {}
Expand All @@ -16,27 +17,27 @@ public final class DeveloperServicesAssignAppGroupRequest: DeveloperServicesPlat
public let platform: DeveloperServicesPlatform
public let teamID: DeveloperServicesTeam.ID
public let appIDID: String
public let groupID: DeveloperServicesAppGroup.ID
public let groupIDs: [DeveloperServicesAppGroup.ID]

var subAction: String { return "assignApplicationGroupToAppId" }
var subParameters: [String: Any] {
return [
"teamId": teamID.rawValue,
"appIdId": appIDID,
"applicationGroups": groupID.rawValue
"applicationGroups": groupIDs.map(\.rawValue)
]
}

public init(
platform: DeveloperServicesPlatform,
teamID: DeveloperServicesTeam.ID,
appIDID: String,
groupID: DeveloperServicesAppGroup.ID
groupIDs: [DeveloperServicesAppGroup.ID]
) {
self.platform = platform
self.teamID = teamID
self.appIDID = appIDID
self.groupID = groupID
self.groupIDs = groupIDs
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import Foundation
import DeveloperAPI
import XUtils

public struct DeveloperServicesAssignAppGroupsOperation: DeveloperServicesOperation {

Expand All @@ -33,36 +34,23 @@ public struct DeveloperServicesAssignAppGroupsOperation: DeveloperServicesOperat
self.client = DeveloperServicesClient(authData: authData)
}

private func upsertAppGroup(
private func getOrCreateAppGroup(
_ groupID: DeveloperServicesAppGroup.GroupID,
existingGroups: [String: DeveloperServicesAppGroup]
) async throws -> DeveloperServicesAppGroup.GroupID {
) async throws -> DeveloperServicesAppGroup {
let sanitized = ProvisioningIdentifiers.sanitize(groupID: groupID)
let group: DeveloperServicesAppGroup
if let existingGroup = existingGroups[sanitized] {
group = existingGroup
} else {
let groupID = ProvisioningIdentifiers.groupID(fromSanitized: sanitized, context: context)
let name = ProvisioningIdentifiers.groupName(fromSanitized: sanitized)
let request = DeveloperServicesAddAppGroupRequest(
platform: .iOS,
teamID: xcodeAuthData.teamID,
name: name,
groupID: groupID
)
group = try await client.send(request)
return existingGroup
}

_ = try await client.send(
DeveloperServicesAssignAppGroupRequest(
platform: .iOS,
teamID: xcodeAuthData.teamID,
appIDID: appID.id,
groupID: group.id
)
let groupID = ProvisioningIdentifiers.groupID(fromSanitized: sanitized, context: context)
let name = ProvisioningIdentifiers.groupName(fromSanitized: sanitized)
let request = DeveloperServicesAddAppGroupRequest(
platform: .iOS,
teamID: xcodeAuthData.teamID,
name: name,
groupID: groupID
)

return group.groupID
return try await client.send(request)
}

public func perform() async throws -> [DeveloperServicesAppGroup.GroupID] {
Expand All @@ -71,14 +59,20 @@ public struct DeveloperServicesAssignAppGroupsOperation: DeveloperServicesOperat
))
let sanitized = existing.map { (ProvisioningIdentifiers.sanitize(groupID: $0.groupID), $0) }
let dict = Dictionary(sanitized, uniquingKeysWith: { $1 })
return try await withThrowingTaskGroup(of: DeveloperServicesAppGroup.GroupID.self) { group in
for groupID in groupIDs {
group.addTask {
try await upsertAppGroup(groupID, existingGroups: dict)
}
}
return try await group.reduce(into: []) { $0.append($1) }
let groups = try await groupIDs.concurrentMap { groupID in
try await getOrCreateAppGroup(groupID, existingGroups: dict)
}

_ = try await client.send(
DeveloperServicesAssignAppGroupRequest(
platform: .iOS,
teamID: xcodeAuthData.teamID,
appIDID: appID.id,
groupIDs: groups.map(\.id)
)
)

return groups.map(\.groupID)
}

}
66 changes: 66 additions & 0 deletions Sources/XUtils/Stdlib+Utils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
extension Sequence where Element: Sendable {
/// Like `map`, but runs transformations concurrently.
///
/// - Parameter maxConcurrency: The maximum number of transforms to run concurrently.
/// If `nil`, we choose a reasonable default value. Use `.max` to run all at once.
///
/// - Parameter transform: The transformation to apply to each element.
///
/// - Returns: Transformed elements in the same order as the original sequence.
public nonisolated(nonsending) func concurrentMap<T: Sendable, E: Error>(
maxConcurrency: Int? = nil,
_ transform: @escaping @Sendable @isolated(any) (Element) async throws(E) -> T,
) async throws(E) -> [T] {
try await _concurrentMap(
maxConcurrency: maxConcurrency ?? 10,
transform,
willWaitForNext: nil,
)
}

nonisolated(nonsending) func _concurrentMap<T: Sendable, E: Error>(
maxConcurrency: Int,
_ transform: @escaping @Sendable @isolated(any) (Element) async throws(E) -> T,
willWaitForNext: (() -> Void)?, // hook for testing
) async throws(E) -> [T] {
precondition(maxConcurrency > 0, "maxConcurrency must be > 0")

return try await withTaskGroup(of: (Int, Result<T, E>).self) { group -> Result<[T], E> in
var results: [T?] = []
results.reserveCapacity(self.underestimatedCount)
var iterator = makeIterator()
var exhausted = false
var inFlight = 0
while true {
while !exhausted && inFlight < maxConcurrency {
guard let element = iterator.next() else {
exhausted = true
break
}
let index = results.count
results.append(nil)
group.addTask {
let result = await Result { () async throws(E) in
try await transform(element)
}
return (index, result)
}
inFlight += 1
}
willWaitForNext?()
guard let (index, result) = await group.next() else { break }
inFlight -= 1
switch result {
case .success(let value):
results[index] = value
case .failure(let error):
group.cancelAll()
return .failure(error)
}
}
// we can't use `as! [T]` because it breaks nested optionals (Foo??)
return .success(results.map { $0! })
}
.get()
}
}
Loading
Loading