Procházet zdrojové kódy

Merge pull request #1526 from deltachat/fix_forgotten_background

cleanup temporary Images and keep background image
cyBerta před 3 roky
rodič
revize
43a55712ec

+ 2 - 2
DcShare/Helper/ShareAttachment.swift

@@ -83,7 +83,7 @@ class ShareAttachment {
                 self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
                 self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
             }
             }
             if let result = result {
             if let result = result {
-                let path = ImageFormat.saveImage(image: result)
+                let path = ImageFormat.saveImage(image: result, directory: .cachesDirectory)
                 let msg = self.dcContext.newMessage(viewType: DC_MSG_GIF)
                 let msg = self.dcContext.newMessage(viewType: DC_MSG_GIF)
                 msg.setFile(filepath: path)
                 msg.setFile(filepath: path)
                 self.messages.append(msg)
                 self.messages.append(msg)
@@ -114,7 +114,7 @@ class ShareAttachment {
                 result = nil
                 result = nil
             }
             }
             if let result = result,
             if let result = result,
-               let path = ImageFormat.saveImage(image: result) {
+               let path = ImageFormat.saveImage(image: result, directory: .cachesDirectory) {
                 let msg = self.dcContext.newMessage(viewType: DC_MSG_IMAGE)
                 let msg = self.dcContext.newMessage(viewType: DC_MSG_IMAGE)
                 msg.setFile(filepath: path)
                 msg.setFile(filepath: path)
                 self.messages.append(msg)
                 self.messages.append(msg)

+ 30 - 12
deltachat-ios/Chat/ChatViewController.swift

@@ -60,12 +60,19 @@ class ChatViewController: UITableViewController {
     public lazy var backgroundContainer: UIImageView = {
     public lazy var backgroundContainer: UIImageView = {
         let view = UIImageView()
         let view = UIImageView()
         view.contentMode = .scaleAspectFill
         view.contentMode = .scaleAspectFill
-        if let path = UserDefaults.standard.string(forKey: Constants.Keys.backgroundImageUrl) {
-            view.sd_setImage(with: URL(fileURLWithPath: path), completed: nil)
-        } else if #available(iOS 12.0, *) {
-            view.image = UIImage(named: traitCollection.userInterfaceStyle == .light ? "background_light" : "background_dark")
+        if let backgroundImageName = UserDefaults.standard.string(forKey: Constants.Keys.backgroundImageName) {
+            view.sd_setImage(with: Utils.getBackgroundImageURL(name: backgroundImageName),
+                             placeholderImage: nil,
+                             options: [.retryFailed]) { [weak self] (_, error, _, _) in
+                if let error = error {
+                    logger.error("Error loading background image: \(error.localizedDescription)" )
+                    DispatchQueue.main.async {
+                        self?.setDefaultBackgroundImage(view: view)
+                    }
+                }
+            }
         } else {
         } else {
-            view.image = UIImage(named: "background_light")
+            setDefaultBackgroundImage(view: view)
         }
         }
         return view
         return view
     }()
     }()
@@ -899,7 +906,7 @@ class ChatViewController: UITableViewController {
     override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
     override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
         messageInputBar.inputTextView.layer.borderColor = DcColors.colorDisabled.cgColor
         messageInputBar.inputTextView.layer.borderColor = DcColors.colorDisabled.cgColor
         if #available(iOS 12.0, *),
         if #available(iOS 12.0, *),
-            UserDefaults.standard.string(forKey: Constants.Keys.backgroundImageUrl) == nil {
+            UserDefaults.standard.string(forKey: Constants.Keys.backgroundImageName) == nil {
             backgroundContainer.image = UIImage(named: traitCollection.userInterfaceStyle == .light ? "background_light" : "background_dark")
             backgroundContainer.image = UIImage(named: traitCollection.userInterfaceStyle == .light ? "background_light" : "background_dark")
         }
         }
     }
     }
@@ -1515,15 +1522,16 @@ class ChatViewController: UITableViewController {
     private func stageImage(_ image: UIImage) {
     private func stageImage(_ image: UIImage) {
         DispatchQueue.global().async { [weak self] in
         DispatchQueue.global().async { [weak self] in
             guard let self = self else { return }
             guard let self = self else { return }
-            if let pathInDocDir = ImageFormat.saveImage(image: image) {
+            if let pathInCachesDir = ImageFormat.saveImage(image: image, directory: .cachesDirectory) {
                 DispatchQueue.main.async {
                 DispatchQueue.main.async {
-                    if pathInDocDir.suffix(4).contains(".gif") {
-                        self.draft.setAttachment(viewType: DC_MSG_GIF, path: pathInDocDir)
+                    if pathInCachesDir.suffix(4).contains(".gif") {
+                        self.draft.setAttachment(viewType: DC_MSG_GIF, path: pathInCachesDir)
                     } else {
                     } else {
-                        self.draft.setAttachment(viewType: DC_MSG_IMAGE, path: pathInDocDir)
+                        self.draft.setAttachment(viewType: DC_MSG_IMAGE, path: pathInCachesDir)
                     }
                     }
                     self.configureDraftArea(draft: self.draft)
                     self.configureDraftArea(draft: self.draft)
                     self.messageInputBar.inputTextView.becomeFirstResponder()
                     self.messageInputBar.inputTextView.becomeFirstResponder()
+                    ImageFormat.deleteImage(atPath: pathInCachesDir)
                 }
                 }
             }
             }
         }
         }
@@ -1531,16 +1539,18 @@ class ChatViewController: UITableViewController {
 
 
     private func sendImage(_ image: UIImage, message: String? = nil) {
     private func sendImage(_ image: UIImage, message: String? = nil) {
         DispatchQueue.global().async {
         DispatchQueue.global().async {
-            if let path = ImageFormat.saveImage(image: image) {
+            if let path = ImageFormat.saveImage(image: image, directory: .cachesDirectory) {
                 self.sendAttachmentMessage(viewType: DC_MSG_IMAGE, filePath: path, message: message)
                 self.sendAttachmentMessage(viewType: DC_MSG_IMAGE, filePath: path, message: message)
+                ImageFormat.deleteImage(atPath: path)
             }
             }
         }
         }
     }
     }
 
 
     private func sendSticker(_ image: UIImage) {
     private func sendSticker(_ image: UIImage) {
         DispatchQueue.global().async {
         DispatchQueue.global().async {
-            if let path = ImageFormat.saveImage(image: image) {
+            if let path = ImageFormat.saveImage(image: image, directory: .cachesDirectory) {
                 self.sendAttachmentMessage(viewType: DC_MSG_STICKER, filePath: path, message: nil)
                 self.sendAttachmentMessage(viewType: DC_MSG_STICKER, filePath: path, message: nil)
+                ImageFormat.deleteImage(atPath: path)
             }
             }
         }
         }
     }
     }
@@ -1723,6 +1733,14 @@ class ChatViewController: UITableViewController {
             _ = handleSelection(indexPath: indexPath)
             _ = handleSelection(indexPath: indexPath)
         }
         }
     }
     }
+
+    private func setDefaultBackgroundImage(view: UIImageView) {
+        if #available(iOS 12.0, *) {
+            view.image = UIImage(named: traitCollection.userInterfaceStyle == .light ? "background_light" : "background_dark")
+        } else {
+            view.image = UIImage(named: "background_light")
+        }
+    }
 }
 }
 
 
 // MARK: - BaseMessageCellDelegate
 // MARK: - BaseMessageCellDelegate

+ 15 - 6
deltachat-ios/Controller/SettingsBackgroundSelectionController.swift

@@ -62,8 +62,17 @@ class SettingsBackgroundSelectionController: UIViewController, MediaPickerDelega
         view.contentMode = .scaleAspectFill
         view.contentMode = .scaleAspectFill
         view.translatesAutoresizingMaskIntoConstraints = false
         view.translatesAutoresizingMaskIntoConstraints = false
         view.clipsToBounds = true
         view.clipsToBounds = true
-        if let backgroundImageURL = UserDefaults.standard.string(forKey: Constants.Keys.backgroundImageUrl) {
-            view.sd_setImage(with: URL(fileURLWithPath: backgroundImageURL), placeholderImage: nil, options: .refreshCached, completed: nil)
+        if let backgroundImageName = UserDefaults.standard.string(forKey: Constants.Keys.backgroundImageName) {
+            view.sd_setImage(with: Utils.getBackgroundImageURL(name: backgroundImageName),
+                             placeholderImage: nil,
+                             options: [.retryFailed, .refreshCached]) { [weak self] (_, error, _, _) in
+                    if let error = error {
+                        logger.error("Error loading background image: \(error.localizedDescription)" )
+                        DispatchQueue.main.async {
+                            self?.setDefault(view)
+                        }
+                    }
+                }
         } else {
         } else {
             setDefault(view)
             setDefault(view)
         }
         }
@@ -131,7 +140,7 @@ class SettingsBackgroundSelectionController: UIViewController, MediaPickerDelega
 
 
     @objc private func onDefaultSelected() {
     @objc private func onDefaultSelected() {
         setDefault(backgroundContainer)
         setDefault(backgroundContainer)
-        UserDefaults.standard.set(nil, forKey: Constants.Keys.backgroundImageUrl)
+        UserDefaults.standard.set(nil, forKey: Constants.Keys.backgroundImageName)
         UserDefaults.standard.synchronize()
         UserDefaults.standard.synchronize()
     }
     }
 
 
@@ -145,10 +154,10 @@ class SettingsBackgroundSelectionController: UIViewController, MediaPickerDelega
 
 
     // MARK: MediaPickerDelegate
     // MARK: MediaPickerDelegate
     func onImageSelected(image: UIImage) {
     func onImageSelected(image: UIImage) {
-        if let pathInDocDir = ImageFormat.saveImage(image: image, name: Constants.backgroundImageName) {
-            UserDefaults.standard.set(pathInDocDir, forKey: Constants.Keys.backgroundImageUrl)
+        if let path = ImageFormat.saveImage(image: image, name: Constants.backgroundImageName) {
+            UserDefaults.standard.set(URL(fileURLWithPath: path).lastPathComponent, forKey: Constants.Keys.backgroundImageName)
             UserDefaults.standard.synchronize()
             UserDefaults.standard.synchronize()
-            backgroundContainer.sd_setImage(with: URL(fileURLWithPath: pathInDocDir), placeholderImage: nil, options: .refreshCached, completed: nil)
+            backgroundContainer.sd_setImage(with: URL(fileURLWithPath: path), placeholderImage: nil, options: .refreshCached, completed: nil)
         } else {
         } else {
             logger.error("failed to save background image")
             logger.error("failed to save background image")
         }
         }

+ 1 - 1
deltachat-ios/Helper/Constants.swift

@@ -10,7 +10,7 @@ struct Constants {
         static let deltachatImapEmailKey = "__DELTACHAT_IMAP_EMAIL_KEY__"
         static let deltachatImapEmailKey = "__DELTACHAT_IMAP_EMAIL_KEY__"
         static let deltachatImapPasswordKey = "__DELTACHAT_IMAP_PASSWORD_KEY__"
         static let deltachatImapPasswordKey = "__DELTACHAT_IMAP_PASSWORD_KEY__"
         static let lastSelectedAccountKey = "__DELTACHAT_LAST_SELECTED_ACCOUNT_KEY__"
         static let lastSelectedAccountKey = "__DELTACHAT_LAST_SELECTED_ACCOUNT_KEY__"
-        static let backgroundImageUrl = "__BACKGROUND_IMAGE_URL__"
+        static let backgroundImageName = "__BACKGROUND_IMAGE_NAME__"
         static let notificationTimestamps = "__NOTIFICATION_TIMESTAMPS__"
         static let notificationTimestamps = "__NOTIFICATION_TIMESTAMPS__"
     }
     }
 
 

+ 65 - 10
deltachat-ios/Helper/ImageFormat.swift

@@ -68,12 +68,12 @@ extension ImageFormat {
         return loadImageFrom(data: imageData)
         return loadImageFrom(data: imageData)
     }
     }
 
 
-    public static func saveImage(image: UIImage, name: String? = nil) -> String? {
+    public static func saveImage(image: UIImage, name: String? = nil, directory: FileManager.SearchPathDirectory = .applicationSupportDirectory) -> String? {
         if image.sd_isAnimated,
         if image.sd_isAnimated,
            let data = image.sd_imageData() {
            let data = image.sd_imageData() {
             let format = ImageFormat.get(from: data)
             let format = ImageFormat.get(from: data)
             if format != .unknown {
             if format != .unknown {
-                return ImageFormat.saveImage(data: data, name: name, suffix: format.rawValue)
+                return ImageFormat.saveImage(data: data, name: name, suffix: format.rawValue, directory: directory)
             }
             }
         }
         }
         let suffix = image.isTransparent() ? "png" : "jpg"
         let suffix = image.isTransparent() ? "png" : "jpg"
@@ -84,25 +84,57 @@ extension ImageFormat {
         return saveImage(data: data, name: name, suffix: suffix)
         return saveImage(data: data, name: name, suffix: suffix)
     }
     }
 
 
-    public static func saveImage(data: Data, name: String? = nil, suffix: String) -> String? {
+    // implementation is following Apple's recommendations
+    // https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html
+    public static func saveImage(data: Data, name: String? = nil, suffix: String, directory: FileManager.SearchPathDirectory = .applicationSupportDirectory) -> String? {
         var path: URL?
         var path: URL?
-        guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask,
-                                                              appropriateFor: nil, create: false) as NSURL
-        else { return nil }
+
+        // ensure directory exists (application support dir doesn't exist per default)
+        let fileManager = FileManager.default
+        let urls = fileManager.urls(for: directory, in: .userDomainMask) as [URL]
+        guard let identifier = Bundle.main.bundleIdentifier else {
+            print("err: Could not find bundle identifier")
+            return nil
+        }
+        guard let directoryURL = urls.first else {
+            print("err: Could not find directory url for \(String(describing: directory)) in .userDomainMask")
+            return nil
+        }
+        var subdirectoryURL = directoryURL.appendingPathComponent(identifier)
+        do {
+            if !fileManager.fileExists(atPath: subdirectoryURL.path) {
+                try fileManager.createDirectory(at: subdirectoryURL, withIntermediateDirectories: true, attributes: nil)
+            }
+        } catch {
+            print("err: \(error.localizedDescription)")
+            return nil
+        }
+
+        // Opt out from iCloud backup
+        var resourceValues: URLResourceValues = URLResourceValues()
+        resourceValues.isExcludedFromBackup = true
+        do {
+            try subdirectoryURL.setResourceValues(resourceValues)
+        } catch {
+            print("err: \(error.localizedDescription)")
+            return nil
+        }
+
+        // add file name to path
         if let name = name {
         if let name = name {
-            path = directory.appendingPathComponent("\(name).\(suffix)")
+            path = subdirectoryURL.appendingPathComponent("\(name).\(suffix)")
         } else {
         } else {
             let timestamp = Double(Date().timeIntervalSince1970)
             let timestamp = Double(Date().timeIntervalSince1970)
-            path = directory.appendingPathComponent("\(timestamp).\(suffix)")
+            path = subdirectoryURL.appendingPathComponent("\(timestamp).\(suffix)")
         }
         }
-
         guard let path = path else { return nil }
         guard let path = path else { return nil }
 
 
+        // write data
         do {
         do {
             try data.write(to: path)
             try data.write(to: path)
             return path.relativePath
             return path.relativePath
         } catch {
         } catch {
-            print(error.localizedDescription)
+            print("err: \(error.localizedDescription)")
             return nil
             return nil
         }
         }
     }
     }
@@ -129,4 +161,27 @@ extension ImageFormat {
         }
         }
         return nil
         return nil
     }
     }
+
+    public static func deleteImage(atPath: String) {
+        if Thread.isMainThread {
+            DispatchQueue.global(qos: .background).async {
+                deleteFile(atPath: atPath)
+            }
+        } else {
+            deleteFile(atPath: atPath)
+        }
+    }
+
+    private static func deleteFile(atPath: String) {
+        let fileManager = FileManager.default
+        if !fileManager.fileExists(atPath: atPath) {
+            return
+        }
+
+        do {
+            try fileManager.removeItem(atPath: atPath)
+        } catch {
+            print("err: \(error.localizedDescription)")
+        }
+    }
 }
 }

+ 14 - 0
deltachat-ios/Helper/Utils.swift

@@ -56,4 +56,18 @@ struct Utils {
         // TODO: add more file suffixes
         // TODO: add more file suffixes
         return url.absoluteString.hasSuffix("wav")
         return url.absoluteString.hasSuffix("wav")
     }
     }
+
+    public static func getBackgroundImageURL(name: String) -> URL? {
+        let fileManager = FileManager.default
+        let urls = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask) as [URL]
+        guard let identifier = Bundle.main.bundleIdentifier else {
+            logger.error("backgroundImageURL: Could not find bundle identifier")
+            return nil
+        }
+        guard let directoryURL = urls.last else {
+            logger.error("backgroundImageURL: Could not find directory url for .applicationSupportDirectory in .userDomainMask")
+            return nil
+        }
+        return directoryURL.appendingPathComponent(identifier).appendingPathComponent(name)
+    }
 }
 }