diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
index 6faa9959a..c08f88276 100644
--- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,12 +7,12 @@
AppShared.xcscheme_^#shared#^_
orderHint
- 30
+ 32
CoreDataStack.xcscheme_^#shared#^_
orderHint
- 34
+ 31
Mastodon - ASDK.xcscheme_^#shared#^_
@@ -77,7 +77,7 @@
MastodonIntent.xcscheme_^#shared#^_
orderHint
- 32
+ 33
MastodonIntents.xcscheme_^#shared#^_
@@ -97,7 +97,7 @@
ShareActionExtension.xcscheme_^#shared#^_
orderHint
- 31
+ 30
SuppressBuildableAutocreation
diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift
index 601aeae04..7a6ed8bdd 100644
--- a/Mastodon/Scene/Compose/ComposeViewModel.swift
+++ b/Mastodon/Scene/Compose/ComposeViewModel.swift
@@ -338,6 +338,9 @@ final class ComposeViewModel: NSObject {
if currentState is MastodonAttachmentService.UploadState.Finish {
continue
}
+ if currentState is MastodonAttachmentService.UploadState.Processing {
+ continue
+ }
if currentState is MastodonAttachmentService.UploadState.Uploading {
break
}
diff --git a/Mastodon/Service/APIService/APIService+Media.swift b/Mastodon/Service/APIService/APIService+Media.swift
index d6b1d6c21..0c7822c9d 100644
--- a/Mastodon/Service/APIService/APIService+Media.swift
+++ b/Mastodon/Service/APIService/APIService+Media.swift
@@ -57,6 +57,25 @@ extension APIService {
}
+extension APIService {
+
+ func getMedia(
+ attachmentID: Mastodon.Entity.Attachment.ID,
+ mastodonAuthenticationBox: MastodonAuthenticationBox
+ ) -> AnyPublisher, Error> {
+ let authorization = mastodonAuthenticationBox.userAuthorization
+
+ return Mastodon.API.Media.getMedia(
+ session: session,
+ domain: mastodonAuthenticationBox.domain,
+ attachmentID: attachmentID,
+ authorization: authorization
+ )
+ .eraseToAnyPublisher()
+ }
+
+}
+
extension APIService {
func updateMedia(
diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift
index 8474ac4dd..8ff076dc1 100644
--- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift
+++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift
@@ -47,7 +47,10 @@ extension MastodonAttachmentService.UploadState {
var needsFallback = false
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
- return stateClass == Fail.self || stateClass == Finish.self || stateClass == Uploading.self
+ return stateClass == Fail.self
+ || stateClass == Finish.self
+ || stateClass == Uploading.self
+ || stateClass == Processing.self
}
override func didEnter(from previousState: GKState?) {
@@ -96,11 +99,70 @@ extension MastodonAttachmentService.UploadState {
} receiveValue: { response in
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: upload attachment %s success: %s", ((#file as NSString).lastPathComponent), #line, #function, response.value.id, response.value.url ?? "")
service.attachment.value = response.value
+ if response.statusCode == 202 {
+ // check if still processing
+ stateMachine.enter(Processing.self)
+ } else {
+ stateMachine.enter(Finish.self)
+ }
+ }
+ .store(in: &service.disposeBag)
+ }
+ }
+
+ class Processing: MastodonAttachmentService.UploadState {
+
+ static let retryLimit = 10
+ var retryCount = 0
+
+ override func isValidNextState(_ stateClass: AnyClass) -> Bool {
+ return stateClass == Fail.self || stateClass == Finish.self || stateClass == Processing.self
+ }
+
+ override func didEnter(from previousState: GKState?) {
+ super.didEnter(from: previousState)
+
+ guard let service = service, let stateMachine = stateMachine else { return }
+ guard let authenticationBox = service.authenticationBox else { return }
+ guard let attachment = service.attachment.value else { return }
+
+ retryCount += 1
+ guard retryCount < Processing.retryLimit else {
+ stateMachine.enter(Fail.self)
+ return
+ }
+
+ service.context.apiService.getMedia(
+ attachmentID: attachment.id,
+ mastodonAuthenticationBox: authenticationBox
+ )
+ .retry(3)
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] completion in
+ guard let _ = self else { return }
+ switch completion {
+ case .failure(let error):
+ os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: get attachment fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
+ service.error.send(error)
+ stateMachine.enter(Fail.self)
+ case .finished:
+ os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: get attachment success", ((#file as NSString).lastPathComponent), #line, #function)
+ break
+ }
+ } receiveValue: { [weak self] response in
+ guard let self = self else { return }
+ guard let _ = response.value.url else {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
+ os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: processing, retry in 2s", ((#file as NSString).lastPathComponent), #line, #function)
+ self?.stateMachine?.enter(Processing.self)
+ }
+ return
+ }
+
stateMachine.enter(Finish.self)
}
.store(in: &service.disposeBag)
}
-
}
class Fail: MastodonAttachmentService.UploadState {
diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift
index 2b08b0db0..e42c9bf2e 100644
--- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift
+++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift
@@ -41,6 +41,7 @@ final class MastodonAttachmentService {
let stateMachine = GKStateMachine(states: [
UploadState.Initial(service: self),
UploadState.Uploading(service: self),
+ UploadState.Processing(service: self),
UploadState.Fail(service: self),
UploadState.Finish(service: self),
])
diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Media.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Media.swift
index d05cac01a..da77c65a1 100644
--- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Media.swift
+++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Media.swift
@@ -113,6 +113,49 @@ extension Mastodon.API.Media {
}
+extension Mastodon.API.Media {
+ static func getMediaEndpointURL(domain: String, attachmentID: Mastodon.Entity.Attachment.ID) -> URL {
+ Mastodon.API.endpointURL(domain: domain).appendingPathComponent("media").appendingPathComponent(attachmentID)
+ }
+
+ /// Get media attachment
+ ///
+ /// Get an Attachment, before it is attached to a status and posted, but after it is accepted for processing.
+ ///
+ /// - Since: 0.0.0
+ /// - Version: 3.4.1
+ /// # Last Update
+ /// 2021/8/9
+ /// # Reference
+ /// [Document](https://docs.joinmastodon.org/methods/statuses/media/)
+ /// - Parameters:
+ /// - session: `URLSession`
+ /// - domain: Mastodon instance domain. e.g. "example.com"
+ /// - mediaID: The ID of attachment
+ /// - authorization: User token
+ /// - Returns: `AnyPublisher` contains `Attachment` nested in the response
+ public static func getMedia(
+ session: URLSession,
+ domain: String,
+ attachmentID: Mastodon.Entity.Attachment.ID,
+ authorization: Mastodon.API.OAuth.Authorization?
+ ) -> AnyPublisher, Error> {
+ var request = Mastodon.API.get(
+ url: getMediaEndpointURL(domain: domain, attachmentID: attachmentID),
+ query: nil,
+ authorization: authorization
+ )
+ request.timeoutInterval = 10 // short timeout for quick retry
+ return session.dataTaskPublisher(for: request)
+ .tryMap { data, response in
+ let value = try Mastodon.API.decode(type: Mastodon.Entity.Attachment.self, from: data, response: response)
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+ .eraseToAnyPublisher()
+ }
+}
+
+
extension Mastodon.API.Media {
static func updateMediaEndpointURL(domain: String, attachmentID: Mastodon.Entity.Attachment.ID) -> URL {
diff --git a/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift b/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift
index 9c39615f9..db42169d8 100644
--- a/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift
+++ b/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift
@@ -14,6 +14,7 @@ extension Mastodon.Response {
public let value: T
// standard fields
+ public let statusCode: Int? ///< HTTP Code
public let date: Date?
// application fields
@@ -28,6 +29,8 @@ extension Mastodon.Response {
public init(value: T, response: URLResponse) {
self.value = value
+ self.statusCode = (response as? HTTPURLResponse)?.statusCode
+
self.date = {
guard let string = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "date") else { return nil }
return Mastodon.API.httpHeaderDateFormatter.date(from: string)
@@ -47,6 +50,7 @@ extension Mastodon.Response {
init(value: T, old: Mastodon.Response.Content) {
self.value = value
+ self.statusCode = old.statusCode
self.date = old.date
self.rateLimit = old.rateLimit
self.link = old.link