Swiftgram/submodules/LegacyDataImport/Sources/LegacyDataImport.swift
2019-08-15 19:24:14 +03:00

248 lines
12 KiB
Swift

import Foundation
import UIKit
import TelegramCore
import SwiftSignalKit
import Postbox
#if BUCK
import MtProtoKit
#else
import MtProtoKitDynamic
#endif
public enum AccountImportError: Error {
case generic
}
public enum AccountImportProgressType {
case generic
case messages
case media
}
private func importedAccountData(basePath: String, documentsPath: String, accountManager: AccountManager, account: TemporaryAccount, database: SqliteInterface) -> Signal<(AccountImportProgressType, Float), AccountImportError> {
return deferred { () -> Signal<(AccountImportProgressType, Float), AccountImportError> in
let keychain = MTFileBasedKeychain(name: "Telegram", documentsPath: documentsPath)
guard let masterDatacenterId = keychain.object(forKey: "defaultDatacenterId", group: "persistent") as? Int else {
return .fail(.generic)
}
let keychainContents = keychain.contents(forGroup: "persistent")
let importKeychain = account.postbox.transaction { transaction -> Void in
for (key, value) in keychainContents {
let data = NSKeyedArchiver.archivedData(withRootObject: value)
transaction.setKeychainEntry(data, forKey: "persistent" + ":" + key)
}
}
|> ignoreValues
|> introduceError(AccountImportError.self)
let importData = importPreferencesData(documentsPath: documentsPath, masterDatacenterId: Int32(masterDatacenterId), account: account, database: database)
|> mapToSignal { accountUserId -> Signal<(AccountImportProgressType, Float), AccountImportError> in
return importDatabaseData(accountManager: accountManager, account: account, basePath: basePath, database: database, accountUserId: accountUserId)
}
return importKeychain
|> map { _ -> (AccountImportProgressType, Float) in return (.generic, 0.0) }
|> then(importData)
}
}
private func importPreferencesData(documentsPath: String, masterDatacenterId: Int32, account: TemporaryAccount, database: SqliteInterface) -> Signal<Int32, AccountImportError> {
return deferred { () -> Signal<Int32, AccountImportError> in
let defaultsPath = documentsPath + "/standard.defaults"
var parsedAccountUserId: Int32?
if let data = try? Data(contentsOf: URL(fileURLWithPath: defaultsPath)), let dict = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String: Any], let id = dict["telegraphUserId"] as? Int {
parsedAccountUserId = Int32(id)
}
if parsedAccountUserId == nil {
if let id = UserDefaults.standard.object(forKey: "telegraphUserId") as? Int {
parsedAccountUserId = Int32(id)
}
}
if let parsedAccountUserId = parsedAccountUserId {
return account.postbox.transaction { transaction -> Int32 in
transaction.setState(AuthorizedAccountState(isTestingEnvironment: false, masterDatacenterId: masterDatacenterId, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: parsedAccountUserId), state: nil))
return parsedAccountUserId
}
|> introduceError(AccountImportError.self)
} else {
return .fail(.generic)
}
}
}
private func importDatabaseData(accountManager: AccountManager, account: TemporaryAccount, basePath: String, database: SqliteInterface, accountUserId: Int32) -> Signal<(AccountImportProgressType, Float), AccountImportError> {
return deferred { () -> Signal<(AccountImportProgressType, Float), AccountImportError> in
var importedAccountUser: Signal<Never, AccountImportError> = .complete()
if let (user, presence) = loadLegacyUser(database: database, id: accountUserId) {
importedAccountUser = account.postbox.transaction { transaction -> Void in
updatePeers(transaction: transaction, peers: [user], update: { _, updated in updated })
transaction.updatePeerPresencesInternal(presences: [user.id: presence], merge: { _, updated in return updated })
}
|> ignoreValues
|> introduceError(AccountImportError.self)
}
let importedSecretChats = loadLegacySecretChats(account: account, basePath: basePath, accountPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: accountUserId), database: database)
|> introduceError(AccountImportError.self)
/*let importedFiles = loadLegacyFiles(account: account, basePath: basePath, accountPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: accountUserId), database: database)
|> introduceError(AccountImportError.self)*/
let importedLegacyPreferences = importLegacyPreferences(accountManager: accountManager, account: account, documentsPath: basePath + "/Documents", database: database)
|> introduceError(AccountImportError.self)
return importedAccountUser
|> map { _ -> (AccountImportProgressType, Float) in return (.generic, 0.0) }
|> then(
importedLegacyPreferences
|> map { _ -> (AccountImportProgressType, Float) in return (.generic, 0.0) }
)
|> then(
importedSecretChats
|> map { value -> (AccountImportProgressType, Float) in return (.messages, value) }
)
}
}
public enum ImportedLegacyAccountEvent {
case progress(AccountImportProgressType, Float)
case result(AccountRecordId?)
}
public func importedLegacyAccount(basePath: String, accountManager: AccountManager, encryptionParameters: ValueBoxEncryptionParameters, present: @escaping (UIViewController) -> Void) -> Signal<ImportedLegacyAccountEvent, AccountImportError> {
let queue = Queue()
return deferred { () -> Signal<ImportedLegacyAccountEvent, AccountImportError> in
let documentsPath = basePath + "/Documents"
if FileManager.default.fileExists(atPath: documentsPath + "/importcompleted") {
return .single(.result(nil))
}
let unlockedDatabasePathAndKey: Signal<(String, Data?)?, AccountImportError>
if FileManager.default.fileExists(atPath: documentsPath + "/tgdata.db.y") {
let databasePath = documentsPath + "/tgdata.db.y"
let unlockDatabase = Signal<(String, Data?)?, AccountImportError> { subscriber in
let alertController = UIAlertController(title: nil, message: "Enter your passcode", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "Enter", style: .default) { _ in
let passcode = alertController.textFields?[0].text
func checkPasscode(_ value: String) -> Bool {
guard let database = SqliteInterface(databasePath: databasePath) else {
return false
}
let key = value.data(using: .utf8)!
if !database.unlock(password: hexString(key).data(using: .utf8)!) {
return false
}
return true
}
if checkPasscode(passcode ?? "") {
subscriber.putNext((databasePath, (passcode ?? "").data(using: .utf8)!))
subscriber.putCompletion()
} else {
let alertController = UIAlertController(title: nil, message: "Invalid passcode. Please try again.", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "OK", style: .default) { _ in
subscriber.putCompletion()
}
alertController.addAction(confirmAction)
present(alertController)
}
}
let cancelAction = UIAlertAction(title: "Skip", style: .cancel) { _ in
subscriber.putNext(nil)
subscriber.putCompletion()
}
alertController.addTextField { textField in
textField.placeholder = "Passcode"
}
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
present(alertController)
return EmptyDisposable
}
|> runOn(Queue.mainQueue())
unlockedDatabasePathAndKey = (unlockDatabase
|> mapToSignal { result -> Signal<(String, Data?)?, AccountImportError> in
if let result = result {
return .single(result)
} else {
let askAgain = Signal<(String, Data?)?, AccountImportError> { subscriber in
let alertController = UIAlertController(title: "Warning", message: "If you continue without entering your passcode, all your secret chats will be lost.", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "Skip", style: .destructive) { _ in
subscriber.putError(.generic)
}
let cancelAction = UIAlertAction(title: "Try Again", style: .cancel) { _ in
subscriber.putCompletion()
}
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
present(alertController)
return EmptyDisposable
}
|> runOn(Queue.mainQueue())
return askAgain
}
})
|> restart
|> take(1)
} else if FileManager.default.fileExists(atPath: documentsPath + "/tgdata.db") {
unlockedDatabasePathAndKey = .single((documentsPath + "/tgdata.db", nil))
} else {
return .single(.result(nil))
}
return unlockedDatabasePathAndKey
|> mapToSignal { pathAndKey -> Signal<ImportedLegacyAccountEvent, AccountImportError> in
guard let pathAndKey = pathAndKey else {
return .fail(.generic)
}
guard let database = SqliteInterface(databasePath: pathAndKey.0) else {
return .fail(.generic)
}
if let key = pathAndKey.1 {
if !database.unlock(password: hexString(key).data(using: .utf8)!) {
return .fail(.generic)
}
}
return temporaryAccount(manager: accountManager, rootPath: rootPathForBasePath(basePath), encryptionParameters: encryptionParameters)
|> introduceError(AccountImportError.self)
|> mapToSignal { account -> Signal<ImportedLegacyAccountEvent, AccountImportError> in
let actions = importedAccountData(basePath: basePath, documentsPath: documentsPath, accountManager: accountManager, account: account, database: database)
var result = actions
|> map { typeAndProgress -> ImportedLegacyAccountEvent in
return .progress(typeAndProgress.0, typeAndProgress.1)
}
#if DEBUG
//result = result
//|> then(.never())
#endif
result = result
|> then(.single(.result(account.id)))
return result
}
}
}
|> runOn(queue)
}