2022-10-31 20:41:19 +08:00
//
// M a s t o d o n S t a t u s P u b l i s h e r . s w i f t
//
//
// C r e a t e d b y M a i n a s u K o n 2 0 2 1 - 1 2 - 1 .
//
import os . log
import Foundation
import CoreData
import CoreDataStack
import MastodonCore
import MastodonSDK
public final class MastodonStatusPublisher : NSObject , ProgressReporting {
let logger = Logger ( subsystem : " MastodonStatusPublisher " , category : " Publisher " )
// I n p u t
// a u t h o r
public let author : ManagedObjectRecord < MastodonUser >
// r e f e r
public let replyTo : ManagedObjectRecord < Status > ?
// c o n t e n t w a r n i n g
public let isContentWarningComposing : Bool
public let contentWarning : String
// s t a t u s c o n t e n t
public let content : String
// m e d i a
public let isMediaSensitive : Bool
public let attachmentViewModels : [ AttachmentViewModel ]
// p o l l
public let isPollComposing : Bool
public let pollOptions : [ PollComposeItem . Option ]
public let pollExpireConfigurationOption : PollComposeItem . ExpireConfiguration . Option
public let pollMultipleConfigurationOption : PollComposeItem . MultipleConfiguration . Option
// v i s i b i l i t y
public let visibility : Mastodon . Entity . Status . Visibility
2023-01-23 19:50:10 -05:00
// l a n g u a g e
public let language : String
2022-10-31 20:41:19 +08:00
// O u t p u t
let _progress = Progress ( )
public var progress : Progress { _progress }
@ Published var _state : StatusPublisherState = . pending
public var state : Published < StatusPublisherState > . Publisher { $ _state }
public var reactor : StatusPublisherReactor ?
public init (
author : ManagedObjectRecord < MastodonUser > ,
replyTo : ManagedObjectRecord < Status > ? ,
isContentWarningComposing : Bool ,
contentWarning : String ,
content : String ,
isMediaSensitive : Bool ,
attachmentViewModels : [ AttachmentViewModel ] ,
isPollComposing : Bool ,
pollOptions : [ PollComposeItem . Option ] ,
pollExpireConfigurationOption : PollComposeItem . ExpireConfiguration . Option ,
pollMultipleConfigurationOption : PollComposeItem . MultipleConfiguration . Option ,
2023-01-23 19:50:10 -05:00
visibility : Mastodon . Entity . Status . Visibility ,
language : String
2022-10-31 20:41:19 +08:00
) {
self . author = author
self . replyTo = replyTo
self . isContentWarningComposing = isContentWarningComposing
self . contentWarning = contentWarning
self . content = content
self . isMediaSensitive = isMediaSensitive
self . attachmentViewModels = attachmentViewModels
self . isPollComposing = isPollComposing
self . pollOptions = pollOptions
self . pollExpireConfigurationOption = pollExpireConfigurationOption
self . pollMultipleConfigurationOption = pollMultipleConfigurationOption
self . visibility = visibility
2023-01-23 19:50:10 -05:00
self . language = language
2022-10-31 20:41:19 +08:00
}
}
// MARK: - S t a t u s P u b l i s h e r
extension MastodonStatusPublisher : StatusPublisher {
public func publish (
api : APIService ,
authContext : AuthContext
) async throws -> StatusPublishResult {
let idempotencyKey = UUID ( ) . uuidString
let publishStatusTaskStartDelayWeight : Int64 = 20
let publishStatusTaskStartDelayCount : Int64 = publishStatusTaskStartDelayWeight
let publishAttachmentTaskWeight : Int64 = 100
let publishAttachmentTaskCount : Int64 = Int64 ( attachmentViewModels . count ) * publishAttachmentTaskWeight
let publishStatusTaskWeight : Int64 = 20
let publishStatusTaskCount : Int64 = publishStatusTaskWeight
let taskCount = [
publishStatusTaskStartDelayCount ,
publishAttachmentTaskCount ,
publishStatusTaskCount
] . reduce ( 0 , + )
progress . totalUnitCount = taskCount
progress . completedUnitCount = 0
// s t a r t d e l a y
try ? await Task . sleep ( nanoseconds : 1 * . second )
progress . completedUnitCount += publishStatusTaskStartDelayWeight
// T a s k : a t t a c h m e n t
var attachmentIDs : [ Mastodon . Entity . Attachment . ID ] = [ ]
for attachmentViewModel in attachmentViewModels {
// s e t p r o g r e s s
progress . addChild ( attachmentViewModel . progress , withPendingUnitCount : publishAttachmentTaskWeight )
// u p l o a d m e d i a
do {
2022-11-08 19:40:58 +08:00
guard let attachment = attachmentViewModel . uploadResult else {
// p r e c o n d i t i o n : a l l m e d i a u p l o a d e d
throw AppError . badRequest
2022-10-31 20:41:19 +08:00
}
2022-11-08 19:40:58 +08:00
attachmentIDs . append ( attachment . id )
2022-11-14 00:57:44 +08:00
let caption = attachmentViewModel . caption
guard ! caption . isEmpty else { continue }
_ = try await api . updateMedia (
domain : authContext . mastodonAuthenticationBox . domain ,
attachmentID : attachment . id ,
query : . init (
file : nil ,
thumbnail : nil ,
description : caption ,
focus : nil
) ,
mastodonAuthenticationBox : authContext . mastodonAuthenticationBox
) . singleOutput ( )
2022-11-08 19:40:58 +08:00
// TODO: a l l o w b a c k g r o u n d u p l o a d
// l e t a t t a c h m e n t = t r y a w a i t a t t a c h m e n t V i e w M o d e l . u p l o a d ( c o n t e x t : u p l o a d C o n t e x t )
// l e t a t t a c h m e n t I D = a t t a c h m e n t . i d
// a t t a c h m e n t I D s . a p p e n d ( a t t a c h m e n t I D )
2022-10-31 20:41:19 +08:00
} catch {
logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : upload attachment fail: \( error . localizedDescription ) " )
_state = . failure ( error )
throw error
}
}
let pollOptions : [ String ] ? = {
guard self . isPollComposing else { return nil }
let options = self . pollOptions . compactMap { $0 . text . trimmingCharacters ( in : . whitespacesAndNewlines ) }
return options . isEmpty ? nil : options
} ( )
let pollExpiresIn : Int ? = {
guard self . isPollComposing else { return nil }
guard pollOptions != nil else { return nil }
return self . pollExpireConfigurationOption . seconds
} ( )
let inReplyToID : Mastodon . Entity . Status . ID ? = try await api . backgroundManagedObjectContext . perform {
guard let replyTo = self . replyTo ? . object ( in : api . backgroundManagedObjectContext ) else { return nil }
return replyTo . id
}
let query = Mastodon . API . Statuses . PublishStatusQuery (
status : content ,
mediaIDs : attachmentIDs . isEmpty ? nil : attachmentIDs ,
pollOptions : pollOptions ,
pollExpiresIn : pollExpiresIn ,
inReplyToID : inReplyToID ,
sensitive : isMediaSensitive ,
spoilerText : isContentWarningComposing ? contentWarning : nil ,
2023-01-23 19:50:10 -05:00
visibility : visibility ,
language : language
2022-10-31 20:41:19 +08:00
)
let publishResponse = try await api . publishStatus (
domain : authContext . mastodonAuthenticationBox . domain ,
idempotencyKey : idempotencyKey ,
query : query ,
authenticationBox : authContext . mastodonAuthenticationBox
)
progress . completedUnitCount += publishStatusTaskCount
_state = . success
logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : status published: \( publishResponse . value . id ) " )
return . mastodon ( publishResponse )
}
}