Migrate wallet addresses storage from UserDefaults to a single JSON file #3760
parent
d205e474b3
commit
0cf51ddbbd
@ -0,0 +1,90 @@ |
||||
// |
||||
// DefaultsWalletAddressesStore.swift |
||||
// AlphaWallet |
||||
// |
||||
// Created by Vladyslav Shepitko on 22.01.2022. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
struct DefaultsWalletAddressesStore: WalletAddressesStoreType { |
||||
|
||||
private struct Keys { |
||||
static let watchAddresses = "watchAddresses" |
||||
static let ethereumAddressesWithPrivateKeys = "ethereumAddressesWithPrivateKeys" |
||||
static let ethereumAddressesWithSeed = "ethereumAddressesWithSeed" |
||||
static let ethereumAddressesProtectedByUserPresence = "ethereumAddressesProtectedByUserPresence" |
||||
} |
||||
let userDefaults: UserDefaults |
||||
var hasWallets: Bool { |
||||
return !wallets.isEmpty |
||||
} |
||||
|
||||
var hasMigratedFromKeystoreFiles: Bool { |
||||
return userDefaults.data(forKey: Keys.ethereumAddressesWithPrivateKeys) != nil |
||||
} |
||||
|
||||
init(userDefaults: UserDefaults) { |
||||
self.userDefaults = userDefaults |
||||
} |
||||
|
||||
var wallets: [Wallet] { |
||||
let watchAddresses = self.watchAddresses.compactMap { AlphaWallet.Address(string: $0) }.map { Wallet(type: .watch($0)) } |
||||
let addressesWithPrivateKeys = ethereumAddressesWithPrivateKeys.compactMap { AlphaWallet.Address(string: $0) }.map { Wallet(type: .real($0)) } |
||||
let addressesWithSeed = ethereumAddressesWithSeed.compactMap { AlphaWallet.Address(string: $0) }.map { Wallet(type: .real($0)) } |
||||
return addressesWithSeed + addressesWithPrivateKeys + watchAddresses |
||||
} |
||||
|
||||
var watchAddresses: [String] { |
||||
get { |
||||
guard let data = userDefaults.data(forKey: Keys.watchAddresses) else { |
||||
return [] |
||||
} |
||||
return NSKeyedUnarchiver.unarchiveObject(with: data) as? [String] ?? [] |
||||
} |
||||
set { |
||||
let data = NSKeyedArchiver.archivedData(withRootObject: newValue) |
||||
userDefaults.set(data, forKey: Keys.watchAddresses) |
||||
} |
||||
} |
||||
|
||||
var ethereumAddressesWithPrivateKeys: [String] { |
||||
get { |
||||
guard let data = userDefaults.data(forKey: Keys.ethereumAddressesWithPrivateKeys) else { |
||||
return [] |
||||
} |
||||
return NSKeyedUnarchiver.unarchiveObject(with: data) as? [String] ?? [] |
||||
} |
||||
set { |
||||
let data = NSKeyedArchiver.archivedData(withRootObject: newValue) |
||||
userDefaults.set(data, forKey: Keys.ethereumAddressesWithPrivateKeys) |
||||
} |
||||
} |
||||
|
||||
var ethereumAddressesWithSeed: [String] { |
||||
get { |
||||
guard let data = userDefaults.data(forKey: Keys.ethereumAddressesWithSeed) else { |
||||
return [] |
||||
} |
||||
return NSKeyedUnarchiver.unarchiveObject(with: data) as? [String] ?? [] |
||||
} |
||||
set { |
||||
let data = NSKeyedArchiver.archivedData(withRootObject: newValue) |
||||
userDefaults.set(data, forKey: Keys.ethereumAddressesWithSeed) |
||||
} |
||||
} |
||||
|
||||
var ethereumAddressesProtectedByUserPresence: [String] { |
||||
get { |
||||
guard let data = userDefaults.data(forKey: Keys.ethereumAddressesProtectedByUserPresence) else { |
||||
return [] |
||||
} |
||||
|
||||
return NSKeyedUnarchiver.unarchiveObject(with: data) as? [String] ?? [] |
||||
} |
||||
set { |
||||
let data = NSKeyedArchiver.archivedData(withRootObject: newValue) |
||||
userDefaults.set(data, forKey: Keys.ethereumAddressesProtectedByUserPresence) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,159 @@ |
||||
// |
||||
// JsonWalletAddressesStore.swift |
||||
// AlphaWallet |
||||
// |
||||
// Created by Vladyslav Shepitko on 22.01.2022. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
struct JsonWalletAddressesStore: WalletAddressesStoreType { |
||||
private static let walletsFolderForTests = "testSuiteWalletsForWalletAddresses" |
||||
static func createStorage() -> StorageType { |
||||
let directoryUrl: URL = { |
||||
if isRunningTests() { |
||||
let cacheDirectoryUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] |
||||
let directory = try! FileManager.default.createSubDirectoryIfNotExists(name: walletsFolderForTests, directory: cacheDirectoryUrl) |
||||
return directory |
||||
} else { |
||||
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) |
||||
return paths[0] |
||||
} |
||||
}() |
||||
|
||||
return FileStorage(fileExtension: "json", directoryUrl: directoryUrl) |
||||
} |
||||
|
||||
static func removeWalletsFolderForTests() { |
||||
guard isRunningTests() else { return } |
||||
|
||||
let cacheDirectoryUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] |
||||
let directory = cacheDirectoryUrl.appendingPathComponent(walletsFolderForTests) |
||||
|
||||
//NOTE: we want to clear all elready created wallets in cache directory while performing tests |
||||
FileManager.default.removeAllItems(directory: directory) |
||||
} |
||||
|
||||
private struct Keys { |
||||
static let walletAddresses = "walletAddresses" |
||||
} |
||||
|
||||
private var storage: StorageType |
||||
private var walletAddresses: WalletAddresses |
||||
|
||||
init(storage: StorageType = JsonWalletAddressesStore.createStorage()) { |
||||
self.storage = storage |
||||
|
||||
if let value: WalletAddresses = storage.load(forKey: Keys.walletAddresses) { |
||||
walletAddresses = value |
||||
} else { |
||||
walletAddresses = WalletAddresses() |
||||
} |
||||
} |
||||
|
||||
var hasAnyStoredData: Bool { |
||||
return storage.dataExists(forKey: Keys.walletAddresses) |
||||
} |
||||
|
||||
var hasWallets: Bool { |
||||
return !wallets.isEmpty |
||||
} |
||||
|
||||
var hasMigratedFromKeystoreFiles: Bool { |
||||
walletAddresses.ethereumAddressesWithPrivateKeys != nil |
||||
} |
||||
|
||||
var wallets: [Wallet] { |
||||
let watchAddresses = self.watchAddresses.compactMap { AlphaWallet.Address(string: $0) }.map { Wallet(type: .watch($0)) } |
||||
let addressesWithPrivateKeys = ethereumAddressesWithPrivateKeys.compactMap { AlphaWallet.Address(string: $0) }.map { Wallet(type: .real($0)) } |
||||
let addressesWithSeed = ethereumAddressesWithSeed.compactMap { AlphaWallet.Address(string: $0) }.map { Wallet(type: .real($0)) } |
||||
return addressesWithSeed + addressesWithPrivateKeys + watchAddresses |
||||
} |
||||
|
||||
var watchAddresses: [String] { |
||||
get { |
||||
walletAddresses.watchAddresses ?? [] |
||||
} |
||||
set { |
||||
walletAddresses.watchAddresses = newValue |
||||
saveWalletCollectionToFile() |
||||
} |
||||
} |
||||
|
||||
var ethereumAddressesWithPrivateKeys: [String] { |
||||
get { |
||||
walletAddresses.ethereumAddressesWithPrivateKeys ?? [] |
||||
} |
||||
set { |
||||
walletAddresses.ethereumAddressesWithPrivateKeys = newValue |
||||
saveWalletCollectionToFile() |
||||
} |
||||
} |
||||
|
||||
var ethereumAddressesWithSeed: [String] { |
||||
get { |
||||
walletAddresses.ethereumAddressesWithSeed ?? [] |
||||
} |
||||
set { |
||||
walletAddresses.ethereumAddressesWithSeed = newValue |
||||
saveWalletCollectionToFile() |
||||
} |
||||
} |
||||
|
||||
var ethereumAddressesProtectedByUserPresence: [String] { |
||||
get { |
||||
walletAddresses.ethereumAddressesProtectedByUserPresence ?? [] |
||||
} |
||||
set { |
||||
walletAddresses.ethereumAddressesProtectedByUserPresence = newValue |
||||
saveWalletCollectionToFile() |
||||
} |
||||
} |
||||
|
||||
private func saveWalletCollectionToFile() { |
||||
guard let data = try? JSONEncoder().encode(walletAddresses) else { |
||||
return |
||||
} |
||||
storage.setData(data, forKey: Keys.walletAddresses) |
||||
} |
||||
} |
||||
|
||||
extension EtherKeystore { |
||||
private static let rawJsonWalletStore = JsonWalletAddressesStore.createStorage() |
||||
|
||||
static func migratedWalletAddressesStore(userDefaults: UserDefaults) -> WalletAddressesStoreType { |
||||
if Features.isJsonFileBasedStorageForWalletAddressesEnabled { |
||||
//NOTE: its quite important to remove test wallets right before fetching, otherwise tests will fails, especially Keystore related |
||||
JsonWalletAddressesStore.removeWalletsFolderForTests() |
||||
|
||||
let jsonWalletAddressesStore = JsonWalletAddressesStore(storage: rawJsonWalletStore) |
||||
if !jsonWalletAddressesStore.hasAnyStoredData { |
||||
|
||||
let userDefaultsWalletAddressesStore = DefaultsWalletAddressesStore(userDefaults: userDefaults) |
||||
if jsonWalletAddressesStore.hasWallets && !userDefaultsWalletAddressesStore.hasWallets { |
||||
return jsonWalletAddressesStore |
||||
} else { |
||||
return userDefaultsWalletAddressesStore.migrate(to: jsonWalletAddressesStore) |
||||
} |
||||
} else { |
||||
return jsonWalletAddressesStore |
||||
} |
||||
} else { |
||||
return DefaultsWalletAddressesStore(userDefaults: userDefaults) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private struct WalletAddresses: Codable { |
||||
var watchAddresses: [String]? |
||||
var ethereumAddressesWithPrivateKeys: [String]? |
||||
var ethereumAddressesWithSeed: [String]? |
||||
var ethereumAddressesProtectedByUserPresence: [String]? |
||||
|
||||
init() { |
||||
watchAddresses = [] |
||||
ethereumAddressesWithPrivateKeys = [] |
||||
ethereumAddressesWithSeed = [] |
||||
ethereumAddressesProtectedByUserPresence = [] |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
// |
||||
// WalletStore.swift |
||||
// AlphaWallet |
||||
// |
||||
// Created by Vladyslav Shepitko on 21.01.2022. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
protocol WalletAddressesStoreMigrationType { |
||||
func migrate(to store: WalletAddressesStoreType) -> WalletAddressesStoreType |
||||
} |
||||
|
||||
protocol WalletAddressesStoreType: WalletAddressesStoreMigrationType { |
||||
var watchAddresses: [String] { get set } |
||||
var ethereumAddressesWithPrivateKeys: [String] { get set } |
||||
var ethereumAddressesWithSeed: [String] { get set } |
||||
var ethereumAddressesProtectedByUserPresence: [String] { get set } |
||||
var hasWallets: Bool { get } |
||||
var wallets: [Wallet] { get } |
||||
var hasMigratedFromKeystoreFiles: Bool { get } |
||||
} |
||||
|
||||
extension WalletAddressesStoreType { |
||||
|
||||
func migrate(to store: WalletAddressesStoreType) -> WalletAddressesStoreType { |
||||
var store = store |
||||
store.watchAddresses = watchAddresses |
||||
store.ethereumAddressesWithPrivateKeys = ethereumAddressesWithPrivateKeys |
||||
store.ethereumAddressesWithSeed = ethereumAddressesWithSeed |
||||
store.ethereumAddressesProtectedByUserPresence = ethereumAddressesProtectedByUserPresence |
||||
|
||||
return store |
||||
} |
||||
} |
Loading…
Reference in new issue