mv-experiment #1
|
@ -37,6 +37,13 @@ enum MessageContentType: Codable & Equatable, DatabaseValueConvertible {
|
||||||
case typing
|
case typing
|
||||||
case invite
|
case invite
|
||||||
case attachment(Attachment)
|
case attachment(Attachment)
|
||||||
|
|
||||||
|
var isAttachment: Bool {
|
||||||
|
if case .attachment = self {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MessageStatus: Int, Codable, DatabaseValueConvertible {
|
enum MessageStatus: Int, Codable, DatabaseValueConvertible {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import GRDB
|
||||||
import Photos
|
import Photos
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
@ -12,11 +13,13 @@ final class AttachmentsStore: ObservableObject {
|
||||||
private let client: Client
|
private let client: Client
|
||||||
private let roster: Roster
|
private let roster: Roster
|
||||||
|
|
||||||
|
private var messagesCancellable: AnyCancellable?
|
||||||
private var processing: Set<String> = []
|
private var processing: Set<String> = []
|
||||||
|
|
||||||
init(roster: Roster, client: Client) {
|
init(roster: Roster, client: Client) {
|
||||||
self.client = client
|
self.client = client
|
||||||
self.roster = roster
|
self.roster = roster
|
||||||
|
subscribe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,38 +194,54 @@ extension AttachmentsStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Uploadings/Downloadings
|
// MARK: - Processing attachments
|
||||||
extension AttachmentsStore {
|
private extension AttachmentsStore {
|
||||||
func processAttachment(_ message: Message) {
|
func subscribe() {
|
||||||
// Prevent multiple processing
|
messagesCancellable = ValueObservation.tracking(Message
|
||||||
if processing.contains(message.id) {
|
.filter(
|
||||||
return
|
(Column("to") == roster.bareJid && Column("from") == roster.contactBareJid) ||
|
||||||
}
|
(Column("from") == roster.bareJid && Column("to") == roster.contactBareJid)
|
||||||
|
)
|
||||||
// Process in background
|
.order(Column("date").desc)
|
||||||
Task(priority: .background) {
|
.fetchAll
|
||||||
// Do needed processing
|
)
|
||||||
|
.publisher(in: Database.shared.dbQueue, scheduling: .immediate)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { _ in
|
||||||
|
} receiveValue: { [weak self] messages in
|
||||||
|
let forProcessing = messages
|
||||||
|
// .filter { $0.status == .pending }
|
||||||
|
.filter { self?.processing.contains($0.id) == false }
|
||||||
|
.filter { $0.contentType.isAttachment }
|
||||||
|
for message in forProcessing {
|
||||||
if case .attachment(let attachment) = message.contentType {
|
if case .attachment(let attachment) = message.contentType {
|
||||||
if attachment.localPath != nil, attachment.remotePath == nil {
|
if attachment.localPath != nil, attachment.remotePath == nil {
|
||||||
// Uploading
|
// Uploading
|
||||||
processing.insert(message.id)
|
self?.processing.insert(message.id)
|
||||||
await uploadAttachment(message)
|
Task(priority: .background) {
|
||||||
processing.remove(message.id)
|
await self?.uploadAttachment(message)
|
||||||
|
}
|
||||||
} else if attachment.localPath == nil, attachment.remotePath != nil {
|
} else if attachment.localPath == nil, attachment.remotePath != nil {
|
||||||
// Downloading
|
// Downloading
|
||||||
processing.insert(message.id)
|
self?.processing.insert(message.id)
|
||||||
await downloadAttachment(message)
|
Task(priority: .background) {
|
||||||
processing.remove(message.id)
|
await self?.downloadAttachment(message)
|
||||||
|
}
|
||||||
} else if attachment.localPath != nil, attachment.remotePath != nil, attachment.thumbnailName == nil, attachment.type == .image {
|
} else if attachment.localPath != nil, attachment.remotePath != nil, attachment.thumbnailName == nil, attachment.type == .image {
|
||||||
// Generate thumbnail
|
// Generate thumbnail
|
||||||
processing.insert(message.id)
|
self?.processing.insert(message.id)
|
||||||
await generateThumbnail(message)
|
Task(priority: .background) {
|
||||||
processing.remove(message.id)
|
await self?.generateThumbnail(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Uploadings/Downloadings
|
||||||
|
extension AttachmentsStore {
|
||||||
private func uploadAttachment(_ message: Message) async {
|
private func uploadAttachment(_ message: Message) async {
|
||||||
do {
|
do {
|
||||||
try await message.setStatus(.pending)
|
try await message.setStatus(.pending)
|
||||||
|
@ -246,8 +265,10 @@ extension AttachmentsStore {
|
||||||
message.oobUrl = remotePath
|
message.oobUrl = remotePath
|
||||||
try await message.save()
|
try await message.save()
|
||||||
try await client.sendMessage(message)
|
try await client.sendMessage(message)
|
||||||
|
processing.remove(message.id)
|
||||||
try await message.setStatus(.sent)
|
try await message.setStatus(.sent)
|
||||||
} catch {
|
} catch {
|
||||||
|
processing.remove(message.id)
|
||||||
try? await message.setStatus(.error)
|
try? await message.setStatus(.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -276,6 +297,7 @@ extension AttachmentsStore {
|
||||||
remotePath: remotePath
|
remotePath: remotePath
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
processing.remove(message.id)
|
||||||
try await message.save()
|
try await message.save()
|
||||||
} catch {
|
} catch {
|
||||||
logIt(.error, "Can't download attachment: \(error)")
|
logIt(.error, "Can't download attachment: \(error)")
|
||||||
|
@ -324,6 +346,7 @@ extension AttachmentsStore {
|
||||||
remotePath: attachment.remotePath
|
remotePath: attachment.remotePath
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
processing.remove(message.id)
|
||||||
try? await message.save()
|
try? await message.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,15 +150,12 @@ private struct AttachmentView: View {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.scaleEffect(1.5)
|
.scaleEffect(1.5)
|
||||||
.progressViewStyle(CircularProgressViewStyle(tint: .Material.Elements.active))
|
.progressViewStyle(CircularProgressViewStyle(tint: .Material.Elements.active))
|
||||||
let imageName = progressImageName(attachment.type ?? .file)
|
let imageName = progressImageName(attachment.type)
|
||||||
Image(systemName: imageName)
|
Image(systemName: imageName)
|
||||||
.font(.body1)
|
.font(.body1)
|
||||||
.foregroundColor(.Material.Elements.active)
|
.foregroundColor(.Material.Elements.active)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
|
||||||
attachments.processAttachment(message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder private var failed: some View {
|
@ViewBuilder private var failed: some View {
|
||||||
|
@ -178,7 +175,9 @@ private struct AttachmentView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
attachments.processAttachment(message)
|
Task {
|
||||||
|
try? await message.setStatus(.pending)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue