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/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 9c9c95f949..6337a5877e 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: R.string.localizable.diagnose_backup_restore(), 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: R.string.localizable.diagnose_backup_restore()) + 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..d5e79162c0 --- /dev/null +++ b/Mixin/UserInterface/Controllers/Setting/Diagnose/iTunesBackupDiagnosticViewController.swift @@ -0,0 +1,120 @@ +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 { + defer { + DispatchQueue.main.async { + self.diagnoseButton.isBusy = false + } + } + 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)") + } + } + } + + 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: R.string.localizable.toast_copied()) + } + + func textBarRightButton() -> String? { + R.string.localizable.diagnose_copy_output() + } + +} 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)