diff --git a/Podfile b/Podfile index 705f23a53..ae568f035 100644 --- a/Podfile +++ b/Podfile @@ -23,7 +23,7 @@ abstract_target 'GlobalDependencies' do pod 'Reachability' pod 'PureLayout', '~> 3.1.8' pod 'NVActivityIndicatorView' - pod 'YYImage', git: 'https://github.com/signalapp/YYImage' + pod 'YYImage/libwebp', git: 'https://github.com/signalapp/YYImage' pod 'ZXingObjC' pod 'DifferenceKit' end @@ -51,7 +51,7 @@ abstract_target 'GlobalDependencies' do pod 'Reachability' pod 'SAMKeychain' pod 'SwiftProtobuf', '~> 1.5.0' - pod 'YYImage', git: 'https://github.com/signalapp/YYImage' + pod 'YYImage/libwebp', git: 'https://github.com/signalapp/YYImage' pod 'DifferenceKit' end @@ -71,6 +71,7 @@ abstract_target 'GlobalDependencies' do target 'SessionUtilitiesKit' do pod 'SAMKeychain' + pod 'YYImage/libwebp', git: 'https://github.com/signalapp/YYImage' target 'SessionUtilitiesKitTests' do inherit! :complete diff --git a/Podfile.lock b/Podfile.lock index bab1ae426..41b826741 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -29,6 +29,15 @@ PODS: - DifferenceKit/Core - GRDB.swift/SQLCipher (5.24.1): - SQLCipher (>= 3.4.0) + - libwebp (1.2.1): + - libwebp/demux (= 1.2.1) + - libwebp/mux (= 1.2.1) + - libwebp/webp (= 1.2.1) + - libwebp/demux (1.2.1): + - libwebp/webp + - libwebp/mux (1.2.1): + - libwebp/demux + - libwebp/webp (1.2.1) - Nimble (10.0.0) - NVActivityIndicatorView (5.1.1): - NVActivityIndicatorView/Base (= 5.1.1) @@ -121,9 +130,10 @@ PODS: - YapDatabase/SQLCipher/Core - YapDatabase/SQLCipher/Extensions/View (3.1.1): - YapDatabase/SQLCipher/Core - - YYImage (1.0.4): - - YYImage/Core (= 1.0.4) - YYImage/Core (1.0.4) + - YYImage/libwebp (1.0.4): + - libwebp + - YYImage/Core - ZXingObjC (3.6.5): - ZXingObjC/All (= 3.6.5) - ZXingObjC/All (3.6.5) @@ -148,7 +158,7 @@ DEPENDENCIES: - SwiftProtobuf (~> 1.5.0) - WebRTC-lib - YapDatabase/SQLCipher (from `https://github.com/oxen-io/session-ios-yap-database.git`, branch `signal-release`) - - YYImage (from `https://github.com/signalapp/YYImage`) + - YYImage/libwebp (from `https://github.com/signalapp/YYImage`) - ZXingObjC SPEC REPOS: @@ -158,6 +168,7 @@ SPEC REPOS: - CryptoSwift - DifferenceKit - GRDB.swift + - libwebp - Nimble - NVActivityIndicatorView - OpenSSL-Universal @@ -212,6 +223,7 @@ SPEC CHECKSUMS: Curve25519Kit: e63f9859ede02438ae3defc5e1a87e09d1ec7ee6 DifferenceKit: 5659c430bb7fe45876fa32ce5cba5d6167f0c805 GRDB.swift: b3180ce2135fc06a453297889b746b1478c4d8c7 + libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84 NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667 OpenSSL-Universal: e7311447fd2419f57420c79524b641537387eff2 @@ -230,6 +242,6 @@ SPEC CHECKSUMS: YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 6ab902a81a379cc2c0a9a92c334c78d413190338 +PODFILE CHECKSUM: f0857369c4831b2e5c1946345e76e493f3286805 COCOAPODS: 1.11.2 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 0bd253005..e59aa7179 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -817,6 +817,7 @@ FDF2220B2818F38D000A4995 /* SessionApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF2220A2818F38D000A4995 /* SessionApp.swift */; }; FDF2220F281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */; }; FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */; }; + FDF40CDE2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */; }; FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D12553860800C340D1 /* Array+Utilities.swift */; }; FDFD645B27F26D4600808CA1 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */; }; FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; @@ -1871,6 +1872,7 @@ FDF2220A2818F38D000A4995 /* SessionApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionApp.swift; sourceTree = ""; }; FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryInterfaceRequest+Utilities.swift"; sourceTree = ""; }; FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TableRecord+Utilities.swift"; sourceTree = ""; }; + FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_RemoveLegacyYDB.swift; sourceTree = ""; }; FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGeneralCache.swift; sourceTree = ""; }; FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDismissAnimationController.swift; sourceTree = ""; }; @@ -3517,6 +3519,7 @@ FDF0B7412804EA4F004C14C5 /* _002_SetupStandardJobs.swift */, FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */, FD09B7E4288670BB00ED0B66 /* _005_EmojiReacts.swift */, + FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */, ); path = Migrations; sourceTree = ""; @@ -5150,6 +5153,7 @@ FD09799927FFC1A300936362 /* Attachment.swift in Sources */, FD245C5F2850662200B966DD /* OWSWindowManager.m in Sources */, C3471ECB2555356A00297E91 /* MessageSender+Encryption.swift in Sources */, + FDF40CDE2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift in Sources */, FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */, FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */, FD5C7309285007920029977D /* BlindedIdLookup.swift in Sources */, @@ -6893,7 +6897,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 360; + CURRENT_PROJECT_VERSION = 361; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6965,7 +6969,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 360; + CURRENT_PROJECT_VERSION = 361; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Conversations/Message Cells/Content Views/MediaView.swift b/Session/Conversations/Message Cells/Content Views/MediaView.swift index a194e6588..77f2183c1 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaView.swift @@ -129,7 +129,10 @@ public class MediaView: UIView { configure(forError: .failed) return false } - guard attachment.state != .uploaded else { return false } + + // If this message was uploaded on a different device it'll now be seen as 'downloaded' (but + // will still be outgoing - we don't want to show a loading indicator in this case) + guard attachment.state != .uploaded && attachment.state != .downloaded else { return false } let loader = MediaLoaderView() addSubview(loader) @@ -164,9 +167,13 @@ public class MediaView: UIView { } strongSelf.tryToLoadMedia( loadMediaBlock: { applyMediaBlock in - guard attachment.isValid else { return } + guard attachment.isValid else { + self?.configure(forError: .invalid) + return + } guard let filePath: String = attachment.originalFilePath else { owsFailDebug("Attachment stream missing original file path.") + self?.configure(forError: .invalid) return } @@ -177,6 +184,7 @@ public class MediaView: UIView { guard let image: YYImage = media as? YYImage else { owsFailDebug("Media has unexpected type: \(type(of: media))") + self?.configure(forError: .invalid) return } // FIXME: Animated images flicker when reloading the cells (even though they are in the cache) @@ -216,12 +224,18 @@ public class MediaView: UIView { } self?.tryToLoadMedia( loadMediaBlock: { applyMediaBlock in - guard attachment.isValid else { return } + guard attachment.isValid else { + self?.configure(forError: .invalid) + return + } attachment.thumbnail( size: .large, success: { image, _ in applyMediaBlock(image) }, - failure: { Logger.error("Could not load thumbnail") } + failure: { + Logger.error("Could not load thumbnail") + self?.configure(forError: .invalid) + } ) }, applyMediaBlock: { media in @@ -229,6 +243,7 @@ public class MediaView: UIView { guard let image: UIImage = media as? UIImage else { owsFailDebug("Media has unexpected type: \(type(of: media))") + self?.configure(forError: .invalid) return } @@ -277,12 +292,18 @@ public class MediaView: UIView { } self?.tryToLoadMedia( loadMediaBlock: { applyMediaBlock in - guard attachment.isValid else { return } + guard attachment.isValid else { + self?.configure(forError: .invalid) + return + } attachment.thumbnail( size: .medium, success: { image, _ in applyMediaBlock(image) }, - failure: { Logger.error("Could not load thumbnail") } + failure: { + Logger.error("Could not load thumbnail") + self?.configure(forError: .invalid) + } ) }, applyMediaBlock: { media in @@ -290,6 +311,7 @@ public class MediaView: UIView { guard let image: UIImage = media as? UIImage else { owsFailDebug("Media has unexpected type: \(type(of: media))") + self?.configure(forError: .invalid) return } diff --git a/Session/Media Viewing & Editing/MediaDetailViewController.swift b/Session/Media Viewing & Editing/MediaDetailViewController.swift index 80538eb4a..2d89e625d 100644 --- a/Session/Media Viewing & Editing/MediaDetailViewController.swift +++ b/Session/Media Viewing & Editing/MediaDetailViewController.swift @@ -129,7 +129,15 @@ class MediaDetailViewController: OWSViewController, UIScrollViewDelegate, OWSVid // MARK: - Functions private func updateMinZoomScale() { - guard let image: UIImage = image else { + let maybeImageSize: CGSize? = { + switch self.mediaView { + case let imageView as UIImageView: return (imageView.image?.size ?? .zero) + case let imageView as YYAnimatedImageView: return (imageView.image?.size ?? .zero) + default: return nil + } + }() + + guard let imageSize: CGSize = maybeImageSize else { self.scrollView.minimumZoomScale = 1 self.scrollView.maximumZoomScale = 1 self.scrollView.zoomScale = 1 @@ -138,13 +146,13 @@ class MediaDetailViewController: OWSViewController, UIScrollViewDelegate, OWSVid let viewSize: CGSize = self.scrollView.bounds.size - guard image.size.width > 0 && image.size.height > 0 else { - SNLog("Invalid image dimensions (\(image.size.width), \(image.size.height))") - return; + guard imageSize.width > 0 && imageSize.height > 0 else { + SNLog("Invalid image dimensions (\(imageSize.width), \(imageSize.height))") + return } - let scaleWidth: CGFloat = (viewSize.width / image.size.width) - let scaleHeight: CGFloat = (viewSize.height / image.size.height) + let scaleWidth: CGFloat = (viewSize.width / imageSize.width) + let scaleHeight: CGFloat = (viewSize.height / imageSize.height) let minScale: CGFloat = min(scaleWidth, scaleHeight) if minScale != self.scrollView.minimumZoomScale { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 7067ea3db..41b106584 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -240,54 +240,40 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD private func showFailedMigrationAlert(error: Error?) { let alert = UIAlertController( title: "Session", - message: ((error as? StorageError) == StorageError.devRemigrationRequired ? - "The database has changed since the last version and you need to re-migrate (this will close the app and migrate on the next launch)" : - "DATABASE_MIGRATION_FAILED".localized() - ), + message: "DATABASE_MIGRATION_FAILED".localized(), preferredStyle: .alert ) - - switch (error as? StorageError) { - case .devRemigrationRequired: - alert.addAction(UIAlertAction(title: "Re-Migrate Database", style: .default) { _ in - Storage.deleteDatabaseFiles() - try? Storage.deleteDbKeys() - exit(1) - }) - - default: - alert.addAction(UIAlertAction(title: "modal_share_logs_title".localized(), style: .default) { _ in - ShareLogsModal.shareLogs(from: alert) { [weak self] in - self?.showFailedMigrationAlert(error: error) - } - }) - alert.addAction(UIAlertAction(title: "vc_restore_title".localized(), style: .destructive) { _ in - // Remove the legacy database and any message hashes that have been migrated to the new DB - try? SUKLegacy.deleteLegacyDatabaseFilesAndKey() - - Storage.shared.write { db in - try SnodeReceivedMessageInfo.deleteAll(db) - } - - // The re-run the migration (should succeed since there is no data) - AppSetup.runPostSetupMigrations( - migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in - self?.loadingViewController?.updateProgress( - progress: progress, - minEstimatedTotalTime: minEstimatedTotalTime - ) - }, - migrationsCompletion: { [weak self] error, needsConfigSync in - guard error == nil else { - self?.showFailedMigrationAlert(error: error) - return - } - - self?.completePostMigrationSetup(needsConfigSync: needsConfigSync) - } + alert.addAction(UIAlertAction(title: "modal_share_logs_title".localized(), style: .default) { _ in + ShareLogsModal.shareLogs(from: alert) { [weak self] in + self?.showFailedMigrationAlert(error: error) + } + }) + alert.addAction(UIAlertAction(title: "vc_restore_title".localized(), style: .destructive) { _ in + // Remove the legacy database and any message hashes that have been migrated to the new DB + try? SUKLegacy.deleteLegacyDatabaseFilesAndKey() + + Storage.shared.write { db in + try SnodeReceivedMessageInfo.deleteAll(db) + } + + // The re-run the migration (should succeed since there is no data) + AppSetup.runPostSetupMigrations( + migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in + self?.loadingViewController?.updateProgress( + progress: progress, + minEstimatedTotalTime: minEstimatedTotalTime ) - }) - } + }, + migrationsCompletion: { [weak self] error, needsConfigSync in + guard error == nil else { + self?.showFailedMigrationAlert(error: error) + return + } + + self?.completePostMigrationSetup(needsConfigSync: needsConfigSync) + } + ) + }) alert.addAction(UIAlertAction(title: "Close", style: .default) { _ in DDLog.flushLog() diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 44fab95fb..4650c3a01 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 54f9146e2..1b088606e 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 80a0c8a77..0b46eb925 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 68e05fa66..14d251bd4 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 3111cccfe..bdb3af166 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 746e1ec9a..ca123a3e0 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 88c120a32..cd3797083 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 96e1780e4..1b995f4a7 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index e8847da13..ede8337f5 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index c4b749960..2e3a2d95e 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 5286c9227..ccdf6c570 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 46081a6f8..fa3fc1332 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index e04a1ca4a..075b1e650 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 97bb9f7d2..4ece8d754 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 1a3c16ea0..d24c568c4 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index fa1ef8d65..5b35c9d28 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 0f1435bcf..84f0b412d 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index 803c7fcbf..463cc8ddc 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 3cde333fe..07327949b 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index a526427f6..e7ac16f63 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 8a6e7e88e..d2fcb0c8e 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index 831497ef8..f7c393a48 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -652,7 +652,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore our device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "CHATS_TITLE" = "Chats"; "MESSAGE_TRIMMING_TITLE" = "Message Trimming"; "MESSAGE_TRIMMING_OPEN_GROUP_TITLE" = "Delete Old Open Group Messages"; diff --git a/Session/Path/PathVC.swift b/Session/Path/PathVC.swift index 14d09dfa2..b3fdb9b37 100644 --- a/Session/Path/PathVC.swift +++ b/Session/Path/PathVC.swift @@ -1,5 +1,6 @@ import NVActivityIndicatorView import UIKit +import SessionMessagingKit final class PathVC : BaseVC { diff --git a/Session/Settings/SettingsVC.swift b/Session/Settings/SettingsVC.swift index a58081392..27d4ecec0 100644 --- a/Session/Settings/SettingsVC.swift +++ b/Session/Settings/SettingsVC.swift @@ -298,8 +298,6 @@ final class SettingsVC: BaseVC, AvatarViewHelperDelegate { return button } - let debugReMigrateButton = getSettingButton(withTitle: "DEBUG - Re-Migrate Database", color: Colors.destructive, action: #selector(remigrateDatabase)) - let pathButton = getSettingButton(withTitle: NSLocalizedString("vc_path_title", comment: ""), color: Colors.text, action: #selector(showPath)) let pathStatusView = PathStatusView() pathStatusView.set(.width, to: PathStatusView.size) @@ -310,8 +308,6 @@ final class SettingsVC: BaseVC, AvatarViewHelperDelegate { pathStatusView.autoVCenterInSuperview() return [ - getSeparator(), - debugReMigrateButton, getSeparator(), pathButton, getSeparator(), @@ -603,22 +599,6 @@ final class SettingsVC: BaseVC, AvatarViewHelperDelegate { navigationController!.present(shareVC, animated: true, completion: nil) } - @objc private func remigrateDatabase() { - let alert = UIAlertController( - title: "Session", - message: "Are you sure you want to re-migrate from your old database state?\n\nWarning: If you had a migration error and picked the \"Restore your account\" option this will result in a complete loss of data and the need to manually restore from the seed", - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: "Re-migrate", style: .destructive) { _ in - Storage.deleteDatabaseFiles() - try? Storage.deleteDbKeys() - exit(1) - }) - alert.addAction(UIAlertAction(title: "Cancel", style: .default)) - - navigationController?.present(alert, animated: true) - } - @objc private func showPath() { let pathVC = PathVC() navigationController!.pushViewController(pathVC, animated: true) diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 437164c0e..9a1b73bac 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -13,7 +13,9 @@ public enum SNMessagingKit { // Just to make the external API nice [ _003_YDBToGRDBMigration.self ], - [], + [ + _004_RemoveLegacyYDB.self + ], [ _005_EmojiReacts.self ] diff --git a/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift b/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift new file mode 100644 index 000000000..97aa7462e --- /dev/null +++ b/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift @@ -0,0 +1,20 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import Curve25519Kit +import SessionUtilitiesKit +import SessionSnodeKit + +/// This migration removes the legacy YapDatabase files +enum _004_RemoveLegacyYDB: Migration { + static let target: TargetMigrations.Identifier = .messagingKit + static let identifier: String = "RemoveLegacyYDB" + static let needsConfigSync: Bool = false + static let minExpectedRunDuration: TimeInterval = 0.1 + + static func migrate(_ db: Database) throws { + try? SUKLegacy.deleteLegacyDatabaseFilesAndKey() + Storage.update(progress: 1, for: self, in: target) // In case this is the last migration + } +} diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index a0d18d392..e0ceef673 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -339,6 +339,23 @@ extension Attachment { default: return (self.isValid, self.duration) } }() + // Regenerate this just in case we added support since the attachment was inserted into + // the database (eg. manually downloaded in a later update) + let isVisualMedia: Bool = ( + MIMETypeUtil.isImage(contentType) || + MIMETypeUtil.isVideo(contentType) || + MIMETypeUtil.isAnimated(contentType) + ) + let attachmentResolution: CGSize? = { + if let width: UInt = self.width, let height: UInt = self.height, width > 0, height > 0 { + return CGSize(width: Int(width), height: Int(height)) + } + guard isVisualMedia else { return nil } + guard state == .downloaded else { return nil } + guard let originalFilePath: String = originalFilePath else { return nil } + + return Attachment.imageSize(contentType: contentType, originalFilePath: originalFilePath) + }() return Attachment( id: self.id, @@ -351,10 +368,16 @@ extension Attachment { sourceFilename: sourceFilename, downloadUrl: (downloadUrl ?? self.downloadUrl), localRelativeFilePath: (localRelativeFilePath ?? self.localRelativeFilePath), - width: width, - height: height, + width: attachmentResolution.map { UInt($0.width) }, + height: attachmentResolution.map { UInt($0.height) }, duration: duration, - isVisualMedia: isVisualMedia, + isVisualMedia: ( + // Regenerate this just in case we added support since the attachment was inserted into + // the database (eg. manually downloaded in a later update) + MIMETypeUtil.isImage(contentType) || + MIMETypeUtil.isVideo(contentType) || + MIMETypeUtil.isAnimated(contentType) + ), isValid: isValid, encryptionKey: (encryptionKey ?? self.encryptionKey), digest: (digest ?? self.digest), diff --git a/SessionMessagingKit/Database/Models/BlindedIdLookup.swift b/SessionMessagingKit/Database/Models/BlindedIdLookup.swift index 6d63624ae..d5e8704c6 100644 --- a/SessionMessagingKit/Database/Models/BlindedIdLookup.swift +++ b/SessionMessagingKit/Database/Models/BlindedIdLookup.swift @@ -72,6 +72,7 @@ public extension BlindedIdLookup { static func fetchOrCreate( _ db: Database, blindedId: String, + sessionId: String? = nil, openGroupServer: String, openGroupPublicKey: String, isCheckingForOutbox: Bool, @@ -90,6 +91,22 @@ public extension BlindedIdLookup { // If the lookup already has a resolved sessionId then just return it immediately guard lookup.sessionId == nil else { return lookup } + // If we we given a sessionId then validate it is correct and if so save it + if + let sessionId: String = sessionId, + dependencies.sodium.sessionId( + sessionId, + matchesBlindedId: blindedId, + serverPublicKey: openGroupPublicKey, + genericHash: dependencies.genericHash + ) + { + lookup = try lookup + .with(sessionId: sessionId) + .saved(db) + return lookup + } + // We now need to try to match the blinded id to an existing contact, this can only be done by looping // through all approved contacts and generating a blinded id for the provided open group for each to // see if it matches the provided blindedId diff --git a/SessionMessagingKit/File Server/FileServerAPI.swift b/SessionMessagingKit/File Server/FileServerAPI.swift index c92c9499e..694bf53be 100644 --- a/SessionMessagingKit/File Server/FileServerAPI.swift +++ b/SessionMessagingKit/File Server/FileServerAPI.swift @@ -14,7 +14,7 @@ public final class FileServerAPI: NSObject { public static let oldServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" @objc public static let server = "http://filev2.getsession.org" public static let serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" - public static let maxFileSize = 10_000_000 // 10 MB + public static let maxFileSize = (10 * 1024 * 1024) // 10 MB /// The file server has a file size limit of `maxFileSize`, which the Service Nodes try to enforce as well. However, the limit applied by the Service Nodes /// is on the **HTTP request** and not the actual file size. Because the file server expects the file data to be base 64 encoded, the size of the HTTP /// request for a given file will be at least `ceil(n / 3) * 4` bytes, where n is the file size in bytes. This is the minimum size because there might also diff --git a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift index 21abafe58..0777de7a2 100644 --- a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift @@ -372,6 +372,8 @@ public enum GarbageCollectionJob: JobExecutor { } catch { deletionErrors.append(error) } } + + SNLog("[GarbageCollectionJob] Removed \(orphanedAttachmentFiles.count) orphaned attachment\(orphanedAttachmentFiles.count == 1 ? "" : "s")") } // Orphaned profile avatar files (actual deletion) @@ -393,6 +395,8 @@ public enum GarbageCollectionJob: JobExecutor { } catch { deletionErrors.append(error) } } + + SNLog("[GarbageCollectionJob] Removed \(orphanedAvatarFiles.count) orphaned avatar image\(orphanedAvatarFiles.count == 1 ? "" : "s")") } // Report a single file deletion as a job failure (even if other content was successfully removed) diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 5a2fb5c8a..c72dea525 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -617,28 +617,37 @@ public final class OpenGroupManager: NSObject { dependencies: dependencies ) - // If the message was an outgoing message then attempt to unblind the recipient (this will help put - // messages in the correct thread in case of message request approval race conditions as well as - // during device sync'ing and restoration) + // We want to update the BlindedIdLookup cache with the message info so we can avoid using the + // "expensive" lookup when possible + let lookup: BlindedIdLookup = try { + // Minor optimisation to avoid processing the same sender multiple times in the same + // 'handleMessages' call (since the 'mapping' call is done within a transaction we + // will never have a mapping come through part-way through processing these messages) + if let result: BlindedIdLookup = lookupCache[message.recipient] { + return result + } + + return try BlindedIdLookup.fetchOrCreate( + db, + blindedId: (fromOutbox ? + message.recipient : + message.sender + ), + sessionId: (fromOutbox ? + nil : + processedMessage?.threadId + ), + openGroupServer: server.lowercased(), + openGroupPublicKey: openGroup.publicKey, + isCheckingForOutbox: fromOutbox, + dependencies: dependencies + ) + }() + lookupCache[message.recipient] = lookup + + // We also need to set the 'syncTarget' for outgoing messages to be consistent with + // standard messages if fromOutbox { - // Attempt to un-blind the 'message.recipient' - let lookup: BlindedIdLookup = try { - // Minor optimisation to avoid processing the same sender multiple times in the same - // 'handleMessages' call (since the 'mapping' call is done within a transaction we - // will never have a mapping come through part-way through processing these messages) - if let result: BlindedIdLookup = lookupCache[message.recipient] { - return result - } - - return try BlindedIdLookup.fetchOrCreate( - db, - blindedId: message.recipient, - openGroupServer: server.lowercased(), - openGroupPublicKey: openGroup.publicKey, - isCheckingForOutbox: true, - dependencies: dependencies - ) - }() let syncTarget: String = (lookup.sessionId ?? message.recipient) switch processedMessage?.messageInfo.variant { @@ -650,8 +659,6 @@ public final class OpenGroupManager: NSObject { default: break } - - lookupCache[message.recipient] = lookup } if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo { diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 3ad5dbcc9..a63af0b54 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -128,8 +128,8 @@ public final class PushNotificationAPI : NSObject { promises.append( attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) { OnionRequestAPI.sendOnionRequest(request, to: server, using: .v2, with: serverPublicKey) - .map2 { _, response -> Void in - guard let response: UpdateRegistrationResponse = try? response?.decoded(as: UpdateRegistrationResponse.self) else { + .map2 { _, data -> Void in + guard let response: UpdateRegistrationResponse = try? data?.decoded(as: UpdateRegistrationResponse.self) else { return SNLog("Couldn't register device token.") } guard response.body.code != 0 else { diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 92ba77ec0..87e283918 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -186,30 +186,7 @@ public final class Storage { SNLog("[Migration Error] Migration failed with error: \(error)") } - // TODO: Remove this once everyone has updated - var finalError: Error? = error - let jobTableInfo: [Row] = (try? Row.fetchAll(db, sql: "PRAGMA table_info(\(Job.databaseTableName))")) - .defaulting(to: []) - if !jobTableInfo.contains(where: { $0["name"] == "shouldSkipLaunchBecomeActive" }) { - finalError = StorageError.devRemigrationRequired - } - // Forcibly change any 'infoUpdates' on open groups from '-1' to '0' (-1 is invalid) - try? db.execute(literal: """ - UPDATE openGroup - SET infoUpdates = 0 - WHERE openGroup.infoUpdates = -1 - """) - // TODO: Remove this once everyone has updated - let openGroupTableInfo: [Row] = (try? Row.fetchAll(db, sql: "PRAGMA table_info(openGroup)")) - .defaulting(to: []) - if !openGroupTableInfo.contains(where: { $0["name"] == "pollFailureCount" }) { - try? db.execute(literal: """ - ALTER TABLE openGroup - ADD pollFailureCount INTEGER NOT NULL DEFAULT 0 - """) - } - - onComplete(finalError, needsConfigSync) + onComplete(error, needsConfigSync) } // Note: The non-async migration should only be used for unit tests @@ -314,14 +291,13 @@ public final class Storage { try? self.deleteDbKeys() } - // TODO: Change these back to private - public/*private*/ static func deleteDatabaseFiles() { + private static func deleteDatabaseFiles() { OWSFileSystem.deleteFile(databasePath) OWSFileSystem.deleteFile(databasePathShm) OWSFileSystem.deleteFile(databasePathWal) } - public/*private*/ static func deleteDbKeys() throws { + private static func deleteDbKeys() throws { try SSKDefaultKeychainStorage.shared.remove(service: keychainService, key: dbCipherKeySpecKey) } diff --git a/SessionUtilitiesKit/Media/MIMETypeUtil.h b/SessionUtilitiesKit/Media/MIMETypeUtil.h index 811fea270..697e15e3f 100644 --- a/SessionUtilitiesKit/Media/MIMETypeUtil.h +++ b/SessionUtilitiesKit/Media/MIMETypeUtil.h @@ -12,6 +12,9 @@ extern NSString *const OWSMimeTypeImageTiff1; extern NSString *const OWSMimeTypeImageTiff2; extern NSString *const OWSMimeTypeImageBmp1; extern NSString *const OWSMimeTypeImageBmp2; +extern NSString *const OWSMimeTypeImageWebp; +extern NSString *const OWSMimeTypeImageHeic; +extern NSString *const OWSMimeTypeImageHeif; extern NSString *const OWSMimeTypeUnknownForTests; extern NSString *const kOversizeTextAttachmentUTI; @@ -36,6 +39,10 @@ extern NSString *const kSyncMessageFileExtension; + (nullable NSString *)getSupportedExtensionFromImageMIMEType:(NSString *)supportedMIMEType; + (nullable NSString *)getSupportedExtensionFromAnimatedMIMEType:(NSString *)supportedMIMEType; ++ (NSArray *)supportedImageMIMETypes; ++ (NSArray *)supportedAnimatedImageMIMETypes; ++ (NSArray *)supportedVideoMIMETypes; + + (BOOL)isAnimated:(NSString *)contentType; + (BOOL)isImage:(NSString *)contentType; + (BOOL)isVideo:(NSString *)contentType; diff --git a/SessionUtilitiesKit/Media/MIMETypeUtil.m b/SessionUtilitiesKit/Media/MIMETypeUtil.m index e93ed6bf6..469898125 100644 --- a/SessionUtilitiesKit/Media/MIMETypeUtil.m +++ b/SessionUtilitiesKit/Media/MIMETypeUtil.m @@ -19,6 +19,9 @@ NSString *const OWSMimeTypeImageTiff1 = @"image/tiff"; NSString *const OWSMimeTypeImageTiff2 = @"image/x-tiff"; NSString *const OWSMimeTypeImageBmp1 = @"image/bmp"; NSString *const OWSMimeTypeImageBmp2 = @"image/x-windows-bmp"; +NSString *const OWSMimeTypeImageWebp = @"image/webp"; +NSString *const OWSMimeTypeImageHeic = @"image/heic"; +NSString *const OWSMimeTypeImageHeif = @"image/heif"; NSString *const OWSMimeTypeUnknownForTests = @"unknown/mimetype"; NSString *const OWSMimeTypeApplicationZip = @"application/zip"; NSString *const OWSMimeTypeApplicationPdf = @"application/pdf"; @@ -85,7 +88,8 @@ NSString *const kSyncMessageFileExtension = @"bin"; @"image/bmp" : @"bmp", @"image/x-windows-bmp" : @"bmp", @"image/gif" : @"gif", - @"image/x-icon": @"ico" + @"image/x-icon": @"ico", + OWSMimeTypeImageWebp : @"webp" }; }); return result; @@ -97,6 +101,7 @@ NSString *const kSyncMessageFileExtension = @"bin"; dispatch_once(&onceToken, ^{ result = @{ OWSMimeTypeImageGif : @"gif", + OWSMimeTypeImageWebp : @"image/webp", }; }); return result; @@ -175,7 +180,8 @@ NSString *const kSyncMessageFileExtension = @"bin"; @"jpeg" : @"image/jpeg", @"jpg" : @"image/jpeg", @"tif" : @"image/tiff", - @"tiff" : @"image/tiff" + @"tiff" : @"image/tiff", + @"webp" : OWSMimeTypeImageWebp }; }); return result; @@ -187,6 +193,7 @@ NSString *const kSyncMessageFileExtension = @"bin"; dispatch_once(&onceToken, ^{ result = @{ @"gif" : OWSMimeTypeImageGif, + @"image/webp" : OWSMimeTypeImageWebp }; }); return result; @@ -556,6 +563,36 @@ NSString *const kSyncMessageFileExtension = @"bin"; return result; } ++ (NSArray *)supportedImageMIMETypes +{ + static NSArray *result = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + result = [self supportedImageMIMETypesToExtensionTypes].allKeys; + }); + return result; +} + ++ (NSArray *)supportedAnimatedImageMIMETypes +{ + static NSArray *result = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + result = [self supportedAnimatedMIMETypesToExtensionTypes].allKeys; + }); + return result; +} + ++ (NSArray *)supportedVideoMIMETypes +{ + static NSArray *result = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + result = [self supportedVideoMIMETypesToExtensionTypes].allKeys; + }); + return result; +} + + (NSDictionary *)genericMIMETypesToExtensionTypes { static NSDictionary *result = nil; @@ -1386,6 +1423,8 @@ NSString *const kSyncMessageFileExtension = @"bin"; @"image/fif" : @"fif", @"image/g3fax" : @"g3", @"image/gif" : @"gif", + @"image/heic" : @"heic", + @"image/heif" : @"heif", @"image/ief" : @"ief", @"image/jpeg" : @"jpg", @"image/jutvision" : @"jut", @@ -1935,6 +1974,8 @@ NSString *const kSyncMessageFileExtension = @"bin"; @"hal" : @"application/vnd.hal+xml", @"hbci" : @"application/vnd.hbci", @"hdf" : @"application/x-hdf", + @"heic" : @"image/heic", + @"heif" : @"image/heif", @"hh" : @"text/x-c", @"hlp" : @"application/winhlp", @"hpgl" : @"application/vnd.hp-hpgl", diff --git a/SessionUtilitiesKit/Media/NSData+Image.m b/SessionUtilitiesKit/Media/NSData+Image.m index 9d2747809..5af4610cd 100644 --- a/SessionUtilitiesKit/Media/NSData+Image.m +++ b/SessionUtilitiesKit/Media/NSData+Image.m @@ -2,6 +2,8 @@ #import "MIMETypeUtil.h" #import "OWSFileSystem.h" #import +#import +#import #import NS_ASSUME_NONNULL_BEGIN @@ -13,8 +15,18 @@ typedef NS_ENUM(NSInteger, ImageFormat) { ImageFormat_Tiff, ImageFormat_Jpeg, ImageFormat_Bmp, + ImageFormat_Webp, + ImageFormat_Heic, + ImageFormat_Heif, }; +#pragma mark - + +typedef struct { + CGSize pixelSize; + CGFloat depthBytes; +} ImageDimensionInfo; + // FIXME: Refactor all of these to be in Swift against 'Data' @implementation NSData (Image) @@ -47,40 +59,47 @@ typedef NS_ENUM(NSInteger, ImageFormat) { return YES; } -+ (BOOL)ows_isValidImageAtPath:(NSString *)filePath mimeType:(nullable NSString *)mimeType ++ (nullable NSData *)ows_validImageDataAtPath:(NSString *)filePath mimeType:(nullable NSString *)mimeType { if (mimeType.length < 1) { NSString *fileExtension = [filePath pathExtension].lowercaseString; mimeType = [MIMETypeUtil mimeTypeForFileExtension:fileExtension]; } if (mimeType.length < 1) { - return NO; + return nil; } NSNumber *_Nullable fileSize = [OWSFileSystem fileSizeOfPath:filePath]; if (!fileSize) { - return NO; + return nil; } BOOL isAnimated = [MIMETypeUtil isSupportedAnimatedMIMEType:mimeType]; if (isAnimated) { if (fileSize.unsignedIntegerValue > OWSMediaUtils.kMaxFileSizeAnimatedImage) { - return NO; + return nil; } } else if ([MIMETypeUtil isSupportedImageMIMEType:mimeType]) { if (fileSize.unsignedIntegerValue > OWSMediaUtils.kMaxFileSizeImage) { - return NO; + return nil; } } else { - return NO; + return nil; } NSError *error = nil; - NSData *_Nullable data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&error]; - if (!data || error) { + return [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&error]; +} + ++ (BOOL)ows_isValidImageAtPath:(NSString *)filePath mimeType:(nullable NSString *)mimeType +{ + NSData *_Nullable data = [NSData ows_validImageDataAtPath:filePath mimeType:mimeType]; + if (!data) { return NO; } - if (![self ows_hasValidImageDimensionsAtPath:filePath isAnimated:isAnimated]) { + BOOL isAnimated = [MIMETypeUtil isSupportedAnimatedMIMEType:mimeType]; + + if (![self ows_hasValidImageDimensionsAtPath:filePath withData:data mimeType:mimeType isAnimated:isAnimated]) { return NO; } @@ -93,45 +112,98 @@ typedef NS_ENUM(NSInteger, ImageFormat) { if (imageSource == NULL) { return NO; } - BOOL result = [NSData ows_hasValidImageDimensionWithImageSource:imageSource isAnimated:isAnimated]; + + ImageDimensionInfo dimensionInfo = [NSData ows_imageDimensionWithImageSource:imageSource isAnimated:isAnimated]; CFRelease(imageSource); - return result; + + return [NSData ows_isValidImageDimension:dimensionInfo.pixelSize depthBytes:dimensionInfo.depthBytes isAnimated:isAnimated]; } -+ (BOOL)ows_hasValidImageDimensionsAtPath:(NSString *)path isAnimated:(BOOL)isAnimated ++ (BOOL)ows_hasValidImageDimensionsAtPath:(NSString *)path withData:(NSData *)data mimeType:(nullable NSString *)mimeType isAnimated:(BOOL)isAnimated +{ + CGSize imageDimensions = [self ows_imageDimensionsAtPath:path withData:data mimeType:mimeType isAnimated:isAnimated]; + + if (imageDimensions.width < 1 || imageDimensions.height < 1) { + return NO; + } + + return YES; +} + ++ (CGSize)ows_imageDimensionsAtPath:(NSString *)path withData:(nullable NSData *)data mimeType:(nullable NSString *)mimeType isAnimated:(BOOL)isAnimated { NSURL *url = [NSURL fileURLWithPath:path]; if (!url) { - return NO; + return CGSizeZero; + } + + if ([mimeType isEqualToString:OWSMimeTypeImageWebp]) { + NSData *targetData = data; + + if (targetData == nil) { + NSError *error = nil; + NSData *_Nullable loadedData = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:&error]; + + if (!data || error) { + return CGSizeZero; + } + + targetData = loadedData; + } + + CGSize imageSize = [data sizeForWebpData]; + + if (imageSize.width < 1 || imageSize.height < 1) { + return CGSizeZero; + } + + const CGFloat kExpectedBytePerPixel = 4; + CGFloat kMaxValidImageDimension = OWSMediaUtils.kMaxAnimatedImageDimensions; + CGFloat kMaxBytes = kMaxValidImageDimension * kMaxValidImageDimension * kExpectedBytePerPixel; + + if (data.length > kMaxBytes) { + return CGSizeZero; + } + + return imageSize; } CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL); if (imageSource == NULL) { - return NO; + return CGSizeZero; } - BOOL result = [self ows_hasValidImageDimensionWithImageSource:imageSource isAnimated:isAnimated]; + + ImageDimensionInfo dimensionInfo = [self ows_imageDimensionWithImageSource:imageSource isAnimated:isAnimated]; CFRelease(imageSource); - return result; + + if (![self ows_isValidImageDimension:dimensionInfo.pixelSize depthBytes:dimensionInfo.depthBytes isAnimated:isAnimated]) { + return CGSizeZero; + } + + return dimensionInfo.pixelSize; } -+ (BOOL)ows_hasValidImageDimensionWithImageSource:(CGImageSourceRef)imageSource isAnimated:(BOOL)isAnimated ++ (ImageDimensionInfo)ows_imageDimensionWithImageSource:(CGImageSourceRef)imageSource isAnimated:(BOOL)isAnimated { NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL); + ImageDimensionInfo info; + info.pixelSize = CGSizeZero; + info.depthBytes = 0; if (!imageProperties) { - return NO; + return info; } NSNumber *widthNumber = imageProperties[(__bridge NSString *)kCGImagePropertyPixelWidth]; if (!widthNumber) { - return NO; + return info; } CGFloat width = widthNumber.floatValue; NSNumber *heightNumber = imageProperties[(__bridge NSString *)kCGImagePropertyPixelHeight]; if (!heightNumber) { - return NO; + return info; } CGFloat height = heightNumber.floatValue; @@ -139,7 +211,7 @@ typedef NS_ENUM(NSInteger, ImageFormat) { * key is a CFNumberRef. */ NSNumber *depthNumber = imageProperties[(__bridge NSString *)kCGImagePropertyDepth]; if (!depthNumber) { - return NO; + return info; } NSUInteger depthBits = depthNumber.unsignedIntegerValue; // This should usually be 1. @@ -149,13 +221,27 @@ typedef NS_ENUM(NSInteger, ImageFormat) { * The value of this key is CFStringRef. */ NSString *colorModel = imageProperties[(__bridge NSString *)kCGImagePropertyColorModel]; if (!colorModel) { - return NO; + return info; } if (![colorModel isEqualToString:(__bridge NSString *)kCGImagePropertyColorModelRGB] && ![colorModel isEqualToString:(__bridge NSString *)kCGImagePropertyColorModelGray]) { + return info; + } + + // Update the struct to return + info.pixelSize = CGSizeMake(width, height); + info.depthBytes = depthBytes; + + return info; +} + ++ (BOOL)ows_isValidImageDimension:(CGSize)imageSize depthBytes:(CGFloat)depthBytes isAnimated:(BOOL)isAnimated +{ + if (imageSize.width < 1 || imageSize.height < 1 || depthBytes < 1) { + // Invalid metadata. return NO; } - + // We only support (A)RGB and (A)Grayscale, so worst case is 4. const CGFloat kWorseCastComponentsPerPixel = 4; CGFloat bytesPerPixel = kWorseCastComponentsPerPixel * depthBytes; @@ -164,7 +250,7 @@ typedef NS_ENUM(NSInteger, ImageFormat) { CGFloat kMaxValidImageDimension = (isAnimated ? OWSMediaUtils.kMaxAnimatedImageDimensions : OWSMediaUtils.kMaxStillImageDimensions); CGFloat kMaxBytes = kMaxValidImageDimension * kMaxValidImageDimension * kExpectedBytePerPixel; - CGFloat actualBytes = width * height * bytesPerPixel; + CGFloat actualBytes = imageSize.width * imageSize.height * bytesPerPixel; if (actualBytes > kMaxBytes) { return NO; } @@ -205,6 +291,12 @@ typedef NS_ENUM(NSInteger, ImageFormat) { case ImageFormat_Bmp: return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageBmp1] || [mimeType isEqualToString:OWSMimeTypeImageBmp2]); + case ImageFormat_Webp: + return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageWebp]); + case ImageFormat_Heic: + return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageHeic]); + case ImageFormat_Heif: + return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageHeif]); } } @@ -235,9 +327,52 @@ typedef NS_ENUM(NSInteger, ImageFormat) { } else if (byte0 == 0x49 && byte1 == 0x49) { // Intel byte order TIFF return ImageFormat_Tiff; + } else if (byte0 == 0x52 && byte1 == 0x49) { + // First two letters of RIFF tag. + return ImageFormat_Webp; + } + + return [self ows_guessHighEfficiencyImageFormat]; +} + +- (ImageFormat)ows_guessHighEfficiencyImageFormat +{ + // A HEIF image file has the first 16 bytes like + // 0000 0018 6674 7970 6865 6963 0000 0000 + // so in this case the 5th to 12th bytes shall make a string of "ftypheic" + const NSUInteger kHeifHeaderStartsAt = 4; + const NSUInteger kHeifBrandStartsAt = 8; + // We support "heic", "mif1" or "msf1". Other brands are invalid for us for now. + // The length is 4 + 1 because the brand must be terminated with a null. + // Include the null in the comparison to prevent a bogus brand like "heicfake" + // from being considered valid. + const NSUInteger kHeifSupportedBrandLength = 5; + const NSUInteger kTotalHeaderLength = kHeifBrandStartsAt - kHeifHeaderStartsAt + kHeifSupportedBrandLength; + if (self.length < kHeifBrandStartsAt + kHeifSupportedBrandLength) { + return ImageFormat_Unknown; } return ImageFormat_Unknown; + // These are the brands of HEIF formatted files that are renderable by CoreGraphics + const NSString *kHeifBrandHeaderHeic = @"ftypheic\0"; + const NSString *kHeifBrandHeaderHeif = @"ftypmif1\0"; + const NSString *kHeifBrandHeaderHeifStream = @"ftypmsf1\0"; + + // Pull the string from the header and compare it with the supported formats + unsigned char bytes[kTotalHeaderLength]; + [self getBytes:&bytes range:NSMakeRange(kHeifHeaderStartsAt, kTotalHeaderLength)]; + NSData *data = [[NSData alloc] initWithBytes:bytes length:kTotalHeaderLength]; + NSString *marker = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + if ([kHeifBrandHeaderHeic isEqualToString:marker]) { + return ImageFormat_Heic; + } else if ([kHeifBrandHeaderHeif isEqualToString:marker]) { + return ImageFormat_Heif; + } else if ([kHeifBrandHeaderHeifStream isEqualToString:marker]) { + return ImageFormat_Heif; + } else { + return ImageFormat_Unknown; + } } - (NSString *_Nullable)ows_guessMimeType @@ -304,9 +439,18 @@ typedef NS_ENUM(NSInteger, ImageFormat) { + (CGSize)imageSizeForFilePath:(NSString *)filePath mimeType:(NSString *)mimeType { - if (![NSData ows_isValidImageAtPath:filePath mimeType:mimeType]) { + NSData *_Nullable data = [NSData ows_validImageDataAtPath:filePath mimeType:mimeType]; + if (!data) { return CGSizeZero; } + + BOOL isAnimated = [MIMETypeUtil isSupportedAnimatedMIMEType:mimeType]; + CGSize pixelSize = [NSData ows_imageDimensionsAtPath:filePath withData:data mimeType:mimeType isAnimated:isAnimated]; + + if (pixelSize.width > 0 && pixelSize.height > 0 && [mimeType isEqualToString:OWSMimeTypeImageWebp]) { + return pixelSize; + } + NSURL *url = [NSURL fileURLWithPath:filePath]; // With CGImageSource we avoid loading the whole image into memory. @@ -386,6 +530,42 @@ typedef NS_ENUM(NSInteger, ImageFormat) { return result; } +// MARK: - Webp + ++ (CGSize)sizeForWebpFilePath:(NSString *)filePath +{ + NSError *error = nil; + NSData *_Nullable data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&error]; + if (!data || error) { + return CGSizeZero; + } + return [data sizeForWebpData]; +} + +- (CGSize)sizeForWebpData +{ + WebPData webPData = { 0 }; + webPData.bytes = self.bytes; + webPData.size = self.length; + WebPDemuxer *demuxer = WebPDemux(&webPData); + + if (!demuxer) { + return CGSizeZero; + } + + CGFloat canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH); + CGFloat canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT); + CGFloat frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT); + + WebPDemuxDelete(demuxer); + + if (canvasWidth > 0 && canvasHeight > 0 && frameCount > 0) { + return CGSizeMake(canvasWidth, canvasHeight); + } + + return CGSizeZero; +} + @end NS_ASSUME_NONNULL_END