ios: support XFTP files (#2064)

This commit is contained in:
spaced4ndy
2023-03-24 15:20:15 +04:00
committed by GitHub
parent 8d6fe2be99
commit aeb732c2f6
11 changed files with 124 additions and 28 deletions

View File

@@ -215,12 +215,24 @@ func apiSuspendChat(timeoutMicroseconds: Int) {
logger.error("apiSuspendChat error: \(String(describing: r))")
}
func apiSetTempFolder(tempFolder: String) throws {
let r = chatSendCmdSync(.setTempFolder(tempFolder: tempFolder))
if case .cmdOk = r { return }
throw r
}
func apiSetFilesFolder(filesFolder: String) throws {
let r = chatSendCmdSync(.setFilesFolder(filesFolder: filesFolder))
if case .cmdOk = r { return }
throw r
}
func setXFTPConfig(_ cfg: XFTPFileConfig?) throws {
let r = chatSendCmdSync(.apiSetXFTPConfig(config: cfg))
if case .cmdOk = r { return }
throw r
}
func apiSetIncognito(incognito: Bool) throws {
let r = chatSendCmdSync(.setIncognito(incognito: incognito))
if case .cmdOk = r { return }
@@ -992,7 +1004,9 @@ func initializeChat(start: Bool, dbKey: String? = nil, refreshInvitations: Bool
if encryptionStartedDefault.get() {
encryptionStartedDefault.set(false)
}
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
try setXFTPConfig(getXFTPCfg())
try apiSetIncognito(incognito: incognitoGroupDefault.get())
m.chatInitialized = true
m.currentUser = try apiGetActiveUser()

View File

@@ -16,8 +16,8 @@ struct CIFileView: View {
var body: some View {
let metaReserve = edited
? " "
: " "
? " "
: " "
Button(action: fileAction) {
HStack(alignment: .bottom, spacing: 6) {
fileIndicator()
@@ -45,7 +45,24 @@ struct CIFileView: View {
.padding(.leading, 10)
.padding(.trailing, 12)
}
.disabled(file == nil || (file?.fileStatus != .rcvInvitation && file?.fileStatus != .rcvAccepted && file?.fileStatus != .rcvComplete))
.disabled(!itemInteractive)
}
var itemInteractive: Bool {
if let file = file {
switch (file.fileStatus) {
case .sndStored: return false
case .sndTransfer: return false
case .sndComplete: return false
case .sndCancelled: return false
case .rcvInvitation: return true
case .rcvAccepted: return true
case .rcvTransfer: return false
case .rcvComplete: return true
case .rcvCancelled: return false
}
}
return false
}
func fileSizeValid() -> Bool {
@@ -155,7 +172,7 @@ struct CIFileView_Previews: PreviewProvider {
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), revealed: Binding.constant(false))

View File

@@ -243,7 +243,7 @@ struct CIVoiceView_Previews: PreviewProvider {
)
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage, revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWtFile, revealed: Binding.constant(false))
}
.previewLayout(.fixed(width: 360, height: 360))

View File

@@ -62,7 +62,7 @@ struct FramedCIVoiceView_Previews: PreviewProvider {
Group {
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage, revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), revealed: Binding.constant(false))
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWithQuote, revealed: Binding.constant(false))
}

View File

@@ -7,15 +7,23 @@
//
import SwiftUI
import SimpleXChat
struct ExperimentalFeaturesView: View {
@AppStorage(DEFAULT_EXPERIMENTAL_CALLS) private var enableCalls = false
@AppStorage(GROUP_DEFAULT_XFTP_SEND_ENABLED, store: UserDefaults(suiteName: APP_GROUP_NAME)!) private var xftpSendEnabled = false
var body: some View {
List {
Section("") {
settingsRow("video") {
Toggle("Audio & video calls", isOn: $enableCalls)
settingsRow("arrow.up.doc") {
Toggle("Send files via XFTP", isOn: $xftpSendEnabled)
.onChange(of: xftpSendEnabled) { _ in
do {
try setXFTPConfig(getXFTPCfg())
} catch {
logger.error("setXFTPConfig: cannot set XFTP config \(responseError(error))")
}
}
}
}
}

View File

@@ -277,12 +277,12 @@ struct SettingsView: View {
.padding(.leading, indent)
}
}
// NavigationLink {
// ExperimentalFeaturesView()
// .navigationTitle("Experimental features")
// } label: {
// settingsRow("gauge") { Text("Experimental features") }
// }
NavigationLink {
ExperimentalFeaturesView()
.navigationTitle("Experimental features")
} label: {
settingsRow("gauge") { Text("Experimental features") }
}
NavigationLink {
VersionView()
.navigationBarTitle("App version")

View File

@@ -199,6 +199,7 @@ class NotificationService: UNNotificationServiceExtension {
var chatStarted = false
var networkConfig: NetCfg = getNetCfg()
var xftpConfig: XFTPFileConfig? = getXFTPCfg()
func startChat() -> DBMigrationResult? {
hs_init(0, nil)
@@ -212,10 +213,12 @@ func startChat() -> DBMigrationResult? {
logger.debug("active user \(String(describing: user))")
do {
try setNetworkConfig(networkConfig)
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
try setXFTPConfig(xftpConfig)
let justStarted = try apiStartChat()
chatStarted = true
if justStarted {
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
try apiSetIncognito(incognito: incognitoGroupDefault.get())
chatLastStartGroupDefault.set(Date.now)
Task { await receiveMessages() }
@@ -329,12 +332,24 @@ func apiStartChat() throws -> Bool {
}
}
func apiSetTempFolder(tempFolder: String) throws {
let r = sendSimpleXCmd(.setTempFolder(tempFolder: tempFolder))
if case .cmdOk = r { return }
throw r
}
func apiSetFilesFolder(filesFolder: String) throws {
let r = sendSimpleXCmd(.setFilesFolder(filesFolder: filesFolder))
if case .cmdOk = r { return }
throw r
}
func setXFTPConfig(_ cfg: XFTPFileConfig?) throws {
let r = sendSimpleXCmd(.apiSetXFTPConfig(config: cfg))
if case .cmdOk = r { return }
throw r
}
func apiSetIncognito(incognito: Bool) throws {
let r = sendSimpleXCmd(.setIncognito(incognito: incognito))
if case .cmdOk = r { return }

View File

@@ -26,7 +26,9 @@ public enum ChatCommand {
case apiStopChat
case apiActivateChat
case apiSuspendChat(timeoutMicroseconds: Int)
case setTempFolder(tempFolder: String)
case setFilesFolder(filesFolder: String)
case apiSetXFTPConfig(config: XFTPFileConfig?)
case setIncognito(incognito: Bool)
case apiExportArchive(config: ArchiveConfig)
case apiImportArchive(config: ArchiveConfig)
@@ -117,7 +119,13 @@ public enum ChatCommand {
case .apiStopChat: return "/_stop"
case .apiActivateChat: return "/_app activate"
case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)"
case let .setTempFolder(tempFolder): return "/_temp_folder \(tempFolder)"
case let .setFilesFolder(filesFolder): return "/_files_folder \(filesFolder)"
case let .apiSetXFTPConfig(cfg): if let cfg = cfg {
return "/_xftp on \(encodeJSON(cfg))"
} else {
return "/_xftp off"
}
case let .setIncognito(incognito): return "/incognito \(onOff(incognito))"
case let .apiExportArchive(cfg): return "/_db export \(encodeJSON(cfg))"
case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))"
@@ -219,7 +227,9 @@ public enum ChatCommand {
case .apiStopChat: return "apiStopChat"
case .apiActivateChat: return "apiActivateChat"
case .apiSuspendChat: return "apiSuspendChat"
case .setTempFolder: return "setTempFolder"
case .setFilesFolder: return "setFilesFolder"
case .apiSetXFTPConfig: return "apiSetXFTPConfig"
case .setIncognito: return "setIncognito"
case .apiExportArchive: return "apiExportArchive"
case .apiImportArchive: return "apiImportArchive"
@@ -712,6 +722,10 @@ struct ComposedMessage: Encodable {
var msgContent: MsgContent
}
public struct XFTPFileConfig: Encodable {
var minFileSize: Int64
}
public struct ArchiveConfig: Encodable {
var archivePath: String
var disableCompression: Bool?

View File

@@ -30,6 +30,7 @@ let GROUP_DEFAULT_INCOGNITO = "incognito"
let GROUP_DEFAULT_STORE_DB_PASSPHRASE = "storeDBPassphrase"
let GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE = "initialRandomDBPassphrase"
public let GROUP_DEFAULT_CALL_KIT_ENABLED = "callKitEnabled"
public let GROUP_DEFAULT_XFTP_SEND_ENABLED = "xftpSendEnabled"
public let APP_GROUP_NAME = "group.chat.simplex.app"
@@ -52,7 +53,8 @@ public func registerGroupDefaults() {
GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE: false,
GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES: true,
GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE: false,
GROUP_DEFAULT_CALL_KIT_ENABLED: true
GROUP_DEFAULT_CALL_KIT_ENABLED: true,
GROUP_DEFAULT_XFTP_SEND_ENABLED: false
])
}
@@ -123,6 +125,8 @@ public let initialRandomDBPassphraseGroupDefault = BoolDefault(defaults: groupDe
public let callKitEnabledGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_CALL_KIT_ENABLED)
public let xftpSendEnabledGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_XFTP_SEND_ENABLED)
public class DateDefault {
var defaults: UserDefaults
var key: String
@@ -195,6 +199,11 @@ public class Default<T> {
}
}
public func getXFTPCfg() -> XFTPFileConfig? {
let xftpSendEnabled = xftpSendEnabledGroupDefault.get()
return xftpSendEnabled ? XFTPFileConfig(minFileSize: 0) : nil
}
public func getNetCfg() -> NetCfg {
let onionHosts = networkUseOnionHostsGroupDefault.get()
let (hostMode, requiredHostMode) = onionHosts.hostMode

View File

@@ -2237,16 +2237,30 @@ public struct CIFile: Decodable {
}
}
public enum CIFileStatus: String, Decodable {
case sndStored = "snd_stored"
case sndTransfer = "snd_transfer"
case sndComplete = "snd_complete"
case sndCancelled = "snd_cancelled"
case rcvInvitation = "rcv_invitation"
case rcvAccepted = "rcv_accepted"
case rcvTransfer = "rcv_transfer"
case rcvComplete = "rcv_complete"
case rcvCancelled = "rcv_cancelled"
public enum CIFileStatus: Decodable {
case sndStored
case sndTransfer(sndProgress: Int, sndTotal: Int)
case sndComplete
case sndCancelled
case rcvInvitation
case rcvAccepted
case rcvTransfer(rcvProgress: Int, rcvTotal: Int)
case rcvComplete
case rcvCancelled
var id: String {
switch self {
case .sndStored: return "sndStored"
case let .sndTransfer(sndProgress, sndTotal): return "sndTransfer \(sndProgress) \(sndTotal)"
case .sndComplete: return "sndComplete"
case .sndCancelled: return "sndCancelled"
case .rcvInvitation: return "rcvInvitation"
case .rcvAccepted: return "rcvAccepted"
case let .rcvTransfer(rcvProgress, rcvTotal): return "rcvTransfer \(rcvProgress) \(rcvTotal)"
case .rcvComplete: return "rcvComplete"
case .rcvCancelled: return "rcvCancelled"
}
}
}
public enum MsgContent {

View File

@@ -16,7 +16,8 @@ public let MAX_IMAGE_SIZE: Int64 = 236700
public let MAX_IMAGE_SIZE_AUTO_RCV: Int64 = MAX_IMAGE_SIZE * 2
public let MAX_FILE_SIZE: Int64 = 8000000
//public let MAX_FILE_SIZE_SMP: Int64 = 8000000 // TODO distinguish between XFTP and SMP files
public let MAX_FILE_SIZE: Int64 = 1_073_741_824
public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(30)
@@ -158,6 +159,10 @@ public func removeLegacyDatabaseAndFiles() -> Bool {
return r1 && r2
}
public func getTempFilesDirectory() -> URL {
getAppDirectory().appendingPathComponent("temp_files", isDirectory: true)
}
public func getAppFilesDirectory() -> URL {
getAppDirectory().appendingPathComponent("app_files", isDirectory: true)
}