import Foundation import TelegramCore import SwiftSignalKit import Postbox import MtProtoKitDynamic import TelegramUI import LegacyComponents @objc(TGPresentationState) private final class TGPresentationState: NSObject, NSCoding { let pallete: Int32 let userInfo: Int32 let fontSize: Int32 init?(coder aDecoder: NSCoder) { self.pallete = aDecoder.decodeInt32(forKey: "p") self.userInfo = aDecoder.decodeInt32(forKey: "u") self.fontSize = aDecoder.decodeInt32(forKey: "f") } func encode(with aCoder: NSCoder) { assertionFailure() } } private enum PreferencesProvider { case dict([String: Any]) case standard(UserDefaults) subscript(_ key: String) -> Any? { get { switch self { case let .dict(dict): return dict[key] case let .standard(standard): return standard.object(forKey: key) } } } } private func loadLegacyCustomProperyData(database: SqliteInterface, key: String) -> Data? { var result: Data? database.select("SELECT value FROM service_v29 WHERE key=\(murMurHash32(key))", { cursor in result = cursor.getData(at: 0) return false }) return result } private func convertLegacyProxyPort(_ value: Int) -> Int32 { if value < 0 { return Int32(UInt16(bitPattern: Int16(clamping: value))) } else { return Int32(clamping: value) } } func importLegacyPreferences(account: TemporaryAccount, documentsPath: String, database: SqliteInterface) -> Signal { return deferred { () -> Signal in var presentationState: TGPresentationState? if let value = NSKeyedUnarchiver.unarchiveObject(withFile: documentsPath + "/presentation.dat") as? TGPresentationState { presentationState = value } var autoNightPreferences: TGPresentationAutoNightPreferences? if let value = NSKeyedUnarchiver.unarchiveObject(withFile: documentsPath + "/autonight.dat") as? TGPresentationAutoNightPreferences { autoNightPreferences = value } var wallpaperInfo: TGWallpaperInfo? if let data = UserDefaults.standard.object(forKey: "_currentWallpaperInfo") as? [AnyHashable: Any], let value = TGWallpaperInfo(dictionary: data) { wallpaperInfo = value } let autoDownloadPreferences: TGAutoDownloadPreferences? = NSKeyedUnarchiver.unarchiveObject(withFile: documentsPath + "/autoDownload.pref") as? TGAutoDownloadPreferences let preferencesProvider: PreferencesProvider let defaultsPath = documentsPath + "/standard.defaults" let standardPreferences = PreferencesProvider.standard(UserDefaults.standard) if let data = try? Data(contentsOf: URL(fileURLWithPath: defaultsPath)), let dict = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String: Any] { preferencesProvider = .dict(dict) } else { preferencesProvider = standardPreferences } var showCallsTab: Bool? if let data = try? Data(contentsOf: URL(fileURLWithPath: documentsPath + "/enablecalls.tab")), !data.isEmpty { showCallsTab = data.withUnsafeBytes { (bytes: UnsafePointer) -> Bool in return bytes.pointee != 0 } } let parsedAutoplayGifs: Bool? = preferencesProvider["autoPlayAnimations"] as? Bool let soundEnabled: Bool? = preferencesProvider["soundEnabled"] as? Bool let vibrationEnabled: Bool? = preferencesProvider["vibrationEnabled"] as? Bool let bannerEnabled: Bool? = preferencesProvider["bannerEnabled"] as? Bool let callsDataUsageMode: Int? = preferencesProvider["callsDataUsageMode"] as? Int let callsDisableP2P: Bool? = preferencesProvider["callsDisableP2P"] as? Bool let callsDisableCallKit: Bool? = preferencesProvider["callsDisableCallKit"] as? Bool let callsUseProxy: Bool? = preferencesProvider["callsUseProxy"] as? Bool let contactsInhibitSync: Bool? = preferencesProvider["contactsInhibitSync"] as? Bool let stickersSuggestMode: Int? = preferencesProvider["stickersSuggestMode"] as? Int let allowSecretWebpages: Bool? = preferencesProvider["allowSecretWebpages"] as? Bool let allowSecretWebpagesInitialized: Bool? = preferencesProvider["allowSecretWebpagesInitialized"] as? Bool let secretInlineBotsInitialized: Bool? = preferencesProvider["secretInlineBotsInitialized"] as? Bool let musicPlayerOrderType: Int? = standardPreferences["musicPlayerOrderType_v1"] as? Int let musicPlayerRepeatType: Int? = standardPreferences["musicPlayerRepeatType_v1"] as? Int let instantPageFontSize: Float? = standardPreferences["instantPage_fontMultiplier_v0"] as? Float let instantPageFontSerif: Int? = standardPreferences["instantPage_fontSerif_v0"] as? Int let instantPageTheme: Int? = standardPreferences["instantPage_theme_v0"] as? Int let instantPageAutoNightMode: Int? = standardPreferences["instantPage_autoNightTheme_v0"] as? Int let proxyList = NSKeyedUnarchiver.unarchiveObject(withFile: documentsPath + "/proxies.data") as? [TGProxyItem] var selectedProxy: (ProxyServerSettings, Bool)? if let data = loadLegacyCustomProperyData(database: database, key: "socksProxyData"), let dict = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String: Any], let host = dict["ip"] as? String, let port = dict["port"] as? Int { let inactive = (dict["inactive"] as? Bool) ?? true var connection: ProxyServerConnection? if let secretString = dict["secret"] as? String { let secret = dataWithHexString(secretString) var secretIsValid = false if secret.count == 16 { secretIsValid = true } else if secret.count == 17 && MTSocksProxySettings.secretSupportsExtendedPadding(secret) { secretIsValid = true } if secretIsValid { connection = .mtp(secret: secret) } } else { connection = .socks5(username: (dict["username"] as? String) ?? "", password: (dict["password"] as? String) ?? "") } if let connection = connection { selectedProxy = (ProxyServerSettings(host: host, port: convertLegacyProxyPort(port), connection: connection), !inactive) } } var passcodeChallenge: PostboxAccessChallengeData? if let data = try? Data(contentsOf: URL(fileURLWithPath: documentsPath + "/x.y")) { let reader = BufferReader(Buffer(data: data)) if let mode = reader.readBytesAsInt32(1), let length = reader.readInt32(), let passwordData = reader.readBuffer(Int(length))?.makeData(), let passwordText = String(data: passwordData, encoding: .utf8) { var lockTimeout: Int32? if let value = UserDefaults.standard.object(forKey: "Passcode_lockTimeout") as? Int { if value == 0 { lockTimeout = nil } else { lockTimeout = max(60, Int32(clamping: value)) } } else { lockTimeout = 1 * 60 * 60 } if mode == 3 { passcodeChallenge = .numericalPassword(value: passwordText, timeout: lockTimeout, attempts: nil) } else if mode == 4 { passcodeChallenge = PostboxAccessChallengeData.plaintextPassword(value: passwordText, timeout: lockTimeout, attempts: nil) } } } var passcodeEnableBiometrics: Bool = true if let value = UserDefaults.standard.object(forKey: "Passcode_useTouchId") as? Bool { passcodeEnableBiometrics = value } var localization: TGLocalization? if let nativeDocumentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first { localization = NSKeyedUnarchiver.unarchiveObject(withFile: nativeDocumentsPath + "/localization") as? TGLocalization } return account.postbox.transaction { transaction -> Void in transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.presentationThemeSettings, { current in var settings = (current as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings if let presentationState = presentationState { switch presentationState.pallete { case 1: settings.theme = .builtin(.day) if presentationState.userInfo != 0 { settings.themeAccentColor = presentationState.userInfo } settings.chatWallpaper = .color(0xffffff) case 2: settings.theme = .builtin(.nightGrayscale) settings.chatWallpaper = .color(0x00000) case 3: settings.theme = .builtin(.nightAccent) settings.chatWallpaper = .color(0x18222D) default: settings.theme = .builtin(.dayClassic) settings.chatWallpaper = .builtin } let fontSizeMap: [Int32: PresentationFontSize] = [ 14: .extraSmall, 15: .small, 16: .medium, 17: .regular, 19: .large, 23: .extraLarge, 26: .extraLargeX2 ] settings.fontSize = fontSizeMap[presentationState.fontSize] ?? .regular if presentationState.userInfo != 0 { settings.themeAccentColor = presentationState.userInfo } } if let autoNightPreferences = autoNightPreferences { let nightTheme: PresentationBuiltinThemeReference switch autoNightPreferences.preferredPalette { case 1: nightTheme = .nightGrayscale default: nightTheme = .nightAccent } switch autoNightPreferences.mode { case TGPresentationAutoNightModeSunsetSunrise: settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .timeBased(setting: .automatic(latitude: Double(autoNightPreferences.latitude), longitude: Double(autoNightPreferences.longitude), sunset: autoNightPreferences.scheduleStart, sunrise: autoNightPreferences.scheduleEnd, localizedName: autoNightPreferences.cachedLocationName)), theme: nightTheme) case TGPresentationAutoNightModeScheduled: settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .timeBased(setting: .manual(fromSeconds: autoNightPreferences.scheduleStart, toSeconds: autoNightPreferences.scheduleEnd)), theme: nightTheme) case TGPresentationAutoNightModeBrightness: settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .brightness(threshold: Double(autoNightPreferences.brightnessThreshold)), theme: nightTheme) default: break } } if let wallpaperInfo = wallpaperInfo as? TGBuiltinWallpaperInfo, wallpaperInfo.isDefault() { settings.chatWallpaper = .builtin } else if let wallpaperInfo = wallpaperInfo as? TGRemoteWallpaperInfo, let data = try? Data(contentsOf: URL(fileURLWithPath: documentsPath + "/wallpaper-data/_currentWallpaper.jpg")), let image = UIImage(data: data) { let url = wallpaperInfo.fullscreenUrl()! if let resource = resourceFromLegacyImageUrl(url) { settings.chatWallpaper = .image([TelegramMediaImageRepresentation(dimensions: image.size, resource: resource)]) account.postbox.mediaBox.storeResourceData(resource.id, data: data) } } else if let wallpaperInfo = wallpaperInfo as? TGColorWallpaperInfo { settings.chatWallpaper = .color(Int32(bitPattern: wallpaperInfo.color)) } else if let data = try? Data(contentsOf: URL(fileURLWithPath: documentsPath + "/wallpaper-data/_currentWallpaper.jpg")), let image = UIImage(data: data) { let resource = LocalFileMediaResource(fileId: arc4random64()) settings.chatWallpaper = .image([TelegramMediaImageRepresentation(dimensions: image.size, resource: resource)]) account.postbox.mediaBox.storeResourceData(resource.id, data: data) } return settings }) transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings, { current in var settings: AutomaticMediaDownloadSettings = current as? AutomaticMediaDownloadSettings ?? .defaultSettings if let preferences = autoDownloadPreferences, !preferences.isDefaultPreferences() { settings.masterEnabled = !preferences.disabled let peerPaths: [(WritableKeyPath, TGAutoDownloadMode, TGAutoDownloadMode)] = [ (\AutomaticMediaDownloadPeers.contacts, TGAutoDownloadModeCellularContacts, TGAutoDownloadModeWifiContacts), (\AutomaticMediaDownloadPeers.channels, TGAutoDownloadModeCellularChannels, TGAutoDownloadModeWifiChannels), (\AutomaticMediaDownloadPeers.groups, TGAutoDownloadModeCellularGroups, TGAutoDownloadModeWifiGroups), (\AutomaticMediaDownloadPeers.otherPrivate, TGAutoDownloadModeCellularPrivateChats, TGAutoDownloadModeWifiPrivateChats) ] let categoryPaths: [(WritableKeyPath, TGAutoDownloadMode, Int32)] = [ (\AutomaticMediaDownloadCategories.photo, preferences.photos, Int32.max), (\AutomaticMediaDownloadCategories.file, preferences.documents, preferences.maximumDocumentSize), (\AutomaticMediaDownloadCategories.video, preferences.videos, preferences.maximumVideoSize), (\AutomaticMediaDownloadCategories.videoMessage, preferences.videoMessages, 1 * 1024 * 1024), (\AutomaticMediaDownloadCategories.voiceMessage, preferences.voiceMessages, 1 * 1024 * 1024) ] for (categoryPath, category, maxSize) in categoryPaths { for (peerPath, cellular, wifi) in peerPaths { let targetPath = peerPath.appending(path: categoryPath) settings.peers[keyPath: targetPath] = AutomaticMediaDownloadCategory(cellular: (category.rawValue & cellular.rawValue) != 0, wifi: (category.rawValue & wifi.rawValue) != 0, sizeLimit: maxSize) } } } if let parsedAutoplayGifs = parsedAutoplayGifs { settings.autoplayGifs = parsedAutoplayGifs } return settings }) transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.inAppNotificationSettings, { current in var settings: InAppNotificationSettings = current as? InAppNotificationSettings ?? .defaultSettings if let soundEnabled = soundEnabled { settings.playSounds = soundEnabled } if let vibrationEnabled = vibrationEnabled { settings.vibrate = vibrationEnabled } if let bannerEnabled = bannerEnabled { settings.displayPreviews = bannerEnabled } return settings }) transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.voiceCallSettings, { current in var settings: VoiceCallSettings = current as? VoiceCallSettings ?? .defaultSettings if let callsDataUsageMode = callsDataUsageMode { switch callsDataUsageMode { case 1: settings.dataSaving = .cellular case 2: settings.dataSaving = .always default: settings.dataSaving = .never } } if let callsDisableP2P = callsDisableP2P, callsDisableP2P { settings.legacyP2PMode = .never } if let callsDisableCallKit = callsDisableCallKit, callsDisableCallKit { settings.enableSystemIntegration = false } return settings }) transaction.updatePreferencesEntry(key: PreferencesKeys.proxySettings, { current in var settings: ProxySettings = current as? ProxySettings ?? .defaultSettings if let callsUseProxy = callsUseProxy { settings.useForCalls = callsUseProxy } if let proxyList = proxyList { for item in proxyList { let connection: ProxyServerConnection? if item.isMTProxy, let secret = item.secret { let data = dataWithHexString(secret) var secretIsValid = false if data.count == 16 { secretIsValid = true } else if data.count == 17 && MTSocksProxySettings.secretSupportsExtendedPadding(data) { secretIsValid = true } if secretIsValid { connection = .mtp(secret: data) } else { connection = nil } } else if !item.isMTProxy { connection = .socks5(username: item.username ?? "", password: item.password ?? "") } else { connection = nil } if let connection = connection { settings.servers.append(ProxyServerSettings(host: item.server, port: convertLegacyProxyPort(Int(item.port)), connection: connection)) } } } if let (server, active) = selectedProxy { if !settings.servers.contains(server) { settings.servers.insert(server, at: 0) } settings.activeServer = server settings.enabled = active } return settings }) transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.stickerSettings, { current in var settings: StickerSettings = current as? StickerSettings ?? .defaultSettings if let stickersSuggestMode = stickersSuggestMode { switch stickersSuggestMode { case 1: settings.emojiStickerSuggestionMode = .installed case 2: settings.emojiStickerSuggestionMode = .none default: settings.emojiStickerSuggestionMode = .all } } return settings }) transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.contactSynchronizationSettings, { current in var settings: ContactSynchronizationSettings = current as? ContactSynchronizationSettings ?? .defaultSettings if let contactsInhibitSync = contactsInhibitSync, contactsInhibitSync { settings.synchronizeDeviceContacts = false } return settings }) transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.callListSettings, { current in var settings: CallListSettings = current as? CallListSettings ?? .defaultSettings if let showCallsTab = showCallsTab { settings.showTab = showCallsTab } return settings }) transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.presentationPasscodeSettings, { current in var settings: PresentationPasscodeSettings = current as? PresentationPasscodeSettings ?? .defaultSettings if let passcodeChallenge = passcodeChallenge { transaction.setAccessChallengeData(passcodeChallenge) switch passcodeChallenge { case .none: break case let .numericalPassword(_, timeout, _): settings.autolockTimeout = timeout case let .plaintextPassword(_, timeout, _): settings.autolockTimeout = timeout } settings.enableBiometrics = passcodeEnableBiometrics } return settings }) if let localization = localization { transaction.updatePreferencesEntry(key: PreferencesKeys.localizationSettings, { _ in var entries: [LocalizationEntry] = [] for (key, value) in localization.dict() { entries.append(LocalizationEntry.string(key: key, value: value)) } return LocalizationSettings(primaryComponent: LocalizationComponent(languageCode: localization.code, localizedName: "", localization: Localization(version: 0, entries: entries), customPluralizationCode: nil), secondaryComponent: nil) }) } if let secretInlineBotsInitialized = secretInlineBotsInitialized, secretInlineBotsInitialized { ApplicationSpecificNotice.setSecretChatInlineBotUsage(transaction: transaction) } if let allowSecretWebpagesInitialized = allowSecretWebpagesInitialized, allowSecretWebpagesInitialized, let allowSecretWebpages = allowSecretWebpages { ApplicationSpecificNotice.setSecretChatLinkPreviews(transaction: transaction, value: allowSecretWebpages) } transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.musicPlaybackSettings, { current in var settings: MusicPlaybackSettings = current as? MusicPlaybackSettings ?? .defaultSettings if let musicPlayerOrderType = musicPlayerOrderType { switch musicPlayerOrderType { case 1: settings.order = .reversed case 2: settings.order = .random default: settings.order = .regular } } if let musicPlayerRepeatType = musicPlayerRepeatType { switch musicPlayerRepeatType { case 1: settings.looping = .all case 2: settings.looping = .item default: settings.looping = .none } } return settings }) transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.instantPagePresentationSettings, { current in var settings: InstantPagePresentationSettings = current as? InstantPagePresentationSettings ?? .defaultSettings if let instantPageFontSize = instantPageFontSize { switch instantPageFontSize { case 0.85: settings.fontSize = .small case 1.15: settings.fontSize = .large case 1.30: settings.fontSize = .xlarge case 1.50: settings.fontSize = .xxlarge default: settings.fontSize = .standard } } if let instantPageFontSerif = instantPageFontSerif { settings.forceSerif = instantPageFontSerif == 1 } if let instantPageTheme = instantPageTheme { switch instantPageTheme { case 1: settings.themeType = .sepia case 2: settings.themeType = .gray case 3: settings.themeType = .dark default: settings.themeType = .light } } if let instantPageAutoNightMode = instantPageAutoNightMode { settings.autoNightMode = instantPageAutoNightMode == 1 } return settings }) } |> ignoreValues } }