2022-06-24 13:52:20 +01:00
//
// M i g r a t e T o G r o u p V i e w . s w i f t
// S i m p l e X ( i O S )
//
// C r e a t e d b y E v g e n y o n 2 0 / 0 6 / 2 0 2 2 .
// C o p y r i g h t © 2 0 2 2 S i m p l e X C h a t . A l l r i g h t s r e s e r v e d .
//
import SwiftUI
import SimpleXChat
enum V3DBMigrationState : String {
case offer
case postponed
case exporting
case export_error
case exported
case migrating
case migration_error
case migrated
case ready
var startChat : Bool {
switch self {
case . postponed : return true
case . ready : return true
default : return false
}
}
}
let v3DBMigrationDefault = EnumDefault < V3DBMigrationState > (
defaults : UserDefaults . standard ,
forKey : DEFAULT_CHAT_V3_DB_MIGRATION ,
withDefault : . offer
)
struct MigrateToAppGroupView : View {
@ EnvironmentObject var chatModel : ChatModel
@ State private var migrationError = " "
@ AppStorage ( DEFAULT_CHAT_ARCHIVE_NAME ) private var chatArchiveName : String ?
@ AppStorage ( DEFAULT_CHAT_ARCHIVE_TIME ) private var chatArchiveTime : Double = 0
var body : some View {
ZStack ( alignment : . topLeading ) {
2022-07-03 19:53:07 +01:00
Text ( " Push notifications " ) . font ( . largeTitle )
2022-06-24 13:52:20 +01:00
2022-07-01 22:45:58 +01:00
switch chatModel . v3DBMigration {
2022-06-24 13:52:20 +01:00
case . offer :
VStack ( alignment : . leading , spacing : 16 ) {
Text ( " To support instant push notifications the chat database has to be migrated. " )
2022-07-03 19:53:07 +01:00
Text ( " If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app). " )
2022-06-24 13:52:20 +01:00
}
. padding ( . top , 56 )
center {
Button {
migrateDatabaseToV3 ( )
} label : {
Text ( " Start migration " )
. font ( . title )
. frame ( maxWidth : . infinity )
}
}
skipMigration ( )
case . exporting :
center {
ProgressView ( value : 0.33 )
2023-07-10 19:01:22 +04:00
Text ( " Exporting database archive… " )
2022-06-24 13:52:20 +01:00
}
migrationProgress ( )
case . export_error :
migrationFailed ( ) . padding ( . top , 56 )
center {
Text ( " Export error: " ) . font ( . headline )
Text ( migrationError )
}
skipMigration ( )
case . exported :
center {
Text ( " Exported database archive. " )
}
case . migrating :
center {
ProgressView ( value : 0.67 )
2023-07-10 19:01:22 +04:00
Text ( " Migrating database archive… " )
2022-06-24 13:52:20 +01:00
}
migrationProgress ( )
case . migration_error :
VStack ( alignment : . leading , spacing : 16 ) {
migrationFailed ( )
Text ( " The created archive is available via app Settings / Database / Old database archive. " )
}
. padding ( . top , 56 )
center {
Text ( " Migration error: " ) . font ( . headline )
Text ( migrationError )
}
skipMigration ( )
case . migrated :
center {
ProgressView ( value : 1.0 )
Text ( " Migration is completed " )
}
VStack {
Spacer ( )
Spacer ( )
Spacer ( )
Button {
do {
resetChatCtrl ( )
try initializeChat ( start : true )
2023-05-05 12:56:48 +04:00
onboardingStageDefault . set ( . step4_SetNotificationsMode )
2023-05-01 20:36:52 +04:00
chatModel . onboardingStage = . step4_SetNotificationsMode
2022-06-24 13:52:20 +01:00
setV3DBMigration ( . ready )
} catch let error {
dbContainerGroupDefault . set ( . documents )
setV3DBMigration ( . migration_error )
migrationError = " Error starting chat: \( responseError ( error ) ) "
}
deleteOldArchive ( )
} label : {
2022-07-03 19:53:07 +01:00
Text ( " Start chat " )
2022-06-24 13:52:20 +01:00
. font ( . title )
. frame ( maxWidth : . infinity )
}
Spacer ( )
}
. frame ( maxWidth : . infinity , maxHeight : . infinity , alignment : . bottom )
default :
Spacer ( )
Text ( " Unexpected migration state " )
2022-07-01 22:45:58 +01:00
Text ( " \( chatModel . v3DBMigration . rawValue ) " )
2022-06-24 13:52:20 +01:00
Spacer ( )
skipMigration ( )
}
}
. padding ( )
}
private func center < Content > ( @ ViewBuilder c : @ escaping ( ) -> Content ) -> some View where Content : View {
VStack ( alignment : . leading , spacing : 8 ) { c ( ) }
. frame ( maxWidth : . infinity , maxHeight : . infinity , alignment : . leading )
}
private func migrationProgress ( ) -> some View {
VStack {
Spacer ( )
ProgressView ( ) . scaleEffect ( 2 )
Spacer ( )
Spacer ( )
Spacer ( )
}
. frame ( maxWidth : . infinity )
}
private func migrationFailed ( ) -> some View {
Text ( " Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat). " )
}
private func skipMigration ( ) -> some View {
ZStack {
Button {
setV3DBMigration ( . postponed )
do {
try startChat ( )
} catch let error {
fatalError ( " Failed to start or load chats: \( responseError ( error ) ) " )
}
} label : {
2022-07-03 19:53:07 +01:00
Text ( " Do it later " )
2022-06-24 13:52:20 +01:00
. frame ( maxWidth : . infinity , alignment : . trailing )
}
}
. frame ( maxWidth : . infinity , maxHeight : . infinity , alignment : . bottomTrailing )
}
2022-07-03 19:53:07 +01:00
private func setV3DBMigration ( _ state : V3DBMigrationState ) {
chatModel . v3DBMigration = state
v3DBMigrationDefault . set ( state )
2022-06-24 13:52:20 +01:00
}
func migrateDatabaseToV3 ( ) {
setV3DBMigration ( . exporting )
let archiveTime = Date . now
let archiveName = " simplex-chat. \( archiveTime . ISO8601Format ( ) ) .zip "
chatArchiveTime = archiveTime . timeIntervalSince1970
chatArchiveName = archiveName
let config = ArchiveConfig ( archivePath : getDocumentsDirectory ( ) . appendingPathComponent ( archiveName ) . path )
Task {
do {
try await apiExportArchive ( config : config )
await MainActor . run { setV3DBMigration ( . exported ) }
} catch let error {
await MainActor . run {
setV3DBMigration ( . export_error )
migrationError = responseError ( error )
}
return
}
do {
await MainActor . run { setV3DBMigration ( . migrating ) }
dbContainerGroupDefault . set ( . group )
resetChatCtrl ( )
2022-07-01 22:45:58 +01:00
try await MainActor . run { try initializeChat ( start : false ) }
2023-05-24 14:22:12 +04:00
let _ = try await apiImportArchive ( config : config )
2022-06-24 13:52:20 +01:00
await MainActor . run { setV3DBMigration ( . migrated ) }
} catch let error {
dbContainerGroupDefault . set ( . documents )
await MainActor . run {
setV3DBMigration ( . migration_error )
migrationError = responseError ( error )
}
}
}
}
}
func exportChatArchive ( ) async throws -> URL {
let archiveTime = Date . now
let ts = archiveTime . ISO8601Format ( Date . ISO8601FormatStyle ( timeSeparator : . omitted ) )
let archiveName = " simplex-chat. \( ts ) .zip "
let archivePath = getDocumentsDirectory ( ) . appendingPathComponent ( archiveName )
let config = ArchiveConfig ( archivePath : archivePath . path )
try await apiExportArchive ( config : config )
deleteOldArchive ( )
UserDefaults . standard . set ( archiveName , forKey : DEFAULT_CHAT_ARCHIVE_NAME )
chatArchiveTimeDefault . set ( archiveTime )
return archivePath
}
func deleteOldArchive ( ) {
let d = UserDefaults . standard
if let archiveName = d . string ( forKey : DEFAULT_CHAT_ARCHIVE_NAME ) {
do {
try FileManager . default . removeItem ( atPath : getDocumentsDirectory ( ) . appendingPathComponent ( archiveName ) . path )
d . set ( nil , forKey : DEFAULT_CHAT_ARCHIVE_NAME )
d . set ( 0 , forKey : DEFAULT_CHAT_ARCHIVE_TIME )
} catch let error {
logger . error ( " removeItem error \( String ( describing : error ) ) " )
}
}
}
struct MigrateToGroupView_Previews : PreviewProvider {
static var previews : some View {
2022-07-03 19:53:07 +01:00
let chatModel = ChatModel ( )
chatModel . v3DBMigration = . migrated
return MigrateToAppGroupView ( )
. environmentObject ( chatModel )
2022-06-24 13:52:20 +01:00
}
}