wip
This commit is contained in:
parent
1780360fb4
commit
7666b71ef9
|
@ -6,8 +6,7 @@ enum XMPPAction: Codable {
|
||||||
case xmppMessageSendFailed(msgId: String)
|
case xmppMessageSendFailed(msgId: String)
|
||||||
case xmppMessageSendSuccess(msgId: String)
|
case xmppMessageSendSuccess(msgId: String)
|
||||||
|
|
||||||
case xmppAttachmentUpload(Message)
|
case xmppAttachmentTryUpload(Message)
|
||||||
// case xmppAttachmentSlotRequestDone(String) //TODO: ???
|
|
||||||
case xmppAttachmentUploadFailed(msgId: String, reason: String)
|
case xmppAttachmentUploadFailed(msgId: String, reason: String)
|
||||||
case xmppAttachmentUploadSuccess(msgId: String, attachmentRemotePath: String)
|
case xmppAttachmentUploadSuccess(msgId: String, attachmentRemotePath: String)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ private extension Database {
|
||||||
// verbose and debugging in DEBUG builds only.
|
// verbose and debugging in DEBUG builds only.
|
||||||
config.publicStatementArguments = true
|
config.publicStatementArguments = true
|
||||||
config.prepareDatabase { db in
|
config.prepareDatabase { db in
|
||||||
db.trace { print("SQL> \($0)") }
|
db.trace { print("SQL> \($0)\n") }
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return config
|
return config
|
||||||
|
|
|
@ -355,6 +355,7 @@ final class DatabaseMiddleware {
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
|
// MARK: Sharing
|
||||||
case .conversationAction(.sendMediaMessages(let from, let to, let messageIds, let localFilesNames)):
|
case .conversationAction(.sendMediaMessages(let from, let to, let messageIds, let localFilesNames)):
|
||||||
return Future<AppAction, Never> { promise in
|
return Future<AppAction, Never> { promise in
|
||||||
Task(priority: .background) { [weak self] in
|
Task(priority: .background) { [weak self] in
|
||||||
|
@ -378,6 +379,7 @@ final class DatabaseMiddleware {
|
||||||
date: Date(),
|
date: Date(),
|
||||||
pending: true,
|
pending: true,
|
||||||
sentError: false,
|
sentError: false,
|
||||||
|
attachmentType: localFilesNames[index].attachmentType,
|
||||||
attachmentLocalName: localFilesNames[index]
|
attachmentLocalName: localFilesNames[index]
|
||||||
)
|
)
|
||||||
try database._db.write { db in
|
try database._db.write { db in
|
||||||
|
@ -393,6 +395,52 @@ final class DatabaseMiddleware {
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
|
case .xmppAction(.xmppAttachmentUploadSuccess(let messageId, let remotePath)):
|
||||||
|
return Future<AppAction, Never> { promise in
|
||||||
|
Task(priority: .background) { [weak self] in
|
||||||
|
guard let database = self?.database else {
|
||||||
|
promise(.success(.databaseAction(.updateAttachmentFailed(id: messageId, reason: L10n.Global.Error.genericDbError)))
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
_ = try database._db.write { db in
|
||||||
|
try Message
|
||||||
|
.filter(Column("id") == messageId)
|
||||||
|
.updateAll(db, Column("attachmentRemotePath").set(to: remotePath), Column("pending").set(to: false))
|
||||||
|
}
|
||||||
|
promise(.success(.empty))
|
||||||
|
} catch {
|
||||||
|
promise(.success(.databaseAction(.updateAttachmentFailed(id: messageId, reason: error.localizedDescription)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
|
case .xmppAction(.xmppAttachmentUploadFailed(let messageId, _)):
|
||||||
|
return Future<AppAction, Never> { promise in
|
||||||
|
Task(priority: .background) { [weak self] in
|
||||||
|
guard let database = self?.database else {
|
||||||
|
promise(.success(.databaseAction(.updateAttachmentFailed(id: messageId, reason: L10n.Global.Error.genericDbError)))
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
_ = try database._db.write { db in
|
||||||
|
try Message
|
||||||
|
.filter(Column("id") == messageId)
|
||||||
|
.updateAll(db, Column("pending").set(to: false), Column("sentError").set(to: true))
|
||||||
|
}
|
||||||
|
promise(.success(.empty))
|
||||||
|
} catch {
|
||||||
|
promise(.success(.databaseAction(.updateAttachmentFailed(id: messageId, reason: error.localizedDescription)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Empty().eraseToAnyPublisher()
|
return Empty().eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ final class FileMiddleware {
|
||||||
promise(.success(.empty))
|
promise(.success(.empty))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for incoming messages with attachments
|
||||||
for message in messages where message.attachmentRemotePath != nil && message.attachmentLocalPath == nil {
|
for message in messages where message.attachmentRemotePath != nil && message.attachmentLocalPath == nil {
|
||||||
if wSelf.downloadingMessageIDs.contains(message.id) {
|
if wSelf.downloadingMessageIDs.contains(message.id) {
|
||||||
continue
|
continue
|
||||||
|
@ -25,6 +27,27 @@ final class FileMiddleware {
|
||||||
store.dispatch(.fileAction(.downloadAttachmentFile(messageId: message.id, attachmentRemotePath: message.attachmentRemotePath!)))
|
store.dispatch(.fileAction(.downloadAttachmentFile(messageId: message.id, attachmentRemotePath: message.attachmentRemotePath!)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for outgoing messages with shared attachments
|
||||||
|
for message in messages where message.attachmentLocalPath != nil && message.attachmentRemotePath == nil && message.pending {
|
||||||
|
if wSelf.downloadingMessageIDs.contains(message.id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wSelf.downloadingMessageIDs.insert(message.id)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
store.dispatch(.xmppAction(.xmppAttachmentTryUpload(message)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for outgoing messages with shared attachments which are already uploaded
|
||||||
|
// but have no thumbnail
|
||||||
|
for message in messages where message.attachmentLocalName != nil && message.attachmentRemotePath != nil && message.attachmentThumbnailName == nil && !message.pending && !message.sentError {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
// swiftlint:disable:next force_unwrapping
|
||||||
|
store.dispatch(.fileAction(.createAttachmentThumbnail(messageId: message.id, localName: message.attachmentLocalName!)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
promise(.success(.empty))
|
promise(.success(.empty))
|
||||||
}.eraseToAnyPublisher()
|
}.eraseToAnyPublisher()
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import UIKit
|
||||||
final class SharingMiddleware {
|
final class SharingMiddleware {
|
||||||
static let shared = SharingMiddleware()
|
static let shared = SharingMiddleware()
|
||||||
|
|
||||||
// swiftlint:disable:next function_body_length
|
|
||||||
func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
|
func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
|
||||||
switch action {
|
switch action {
|
||||||
// MARK: - Camera and Gallery Access
|
// MARK: - Camera and Gallery Access
|
||||||
|
|
|
@ -6,6 +6,7 @@ final class XMPPMiddleware {
|
||||||
static let shared = XMPPMiddleware()
|
static let shared = XMPPMiddleware()
|
||||||
private let service = XMPPService(manager: Database.shared)
|
private let service = XMPPService(manager: Database.shared)
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
private var uploadingMessageIDs = ThreadSafeSet<String>()
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
service.clientState.sink { client, state in
|
service.clientState.sink { client, state in
|
||||||
|
@ -100,14 +101,20 @@ final class XMPPMiddleware {
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
case .xmppAction(.xmppAttachmentUpload(let message)):
|
case .xmppAction(.xmppAttachmentTryUpload(let message)):
|
||||||
return Future<AppAction, Never> { [weak self] promise in
|
return Future<AppAction, Never> { [weak self] promise in
|
||||||
DispatchQueue.global().async {
|
if self?.uploadingMessageIDs.contains(message.id) ?? false {
|
||||||
self?.service.uploadAttachment(message: message) { done, remotePath in
|
return promise(.success(.empty))
|
||||||
if done {
|
|
||||||
promise(.success(.xmppAction(.xmppAttachmentUploadSuccess(msgId: message.id, attachmentRemotePath: remotePath))))
|
|
||||||
} else {
|
} else {
|
||||||
promise(.success(.xmppAction(.xmppAttachmentUploadFailed(msgId: message.id, reason: "Upload failed"))))
|
self?.uploadingMessageIDs.insert(message.id)
|
||||||
|
DispatchQueue.global().async {
|
||||||
|
self?.service.uploadAttachment(message: message) { error, remotePath in
|
||||||
|
self?.uploadingMessageIDs.remove(message.id)
|
||||||
|
if let error {
|
||||||
|
promise(.success(.xmppAction(.xmppAttachmentUploadFailed(msgId: message.id, reason: error.localizedDescription))))
|
||||||
|
} else {
|
||||||
|
promise(.success(.xmppAction(.xmppAttachmentUploadSuccess(msgId: message.id, attachmentRemotePath: remotePath))))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,10 @@ final class XMPPService: ObservableObject {
|
||||||
completion(XMPPError.bad_request("No such file"), "")
|
completion(XMPPError.bad_request("No such file"), "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
guard let chat = client.module(MessageModule.self).chatManager.chat(for: client.context, with: BareJID(to)) else {
|
||||||
|
completion(XMPPError.bad_request("No such chat"), "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let httpModule = client.module(HttpFileUploadModule.self)
|
let httpModule = client.module(HttpFileUploadModule.self)
|
||||||
httpModule.findHttpUploadComponent { res in
|
httpModule.findHttpUploadComponent { res in
|
||||||
|
@ -173,73 +177,28 @@ final class XMPPService: ObservableObject {
|
||||||
if code == 200 {
|
if code == 200 {
|
||||||
completion(XMPPError.bad_request("Invalid response code"), "")
|
completion(XMPPError.bad_request("Invalid response code"), "")
|
||||||
} else {
|
} else {
|
||||||
|
let mesg = chat.createMessage(text: slot.getUri.absoluteString, id: message.id)
|
||||||
|
mesg.oob = slot.getUri.absoluteString
|
||||||
|
chat.send(message: mesg) { res in
|
||||||
|
switch res {
|
||||||
|
case .success:
|
||||||
completion(nil, slot.getUri.absoluteString)
|
completion(nil, slot.getUri.absoluteString)
|
||||||
|
|
||||||
|
case .failure:
|
||||||
|
completion(XMPPError.bad_request("File uploaded, but message sent failed"), slot.getUri.absoluteString)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.resume()
|
}.resume()
|
||||||
|
|
||||||
case .failure:
|
case .failure(let error):
|
||||||
completion(XMPPError.bad_request("Upload failed"), "")
|
completion(error, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case .failure:
|
case .failure(let error):
|
||||||
completion(XMPPError.bad_request("No such component"), "")
|
completion(error, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// open class HTTPFileUploadHelper {
|
|
||||||
//
|
|
||||||
// private static let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "HTTPFileUploadHelper")
|
|
||||||
//
|
|
||||||
// public static func upload(for context: Context, filename: String, inputStream: InputStream, filesize size: Int, mimeType: String, delegate: URLSessionDelegate?, completionHandler: @escaping (Result<URL,ShareError>)->Void) {
|
|
||||||
// let httpUploadModule = context.module(.httpFileUpload);
|
|
||||||
// httpUploadModule.findHttpUploadComponent(completionHandler: { result in
|
|
||||||
// switch result {
|
|
||||||
// case .success(let components):
|
|
||||||
// guard let component = components.first(where: { $0.maxSize > size }) else {
|
|
||||||
// completionHandler(.failure(.fileTooBig));
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// httpUploadModule.requestUploadSlot(componentJid: component.jid, filename: filename, size: size, contentType: mimeType, completionHandler: { result in
|
|
||||||
// switch result {
|
|
||||||
// case .success(let slot):
|
|
||||||
// var request = URLRequest(url: slot.putUri);
|
|
||||||
// slot.putHeaders.forEach({ (k,v) in
|
|
||||||
// request.addValue(v, forHTTPHeaderField: k);
|
|
||||||
// });
|
|
||||||
// request.httpMethod = "PUT";
|
|
||||||
// request.httpBodyStream = inputStream;
|
|
||||||
// request.addValue(String(size), forHTTPHeaderField: "Content-Length");
|
|
||||||
// request.addValue(mimeType, forHTTPHeaderField: "Content-Type");
|
|
||||||
// let session = URLSession(configuration: URLSessionConfiguration.default, delegate: delegate, delegateQueue: OperationQueue.main);
|
|
||||||
// session.dataTask(with: request) { (data, response, error) in
|
|
||||||
// let code = (response as? HTTPURLResponse)?.statusCode ?? 500;
|
|
||||||
// guard error == nil && (code == 200 || code == 201) else {
|
|
||||||
// logger.error("upload of file \(filename) failed, error: \(error as Any), response: \(response as Any)");
|
|
||||||
// completionHandler(.failure(.httpError));
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// if code == 200 {
|
|
||||||
// completionHandler(.failure(.invalidResponseCode(url: slot.getUri)));
|
|
||||||
// } else {
|
|
||||||
// completionHandler(.success(slot.getUri));
|
|
||||||
// }
|
|
||||||
// }.resume();
|
|
||||||
// case .failure(let error):
|
|
||||||
// logger.error("upload of file \(filename) failed, upload component returned error: \(error as Any)");
|
|
||||||
// completionHandler(.failure(.unknownError));
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// case .failure(let error):
|
|
||||||
// completionHandler(.failure(error.errorCondition == .item_not_found ? .notSupported : .unknownError));
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public enum UploadResult {
|
|
||||||
// case success(url: URL, filesize: Int, mimeType: String?)
|
|
||||||
// case failure(ShareError)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
Loading…
Reference in a new issue