Add MP3 upload pipeline foundation
This commit is contained in:
@@ -38,6 +38,25 @@ public protocol VelodyAPIClient: Sendable {
|
||||
) async throws -> DeviceHeartbeatResponse
|
||||
|
||||
func fetchSyncBootstrap() async throws -> SyncBootstrapResponse
|
||||
|
||||
func prepareUpload(
|
||||
_ payload: UploadPrepareRequest
|
||||
) async throws -> UploadPrepareResponse
|
||||
|
||||
func fetchUploadStatus(
|
||||
uploadId: String
|
||||
) async throws -> UploadSessionStatusResponse
|
||||
|
||||
func uploadFile(
|
||||
uploadId: String,
|
||||
fileURL: URL,
|
||||
mimeType: String
|
||||
) async throws -> UploadSessionStatusResponse
|
||||
|
||||
func finalizeUpload(
|
||||
uploadId: String,
|
||||
payload: UploadFinalizeRequest
|
||||
) async throws -> UploadFinalizeResponse
|
||||
}
|
||||
|
||||
public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
@@ -87,6 +106,71 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
)
|
||||
}
|
||||
|
||||
public func prepareUpload(
|
||||
_ payload: UploadPrepareRequest
|
||||
) async throws -> UploadPrepareResponse {
|
||||
try await sendRequest(
|
||||
method: "POST",
|
||||
pathComponents: ["api", "v1", "uploads", "prepare"],
|
||||
body: payload,
|
||||
responseType: UploadPrepareResponse.self
|
||||
)
|
||||
}
|
||||
|
||||
public func fetchUploadStatus(
|
||||
uploadId: String
|
||||
) async throws -> UploadSessionStatusResponse {
|
||||
try await sendRequest(
|
||||
method: "GET",
|
||||
pathComponents: ["api", "v1", "uploads", uploadId],
|
||||
responseType: UploadSessionStatusResponse.self
|
||||
)
|
||||
}
|
||||
|
||||
public func uploadFile(
|
||||
uploadId: String,
|
||||
fileURL: URL,
|
||||
mimeType: String = "audio/mpeg"
|
||||
) async throws -> UploadSessionStatusResponse {
|
||||
guard FileManager.default.fileExists(atPath: fileURL.path) else {
|
||||
throw VelodyAPIError.requestFailed("The selected file could not be found.")
|
||||
}
|
||||
|
||||
let request = try buildRequest(
|
||||
method: "PUT",
|
||||
pathComponents: ["api", "v1", "uploads", uploadId, "file"],
|
||||
bodyData: nil,
|
||||
contentType: mimeType
|
||||
)
|
||||
|
||||
let data: Data
|
||||
let response: URLResponse
|
||||
|
||||
do {
|
||||
(data, response) = try await session.upload(for: request, fromFile: fileURL)
|
||||
} catch {
|
||||
throw VelodyAPIError.requestFailed(error.localizedDescription)
|
||||
}
|
||||
|
||||
return try decodeResponse(
|
||||
data: data,
|
||||
response: response,
|
||||
responseType: UploadSessionStatusResponse.self
|
||||
)
|
||||
}
|
||||
|
||||
public func finalizeUpload(
|
||||
uploadId: String,
|
||||
payload: UploadFinalizeRequest
|
||||
) async throws -> UploadFinalizeResponse {
|
||||
try await sendRequest(
|
||||
method: "POST",
|
||||
pathComponents: ["api", "v1", "uploads", uploadId, "finalize"],
|
||||
body: payload,
|
||||
responseType: UploadFinalizeResponse.self
|
||||
)
|
||||
}
|
||||
|
||||
private func sendRequest<Response: Decodable>(
|
||||
method: String,
|
||||
pathComponents: [String],
|
||||
@@ -118,7 +202,8 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
let request = try buildRequest(
|
||||
method: method,
|
||||
pathComponents: pathComponents,
|
||||
bodyData: bodyData
|
||||
bodyData: bodyData,
|
||||
contentType: "application/json"
|
||||
)
|
||||
|
||||
return try await execute(request, responseType: responseType)
|
||||
@@ -127,7 +212,8 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
private func buildRequest(
|
||||
method: String,
|
||||
pathComponents: [String],
|
||||
bodyData: Data?
|
||||
bodyData: Data?,
|
||||
contentType: String? = nil
|
||||
) throws -> URLRequest {
|
||||
guard let url = endpointURL(pathComponents: pathComponents) else {
|
||||
throw VelodyAPIError.invalidServerURL(environment.baseURL.absoluteString)
|
||||
@@ -139,7 +225,10 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
|
||||
if let bodyData {
|
||||
request.httpBody = bodyData
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
}
|
||||
|
||||
if let contentType {
|
||||
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
||||
}
|
||||
|
||||
return request
|
||||
@@ -158,6 +247,28 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
throw VelodyAPIError.requestFailed(error.localizedDescription)
|
||||
}
|
||||
|
||||
return try decodeResponse(
|
||||
data: data,
|
||||
response: response,
|
||||
responseType: responseType
|
||||
)
|
||||
}
|
||||
|
||||
private func decodeResponse<Response: Decodable>(
|
||||
data: Data,
|
||||
response: URLResponse,
|
||||
responseType: Response.Type
|
||||
) throws -> Response {
|
||||
try validate(response: response, data: data)
|
||||
|
||||
do {
|
||||
return try decoder.decode(responseType, from: data)
|
||||
} catch {
|
||||
throw VelodyAPIError.decodingFailed(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func validate(response: URLResponse, data: Data) throws {
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
throw VelodyAPIError.invalidResponse
|
||||
}
|
||||
@@ -171,12 +282,6 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
message: message?.isEmpty == true ? nil : message
|
||||
)
|
||||
}
|
||||
|
||||
do {
|
||||
return try decoder.decode(responseType, from: data)
|
||||
} catch {
|
||||
throw VelodyAPIError.decodingFailed(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func endpointURL(pathComponents: [String]) -> URL? {
|
||||
@@ -234,4 +339,58 @@ public struct StubVelodyAPIClient: VelodyAPIClient {
|
||||
serverTime: ISO8601DateFormatter().string(from: .now)
|
||||
)
|
||||
}
|
||||
|
||||
public func prepareUpload(
|
||||
_ payload: UploadPrepareRequest
|
||||
) async throws -> UploadPrepareResponse {
|
||||
_ = payload
|
||||
|
||||
return UploadPrepareResponse(
|
||||
status: .uploadRequired,
|
||||
uploadId: UUID().uuidString,
|
||||
nextOffset: 0
|
||||
)
|
||||
}
|
||||
|
||||
public func fetchUploadStatus(
|
||||
uploadId: String
|
||||
) async throws -> UploadSessionStatusResponse {
|
||||
UploadSessionStatusResponse(
|
||||
uploadId: uploadId,
|
||||
status: .completed,
|
||||
receivedBytes: "0",
|
||||
expectedSizeBytes: "0",
|
||||
nextOffset: "0"
|
||||
)
|
||||
}
|
||||
|
||||
public func uploadFile(
|
||||
uploadId: String,
|
||||
fileURL: URL,
|
||||
mimeType: String
|
||||
) async throws -> UploadSessionStatusResponse {
|
||||
_ = fileURL
|
||||
_ = mimeType
|
||||
|
||||
return UploadSessionStatusResponse(
|
||||
uploadId: uploadId,
|
||||
status: .completed,
|
||||
receivedBytes: "0",
|
||||
expectedSizeBytes: "0",
|
||||
nextOffset: "0"
|
||||
)
|
||||
}
|
||||
|
||||
public func finalizeUpload(
|
||||
uploadId: String,
|
||||
payload: UploadFinalizeRequest
|
||||
) async throws -> UploadFinalizeResponse {
|
||||
_ = uploadId
|
||||
_ = payload
|
||||
|
||||
return UploadFinalizeResponse(
|
||||
trackId: UUID().uuidString,
|
||||
assetId: UUID().uuidString
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user