Browse Source

Refactor Share extension item extraction

- download content from urls synchronously, catch errors if timeout
- let user save both data from urls and other items in one (which Safari often does, which can cause duplicates)
- fix error code check

Signed-off-by: Henrik Storch <henrik.storch@nextcloud.com>
Henrik Storch 3 years ago
parent
commit
c4c95b8ebf

+ 8 - 1
Nextcloud.xcodeproj/project.pbxproj

@@ -23,7 +23,8 @@
 		AF22B208277B4E4C00DAB0CC /* NCCreateFormUploadConflictCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F704B5E82430C0B800632F5F /* NCCreateFormUploadConflictCell.swift */; };
 		AF22B209277B4E4C00DAB0CC /* NCCreateFormUploadConflictCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F704B5E62430C06700632F5F /* NCCreateFormUploadConflictCell.xib */; };
 		AF22B20C277C6F4D00DAB0CC /* NCShareCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF22B20B277C6F4D00DAB0CC /* NCShareCell.swift */; };
-		AF2AC8CC27620F9300F249C7 /* XLForm in Frameworks */ = {isa = PBXBuildFile; productRef = AF2AC8CB27620F9300F249C7 /* XLForm */; };
+		AF22B217277D196700DAB0CC /* NCShareExtension+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF22B215277D196700DAB0CC /* NCShareExtension+DataSource.swift */; };
+		AF22B218277D196700DAB0CC /* NCShareExtension+Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF22B216277D196700DAB0CC /* NCShareExtension+Files.swift */; };
 		AF2D7C7C2742556F00ADF566 /* NCShareLinkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF2D7C7B2742556F00ADF566 /* NCShareLinkCell.swift */; };
 		AF2D7C7E2742559100ADF566 /* NCShareUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF2D7C7D2742559100ADF566 /* NCShareUserCell.swift */; };
 		AF4BF614275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4BF613275629E20081CEEF /* NCManageDatabase+Account.swift */; };
@@ -453,6 +454,8 @@
 		3781B9AF23DB2B7E006B4B1D /* AppDelegate+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Menu.swift"; sourceTree = "<group>"; };
 		8491B1CC273BBA82001C8C5B /* UIViewController+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Menu.swift"; sourceTree = "<group>"; };
 		AF22B20B277C6F4D00DAB0CC /* NCShareCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCell.swift; sourceTree = "<group>"; };
+		AF22B215277D196700DAB0CC /* NCShareExtension+DataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCShareExtension+DataSource.swift"; sourceTree = "<group>"; };
+		AF22B216277D196700DAB0CC /* NCShareExtension+Files.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCShareExtension+Files.swift"; sourceTree = "<group>"; };
 		AF2D7C7B2742556F00ADF566 /* NCShareLinkCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareLinkCell.swift; sourceTree = "<group>"; };
 		AF2D7C7D2742559100ADF566 /* NCShareUserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareUserCell.swift; sourceTree = "<group>"; };
 		AF4BF613275629E20081CEEF /* NCManageDatabase+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Account.swift"; sourceTree = "<group>"; };
@@ -1442,6 +1445,8 @@
 			children = (
 				F714803A262EBE3900693E51 /* MainInterface.storyboard */,
 				F7148040262EBE4000693E51 /* NCShareExtension.swift */,
+				AF22B215277D196700DAB0CC /* NCShareExtension+DataSource.swift */,
+				AF22B216277D196700DAB0CC /* NCShareExtension+Files.swift */,
 				AF22B20B277C6F4D00DAB0CC /* NCShareCell.swift */,
 				F7148046262EBE4B00693E51 /* Share-Bridging-Header.h */,
 			);
@@ -2271,6 +2276,7 @@
 				AF22B206277B4E4C00DAB0CC /* NCCreateFormUploadConflict.swift in Sources */,
 				F7BD71E62636EAFC00643C34 /* NCNetworkingE2EE.swift in Sources */,
 				F7F878AF1FB9E3B900599E4F /* NCEndToEndMetadata.swift in Sources */,
+				AF22B218277D196700DAB0CC /* NCShareExtension+Files.swift in Sources */,
 				F702F2D025EE5B5C008F8E80 /* NCGlobal.swift in Sources */,
 				F7EDE4DB262D7BA200414FE6 /* NCCellProtocol.swift in Sources */,
 				F7EDE4D1262D7B8400414FE6 /* NCDataSource.swift in Sources */,
@@ -2281,6 +2287,7 @@
 				F7B8CD96261AF401007C1359 /* NCNetworkingChunkedUpload.swift in Sources */,
 				F7BAADC91ED5A87C00B7EAD4 /* NCDatabase.swift in Sources */,
 				F7D57C8B26317BDE00DE301D /* NCAccountRequest.swift in Sources */,
+				AF22B217277D196700DAB0CC /* NCShareExtension+DataSource.swift in Sources */,
 				F780710A1EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.m in Sources */,
 				F79EC77F26316193004E59D6 /* NCRenameFile.swift in Sources */,
 				AF22B208277B4E4C00DAB0CC /* NCCreateFormUploadConflictCell.swift in Sources */,

+ 4 - 4
Share/NCShareCell.swift

@@ -9,7 +9,7 @@
 import UIKit
 import NCCommunication
 
-protocol NCShareCellDelegate {
+protocol NCShareCellDelegate: AnyObject {
     func removeFile(named fileName: String)
 }
 
@@ -18,7 +18,7 @@ class NCShareCell: UITableViewCell {
     @IBOutlet weak var fileNameCell: UILabel!
     @IBOutlet weak var moreButton: UIButton!
     @IBOutlet weak var sizeCell: UILabel!
-    var delegate: NCShareCellDelegate?
+    weak var delegate: NCShareCellDelegate?
     var fileName = ""
 
     func setup(fileName: String) {
@@ -32,7 +32,7 @@ class NCShareCell: UITableViewCell {
         if let image = UIImage(contentsOfFile: (NSTemporaryDirectory() + fileName)) {
             imageCell?.image = image.resizeImage(size: CGSize(width: 80, height: 80), isAspectRation: true)
         } else {
-            if resultInternalType.iconName.count > 0 {
+            if !resultInternalType.iconName.isEmpty {
                 imageCell?.image = UIImage(named: resultInternalType.iconName)
             } else {
                 imageCell?.image = NCBrandColor.cacheImages.file
@@ -43,7 +43,7 @@ class NCShareCell: UITableViewCell {
 
         let fileSize = NCUtilityFileSystem.shared.getFileSize(filePath: (NSTemporaryDirectory() + fileName))
         sizeCell?.text = CCUtility.transformedSize(fileSize)
-        
+
         moreButton?.setImage(NCUtility.shared.loadImage(named: "deleteScan").image(color: NCBrandColor.shared.label, size: 15), for: .normal)
     }
 

+ 107 - 147
Share/NCShareExtension+Files.swift

@@ -67,174 +67,134 @@ extension NCShareExtension {
             }
         }
     }
+}
 
-    func getFilesExtensionContext(completion: @escaping (_ filesName: [String]) -> Void) {
-
-        var itemsProvider: [NSItemProvider] = []
-        var filesName: [String] = []
-        var conuter = 0
-        let dateFormatter = DateFormatter()
-
-        // ----------------------------------------------------------------------------------------
-
-        // Image
-        func getItem(image: UIImage, fileNameOriginal: String?) {
-
-            var fileName: String = ""
-
-            if let pngImageData = image.pngData() {
-
-                if fileNameOriginal != nil {
-                    fileName = fileNameOriginal!
-                } else {
-                    fileName = "\(dateFormatter.string(from: Date()))\(conuter).png"
+class NCFilesExtensionHandler {
+    var itemsProvider: [NSItemProvider] = []
+    var counter = 0
+    lazy var filesName: [String] = []
+    var completion: ([String]) -> Void
+    let dateFormatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "yyyy-MM-dd HH-mm-ss-"
+        return formatter
+    }()
+
+    @discardableResult
+    init(items: [NSExtensionItem], completion: @escaping ([String]) -> Void) {
+        CCUtility.emptyTemporaryDirectory()
+        self.completion = completion
+        self.itemsProvider = items.compactMap({ $0.attachments }).flatMap { $0.filter({
+            $0.hasItemConformingToTypeIdentifier(kUTTypeItem as String) || $0.hasItemConformingToTypeIdentifier("public.url")
+        }) }
+
+        for (ix, provider) in itemsProvider.enumerated() {
+            provider.loadItem(forTypeIdentifier: provider.typeIdentifier) { [self] item, error in
+                defer {
+                    counter += 1
+                    if counter == itemsProvider.count { completion(self.filesName) }
                 }
+                guard error == nil else { return }
+                var originalName = (dateFormatter.string(from: Date())) + String(ix)
 
-                let filenamePath = NSTemporaryDirectory() + fileName
+                if let url = item as? URL, url.isFileURL, !url.lastPathComponent.isEmpty {
+                    originalName = url.lastPathComponent
+                }
 
-                if (try? pngImageData.write(to: URL(fileURLWithPath: filenamePath), options: [.atomic])) != nil {
-                    filesName.append(fileName)
+                var fileName: String?
+                switch item {
+                case let image as UIImage:
+                    fileName = getItem(image: image, fileName: originalName)
+                case let url as URL:
+                    fileName = getItem(url: url, fileName: originalName)
+                case let data as Data:
+                    fileName = getItem(data: data, fileName: originalName, provider: provider)
+                case let text as String:
+                    fileName = getItem(string: text, fileName: originalName)
+                default: return
                 }
+
+                if let fileName = fileName { filesName.append(fileName) }
             }
         }
+    }
 
-        // URL
-        func getItem(url: NSURL, fileNameOriginal: String?) {
-
-            guard let path = url.path else { return }
-
-            var fileName: String = ""
-
-            if fileNameOriginal != nil {
-                fileName = fileNameOriginal!
-            } else {
-                if let ext = url.pathExtension {
-                    fileName = "\(dateFormatter.string(from: Date()))\(conuter)." + ext
-                }
-            }
+    // Image
+    func getItem(image: UIImage, fileName: String) -> String? {
+        let filenamePath = NSTemporaryDirectory() + fileName
+        guard let pngImageData = image.pngData(),
+              (try? pngImageData.write(to: URL(fileURLWithPath: filenamePath), options: [.atomic])) != nil
+        else { return nil }
+        return fileName
+    }
 
+    // URL
+    // FIXME: Does not work for directories
+    func getItem(url: URL, fileName: String) -> String? {
+        var fileName = fileName
+        guard url.isFileURL else {
+            guard !filesName.contains(url.lastPathComponent) else { return nil }
+            if !url.deletingPathExtension().lastPathComponent.isEmpty { fileName = url.deletingPathExtension().lastPathComponent }
+            fileName += "." + (url.pathExtension.isEmpty ? "html" : url.pathExtension)
             let filenamePath = NSTemporaryDirectory() + fileName
 
             do {
-                try FileManager.default.removeItem(atPath: filenamePath)
-            } catch { }
-
-            do {
-                try FileManager.default.copyItem(atPath: path, toPath: filenamePath)
-
-                do {
-                    let attr = try FileManager.default.attributesOfItem(atPath: filenamePath) as NSDictionary?
-
-                    if let xattr = attr {
-                        if xattr.fileSize() > 0 {
-                            filesName.append(fileName)
-                        }
-                    }
-
-                } catch { }
-            } catch { }
-        }
-
-        // Data
-        func getItem(data: Data, fileNameOriginal: String?, description: String) {
-
-            var fileName: String = ""
-
-            if !data.isEmpty {
-
-                if fileNameOriginal != nil {
-                    fileName = fileNameOriginal!
-                } else {
-                    let fullNameArr = description.components(separatedBy: "\"")
-                    let fileExtArr = fullNameArr[1].components(separatedBy: ".")
-                    let pathExtention = (fileExtArr[fileExtArr.count - 1]).uppercased()
-                    fileName = "\(dateFormatter.string(from: Date()))\(conuter).\(pathExtention)"
-                }
-
-                let filenamePath = NSTemporaryDirectory() + fileName
-                FileManager.default.createFile(atPath: filenamePath, contents: data, attributes: nil)
-                filesName.append(fileName)
-            }
+                let downloadedContent = try Data(contentsOf: url)
+                guard !FileManager.default.fileExists(atPath: filenamePath) else { return nil }
+                try downloadedContent.write(to: URL(fileURLWithPath: filenamePath))
+            } catch { print(error); return nil }
+            return fileName
         }
 
-        // String
-        func getItem(string: NSString, fileNameOriginal: String?) {
+        let filenamePath = NSTemporaryDirectory() + fileName
 
-            var fileName: String = ""
-
-            if string.length > 0 {
-
-                fileName = "\(dateFormatter.string(from: Date()))\(conuter).txt"
-                let filenamePath = NSTemporaryDirectory() + "\(dateFormatter.string(from: Date()))\(conuter).txt"
-                FileManager.default.createFile(atPath: filenamePath, contents: string.data(using: String.Encoding.utf8.rawValue), attributes: nil)
-                filesName.append(fileName)
-            }
-        }
+        try? FileManager.default.removeItem(atPath: filenamePath)
 
-        // ----------------------------------------------------------------------------------------
+        do {
+            try FileManager.default.copyItem(atPath: url.path, toPath: filenamePath)
 
-        guard let inputItems: [NSExtensionItem] = extensionContext?.inputItems as? [NSExtensionItem] else {
-            return completion(filesName)
-        }
+            let attr = try FileManager.default.attributesOfItem(atPath: filenamePath)
+            guard !attr.isEmpty else { return nil }
+            return fileName
+        } catch { return nil }
+    }
 
-        for item: NSExtensionItem in inputItems {
-            if let attachments = item.attachments {
-                if attachments.isEmpty { continue }
-                for itemProvider in attachments {
-                    if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeItem as String) || itemProvider.hasItemConformingToTypeIdentifier("public.url") {
-                        itemsProvider.append(itemProvider)
-                    }
-                }
-            }
+    // Data
+    func getItem(data: Data, fileName: String, provider: NSItemProvider) -> String? {
+        guard !data.isEmpty else { return nil }
+        var fileName = fileName
+
+        if let url = URL(string: fileName), !url.pathExtension.isEmpty {
+            fileName = url.lastPathComponent
+        } else if let name = provider.suggestedName {
+            fileName = name
+        } else if let ext = provider.registeredTypeIdentifiers.last?.split(separator: ".").last {
+            fileName += "." + ext
+        } // else: no file information, use default name without ext
+
+        // when sharing images in safari only data is retuned.
+        // also, when sharing option "Automatic" is slected extension will return both raw data and a url, which will be downloaded, causing the image to appear twice with different names
+        if let image = UIImage(data: data) {
+            return getItem(image: image, fileName: fileName)
         }
 
-        CCUtility.emptyTemporaryDirectory()
-        dateFormatter.dateFormat = "yyyy-MM-dd HH-mm-ss-"
-
-        for itemProvider in itemsProvider {
-
-            var typeIdentifier = ""
-            if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeItem as String) { typeIdentifier = kUTTypeItem as String }
-            if itemProvider.hasItemConformingToTypeIdentifier("public.url") { typeIdentifier = "public.url" }
-
-            itemProvider.loadItem(forTypeIdentifier: typeIdentifier, options: nil, completionHandler: {item, error -> Void in
-
-                if error == nil {
-
-                    var fileNameOriginal: String?
-
-                    if let url = item as? NSURL {
-                        if FileManager.default.fileExists(atPath: url.path ?? "") {
-                            fileNameOriginal = url.lastPathComponent!
-                        } else if url.scheme?.lowercased().contains("http") == true {
-                            fileNameOriginal = "\(dateFormatter.string(from: Date()))\(conuter).html"
-                        } else {
-                            fileNameOriginal = "\(dateFormatter.string(from: Date()))\(conuter)"
-                        }
-                    }
-
-                    if let image = item as? UIImage {
-                       getItem(image: image, fileNameOriginal: fileNameOriginal)
-                    }
-
-                    if let url = item as? URL {
-                        getItem(url: url as NSURL, fileNameOriginal: fileNameOriginal)
-                    }
-
-                    if let data = item as? Data {
-                        getItem(data: data, fileNameOriginal: fileNameOriginal, description: itemProvider.description)
-                    }
+        let filenamePath = NSTemporaryDirectory() + fileName
+        FileManager.default.createFile(atPath: filenamePath, contents: data, attributes: nil)
+        return fileName
+    }
 
-                    if let string = item as? NSString {
-                        getItem(string: string, fileNameOriginal: fileNameOriginal)
-                    }
-                }
+    // String
+    func getItem(string: String, fileName: String) -> String? {
+        guard !string.isEmpty else { return nil }
+        let filenamePath = NSTemporaryDirectory() + fileName + ".txt"
+        FileManager.default.createFile(atPath: filenamePath, contents: string.data(using: String.Encoding.utf8), attributes: nil)
+        return fileName
+    }
+}
 
-                conuter += 1
-                if conuter == itemsProvider.count {
-                    completion(filesName)
-                }
-            })
-        }
+extension NSItemProvider {
+    var typeIdentifier: String {
+        if hasItemConformingToTypeIdentifier("public.url") { return "public.url" } else
+        if hasItemConformingToTypeIdentifier(kUTTypeItem as String) { return kUTTypeItem as String } else { return "" }
     }
 }

+ 16 - 37
Share/NCShareExtension.swift

@@ -147,46 +147,25 @@ class NCShareExtension: UIViewController, NCListCellDelegate, NCEmptyDataSetDele
 
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
+        guard serverUrl.isEmpty else { return }
 
-        if serverUrl == "" {
-
-            if let activeAccount = NCManageDatabase.shared.getActiveAccount() {
-
-                setAccount(account: activeAccount.account)
-                getFilesExtensionContext { filesName in
-
-                    self.filesName = filesName
-                    DispatchQueue.main.async {
-
-                        var saveHtml: [String] = []
-                        var saveOther: [String] = []
-
-                        for fileName in self.filesName {
-                            if (fileName as NSString).pathExtension.lowercased() == "html" {
-                                saveHtml.append(fileName)
-                            } else {
-                                saveOther.append(fileName)
-                            }
-                        }
-
-                        if saveOther.count > 0 && saveHtml.count > 0 {
-                            for file in saveHtml {
-                                self.filesName = self.filesName.filter {$0 != file}
-                            }
-                        }
-
-                        self.setCommandView()
-                    }
-                }
-
-            } else {
-                showAlert(description: "_no_active_account_") {
-                    self.extensionContext?.cancelRequest(withError: NCShareExtensionError.noAccount)
-                }
+        guard let activeAccount = NCManageDatabase.shared.getActiveAccount() else {
+            return showAlert(description: "_no_active_account_") {
+                self.extensionContext?.cancelRequest(withError: NCShareExtensionError.noAccount)
             }
         }
+
+        setAccount(account: activeAccount.account)
+        guard let inputItems = extensionContext?.inputItems as? [NSExtensionItem] else {
+            self.extensionContext?.cancelRequest(withError: NCShareExtensionError.noFiles)
+            return
+        }
+        NCFilesExtensionHandler(items: inputItems) { fileNames in
+            self.filesName = fileNames
+            DispatchQueue.main.async { self.setCommandView() }
+        }
     }
-    
+
     func showAlert(title: String = "_error_", description: String, onDismiss: (() -> Void)? = nil) {
         let alertController = UIAlertController(title: NSLocalizedString(title, comment: ""), message: NSLocalizedString(description, comment: ""), preferredStyle: .alert)
         alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in
@@ -500,7 +479,7 @@ class NCShareExtension: UIViewController, NCListCellDelegate, NCEmptyDataSetDele
 
         } completion: { errorCode, _ in
             defer { self.uploadDispatchGroup?.leave() }
-            if errorCode != 0 {
+            if errorCode == 0 {
                 self.counterUpload += 1
             } else {
                 NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))