From 22115f6ac993185c0734294cd2a5ad0652121e81 Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Tue, 12 Oct 2021 21:41:21 +0800 Subject: [PATCH 1/3] Detect and fix iTunes backup failures --- Mixin.xcodeproj/project.pbxproj | 8 ++ .../Diagnose/DiagnoseViewController.swift | 19 ++- .../Diagnose/iTunesBackupDiagnosticView.xib | 87 +++++++++++++ ...iTunesBackupDiagnosticViewController.swift | 118 ++++++++++++++++++ .../File Management/AppGroupContainer.swift | 2 +- 5 files changed, 230 insertions(+), 4 deletions(-) create mode 100644 Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticView.xib create mode 100644 Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift diff --git a/Mixin.xcodeproj/project.pbxproj b/Mixin.xcodeproj/project.pbxproj index f078aed0c2..1a5ac042a6 100644 --- a/Mixin.xcodeproj/project.pbxproj +++ b/Mixin.xcodeproj/project.pbxproj @@ -606,6 +606,8 @@ 9427D52725E0EB5B00B1EF0E /* PlaylistItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9427D52625E0EB5B00B1EF0E /* PlaylistItem.swift */; }; 942F829825DD56E3004F913B /* opusenc_set_bitrate.c in Sources */ = {isa = PBXBuildFile; fileRef = 942F829725DD56E3004F913B /* opusenc_set_bitrate.c */; }; 9430F9C525E38AB5001CCA2F /* MessageDAO+PlaylistItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9430F9C425E38AB5001CCA2F /* MessageDAO+PlaylistItem.swift */; }; + 943673342715B0C900706F28 /* iTunesBackupDiagnosticViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943673332715B0C900706F28 /* iTunesBackupDiagnosticViewController.swift */; }; + 943673362715B0E100706F28 /* iTunesBackupDiagnosticView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 943673352715B0E100706F28 /* iTunesBackupDiagnosticView.xib */; }; 9438252725EE697300709B7D /* CacheableAssetFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9438252625EE697300709B7D /* CacheableAssetFileManager.swift */; }; 94438D57252DAD73005760AF /* Clip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94438D56252DAD73005760AF /* Clip.swift */; }; 944C656125D9BA0A008BDDD3 /* OggOpusWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944C656025D9BA0A008BDDD3 /* OggOpusWriter.swift */; }; @@ -1583,6 +1585,8 @@ 942F829625DD56E3004F913B /* opusenc_set_bitrate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = opusenc_set_bitrate.h; sourceTree = ""; }; 942F829725DD56E3004F913B /* opusenc_set_bitrate.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = opusenc_set_bitrate.c; sourceTree = ""; }; 9430F9C425E38AB5001CCA2F /* MessageDAO+PlaylistItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageDAO+PlaylistItem.swift"; sourceTree = ""; }; + 943673332715B0C900706F28 /* iTunesBackupDiagnosticViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iTunesBackupDiagnosticViewController.swift; sourceTree = ""; }; + 943673352715B0E100706F28 /* iTunesBackupDiagnosticView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = iTunesBackupDiagnosticView.xib; sourceTree = ""; }; 9438252625EE697300709B7D /* CacheableAssetFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheableAssetFileManager.swift; sourceTree = ""; }; 94438D56252DAD73005760AF /* Clip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clip.swift; sourceTree = ""; }; 944C656025D9BA0A008BDDD3 /* OggOpusWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OggOpusWriter.swift; sourceTree = ""; }; @@ -2640,6 +2644,8 @@ 94B8D184266E37ED00F43CBB /* DiagnoseViewController.swift */, 94B8D188266E41D300F43CBB /* DatabaseDiagnosticView.xib */, 94B8D187266E41D300F43CBB /* DatabaseDiagnosticViewController.swift */, + 943673352715B0E100706F28 /* iTunesBackupDiagnosticView.xib */, + 943673332715B0C900706F28 /* iTunesBackupDiagnosticViewController.swift */, ); path = Diagnose; sourceTree = ""; @@ -3870,6 +3876,7 @@ 7B2A115D22C1EAC900AD029C /* GalleryVideoControlView.xib in Resources */, 7B9A18162428932A00187693 /* PageControlView.xib in Resources */, DF2A0D952295721200CA0280 /* AddressView.xib in Resources */, + 943673362715B0E100706F28 /* iTunesBackupDiagnosticView.xib in Resources */, 7BCEAA782350A29000F07635 /* SharedMediaDataCell.xib in Resources */, E0BC7BB7238FADE000E3EF2C /* MyFavoriteAppProfileMenuItemView.xib in Resources */, 7B81BF2922893F8B00266A77 /* GroupParticipantCell.xib in Resources */, @@ -4299,6 +4306,7 @@ 7B88655920AC330D002D15E4 /* PhotoRepresentableMessageViewModel.swift in Sources */, 7BBD2792221E7C9E0047E7D1 /* CheckmarkView.swift in Sources */, 7BB8E2D124529E910098B1E8 /* AppearanceSettingsViewController.swift in Sources */, + 943673342715B0C900706F28 /* iTunesBackupDiagnosticViewController.swift in Sources */, 7B1ABB07211968D1009FAA6C /* StickersViewController.swift in Sources */, 7B8A6A742456E0EC00ADC9EB /* SharedMediaPostCell.swift in Sources */, DF2EB15C23F93870005E5A4F /* CleanUpUnusedAttachmentJob.swift in Sources */, diff --git a/Mixin/UserInterface/Controllers/Setting/Diagnose/DiagnoseViewController.swift b/Mixin/UserInterface/Controllers/Setting/Diagnose/DiagnoseViewController.swift index 9c9c95f949..0e192c92aa 100644 --- a/Mixin/UserInterface/Controllers/Setting/Diagnose/DiagnoseViewController.swift +++ b/Mixin/UserInterface/Controllers/Setting/Diagnose/DiagnoseViewController.swift @@ -5,6 +5,9 @@ class DiagnoseViewController: SettingsTableViewController { private let dataSource = SettingsDataSource(sections: [ SettingsSection(header: R.string.localizable.diagnose_warning(), rows: [ SettingsRow(title: R.string.localizable.diagnose_database_access(), accessory: .disclosure), + ]), + SettingsSection(rows: [ + SettingsRow(title: "iTunes 备份诊断", accessory: .disclosure), ]) ]) @@ -19,9 +22,19 @@ class DiagnoseViewController: SettingsTableViewController { extension DiagnoseViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let container = ContainerViewController.instance(viewController: DatabaseDiagnosticViewController(), - title: R.string.localizable.diagnose_database_access()) - navigationController?.pushViewController(container, animated: true) + tableView.deselectRow(at: indexPath, animated: true) + switch indexPath.section { + case 0: + let container = ContainerViewController.instance(viewController: DatabaseDiagnosticViewController(), + title: R.string.localizable.diagnose_database_access()) + navigationController?.pushViewController(container, animated: true) + case 1: + let container = ContainerViewController.instance(viewController: iTunesBackupDiagnosticViewController(), + title: "iTunes 备份诊断") + navigationController?.pushViewController(container, animated: true) + default: + break + } } } diff --git a/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticView.xib b/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticView.xib new file mode 100644 index 0000000000..198b577f2c --- /dev/null +++ b/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticView.xib @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift b/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift new file mode 100644 index 0000000000..26c54ab806 --- /dev/null +++ b/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift @@ -0,0 +1,118 @@ +import UIKit +import MixinServices + +class iTunesBackupDiagnosticViewController: UIViewController { + + @IBOutlet weak var diagnoseButton: BusyButton! + @IBOutlet weak var outputTextView: UITextView! + + override func viewDidLoad() { + super.viewDidLoad() + outputTextView.contentInset.left = 20 + outputTextView.contentInset.right = 20 + container?.rightButton.isEnabled = true + } + + @IBAction func diagnose(_ sender: Any) { + outputTextView.text = "" + diagnoseButton.isBusy = true + DispatchQueue.global().async { + let urls = [ + AppGroupContainer.url, + + AppGroupContainer.documentsUrl, + + AppGroupContainer.signalDatabaseUrl, + AppGroupContainer.documentsUrl.appendingPathComponent("signal.db-wal", isDirectory: false), + AppGroupContainer.documentsUrl.appendingPathComponent("signal.db-shm", isDirectory: false), + + AppGroupContainer.accountUrl, + + AppGroupContainer.userDatabaseUrl, + AppGroupContainer.accountUrl.appendingPathComponent("mixin.db-wal", isDirectory: false), + AppGroupContainer.accountUrl.appendingPathComponent("mixin.db-shm", isDirectory: false), + + AppGroupContainer.taskDatabaseUrl, + AppGroupContainer.accountUrl.appendingPathComponent("task.db-wal", isDirectory: false), + AppGroupContainer.accountUrl.appendingPathComponent("task.db-shm", isDirectory: false), + + AttachmentContainer.url + ] + for url in urls { + do { + let values = try url.resourceValues(forKeys: [.isExcludedFromBackupKey]) + let isExcludedFromBackup = values.isExcludedFromBackup ?? true + if isExcludedFromBackup { + try (url as NSURL).setResourceValue(false, forKey: .isExcludedFromBackupKey) + self.output("⚠️ Fixed a non-backup URL: \(url)") + } else { + self.output("✅ Backup URL: \(url)") + } + } catch { + self.output("❌ Failed to manipulate URL: \(url), error: \(error)") + } + } + + let enumerator = FileManager.default.enumerator(at: AttachmentContainer.url, includingPropertiesForKeys: [.isExcludedFromBackupKey]) { url, error in + self.output("❌ Enumeration error on URL: \(url), error: \(error)") + return true + } + guard let enumerator = enumerator else { + self.output("❌ Enumerator is nil") + return + } + var backupCount = 0 + var fixedURLs: [URL] = [] + var failedURLs: [(URL, Error)] = [] + while let url = enumerator.nextObject() as? URL { + do { + let values = try url.resourceValues(forKeys: [.isExcludedFromBackupKey]) + let isExcludedFromBackup = values.isExcludedFromBackup ?? true + if isExcludedFromBackup { + do { + try (url as NSURL).setResourceValue(false, forKey: .isExcludedFromBackupKey) + fixedURLs.append(url) + } catch { + failedURLs.append((url, error)) + } + } else { + backupCount += 1 + } + } catch { + failedURLs.append((url, error)) + } + } + self.output("\nAttachment enumeration:\n✅ \(backupCount) files are OK\n") + for url in fixedURLs { + self.output("⚠️ Non-backup URL is fixed: \(url)") + } + for (url, error) in failedURLs { + self.output("❌ Failed to manipulate URL: \(url), error: \(error)") + } + DispatchQueue.main.async { + self.diagnoseButton.isBusy = false + } + } + } + + private func output(_ text: String) { + DispatchQueue.main.async { + let newLine = text + "\n" + self.outputTextView.text.append(newLine) + } + } + +} + +extension iTunesBackupDiagnosticViewController: ContainerViewControllerDelegate { + + func barRightButtonTappedAction() { + UIPasteboard.general.string = outputTextView.text + showAutoHiddenHud(style: .notification, text: "已复制") + } + + func textBarRightButton() -> String? { + "复制结果" + } + +} diff --git a/MixinServices/MixinServices/Foundation/File Management/AppGroupContainer.swift b/MixinServices/MixinServices/Foundation/File Management/AppGroupContainer.swift index f1d6113733..9c406b998c 100644 --- a/MixinServices/MixinServices/Foundation/File Management/AppGroupContainer.swift +++ b/MixinServices/MixinServices/Foundation/File Management/AppGroupContainer.swift @@ -3,7 +3,7 @@ import Foundation public enum AppGroupContainer { // In iOS, the value is nil when the group identifier is invalid. - static let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)! + public static let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)! public static let documentsUrl: URL = { let url = AppGroupContainer.url.appendingPathComponent("Documents", isDirectory: true) From d2bada73eff113887d10ae35d0ce88e2364a5316 Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Wed, 13 Oct 2021 01:06:29 +0800 Subject: [PATCH 2/3] Localization --- Mixin/Resources/en.lproj/Localizable.strings | 2 ++ Mixin/Resources/zh-Hans.lproj/Localizable.strings | 2 ++ .../Controllers/Setting/Diagnose/DiagnoseViewController.swift | 4 ++-- .../Diagnose/iTunesBackupDiagnosticViewController.swift | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Mixin/Resources/en.lproj/Localizable.strings b/Mixin/Resources/en.lproj/Localizable.strings index c580353fe0..bbb8dae03b 100644 --- a/Mixin/Resources/en.lproj/Localizable.strings +++ b/Mixin/Resources/en.lproj/Localizable.strings @@ -932,6 +932,8 @@ // Diagnose "diagnose_database" = "Database Diagnostics"; "diagnose_database_description" = "Database Diagnostics"; +"diagnose_backup_restore" = "Backup & Restore"; +"diagnose_copy_output" = "Copy Output"; // Notifications "notification_reply" = "Reply"; diff --git a/Mixin/Resources/zh-Hans.lproj/Localizable.strings b/Mixin/Resources/zh-Hans.lproj/Localizable.strings index 100cf1fbd9..5f3c5b29ab 100644 --- a/Mixin/Resources/zh-Hans.lproj/Localizable.strings +++ b/Mixin/Resources/zh-Hans.lproj/Localizable.strings @@ -913,6 +913,8 @@ // Diagnose "diagnose_warning" = "⚠️请在Mixin团队指导下使用"; "diagnose_database_access" = "数据库访问"; +"diagnose_backup_restore" = "备份恢复"; +"diagnose_copy_output" = "复制结果"; "report_title"="给开发人员发送聊天日志?"; "report_button"="把日志发给开发者"; diff --git a/Mixin/UserInterface/Controllers/Setting/Diagnose/DiagnoseViewController.swift b/Mixin/UserInterface/Controllers/Setting/Diagnose/DiagnoseViewController.swift index 0e192c92aa..6337a5877e 100644 --- a/Mixin/UserInterface/Controllers/Setting/Diagnose/DiagnoseViewController.swift +++ b/Mixin/UserInterface/Controllers/Setting/Diagnose/DiagnoseViewController.swift @@ -7,7 +7,7 @@ class DiagnoseViewController: SettingsTableViewController { SettingsRow(title: R.string.localizable.diagnose_database_access(), accessory: .disclosure), ]), SettingsSection(rows: [ - SettingsRow(title: "iTunes 备份诊断", accessory: .disclosure), + SettingsRow(title: R.string.localizable.diagnose_backup_restore(), accessory: .disclosure), ]) ]) @@ -30,7 +30,7 @@ extension DiagnoseViewController: UITableViewDelegate { navigationController?.pushViewController(container, animated: true) case 1: let container = ContainerViewController.instance(viewController: iTunesBackupDiagnosticViewController(), - title: "iTunes 备份诊断") + title: R.string.localizable.diagnose_backup_restore()) navigationController?.pushViewController(container, animated: true) default: break diff --git a/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift b/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift index 26c54ab806..a974153832 100644 --- a/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift +++ b/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift @@ -108,11 +108,11 @@ extension iTunesBackupDiagnosticViewController: ContainerViewControllerDelegate func barRightButtonTappedAction() { UIPasteboard.general.string = outputTextView.text - showAutoHiddenHud(style: .notification, text: "已复制") + showAutoHiddenHud(style: .notification, text: R.string.localizable.toast_copied()) } func textBarRightButton() -> String? { - "复制结果" + R.string.localizable.diagnose_copy_output() } } From 34e37b53eddace38843b618d8a8f753bad16c8e7 Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Wed, 13 Oct 2021 11:21:31 +0800 Subject: [PATCH 3/3] Fix busy indicator not hidden after finished --- .../Diagnose/iTunesBackupDiagnosticViewController.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift b/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift index a974153832..d5e79162c0 100644 --- a/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift +++ b/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift @@ -17,6 +17,11 @@ class iTunesBackupDiagnosticViewController: UIViewController { outputTextView.text = "" diagnoseButton.isBusy = true DispatchQueue.global().async { + defer { + DispatchQueue.main.async { + self.diagnoseButton.isBusy = false + } + } let urls = [ AppGroupContainer.url, @@ -89,9 +94,6 @@ class iTunesBackupDiagnosticViewController: UIViewController { for (url, error) in failedURLs { self.output("❌ Failed to manipulate URL: \(url), error: \(error)") } - DispatchQueue.main.async { - self.diagnoseButton.isBusy = false - } } }