Skip to content

Commit

Permalink
Update full backup logic
Browse files Browse the repository at this point in the history
- Fix contacts replace on restore
- Add restore screens
- Refactor navigation for screens
  • Loading branch information
ant013 committed Oct 11, 2023
1 parent ed9b449 commit e5a0366
Show file tree
Hide file tree
Showing 22 changed files with 376 additions and 247 deletions.
40 changes: 20 additions & 20 deletions UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -411,17 +411,24 @@ extension ContactBookManager {
return contacts
}

func restore(contacts: [BackupContact]) throws {
func restore(contacts: [BackupContact], mergePolitics: MergePolitics) throws {
guard let localUrl else {
state = .failed(ContactBookManager.StorageError.localUrlNotAvailable)
return
}

let newContactBook = helper.contactBook(contacts: contacts, lastVersion: state.data?.version)
let resolved: ContactBook

try save(url: localUrl, newContactBook)
switch mergePolitics {
case .replace:
resolved = helper.contactBook(contacts: contacts, lastVersion: state.data?.version)
case .insert:
resolved = helper.insert(contacts: contacts, book: state.data)
}

try save(url: localUrl, resolved)
if remoteSync {
try? saveToICloud(book: newContactBook)
try? saveToICloud(book: resolved)
}
}

Expand All @@ -447,6 +454,10 @@ extension ContactBookManager {
}

extension ContactBookManager {
enum MergePolitics {
case replace, insert
}

enum StorageError: Error {
case cloudUrlNotAvailable
case localUrlNotAvailable
Expand Down
40 changes: 17 additions & 23 deletions UnstoppableWallet/UnstoppableWallet/Models/Contact.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class ContactAddress: Codable, ImmutableMappable, Hashable, Equatable {

enum CodingKeys: String, CodingKey {
case blockchainUid = "blockchain_uid"
case address = "address"
case address
}

init(blockchainUid: String, address: String) {
Expand All @@ -22,27 +22,24 @@ class ContactAddress: Codable, ImmutableMappable, Hashable, Equatable {

func mapping(map: Map) {
blockchainUid >>> map[CodingKeys.blockchainUid.rawValue]
address >>> map[CodingKeys.address.rawValue]
address >>> map[CodingKeys.address.rawValue]
}

func hash(into hasher: inout Hasher) {
hasher.combine(blockchainUid)
hasher.combine(address.lowercased())
}

static func ==(lhs: ContactAddress, rhs: ContactAddress) -> Bool {
static func == (lhs: ContactAddress, rhs: ContactAddress) -> Bool {
lhs.address.lowercased() == rhs.address.lowercased() &&
lhs.blockchainUid == rhs.blockchainUid
lhs.blockchainUid == rhs.blockchainUid
}

}

extension Array where Element == ContactAddress {

static func ==(lhs: [ContactAddress], rhs: [ContactAddress]) -> Bool {
static func == (lhs: [ContactAddress], rhs: [ContactAddress]) -> Bool {
Set(lhs) == Set(rhs)
}

}

class Contact: Codable, ImmutableMappable, Hashable, Equatable {
Expand All @@ -66,13 +63,13 @@ class Contact: Codable, ImmutableMappable, Hashable, Equatable {
}

func mapping(map: Map) {
uid >>> map["uid"]
modifiedAt >>> map["modified_at"]
name >>> map["name"]
addresses >>> map["addresses"]
uid >>> map["uid"]
modifiedAt >>> map["modified_at"]
name >>> map["name"]
addresses >>> map["addresses"]
}

static func ==(lhs: Contact, rhs: Contact) -> Bool {
static func == (lhs: Contact, rhs: Contact) -> Bool {
lhs.uid == rhs.uid
}

Expand All @@ -81,9 +78,8 @@ class Contact: Codable, ImmutableMappable, Hashable, Equatable {
}

func address(blockchainUid: String) -> ContactAddress? {
addresses.first { $0.blockchainUid == blockchainUid }
addresses.first { $0.blockchainUid == blockchainUid }
}

}

class DeletedContact: Codable, ImmutableMappable, Hashable, Equatable {
Expand All @@ -101,18 +97,17 @@ class DeletedContact: Codable, ImmutableMappable, Hashable, Equatable {
}

func mapping(map: Map) {
uid >>> map["uid"]
deletedAt >>> map["deleted_at"]
uid >>> map["uid"]
deletedAt >>> map["deleted_at"]
}

static func ==(lhs: DeletedContact, rhs: DeletedContact) -> Bool {
static func == (lhs: DeletedContact, rhs: DeletedContact) -> Bool {
lhs.uid == rhs.uid
}

func hash(into hasher: inout Hasher) {
hasher.combine(uid)
}

}

class ContactBook: Codable, ImmutableMappable {
Expand All @@ -134,9 +129,8 @@ class ContactBook: Codable, ImmutableMappable {
}

func mapping(map: Map) {
version >>> map["version"]
contacts >>> map["contacts"]
deleted >>> map["deleted"]
version >>> map["version"]
contacts >>> map["contacts"]
deleted >>> map["deleted"]
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ extension AppBackupProvider {
favoritesManager.add(coinUids: raw.watchlistIds)

if !raw.contacts.isEmpty {
try? contactManager.restore(contacts: raw.contacts)
try? contactManager.restore(contacts: raw.contacts, mergePolitics: .replace)
}

evmSyncSourceManager.restore(selected: raw.settings.evmSyncSources.selected, custom: raw.customSyncSources)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Foundation

class ContactBookHelper {

// Merge contacts with replace older contacts by newer
func mergeNewer(lhs: [Contact], rhs: [Contact]) -> [Contact] {
let left = Set(lhs)
Expand All @@ -12,8 +11,8 @@ class ContactBookHelper {

let mergedNewer = intersectionLeft.map { lContact in
if let rContact = right.first(where: { $0.uid == lContact.uid }),
rContact.modifiedAt > lContact.modifiedAt {

rContact.modifiedAt > lContact.modifiedAt
{
return rContact
}
return lContact
Expand All @@ -32,8 +31,8 @@ class ContactBookHelper {

let mergedNewer = intersectionLeft.map { lContact in
if let rContact = right.first(where: { $0.uid == lContact.uid }),
rContact.deletedAt > lContact.deletedAt {

rContact.deletedAt > lContact.deletedAt
{
return rContact
}
return lContact
Expand All @@ -48,7 +47,6 @@ class ContactBookHelper {

let contacts = contacts.filter { contact in
if let deletedIndex = deleted.firstIndex(where: { contact.uid == $0.uid }) {

if deleted[deletedIndex].deletedAt > contact.modifiedAt {
return false
} else {
Expand All @@ -71,10 +69,41 @@ class ContactBookHelper {

return Set(lhsContacts) == Set(rhsContacts) && Set(lhsDeleted) == Set(rhsDeleted)
}

}

extension ContactBookHelper {
func insert(contacts: [BackupContact], book: ContactBook?) -> ContactBook {
var updatedContacts = book?.contacts ?? []

for contact in contacts {
var name = contact.name
if let index = updatedContacts.firstIndex(where: { $0.name == contact.name }) {
if updatedContacts[index].addresses == contact.addresses {
continue
}

name = RestoreFileHelper.resolve(
name: contact.name,
elements: updatedContacts.map { $0.name },
style: "(%d)"
)
}
updatedContacts.append(
Contact(
uid: UUID().uuidString,
modifiedAt: Date().timeIntervalSince1970,
name: name,
addresses: contact.addresses
)
)
}

return ContactBook(
version: (book?.version ?? 0) + 1,
contacts: updatedContacts,
deletedContacts: book?.deleted ?? []
)
}

func update(contact: Contact, book: ContactBook) -> ContactBook {
var contacts = book.contacts
Expand Down Expand Up @@ -104,7 +133,6 @@ extension ContactBookHelper {

// try resolve contact book. If one of them not changed - return it, else return new one
func resolved(lhs: ContactBook, rhs: ContactBook) -> ResolveResult {

if lhs.version > rhs.version {
return .left
}
Expand Down Expand Up @@ -138,34 +166,33 @@ extension ContactBookHelper {

func backupContactBook(contactBook: ContactBook) -> BackupContactBook {
BackupContactBook(
contacts: contactBook
.contacts
.map { BackupContact(uid: $0.uid, name: $0.name, addresses: $0.addresses) }
contacts: contactBook
.contacts
.map { BackupContact(uid: $0.uid, name: $0.name, addresses: $0.addresses) }
)
}

func contactBook(contacts: [BackupContact], lastVersion: Int?) -> ContactBook {
// we need increase version and create new book with latest timestamps for all contacts
ContactBook(
version: (lastVersion ?? 0) + 1,
contacts: contacts
.map { Contact(uid: $0.uid,
modifiedAt: Date().timeIntervalSince1970,
name: $0.name,
addresses: $0.addresses)
},
deletedContacts: [])
version: (lastVersion ?? 0) + 1,
contacts: contacts
.map { Contact(uid: $0.uid,
modifiedAt: Date().timeIntervalSince1970,
name: $0.name,
addresses: $0.addresses)
},
deletedContacts: []
)
}

}

extension ContactBookHelper {

private struct EqualContactData: Equatable, Hashable {
let uid: String
let timestamp: TimeInterval

static func ==(lhs: EqualContactData, rhs: EqualContactData) -> Bool {
static func == (lhs: EqualContactData, rhs: EqualContactData) -> Bool {
lhs.uid == rhs.uid && lhs.timestamp == rhs.timestamp
}

Expand All @@ -181,5 +208,4 @@ extension ContactBookHelper {
case right
case merged(ContactBook)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ extension ContactBookSettingsService {
}

func replace(contacts: [BackupContact]) throws {
try contactManager.restore(contacts: contacts)
try contactManager.restore(contacts: contacts, mergePolitics: .replace)
}

func createBackupFile() throws -> URL {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class RestoreFileConfigurationViewController: KeyboardAwareViewController {
.highlightedDescription(text: "backup_app.restore.notice.description".localized),
],
buttons: [
.init(style: .yellow, title: "backup_app.restore.notice.merge".localized, actionType: .afterClose) { [weak self] in
.init(style: .red, title: "backup_app.restore.notice.merge".localized, actionType: .afterClose) { [weak self] in
self?.viewModel.onTapRestore()
},
.init(style: .transparent, title: "button.cancel".localized, actionType: .afterClose),
Expand All @@ -105,13 +105,23 @@ class RestoreFileConfigurationViewController: KeyboardAwareViewController {
}

private func row(item: BackupAppModule.Item, rowInfo: RowInfo) -> RowProtocol {
tableView.universalRow62(
id: item.title,
title: .body(item.title),
description: .subhead2(item.description),
isFirst: rowInfo.isFirst,
isLast: rowInfo.isLast
)
if let description = item.description {
return tableView.universalRow62(
id: item.title,
title: .body(item.title),
description: .subhead2(description),
isFirst: rowInfo.isFirst,
isLast: rowInfo.isLast
)
} else {
return tableView.universalRow48(
id: item.title,
title: .body(item.title),
value: .subhead1(item.value, color: .themeGray),
isFirst: rowInfo.isFirst,
isLast: rowInfo.isLast
)
}
}

private var descriptionSection: SectionProtocol {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ extension RestoreFileConfigurationViewModel {
}

var otherItems: [BackupAppModule.Item] {
let contactAddressCount = rawBackup.contacts.reduce(into: 0) { $0 += $1.addresses.count }
let contactAddressCount = rawBackup.contacts.count
let watchAccounts = rawBackup
.accounts
.filter { $0.account.watchAccount }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,33 @@ struct RestoreFileHelper {
let filename = NSString(string: url.lastPathComponent).deletingPathExtension

if let oneWallet = try? JSONDecoder().decode(WalletBackup.self, from: data) {

return .init(name: filename, source: .wallet(oneWallet))
}

if let fullBackup = try? JSONDecoder().decode(FullBackup.self, from: data) {

return .init(name: filename, source: .full(fullBackup))
}

throw ParseError.wrongFile
}

static func resolve(name: String, elements: [String], checkRaw: Bool = false, style: String = "%d") -> String {
let name: (String?) -> String = { [name, $0].compactMap { $0 }.joined(separator: " ") }

if checkRaw {
if !elements.contains(where: { $0.lowercased() == name(nil).lowercased() }) {
return name(nil)
}
}

for i in 1 ..< elements.count + 1 {
let newName = name(style.localized(i))
if !elements.contains(where: { $0.lowercased() == newName.lowercased() }) {
return newName
}
}
return name(style.localized(elements.count + 1))
}
}

extension RestoreFileHelper {
Expand Down
Loading

0 comments on commit e5a0366

Please sign in to comment.