From 4488571f4964dc03ce48b3b55e5728ea86f61445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Tue, 5 May 2020 15:01:51 +0200 Subject: [PATCH 1/6] First implementation of compressing files --- ownCloud.xcodeproj/project.pbxproj | 8 ++ ownCloud/AppDelegate.swift | 1 + ownCloud/Client/Actions/Action.swift | 14 +- .../DocumentEditingAction.swift | 6 +- .../Actions+Extensions/OpenInAction.swift | 12 +- .../Client/Actions/MoreViewController.swift | 6 +- ownCloud/Client/Actions/MoreViewHeader.swift | 27 ++++ .../Client/ClientQueryViewController.swift | 41 +++++- .../Client/Viewer/DisplayViewController.swift | 2 +- .../DownloadItemsHUDViewController.swift | 18 +-- .../FileListTableViewController.swift | 2 +- .../SDK Extensions/OCCore+Extension.swift | 17 +++ ownCloudAppFramework/ZIP Archive/ZIPArchive.h | 14 ++ ownCloudAppFramework/ZIP Archive/ZIPArchive.m | 125 ++++++++++++++++++ 14 files changed, 264 insertions(+), 29 deletions(-) diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 20e999434..b52345421 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -93,6 +93,8 @@ 39B289A8226F1EE000BE0E11 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B289A7226F1EE000BE0E11 /* MessageView.swift */; }; 39B9675022BE0FBA0074DB22 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 593A821320C7D4C5000E2A90 /* Localizable.strings */; }; 39BC9C3023DB831F0097C52D /* DocumentEditingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CD755123D787E400193950 /* DocumentEditingAction.swift */; }; + 39BCDD7F246006AD00FE3D23 /* ZipAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BCDD79246006AC00FE3D23 /* ZipAction.swift */; }; + 39BCDD812460858300FE3D23 /* CompressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BCDD802460858200FE3D23 /* CompressViewController.swift */; }; 39BE385D23435AFE0062A2FE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BE385C23435AFE0062A2FE /* String+Extension.swift */; }; 39CC8AE6228C12100020253B /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CC8AE5228C12100020253B /* Array+Extension.swift */; }; 39CC8B01228C8A950020253B /* MediaUploadSettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CC8B00228C8A950020253B /* MediaUploadSettingsSection.swift */; }; @@ -833,6 +835,8 @@ 39AFC3D0225E72FB00A6D3AE /* GroupSharingTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupSharingTableViewController.swift; sourceTree = ""; }; 39AFC3D7225E79CD00A6D3AE /* GroupSharingEditTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupSharingEditTableViewController.swift; sourceTree = ""; }; 39B289A7226F1EE000BE0E11 /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = ""; }; + 39BCDD79246006AC00FE3D23 /* ZipAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZipAction.swift; sourceTree = ""; }; + 39BCDD802460858200FE3D23 /* CompressViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompressViewController.swift; sourceTree = ""; }; 39BE385C23435AFE0062A2FE /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 39CC8AE5228C12100020253B /* Array+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = ""; }; 39CC8B00228C8A950020253B /* MediaUploadSettingsSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaUploadSettingsSection.swift; sourceTree = ""; }; @@ -1409,6 +1413,7 @@ 23D77FC6212BFBD100DE76F1 /* NamingViewController.swift */, 6E37F48A2188B27D00CF16CA /* Action.swift */, DC3393A322E0A75C00DD3DA4 /* ClientItemResolvingCell.swift */, + 39BCDD802460858200FE3D23 /* CompressViewController.swift */, 39CD755323D8392D00193950 /* EditDocumentViewController.swift */, ); path = Actions; @@ -1758,6 +1763,7 @@ DC62514B225D254500736874 /* UploadBaseAction.swift */, DC625147225CEB2C00736874 /* UploadFileAction.swift */, DC625149225CEB4300736874 /* UploadMediaAction.swift */, + 39BCDD79246006AC00FE3D23 /* ZipAction.swift */, ); path = "Actions+Extensions"; sourceTree = ""; @@ -3368,6 +3374,7 @@ 394804DA225CBDBA00AA8183 /* BreadCrumbTableViewController.swift in Sources */, 4C235CEE21F88C0300A989A8 /* UIViewController+Extension.swift in Sources */, DC27A19D20CAB602008ACB6C /* FileProviderInterfaceManager.swift in Sources */, + 39BCDD7F246006AD00FE3D23 /* ZipAction.swift in Sources */, 4C51727D22DE04BD001BC97F /* ScheduledTaskExtension.swift in Sources */, DCC085512293ED52008CC05C /* DisplaySettingsSection.swift in Sources */, 23EC77582137F3DD0032D4E6 /* PDFViewerViewController.swift in Sources */, @@ -3439,6 +3446,7 @@ DC29F09022974AEA00F77349 /* QueryFileListTableViewController.swift in Sources */, 6E586D002199A78E00F680C4 /* DeleteAction.swift in Sources */, DC3317CE2084966700E36C8F /* ThemeTableViewCell.swift in Sources */, + 39BCDD812460858300FE3D23 /* CompressViewController.swift in Sources */, 3913213822946E5E00EF88F4 /* FileListTableViewController.swift in Sources */, DC0196AB20F7690C00C41B78 /* OCBookmark+FileProvider.m in Sources */, 4C6B78122226B86300C5F3DB /* PhotoAlbumTableViewCell.swift in Sources */, diff --git a/ownCloud/AppDelegate.swift b/ownCloud/AppDelegate.swift index cfbf8ee03..769a9daf8 100644 --- a/ownCloud/AppDelegate.swift +++ b/ownCloud/AppDelegate.swift @@ -85,6 +85,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { OCExtensionManager.shared.addExtension(LinksAction.actionExtension) OCExtensionManager.shared.addExtension(FavoriteAction.actionExtension) OCExtensionManager.shared.addExtension(UnfavoriteAction.actionExtension) + OCExtensionManager.shared.addExtension(ZipAction.actionExtension) if #available(iOS 13.0, *) { if UIDevice.current.isIpad() { // iPad & iOS 13+ only diff --git a/ownCloud/Client/Actions/Action.swift b/ownCloud/Client/Actions/Action.swift index aeec36382..48358c0f4 100644 --- a/ownCloud/Client/Actions/Action.swift +++ b/ownCloud/Client/Actions/Action.swift @@ -173,12 +173,19 @@ class Action : NSObject { // MARK: - Provide Card view controller - class func cardViewController(for item: OCItem, with context: ActionContext, progressHandler: ActionProgressHandler? = nil, completionHandler: ((Action, Error?) -> Void)? = nil) -> UIViewController? { + class func cardViewController(for items: [OCItem], with context: ActionContext, progressHandler: ActionProgressHandler? = nil, completionHandler: ((Action, Error?) -> Void)? = nil) -> UIViewController? { guard let core = context.core else { return nil } let tableViewController = MoreStaticTableViewController(style: .grouped) - let header = MoreViewHeader(for: item, with: core) - let moreViewController = MoreViewController(item: item, core: core, header: header, viewController: tableViewController) + + var moreViewController : MoreViewController! + + if items.count > 1 { + let header = MoreViewHeader(title: String(format: "%ld Items Selected".localized, items.count)) + moreViewController = MoreViewController(core: core, header: header, viewController: tableViewController) + } else if let item = items.first { + let header = MoreViewHeader(for: item, with: core) + moreViewController = MoreViewController(core: core, header: header, viewController: tableViewController) if core.connectionStatus == .online { if core.connection.capabilities?.sharingAPIEnabled == 1 { @@ -214,6 +221,7 @@ class Action : NSObject { } } } + } let title = NSAttributedString(string: "Actions".localized, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20, weight: .heavy)]) diff --git a/ownCloud/Client/Actions/Actions+Extensions/DocumentEditingAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DocumentEditingAction.swift index bbb766ffb..f326b051c 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DocumentEditingAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DocumentEditingAction.swift @@ -65,7 +65,7 @@ class DocumentEditingAction : Action { return } - let hudViewController = DownloadItemsHUDViewController(core: core, downloadItems: context.items) { [weak hostViewController] (error, files) in + let hudViewController = DownloadItemsHUDViewController(core: core, downloadItems: context.items) { [weak hostViewController] (error, downloadedItems) in if let error = error { if (error as NSError).isOCError(withCode: .cancelled) { return @@ -76,8 +76,8 @@ class DocumentEditingAction : Action { hostViewController?.present(alertController, animated: true) } else { - guard let files = files, files.count > 0, let viewController = hostViewController else { return } - if let fileURL = files.first?.url, let item = self.context.items.first { + guard let downloadedItems = downloadedItems, downloadedItems.count > 0, let viewController = hostViewController else { return } + if let fileURL = downloadedItems.first?.file.url, let item = self.context.items.first { let editDocumentViewController = EditDocumentViewController(with: fileURL, item: item, core: self.core) let navigationController = ThemeNavigationController(rootViewController: editDocumentViewController) navigationController.modalPresentationStyle = .overFullScreen diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 77a841c8c..7a84e2520 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -42,7 +42,7 @@ class OpenInAction: Action { return } - let hudViewController = DownloadItemsHUDViewController(core: core, downloadItems: context.items) { [weak hostViewController] (error, files) in + let hudViewController = DownloadItemsHUDViewController(core: core, downloadItems: context.items) { [weak hostViewController] (error, downloadedItems) in if let error = error { if (error as NSError).isOCError(withCode: .cancelled) { return @@ -53,10 +53,10 @@ class OpenInAction: Action { hostViewController?.present(alertController, animated: true) } else { - guard let files = files, files.count > 0, let viewController = hostViewController else { return } + guard let downloadedItems = downloadedItems, downloadedItems.count > 0, let viewController = hostViewController else { return } // UIDocumentInteractionController can only be used with a single file - if files.count == 1 { - if let fileURL = files.first?.url { + if downloadedItems.count == 1 { + if let fileURL = downloadedItems.first?.file.url { // Make sure self is around until interactionControllerDispatchGroup.leave() is called by the documentInteractionControllerDidDismissOptionsMenu delegate method implementation self.interactionControllerDispatchGroup = DispatchGroup() self.interactionControllerDispatchGroup?.enter() @@ -90,8 +90,8 @@ class OpenInAction: Action { } } else { // Handle multiple files with a fallback solution - let urls = files.map { (file) -> URL in - return file.url! + let urls = downloadedItems.map { (item) -> URL in + return item.file.url! } let activityController = UIActivityViewController(activityItems: urls, applicationActivities: nil) diff --git a/ownCloud/Client/Actions/MoreViewController.swift b/ownCloud/Client/Actions/MoreViewController.swift index f11a71635..da671d23d 100644 --- a/ownCloud/Client/Actions/MoreViewController.swift +++ b/ownCloud/Client/Actions/MoreViewController.swift @@ -21,7 +21,7 @@ import ownCloudSDK class MoreViewController: UIViewController, CardPresentationSizing { - private var item: OCItem? + //private var item: OCItem? private weak var core: OCCore? private var headerView: UIView @@ -35,8 +35,8 @@ class MoreViewController: UIViewController, CardPresentationSizing { } } - init(item: OCItem, core: OCCore, header: UIView, viewController: UIViewController) { - self.item = item + init(core: OCCore, header: UIView, viewController: UIViewController) { + //self.item = item self.core = core self.headerView = header self.viewController = viewController diff --git a/ownCloud/Client/Actions/MoreViewHeader.swift b/ownCloud/Client/Actions/MoreViewHeader.swift index 6d4e7c505..7db5ce7a8 100644 --- a/ownCloud/Client/Actions/MoreViewHeader.swift +++ b/ownCloud/Client/Actions/MoreViewHeader.swift @@ -38,6 +38,7 @@ class MoreViewHeader: UIView { var item: OCItem weak var core: OCCore? var url: URL? + var titleString: String? init(for item: OCItem, with core: OCCore, favorite: Bool = true, adaptBackgroundColor: Bool = false, showActivityIndicator: Bool = false) { self.item = item @@ -85,6 +86,30 @@ class MoreViewHeader: UIView { render() } + init(title : String) { + self.showFavoriteButton = false + self.showActivityIndicator = false + self.adaptBackgroundColor = false + self.item = OCItem() + self.url = nil + titleString = title + + iconView = UIImageView() + titleLabel = UILabel() + detailLabel = UILabel() + labelContainerView = UIView() + favoriteButton = UIButton() + activityIndicator = UIActivityIndicatorView(style: .white) + + super.init(frame: .zero) + + self.translatesAutoresizingMaskIntoConstraints = false + + Theme.shared.register(client: self) + + render() + } + deinit { Theme.shared.unregister(client: self) } @@ -181,6 +206,8 @@ class MoreViewHeader: UIView { } catch { print("Error: \(error)") } + } else if let titleString = titleString { + titleLabel.attributedText = NSAttributedString(string: titleString, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17, weight: .semibold)]) } else { titleLabel.attributedText = NSAttributedString(string: item.name ?? "", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17, weight: .semibold)]) diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index 9bc88a0ee..4d78b14a3 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -52,6 +52,7 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac var copyMultipleBarButtonItem: UIBarButtonItem? var openMultipleBarButtonItem: UIBarButtonItem? + var folderActionMultipleBarButton: UIBarButtonItem? var folderActionBarButton: UIBarButtonItem? var plusBarButton: UIBarButtonItem? var selectDeselectAllButtonItem: UIBarButtonItem? @@ -140,6 +141,7 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac self.tableView.dragInteractionEnabled = true self.tableView.allowsMultipleSelectionDuringEditing = true + folderActionMultipleBarButton = UIBarButtonItem(image: UIImage(named: "more-dots"), style: .plain, target: self, action: #selector(multipleMoreBarButtonPressed)) folderActionBarButton = UIBarButtonItem(image: UIImage(named: "more-dots"), style: .plain, target: self, action: #selector(moreBarButtonPressed)) folderActionBarButton?.accessibilityIdentifier = "client.folder-action" plusBarButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(plusBarButtonPressed)) @@ -151,6 +153,7 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac exitMultipleSelectionBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(exitMultipleSelection)) // Create bar button items for the toolbar + deleteMultipleBarButtonItem?.isEnabled = true deleteMultipleBarButtonItem = UIBarButtonItem(image: UIImage(named:"trash"), target: self as AnyObject, action: #selector(actOnMultipleItems), dropTarget: self, actionIdentifier: DeleteAction.identifier!) deleteMultipleBarButtonItem?.isEnabled = false @@ -381,6 +384,8 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac for item in toolbarItems { if self.actions?.contains(where: {type(of:$0).identifier == item.actionIdentifier}) ?? false { item.isEnabled = true + } else if item.isEqual(folderActionMultipleBarButton) { + item.isEnabled = true } else { item.isEnabled = false } @@ -444,9 +449,9 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac flexibleSpaceBarButton, copyMultipleBarButtonItem!, flexibleSpaceBarButton, - duplicateMultipleBarButtonItem!, + deleteMultipleBarButtonItem!, flexibleSpaceBarButton, - deleteMultipleBarButtonItem!]) + folderActionMultipleBarButton!]) } @objc func actOnMultipleItems(_ sender: UIButton) { @@ -566,7 +571,37 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .moreFolder) let actionContext = ActionContext(viewController: self, core: core, query: query, items: [rootItem], location: actionsLocation, sender: sender) - if let moreViewController = Action.cardViewController(for: rootItem, with: actionContext, progressHandler: makeActionProgressHandler()) { + if let moreViewController = Action.cardViewController(for: [rootItem], with: actionContext, progressHandler: makeActionProgressHandler()) { + self.present(asCard: moreViewController, animated: true) + } + } + + @objc func multipleMoreBarButtonPressed(_ sender: UIBarButtonItem) { + guard let core = core else { + return + } + + var selectedItems = [OCItem]() + + // Do we have selected items? + if let selectedIndexPaths = self.tableView.indexPathsForSelectedRows { + if selectedIndexPaths.count > 0 { + + // Get array of OCItems from selected table view index paths + for indexPath in selectedIndexPaths { + if let item = itemAt(indexPath: indexPath) { + selectedItems.append(item) + } + } + } + } + + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .moreFolder) + let actionContext = ActionContext(viewController: self, core: core, query: query, items: selectedItems, location: actionsLocation, sender: sender) + + print("--> multipleMoreBarButtonPressed \(selectedItems)") + + if let moreViewController = Action.cardViewController(for: selectedItems, with: actionContext, progressHandler: makeActionProgressHandler()) { self.present(asCard: moreViewController, animated: true) } } diff --git a/ownCloud/Client/Viewer/DisplayViewController.swift b/ownCloud/Client/Viewer/DisplayViewController.swift index 5c03412ed..249bdad01 100644 --- a/ownCloud/Client/Viewer/DisplayViewController.swift +++ b/ownCloud/Client/Viewer/DisplayViewController.swift @@ -396,7 +396,7 @@ class DisplayViewController: UIViewController, OCQueryDelegate { let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .moreItem) let actionContext = ActionContext(viewController: self, core: core, items: [item], location: actionsLocation, sender: sender) - if let moreViewController = Action.cardViewController(for: item, with: actionContext, completionHandler: nil) { + if let moreViewController = Action.cardViewController(for: [item], with: actionContext, completionHandler: nil) { self.present(asCard: moreViewController, animated: true) } } diff --git a/ownCloud/Client/Viewer/DownloadItemsHUDViewController.swift b/ownCloud/Client/Viewer/DownloadItemsHUDViewController.swift index c44e25bf5..23584b213 100644 --- a/ownCloud/Client/Viewer/DownloadItemsHUDViewController.swift +++ b/ownCloud/Client/Viewer/DownloadItemsHUDViewController.swift @@ -18,12 +18,13 @@ import UIKit import ownCloudSDK +import ownCloudApp -typealias DownloadItemsHUDViewControllerCompletionHandler = (Error?, [OCFile]?) -> Void +typealias DownloadItemsHUDViewControllerCompletionHandler = (Error?, [DownloadItem]?) -> Void class DownloadItemsHUDViewController: CardViewController { var items : [OCItem] - var downloadedFiles : [OCFile] = [OCFile]() + var downloadedItems : [DownloadItem] = [DownloadItem]() var downloadError : Error? var downloadProgress : [Progress] = [Progress]() var completion : DownloadItemsHUDViewControllerCompletionHandler? @@ -123,7 +124,7 @@ class DownloadItemsHUDViewController: CardViewController { if core.localCopy(of: item) != nil { if let file = item.file(with: core) { core.registerUsage(of: item, completionHandler: nil) - downloadedFiles.append(file) + downloadedItems.append(DownloadItem(file: file, item: item)) return false } @@ -145,7 +146,6 @@ class DownloadItemsHUDViewController: CardViewController { for item in items { downloadGroup.enter() - if let progress = core.downloadItem(item, options: [ .returnImmediatelyIfOfflineOrUnavailable : true, .addTemporaryClaimForPurpose : OCCoreClaimPurpose.view.rawValue @@ -155,7 +155,7 @@ class DownloadItemsHUDViewController: CardViewController { self.downloadError = error } else { if let file = file { - self.downloadedFiles.append(file) + self.downloadedItems.append(DownloadItem(file: file, item: item)) if let claim = file.claim { self.core?.remove(claim, on: item, afterDeallocationOf: [self]) @@ -180,7 +180,7 @@ class DownloadItemsHUDViewController: CardViewController { if let error = self.downloadError { self.completed(with: error) } else { - self.completed(files: self.downloadedFiles) + self.completed(items: self.downloadedItems) } }) } @@ -189,7 +189,7 @@ class DownloadItemsHUDViewController: CardViewController { }) } else { // Done - completed(files: downloadedFiles) + completed(items: downloadedItems) } } else { // No core @@ -197,9 +197,9 @@ class DownloadItemsHUDViewController: CardViewController { } } - func completed(with error: Error? = nil, files: [OCFile]? = nil) { + func completed(with error: Error? = nil, items: [DownloadItem]? = nil) { if let completion = completion { - completion(error, files) + completion(error, items) self.completion = nil } } diff --git a/ownCloud/FileLists/FileListTableViewController.swift b/ownCloud/FileLists/FileListTableViewController.swift index 7e32db816..4eac2919a 100644 --- a/ownCloud/FileLists/FileListTableViewController.swift +++ b/ownCloud/FileLists/FileListTableViewController.swift @@ -74,7 +74,7 @@ class FileListTableViewController: UITableViewController, ClientItemCellDelegate let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .moreItem) let actionContext = ActionContext(viewController: self, core: core, query: query, items: [item], location: actionsLocation, sender: cell) - if let moreViewController = Action.cardViewController(for: item, with: actionContext, progressHandler: makeActionProgressHandler()) { + if let moreViewController = Action.cardViewController(for: [item], with: actionContext, progressHandler: makeActionProgressHandler()) { self.present(asCard: moreViewController, animated: true) } } diff --git a/ownCloud/SDK Extensions/OCCore+Extension.swift b/ownCloud/SDK Extensions/OCCore+Extension.swift index 97abf44cb..6f6ec4e58 100644 --- a/ownCloud/SDK Extensions/OCCore+Extension.swift +++ b/ownCloud/SDK Extensions/OCCore+Extension.swift @@ -143,4 +143,21 @@ extension OCCore { return parentItems.reversed() } + + func retrieveSubItems(for item: OCItem, completionHandler: ((_ items: [OCItem]?) -> Void)? = nil) { + var newHandler = completionHandler + let subitemsQuery = OCQuery(condition: .require([ + .where(.path, startsWith: item.path!) + ]), inputFilter:nil) + + var items : [OCItem]? + + subitemsQuery.changesAvailableNotificationHandler = { [weak self] query in + items = query.queryResults + self?.stop(query) + newHandler?(items) + newHandler = nil + } + self.start(subitemsQuery) + } } diff --git a/ownCloudAppFramework/ZIP Archive/ZIPArchive.h b/ownCloudAppFramework/ZIP Archive/ZIPArchive.h index 55c701173..4843baa14 100644 --- a/ownCloudAppFramework/ZIP Archive/ZIPArchive.h +++ b/ownCloudAppFramework/ZIP Archive/ZIPArchive.h @@ -20,9 +20,23 @@ NS_ASSUME_NONNULL_BEGIN +@interface DownloadItem : NSObject +{ + OCFile *_file; + OCItem *_item; +} + +@property(strong,nonatomic) OCFile *file; +@property(strong,nonatomic) OCItem *item; + +- (instancetype)initWithFile:(OCFile *)file item:(OCItem *)item; + +@end + @interface ZIPArchive : NSObject + (NSError *)compressContentsOf:(NSURL *)sourceDirectory asZipFile:(NSURL *)zipFileURL; ++ (nullable NSError *)compressContentsOfItems:(NSArray *)sourceDirectorie fromBasePath:(NSString *)basePath asZipFile:(NSURL *)zipFileURL withPassword:(nullable NSString *)password; @end diff --git a/ownCloudAppFramework/ZIP Archive/ZIPArchive.m b/ownCloudAppFramework/ZIP Archive/ZIPArchive.m index e76476a27..fe9405816 100644 --- a/ownCloudAppFramework/ZIP Archive/ZIPArchive.m +++ b/ownCloudAppFramework/ZIP Archive/ZIPArchive.m @@ -20,6 +20,22 @@ #import #import "ZIPArchive.h" + +@implementation DownloadItem + +- (instancetype)initWithFile:(OCFile *)file item:(OCItem *)item +{ + if ((self = [super init]) != nil) + { + self.file = file; + self.item = item; + } + + return(self); +} + +@end + @implementation ZIPArchive + (NSError *)compressContentsOf:(NSURL *)sourceDirectory asZipFile:(NSURL *)zipFileURL @@ -96,6 +112,33 @@ + (NSError *)compressContentsOf:(NSURL *)sourceDirectory asZipFile:(NSURL *)zipF } } } + } else { + NSURL *addURL = sourceDirectory; + zip_source_t *fileSource = NULL; + NSNumber *isDirectory = nil, *size = nil; + + NSString *relativePath = addURL.path.lastPathComponent; + [addURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil]; + [addURL getResourceValue:&size forKey:NSURLFileSizeKey error:nil]; + + // Add file + if ((fileSource = zip_source_file(zipArchive, addURL.path.UTF8String, 0, (zip_int64_t)size.unsignedIntegerValue)) != NULL) + { + if (zip_file_add(zipArchive, relativePath.UTF8String, fileSource, ZIP_FL_ENC_UTF_8) < 0) + { + // Error + error = ErrorFromZipArchive(zipArchive); + OCLogError(@"Error compressing %@: %@", relativePath, error); + + zip_source_free(fileSource); + } + } + else + { + // Error + error = ErrorFromZipArchive(zipArchive); + OCLogError(@"Error adding directory %@: %@", relativePath, error); + } } if (zip_close(zipArchive) < 0) @@ -117,6 +160,88 @@ + (NSError *)compressContentsOf:(NSURL *)sourceDirectory asZipFile:(NSURL *)zipF return (error); } ++ (NSError *)compressContentsOfItems:(NSArray *)sourceItems fromBasePath:(NSString *)basePath asZipFile:(NSURL *)zipFileURL withPassword:(nullable NSString *)password +{ + zip_t *zipArchive = NULL; + int zipError = ZIP_ER_OK; + NSError *error = nil; + NSError *(^ErrorFromZipArchive)(zip_t *zipArchive) = ^(zip_t *zipArchive) { + zip_error_t *zipError = zip_get_error(zipArchive); + + return ([NSError errorWithDomain:LibZipErrorDomain code:zipError->zip_err userInfo:nil]); + }; + + if ((zipArchive = zip_open(zipFileURL.path.UTF8String, ZIP_CREATE, &zipError)) != NULL) + { + zip_uint64_t index = 0; + for (DownloadItem *sourceItem in sourceItems) + { + NSURL *addURL = sourceItem.file.url; + zip_source_t *fileSource = NULL; + NSNumber *size = nil; + NSString *relativePath = [sourceItem.item.path stringByReplacingOccurrencesOfString:basePath withString:@"/"]; + + if (sourceItem.item.type == OCItemTypeCollection) + { + // Add directory + OCLogDebug(@"Adding directory %@ from %@", relativePath, addURL); + + if (zip_dir_add(zipArchive, relativePath.UTF8String, ZIP_FL_ENC_UTF_8) < 0) + { + // Error + error = ErrorFromZipArchive(zipArchive); + OCLogError(@"Error adding directory %@: %@", relativePath, error); + } + } + else + { + // Add file + OCLogDebug(@"Adding file %@ from %@", relativePath, addURL); + + if ((fileSource = zip_source_file(zipArchive, addURL.path.UTF8String, 0, (zip_int64_t)size.unsignedIntegerValue)) != NULL) + { + if (zip_file_add(zipArchive, relativePath.UTF8String, fileSource, ZIP_FL_ENC_UTF_8) < 0) + { + // Error + error = ErrorFromZipArchive(zipArchive); + OCLogError(@"Error compressing %@: %@", relativePath, error); + + zip_source_free(fileSource); + } else { + if (password != nil) { + zip_file_set_encryption(zipArchive, index, ZIP_EM_AES_256, (const char*)[password UTF8String]); + } + index ++; + } + } + else + { + // Error + error = ErrorFromZipArchive(zipArchive); + OCLogError(@"Error adding directory %@: %@", relativePath, error); + } + } + } + } + else + { + // Error + error = [NSError errorWithDomain:LibZipErrorDomain code:zipError userInfo:nil]; + OCLogError(@"Error opening zip archive %@: %@", zipFileURL.path, error); + } + + if (zip_close(zipArchive) < 0) + { + // Error + error = ErrorFromZipArchive(zipArchive); + OCLogError(@"Error closing zip archive %@: %@", zipFileURL.path, error); + + zip_discard(zipArchive); + } + + return (error); +} + @end NSErrorDomain LibZipErrorDomain = @"LibZIPErrorDomain"; From 095801e0569b5db9e51cbd51dcbe4913d18f3e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Tue, 5 May 2020 15:02:09 +0200 Subject: [PATCH 2/6] new files --- .../Actions+Extensions/ZipAction.swift | 167 ++++++++++++++++++ .../Actions/CompressViewController.swift | 55 ++++++ 2 files changed, 222 insertions(+) create mode 100644 ownCloud/Client/Actions/Actions+Extensions/ZipAction.swift create mode 100644 ownCloud/Client/Actions/CompressViewController.swift diff --git a/ownCloud/Client/Actions/Actions+Extensions/ZipAction.swift b/ownCloud/Client/Actions/Actions+Extensions/ZipAction.swift new file mode 100644 index 000000000..ff2b852c1 --- /dev/null +++ b/ownCloud/Client/Actions/Actions+Extensions/ZipAction.swift @@ -0,0 +1,167 @@ +// +// ZipAction.swift +// ownCloud +// +// Created by Matthias Hühne on 05/04/2020. +// Copyright © 2020 ownCloud GmbH. All rights reserved. +// + +/* +* Copyright (C) 2020, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + +import ownCloudSDK +import ownCloudApp + +class ZipAction: Action { + override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.zip") } + override class var category : ActionCategory? { return .normal } + override class var name : String { return "Compress as ZIP file".localized } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreFolder, .toolbar, .keyboardShortcut] } + override class var keyCommand : String? { return "Z" } + override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } + + override class func applicablePosition(forContext: ActionContext) -> ActionPosition { + return .afterMiddle + } + + override func run() { + guard context.items.count > 0, let hostViewController = context.viewController, let core = self.core else { + self.completed(with: NSError(ocError: .insufficientParameters)) + return + } + + var fileItems = context.items.filter { (item) -> Bool in + if item.type == .collection { + return false + } + + return true + } + + var collectionItems = context.items.filter { (item) -> Bool in + if item.type == .file { + return false + } + + return true + } + + let dispatchGroup = DispatchGroup() + for collection in collectionItems { + + dispatchGroup.enter() + core.retrieveSubItems(for: collection) { (items) in + let subFileItems = items?.filter { (item) -> Bool in + if item.type == .collection { + return false + } + + return true + } + let subCollectionItems = items?.filter { (item) -> Bool in + if item.type == .file { + return false + } + + return true + } + fileItems.append(contentsOf: subFileItems!) + collectionItems.append(contentsOf: subCollectionItems!) + dispatchGroup.leave() + } + } + dispatchGroup.wait() + + let hudViewController = DownloadItemsHUDViewController(core: core, downloadItems: fileItems as [OCItem]) { [weak hostViewController] (error, downloadedItems) in + + var unifiedItems = downloadedItems + + unifiedItems?.append(contentsOf: + collectionItems.map { (item) -> DownloadItem in + return DownloadItem(file: OCFile(), item: item) + }) + + if let error = error { + if (error as NSError).isOCError(withCode: .cancelled) { + return + } + + let appName = OCAppIdentity.shared.appName ?? "ownCloud" + let alertController = ThemedAlertController(with: "Cannot connect to ".localized + appName, message: appName + " couldn't download file(s)".localized, okLabel: "OK".localized, action: nil) + + hostViewController?.present(alertController, animated: true) + } else { + guard let unifiedItems = unifiedItems, unifiedItems.count > 0, let viewController = hostViewController else { return } + + var zipName = "Archive.zip" + + if self.context.items.count == 1, let item = self.context.items.first { + zipName = String(format: "%@.zip", item.name ?? "Archive.zip") + } + + let renameViewController = NamingViewController(with: nil, core: self.core, defaultName: zipName, stringValidator: { name in + if name.contains("/") || name.contains("\\") { + return (false, "File name cannot contain / or \\".localized) + } else { + return (true, nil) + } + }, completion: { newName, _ in + + OnBackgroundQueue { + if let newName = newName, error == nil, let fileItem = self.context.items.first, let parentItem = fileItem.parentItem(from: core) { + let zipURL = FileManager.default.temporaryDirectory.appendingPathComponent(newName) + let error = ZIPArchive.compressContents(of: unifiedItems, fromBasePath: parentItem.path ?? "", asZipFile: zipURL, withPassword: nil) + + print("--> error \(error)") + if !self.upload(itemURL: zipURL, to: parentItem, name: zipURL.lastPathComponent) { + self.completed(with: NSError(ocError: .internal)) + return + } else { + self.completed() + } + } + } + }) + + renameViewController.navigationItem.title = "Compress".localized + + let navigationController = ThemeNavigationController(rootViewController: renameViewController) + navigationController.modalPresentationStyle = .overFullScreen + + viewController.present(navigationController, animated: true) + } + } + + hudViewController.presentHUDOn(viewController: hostViewController) + } + + override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { + if location == .moreItem || location == .moreFolder { + if #available(iOS 13.0, *) { + return UIImage(systemName: "cube.box")?.tinted(with: Theme.shared.activeCollection.tintColor) + } else { + // Fallback on earlier versions + } + } + + return nil + } + + internal func upload(itemURL: URL, to rootItem: OCItem, name: String) -> Bool { + + if core != nil, let progress = itemURL.upload(with: core, at: rootItem) { + self.publish(progress: progress) + return true + } else { + Log.debug("Error setting up upload of \(Log.mask(name)) to \(Log.mask(rootItem.path))") + return false + } + } +} diff --git a/ownCloud/Client/Actions/CompressViewController.swift b/ownCloud/Client/Actions/CompressViewController.swift new file mode 100644 index 000000000..1e6955ff8 --- /dev/null +++ b/ownCloud/Client/Actions/CompressViewController.swift @@ -0,0 +1,55 @@ +// +// CompressViewController.swift +// ownCloud +// +// Created by Matthias Hühne on 05/4/2020. +// Copyright © 2020 ownCloud GmbH. All rights reserved. +// + +/* +* Copyright (C) 2020, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + +import UIKit + +class CompressViewController: NamingViewController { + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidShowNotification, object: nil) + Theme.shared.unregister(client: self) + } + + override func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + //super.applyThemeCollection(theme: theme, collection: collection, event: ThemeEvent) + } + + override func viewDidLoad() { + super.viewDidLoad() + + } + + private func render(newTraitCollection: UITraitCollection) { + + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + render(newTraitCollection: traitCollection) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + +} From 4920f7bba839999ca3e619d052fe4426ad9a3ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Wed, 6 May 2020 10:39:43 +0200 Subject: [PATCH 3/6] added shortcut action to compress files --- ownCloud Intents/Info.plist | 1 + ownCloud Intents/IntentHandler.swift | 2 + ownCloud.xcodeproj/project.pbxproj | 6 + .../Actions+Extensions/ZipAction.swift | 12 +- ownCloud/Resources/Info.plist | 1 + .../SDK Extensions/OCCore+Extension.swift | 17 ++ .../Base.lproj/Intents.intentdefinition | 287 +++++++++++++++++- .../CompressPathItemsIntentHandler .swift | 159 ++++++++++ 8 files changed, 479 insertions(+), 6 deletions(-) create mode 100644 ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift diff --git a/ownCloud Intents/Info.plist b/ownCloud Intents/Info.plist index 228b254ed..0ad1dd895 100644 --- a/ownCloud Intents/Info.plist +++ b/ownCloud Intents/Info.plist @@ -30,6 +30,7 @@ IntentsSupported + CompressPathItemsIntent CreateFolderIntent DeletePathItemIntent GetAccountIntent diff --git a/ownCloud Intents/IntentHandler.swift b/ownCloud Intents/IntentHandler.swift index 384eed5d4..b2c0eaba8 100644 --- a/ownCloud Intents/IntentHandler.swift +++ b/ownCloud Intents/IntentHandler.swift @@ -40,6 +40,8 @@ class IntentHandler: INExtension { return PathExistsIntentHandler() } else if intent is DeletePathItemIntent { return DeletePathItemIntentHandler() + } else if intent is CompressPathItemsIntent { + return CompressPathItemsIntentHandler() } fatalError("Unhandled intent type: \(intent)") diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index b52345421..46c9bd8cb 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -79,6 +79,7 @@ 3998F5D3224102FE00B66713 /* UITableView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3998F5D2224102FE00B66713 /* UITableView+Extension.swift */; }; 3998F5D522411EDF00B66713 /* BorderedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3998F5D422411EDF00B66713 /* BorderedLabel.swift */; }; 3998F5D72241486F00B66713 /* OCCertificate+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3998F5D62241486F00B66713 /* OCCertificate+Extension.swift */; }; + 39999DF224629FA800880D45 /* OCCore+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A513AB22674E56002CF1AA /* OCCore+Extension.swift */; }; 399A4C002317CC460027DDD6 /* AppLockHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399A4BFF2317CC460027DDD6 /* AppLockHelper.swift */; }; 399A4C032317D1ED0027DDD6 /* OCBookmarkManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399A4C022317D1ED0027DDD6 /* OCBookmarkManager+Extension.swift */; }; 399A4C1023190ADF0027DDD6 /* PathExistsIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399A4C0F23190ADF0027DDD6 /* PathExistsIntentHandler.swift */; }; @@ -106,6 +107,7 @@ 39E2FDED21FDEC7500F0117F /* ServerListTableHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E2FDEC21FDEC7500F0117F /* ServerListTableHeaderView.swift */; }; 39E2FE0021FF814A00F0117F /* ThemeRoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E2FDFF21FF814A00F0117F /* ThemeRoundedButton.swift */; }; 39E42D1C2315288B00B82AC3 /* KeyCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E42D1B2315288B00B82AC3 /* KeyCommands.swift */; }; + 39E49DBF24619CB40069414A /* CompressPathItemsIntentHandler .swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E49DBE24619CB40069414A /* CompressPathItemsIntentHandler .swift */; }; 39E6DE84233CC39A008DAE04 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 39057AAA233BA7A60008E6C0 /* Intents.intentdefinition */; }; 39E6DE86233CDF1E008DAE04 /* OCItemTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E6DE85233CDF1E008DAE04 /* OCItemTracker.swift */; }; 39E98B3E22797D1B009911F1 /* PublicLinkTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E98B3D22797D1B009911F1 /* PublicLinkTableViewController.swift */; }; @@ -849,6 +851,7 @@ 39E2FDEC21FDEC7500F0117F /* ServerListTableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListTableHeaderView.swift; sourceTree = ""; }; 39E2FDFF21FF814A00F0117F /* ThemeRoundedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeRoundedButton.swift; sourceTree = ""; }; 39E42D1B2315288B00B82AC3 /* KeyCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyCommands.swift; sourceTree = ""; }; + 39E49DBE24619CB40069414A /* CompressPathItemsIntentHandler .swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CompressPathItemsIntentHandler .swift"; sourceTree = ""; }; 39E6DE85233CDF1E008DAE04 /* OCItemTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCItemTracker.swift; sourceTree = ""; }; 39E98B3D22797D1B009911F1 /* PublicLinkTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicLinkTableViewController.swift; sourceTree = ""; }; 39E98B442279ACF5009911F1 /* PublicLinkEditTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicLinkEditTableViewController.swift; sourceTree = ""; }; @@ -1540,6 +1543,7 @@ 39A5C3A0231566D9009D9EE3 /* GetFileInfoIntentHandler.swift */, 399A4C0F23190ADF0027DDD6 /* PathExistsIntentHandler.swift */, 3984F56B2319202200DC2639 /* DeletePathItemIntentHandler.swift */, + 39E49DBE24619CB40069414A /* CompressPathItemsIntentHandler .swift */, ); path = Intent; sourceTree = ""; @@ -3493,7 +3497,9 @@ 397754E223279EED00119FCB /* OCItem+Extension.swift in Sources */, 39BE385D23435AFE0062A2FE /* String+Extension.swift in Sources */, 39E6DE86233CDF1E008DAE04 /* OCItemTracker.swift in Sources */, + 39999DF224629FA800880D45 /* OCCore+Extension.swift in Sources */, 393D2B3F23FEB6DC00ED4F8C /* DispatchQueueTools.swift in Sources */, + 39E49DBF24619CB40069414A /* CompressPathItemsIntentHandler .swift in Sources */, 3984F5712319202200DC2639 /* DeletePathItemIntentHandler.swift in Sources */, 39F689AC22F0206900E63429 /* OCBookmark+Extension.swift in Sources */, 395E16FF22F172C900DE89A1 /* CreateFolderIntentHandler.swift in Sources */, diff --git a/ownCloud/Client/Actions/Actions+Extensions/ZipAction.swift b/ownCloud/Client/Actions/Actions+Extensions/ZipAction.swift index ff2b852c1..66803230d 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/ZipAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/ZipAction.swift @@ -31,6 +31,8 @@ class ZipAction: Action { return .afterMiddle } + let defaultZipName = "Archive.zip".localized + override func run() { guard context.items.count > 0, let hostViewController = context.viewController, let core = self.core else { self.completed(with: NSError(ocError: .insufficientParameters)) @@ -100,10 +102,10 @@ class ZipAction: Action { } else { guard let unifiedItems = unifiedItems, unifiedItems.count > 0, let viewController = hostViewController else { return } - var zipName = "Archive.zip" + var zipName = self.defaultZipName if self.context.items.count == 1, let item = self.context.items.first { - zipName = String(format: "%@.zip", item.name ?? "Archive.zip") + zipName = String(format: "%@.zip", item.name ?? self.defaultZipName) } let renameViewController = NamingViewController(with: nil, core: self.core, defaultName: zipName, stringValidator: { name in @@ -119,13 +121,17 @@ class ZipAction: Action { let zipURL = FileManager.default.temporaryDirectory.appendingPathComponent(newName) let error = ZIPArchive.compressContents(of: unifiedItems, fromBasePath: parentItem.path ?? "", asZipFile: zipURL, withPassword: nil) - print("--> error \(error)") if !self.upload(itemURL: zipURL, to: parentItem, name: zipURL.lastPathComponent) { self.completed(with: NSError(ocError: .internal)) return } else { self.completed() } + + do { + try FileManager.default.removeItem(at: zipURL) + } catch { + } } } }) diff --git a/ownCloud/Resources/Info.plist b/ownCloud/Resources/Info.plist index e417984f6..84e9d0547 100644 --- a/ownCloud/Resources/Info.plist +++ b/ownCloud/Resources/Info.plist @@ -64,6 +64,7 @@ This permission is needed to upload photos and videos from your photo library. NSUserActivityTypes + CompressPathItemsIntent CreateFolderIntent DeletePathItemIntent GetAccountIntent diff --git a/ownCloud/SDK Extensions/OCCore+Extension.swift b/ownCloud/SDK Extensions/OCCore+Extension.swift index 6f6ec4e58..02fdf6734 100644 --- a/ownCloud/SDK Extensions/OCCore+Extension.swift +++ b/ownCloud/SDK Extensions/OCCore+Extension.swift @@ -18,6 +18,7 @@ import Foundation import ownCloudSDK +import ownCloudApp extension OCCore { @@ -160,4 +161,20 @@ extension OCCore { } self.start(subitemsQuery) } + + func localFile(for item: OCItem, completionHandler: @escaping (_ item: DownloadItem?) -> Void) { + if self.localCopy(of: item) == nil { + self.downloadItem(item, options: [ .returnImmediatelyIfOfflineOrUnavailable : true ], resultHandler: { (error, core, item, file) in + if error == nil, let item = item, let file = item.file(with: core) { + completionHandler(DownloadItem(file: file, item: item)) + } else { + completionHandler(nil) + } + }) + } else if let file = item.file(with: self) { + completionHandler(DownloadItem(file: file, item: item)) + } else { + completionHandler(nil) + } + } } diff --git a/ownCloudAppShared/Intent/Base.lproj/Intents.intentdefinition b/ownCloudAppShared/Intent/Base.lproj/Intents.intentdefinition index dfb484931..ab49c9606 100644 --- a/ownCloudAppShared/Intent/Base.lproj/Intents.intentdefinition +++ b/ownCloudAppShared/Intent/Base.lproj/Intents.intentdefinition @@ -126,11 +126,11 @@ INIntentDefinitionNamespace K5U8aR INIntentDefinitionSystemVersion - 19D76 + 19E266 INIntentDefinitionToolsBuildVersion - 11C504 + 11E146 INIntentDefinitionToolsVersion - 11.3.1 + 11.4 INIntents @@ -2506,6 +2506,287 @@ INIntentVerb Get + + INIntentCategory + generic + INIntentConfigurable + + INIntentDescription + Compress the given path items into a zip file + INIntentDescriptionID + hgRmHs + INIntentLastParameterTag + 8 + INIntentManagedParameterCombinations + + pathItems,account,filename + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Compress ${pathItems} of ${account}to ${filename} + INIntentParameterCombinationTitleID + oBIFKF + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + CompressPathItems + INIntentParameterCombinations + + pathItems,account,filename + + INIntentParameterCombinationIsLinked + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Compress ${pathItems} of ${account}to ${filename} + INIntentParameterCombinationTitleID + yaob48 + + + INIntentParameters + + + INIntentParameterDisplayName + Path Items + INIntentParameterDisplayNameID + YnFYyb + INIntentParameterDisplayPriority + 1 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + + INIntentParameterName + pathItems + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Enter at minimum one path to which should be compressed + INIntentParameterPromptDialogFormatStringID + X1OVs4 + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsMultipleValues + + INIntentParameterSupportsResolution + + INIntentParameterTag + 7 + INIntentParameterType + String + + + INIntentParameterCustomDisambiguation + + INIntentParameterDisplayName + Account + INIntentParameterDisplayNameID + gmaNlp + INIntentParameterDisplayPriority + 2 + INIntentParameterName + account + INIntentParameterObjectType + Account + INIntentParameterObjectTypeNamespace + K5U8aR + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${account}’. + INIntentParameterPromptDialogFormatStringID + gMfROG + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogFormatString + Which one? + INIntentParameterPromptDialogFormatStringID + IWGvu6 + INIntentParameterPromptDialogType + DisambiguationSelection + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${account}’? + INIntentParameterPromptDialogFormatStringID + Og0voM + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterSupportsResolution + + INIntentParameterTag + 5 + INIntentParameterType + Object + + + INIntentParameterDisplayName + Filename (optional) + INIntentParameterDisplayNameID + dPSUsQ + INIntentParameterDisplayPriority + 3 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + + INIntentParameterName + filename + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Enter a file name for the compressed file + INIntentParameterPromptDialogFormatStringID + nHQ237 + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsResolution + + INIntentParameterTag + 8 + INIntentParameterType + String + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + INIntentResponseCodeConciseFormatString + App is currently locked by pass code. Please disable pass code to proceed. + INIntentResponseCodeConciseFormatStringID + Pn1D1B + INIntentResponseCodeFormatString + App is currently locked by pass code. Please disable pass code to proceed. + INIntentResponseCodeFormatStringID + 25zMZv + INIntentResponseCodeName + authenticationRequired + + + INIntentResponseCodeConciseFormatString + The account with the given UUID does not exists + INIntentResponseCodeConciseFormatStringID + bDZ7q6 + INIntentResponseCodeFormatString + The account with the given UUID does not exists + INIntentResponseCodeFormatStringID + llOtTJ + INIntentResponseCodeName + accountFailure + + + INIntentResponseCodeConciseFormatString + Shortcuts support is disabled. Please open the app and enable Shortcuts in Settings. + INIntentResponseCodeConciseFormatStringID + h7Yg4L + INIntentResponseCodeFormatString + Shortcuts support is disabled. Please open the app and enable Shortcuts in Settings. + INIntentResponseCodeFormatStringID + XlVjYV + INIntentResponseCodeName + disabled + + + INIntentResponseCodeConciseFormatString + This action is a Pro Feature and currently not unlocked. For more information and available unlocking options, please see Settings > Pro Features > Shortcuts. + INIntentResponseCodeConciseFormatStringID + Ra4dpr + INIntentResponseCodeFormatString + This action is a Pro Feature and currently not unlocked. For more information and available unlocking options, please see Settings > Pro Features > Shortcuts. + INIntentResponseCodeFormatStringID + 1kJ8De + INIntentResponseCodeName + unlicensed + + + INIntentResponseCodeConciseFormatString + The given path does not exists + INIntentResponseCodeConciseFormatStringID + NlhMFB + INIntentResponseCodeFormatString + The given path does not exists + INIntentResponseCodeFormatStringID + GzUAMf + INIntentResponseCodeName + pathFailure + + + INIntentResponseLastParameterTag + 2 + INIntentResponseOutput + file + INIntentResponseParameters + + + INIntentResponseParameterDisplayName + File + INIntentResponseParameterDisplayNameID + 7g5hLt + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterName + file + INIntentResponseParameterTag + 2 + INIntentResponseParameterType + File + + + + INIntentTitle + Compress Path Items + INIntentTitleID + 3CMRXn + INIntentType + Custom + INIntentVerb + Do + INTypes diff --git a/ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift b/ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift new file mode 100644 index 000000000..82bfdbdcd --- /dev/null +++ b/ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift @@ -0,0 +1,159 @@ +// +// CompressPathItemsIntentHandler.swift +// ownCloudAppShared +// +// Created by Matthias Hühne on 30.08.19. +// Copyright © 2019 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2019, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import Intents +import ownCloudSDK +import ownCloudApp + +@available(iOS 13.0, *) +public class CompressPathItemsIntentHandler: NSObject, CompressPathItemsIntentHandling { + + let defaultZipName = "Archive.zip".localized + + public func handle(intent: CompressPathItemsIntent, completion: @escaping (CompressPathItemsIntentResponse) -> Void) { + + guard IntentSettings.shared.isEnabled else { + completion(CompressPathItemsIntentResponse(code: .disabled, userActivity: nil)) + return + } + + guard !AppLockHelper().isPassCodeEnabled else { + completion(CompressPathItemsIntentResponse(code: .authenticationRequired, userActivity: nil)) + return + } + + guard let pathItems = intent.pathItems, let uuid = intent.account?.uuid else { + completion(CompressPathItemsIntentResponse(code: .failure, userActivity: nil)) + return + } + + guard let bookmark = OCBookmarkManager.shared.bookmark(for: uuid) else { + completion(CompressPathItemsIntentResponse(code: .accountFailure, userActivity: nil)) + return + } + + guard IntentSettings.shared.isLicensedFor(bookmark: bookmark) else { + completion(CompressPathItemsIntentResponse(code: .unlicensed, userActivity: nil)) + return + } + + var unifiedItems : [DownloadItem] = [] + let dispatchGroup = DispatchGroup() + + for path in pathItems { + dispatchGroup.enter() + OCItemTracker().item(for: bookmark, at: path) { (error, core, item) in + if error == nil, let item = item, let core = core { + if item.type == .file { + core.localFile(for: item) { (downloadItem) in + if let downloadItem = downloadItem { + unifiedItems.append(downloadItem) + } + dispatchGroup.leave() + } + } else { + unifiedItems.append(DownloadItem(file: OCFile(), item: item)) + + core.retrieveSubItems(for: item) { (items) in + for item in items! { + if item.type == .file { + dispatchGroup.enter() + core.localFile(for: item) { (downloadItem) in + if let downloadItem = downloadItem { + unifiedItems.append(downloadItem) + } + dispatchGroup.leave() + } + } else { + unifiedItems.append(DownloadItem(file: OCFile(), item: item)) + } + } + dispatchGroup.leave() + } + } + } else if core != nil { + completion(CompressPathItemsIntentResponse(code: .pathFailure, userActivity: nil)) + } else { + completion(CompressPathItemsIntentResponse(code: .failure, userActivity: nil)) + } + } + } + dispatchGroup.wait() + + if unifiedItems.count > 0 { + var zipFileName = defaultZipName + if let filename = intent.filename, filename.count > 0 { + zipFileName = filename + } else if unifiedItems.count == 1, let item = unifiedItems.first?.item { + zipFileName = String(format: "%@.zip", item.name ?? defaultZipName) + } + + let zipURL = FileManager.default.temporaryDirectory.appendingPathComponent(zipFileName) + let error = ZIPArchive.compressContents(of: unifiedItems, fromBasePath: "/", asZipFile: zipURL, withPassword: nil) + + let file = INFile(fileURL: zipURL, filename: zipFileName, typeIdentifier: nil) + completion(CompressPathItemsIntentResponse.success(file: file)) + } else { + completion(CompressPathItemsIntentResponse(code: .failure, userActivity: nil)) + } + } + + public func resolveAccount(for intent: CompressPathItemsIntent, with completion: @escaping (AccountResolutionResult) -> Void) { + if let account = intent.account { + completion(AccountResolutionResult.success(with: account)) + } else { + completion(AccountResolutionResult.needsValue()) + } + } + + public func provideAccountOptions(for intent: CompressPathItemsIntent, with completion: @escaping ([Account]?, Error?) -> Void) { + completion(OCBookmarkManager.shared.accountList, nil) + } + + public func resolvePathItems(for intent: CompressPathItemsIntent, with completion: @escaping ([INStringResolutionResult]) -> Void) { + if let pathItems = intent.pathItems { + + var resolutionResults = [INStringResolutionResult]() + for pathItem in pathItems { + if pathItem.count > 0 { + resolutionResults.append(INStringResolutionResult.success(with: pathItem)) + } else { + resolutionResults.append(INStringResolutionResult.needsValue()) + } + } + completion(resolutionResults) + } else { + completion([INStringResolutionResult.needsValue()]) + } + } + + public func resolveFilename(for intent: CompressPathItemsIntent, with completion: @escaping (INStringResolutionResult) -> Void) { + completion(INStringResolutionResult.success(with: intent.filename ?? "")) + } +} + +@available(iOS 13.0, *) +extension CompressPathItemsIntentResponse { + + public static func success(file: INFile) -> CompressPathItemsIntentResponse { + let intentResponse = CompressPathItemsIntentResponse(code: .success, userActivity: nil) + intentResponse.file = file + return intentResponse + } +} From 3865f8d57aad5697509d8921b1ab416321620942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Wed, 6 May 2020 15:28:17 +0200 Subject: [PATCH 4/6] added uncompress action --- ownCloud.xcodeproj/project.pbxproj | 12 +- ownCloud/AppDelegate.swift | 3 +- .../{ZipAction.swift => CompressAction.swift} | 26 ++-- .../Actions+Extensions/UncompressAction.swift | 140 ++++++++++++++++++ ownCloudAppFramework/ZIP Archive/ZIPArchive.h | 16 ++ ownCloudAppFramework/ZIP Archive/ZIPArchive.m | 110 +++++++++++++- 6 files changed, 292 insertions(+), 15 deletions(-) rename ownCloud/Client/Actions/Actions+Extensions/{ZipAction.swift => CompressAction.swift} (91%) create mode 100644 ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 46c9bd8cb..1afd821f5 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 3998F5D522411EDF00B66713 /* BorderedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3998F5D422411EDF00B66713 /* BorderedLabel.swift */; }; 3998F5D72241486F00B66713 /* OCCertificate+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3998F5D62241486F00B66713 /* OCCertificate+Extension.swift */; }; 39999DF224629FA800880D45 /* OCCore+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A513AB22674E56002CF1AA /* OCCore+Extension.swift */; }; + 39999DF42462D57800880D45 /* UncompressAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39999DF32462D57800880D45 /* UncompressAction.swift */; }; 399A4C002317CC460027DDD6 /* AppLockHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399A4BFF2317CC460027DDD6 /* AppLockHelper.swift */; }; 399A4C032317D1ED0027DDD6 /* OCBookmarkManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399A4C022317D1ED0027DDD6 /* OCBookmarkManager+Extension.swift */; }; 399A4C1023190ADF0027DDD6 /* PathExistsIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399A4C0F23190ADF0027DDD6 /* PathExistsIntentHandler.swift */; }; @@ -94,7 +95,7 @@ 39B289A8226F1EE000BE0E11 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B289A7226F1EE000BE0E11 /* MessageView.swift */; }; 39B9675022BE0FBA0074DB22 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 593A821320C7D4C5000E2A90 /* Localizable.strings */; }; 39BC9C3023DB831F0097C52D /* DocumentEditingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CD755123D787E400193950 /* DocumentEditingAction.swift */; }; - 39BCDD7F246006AD00FE3D23 /* ZipAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BCDD79246006AC00FE3D23 /* ZipAction.swift */; }; + 39BCDD7F246006AD00FE3D23 /* CompressAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BCDD79246006AC00FE3D23 /* CompressAction.swift */; }; 39BCDD812460858300FE3D23 /* CompressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BCDD802460858200FE3D23 /* CompressViewController.swift */; }; 39BE385D23435AFE0062A2FE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BE385C23435AFE0062A2FE /* String+Extension.swift */; }; 39CC8AE6228C12100020253B /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CC8AE5228C12100020253B /* Array+Extension.swift */; }; @@ -823,6 +824,7 @@ 3998F5D2224102FE00B66713 /* UITableView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Extension.swift"; sourceTree = ""; }; 3998F5D422411EDF00B66713 /* BorderedLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BorderedLabel.swift; sourceTree = ""; }; 3998F5D62241486F00B66713 /* OCCertificate+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCCertificate+Extension.swift"; sourceTree = ""; }; + 39999DF32462D57800880D45 /* UncompressAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UncompressAction.swift; sourceTree = ""; }; 399A4BFF2317CC460027DDD6 /* AppLockHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockHelper.swift; sourceTree = ""; }; 399A4C022317D1ED0027DDD6 /* OCBookmarkManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCBookmarkManager+Extension.swift"; sourceTree = ""; }; 399A4C0F23190ADF0027DDD6 /* PathExistsIntentHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PathExistsIntentHandler.swift; sourceTree = ""; }; @@ -837,7 +839,7 @@ 39AFC3D0225E72FB00A6D3AE /* GroupSharingTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupSharingTableViewController.swift; sourceTree = ""; }; 39AFC3D7225E79CD00A6D3AE /* GroupSharingEditTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupSharingEditTableViewController.swift; sourceTree = ""; }; 39B289A7226F1EE000BE0E11 /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = ""; }; - 39BCDD79246006AC00FE3D23 /* ZipAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZipAction.swift; sourceTree = ""; }; + 39BCDD79246006AC00FE3D23 /* CompressAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompressAction.swift; sourceTree = ""; }; 39BCDD802460858200FE3D23 /* CompressViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompressViewController.swift; sourceTree = ""; }; 39BE385C23435AFE0062A2FE /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 39CC8AE5228C12100020253B /* Array+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = ""; }; @@ -1746,6 +1748,7 @@ 6E586CF52199A70100F680C4 /* Actions+Extensions */ = { isa = PBXGroup; children = ( + 39999DF32462D57800880D45 /* UncompressAction.swift */, 39265BB123D9987500B0C4CA /* MediaEditingAction.swift */, 39CD755123D787E400193950 /* DocumentEditingAction.swift */, 397754F22327A33500119FCB /* OpenSceneAction.swift */, @@ -1767,7 +1770,7 @@ DC62514B225D254500736874 /* UploadBaseAction.swift */, DC625147225CEB2C00736874 /* UploadFileAction.swift */, DC625149225CEB4300736874 /* UploadMediaAction.swift */, - 39BCDD79246006AC00FE3D23 /* ZipAction.swift */, + 39BCDD79246006AC00FE3D23 /* CompressAction.swift */, ); path = "Actions+Extensions"; sourceTree = ""; @@ -3279,6 +3282,7 @@ 4C464BF12187AF1500D30602 /* PDFTocTableViewCell.swift in Sources */, DC1B270C209CF34B004715E1 /* BookmarkViewController.swift in Sources */, DC63208321FCAC1E007EC0A8 /* ClientActivityViewController.swift in Sources */, + 39999DF42462D57800880D45 /* UncompressAction.swift in Sources */, DC3DEC7E22B03ACD00F3352D /* CardPresentationController.swift in Sources */, 4C464BF62187AF1500D30602 /* PDFTocItem.swift in Sources */, 6E3A103E219D5BBA00F90C96 /* RenameAction.swift in Sources */, @@ -3378,7 +3382,7 @@ 394804DA225CBDBA00AA8183 /* BreadCrumbTableViewController.swift in Sources */, 4C235CEE21F88C0300A989A8 /* UIViewController+Extension.swift in Sources */, DC27A19D20CAB602008ACB6C /* FileProviderInterfaceManager.swift in Sources */, - 39BCDD7F246006AD00FE3D23 /* ZipAction.swift in Sources */, + 39BCDD7F246006AD00FE3D23 /* CompressAction.swift in Sources */, 4C51727D22DE04BD001BC97F /* ScheduledTaskExtension.swift in Sources */, DCC085512293ED52008CC05C /* DisplaySettingsSection.swift in Sources */, 23EC77582137F3DD0032D4E6 /* PDFViewerViewController.swift in Sources */, diff --git a/ownCloud/AppDelegate.swift b/ownCloud/AppDelegate.swift index 769a9daf8..634b96312 100644 --- a/ownCloud/AppDelegate.swift +++ b/ownCloud/AppDelegate.swift @@ -85,7 +85,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { OCExtensionManager.shared.addExtension(LinksAction.actionExtension) OCExtensionManager.shared.addExtension(FavoriteAction.actionExtension) OCExtensionManager.shared.addExtension(UnfavoriteAction.actionExtension) - OCExtensionManager.shared.addExtension(ZipAction.actionExtension) + OCExtensionManager.shared.addExtension(CompressAction.actionExtension) + OCExtensionManager.shared.addExtension(UncompressAction.actionExtension) if #available(iOS 13.0, *) { if UIDevice.current.isIpad() { // iPad & iOS 13+ only diff --git a/ownCloud/Client/Actions/Actions+Extensions/ZipAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CompressAction.swift similarity index 91% rename from ownCloud/Client/Actions/Actions+Extensions/ZipAction.swift rename to ownCloud/Client/Actions/Actions+Extensions/CompressAction.swift index 66803230d..a1c4d9462 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/ZipAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CompressAction.swift @@ -1,5 +1,5 @@ // -// ZipAction.swift +// CompressAction.swift // ownCloud // // Created by Matthias Hühne on 05/04/2020. @@ -19,15 +19,19 @@ import ownCloudSDK import ownCloudApp -class ZipAction: Action { - override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.zip") } +class CompressAction: Action { + override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.compress") } override class var category : ActionCategory? { return .normal } - override class var name : String { return "Compress as ZIP file".localized } + override class var name : String { return "Compress".localized } override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreFolder, .toolbar, .keyboardShortcut] } override class var keyCommand : String? { return "Z" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } override class func applicablePosition(forContext: ActionContext) -> ActionPosition { + if forContext.items.count == 1, forContext.items.first?.mimeType == "application/zip" { + return .none + } + return .afterMiddle } @@ -122,16 +126,13 @@ class ZipAction: Action { let error = ZIPArchive.compressContents(of: unifiedItems, fromBasePath: parentItem.path ?? "", asZipFile: zipURL, withPassword: nil) if !self.upload(itemURL: zipURL, to: parentItem, name: zipURL.lastPathComponent) { + self.removeFiles(url: zipURL) self.completed(with: NSError(ocError: .internal)) return } else { + self.removeFiles(url: zipURL) self.completed() } - - do { - try FileManager.default.removeItem(at: zipURL) - } catch { - } } } }) @@ -148,6 +149,13 @@ class ZipAction: Action { hudViewController.presentHUDOn(viewController: hostViewController) } + private func removeFiles(url : URL) { + do { + try FileManager.default.removeItem(at: url) + } catch { + } + } + override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreFolder { if #available(iOS 13.0, *) { diff --git a/ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift new file mode 100644 index 000000000..3c0db8918 --- /dev/null +++ b/ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift @@ -0,0 +1,140 @@ +// +// UncompressAction.swift +// ownCloud +// +// Created by Matthias Hühne on 05/06/2020. +// Copyright © 2020 ownCloud GmbH. All rights reserved. +// + +/* +* Copyright (C) 2020, ownCloud GmbH. +* +* This code is covered by the GNU Public License Version 3. +* +* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ +* You should have received a copy of this license along with this program. If not, see . +* +*/ + +import ownCloudSDK +import ownCloudApp + +class UncompressAction: Action { + override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.uncompress") } + override class var category : ActionCategory? { return .normal } + override class var name : String { return "Uncompress".localized } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreFolder, .toolbar, .keyboardShortcut] } + override class var keyCommand : String? { return "Z" } + override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } + class var supportedMimeTypes : [String] { return ["application/zip"] } + + override class func applicablePosition(forContext: ActionContext) -> ActionPosition { + if let core = forContext.core, forContext.items.count == 1, forContext.items.contains(where: {$0.type == .file && ($0.permissions.contains(.writable) || $0.parentItem(from: core)? .permissions.contains(.createFile) == true)}) { + if let item = forContext.items.first, let mimeType = item.mimeType { + if supportedMimeTypes.filter({ + if mimeType.contains($0) { + return true + } + + return false + }).count == 1 { + return .middle + } + } + } + + // Examine items in context + return .none + } + + override func run() { + guard context.items.count > 0, let hostViewController = context.viewController, let core = self.core else { + self.completed(with: NSError(ocError: .insufficientParameters)) + return + } + + let hudViewController = DownloadItemsHUDViewController(core: core, downloadItems: context.items as [OCItem]) { [weak hostViewController] (error, downloadedItems) in + + if let downloadedItems = downloadedItems, let downloadedItem = downloadedItems.first, error == nil, let fileItem = self.context.items.first, let parentItem = fileItem.parentItem(from: core), let fileURL = downloadedItem.file.url, let parentPath = parentItem.path, let fileName = fileItem.path { + + print("--> uncompress \(fileURL)") + + let zipItems = ZIPArchive.uncompressContents(ofZipFile: fileURL, parentItem: parentItem, withPassword: nil, with: core) +/* + for item in zipItems { + print("--> \(item.filepath) \(item.isDirectory) \(item.absolutePath)") + }*/ + + let collectionItems = zipItems.filter { (item) -> Bool in + return item.isDirectory + } + + let fileItems = zipItems.filter { (item) -> Bool in + return !item.isDirectory + } + let fileName = ((fileName as NSString).lastPathComponent as NSString).deletingPathExtension + + let dispatchGroup = DispatchGroup() + + core.createFolder(fileName, inside: parentItem, options: [ + .returnImmediatelyIfOfflineOrUnavailable : true, + .addTemporaryClaimForPurpose : OCCoreClaimPurpose.view.rawValue + ]) { (error, subcore, containerItem, _) in + + guard let containerItem = containerItem else { return } + var lastItem = containerItem + for collectionItem in collectionItems { + OnMainThread { + dispatchGroup.enter() + let newFolderPath = (collectionItem.filepath as NSString).lastPathComponent + + var insideItem = containerItem + if let lastpath = lastItem.path, lastpath.hasSuffix(collectionItem.filepath) { + insideItem = lastItem + } + print("--> create folder \(newFolderPath) in \(insideItem.path) \(collectionItem.filepath)") + + subcore.createFolder(newFolderPath, inside: insideItem, options: [ + .returnImmediatelyIfOfflineOrUnavailable : true, + .addTemporaryClaimForPurpose : OCCoreClaimPurpose.view.rawValue + ]) { (error, core, item, _) in + print("create folder finished \(item?.path)") + if let item = item { + lastItem = item + } + dispatchGroup.leave() + } + dispatchGroup.wait() + } + } + } + } + + } + + hudViewController.presentHUDOn(viewController: hostViewController) + } + + override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { + if location == .moreItem || location == .moreFolder { + if #available(iOS 13.0, *) { + return UIImage(systemName: "cube.box")?.tinted(with: Theme.shared.activeCollection.tintColor) + } else { + // Fallback on earlier versions + } + } + + return nil + } + + internal func upload(itemURL: URL, to rootItem: OCItem, name: String) -> Bool { + + if core != nil, let progress = itemURL.upload(with: core, at: rootItem) { + self.publish(progress: progress) + return true + } else { + Log.debug("Error setting up upload of \(Log.mask(name)) to \(Log.mask(rootItem.path))") + return false + } + } +} diff --git a/ownCloudAppFramework/ZIP Archive/ZIPArchive.h b/ownCloudAppFramework/ZIP Archive/ZIPArchive.h index 4843baa14..422fc726c 100644 --- a/ownCloudAppFramework/ZIP Archive/ZIPArchive.h +++ b/ownCloudAppFramework/ZIP Archive/ZIPArchive.h @@ -33,10 +33,26 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface ZipFileItem : NSObject +{ + NSString *_filepath; + BOOL _isDirectory; + NSString *_absolutePath; +} + +@property(strong,nonatomic) NSString *filepath; +@property(assign,nonatomic) BOOL isDirectory; +@property(strong,nonatomic) NSString *absolutePath; + +- (instancetype)initWithFilepath:(NSString *)filepath isDirectory:(BOOL)isDirectory absolutePath:(NSString *)absolutePath; + +@end + @interface ZIPArchive : NSObject + (NSError *)compressContentsOf:(NSURL *)sourceDirectory asZipFile:(NSURL *)zipFileURL; + (nullable NSError *)compressContentsOfItems:(NSArray *)sourceDirectorie fromBasePath:(NSString *)basePath asZipFile:(NSURL *)zipFileURL withPassword:(nullable NSString *)password; ++ (NSArray *)uncompressContentsOfZipFile:(NSURL *)zipFileURL parentItem:(OCItem *)parentItem withPassword:(nullable NSString *)password withCore:(OCCore *)core; @end diff --git a/ownCloudAppFramework/ZIP Archive/ZIPArchive.m b/ownCloudAppFramework/ZIP Archive/ZIPArchive.m index fe9405816..26dcff242 100644 --- a/ownCloudAppFramework/ZIP Archive/ZIPArchive.m +++ b/ownCloudAppFramework/ZIP Archive/ZIPArchive.m @@ -19,6 +19,7 @@ #import #import #import "ZIPArchive.h" +#import "OCCore+BundleImport.h" @implementation DownloadItem @@ -36,6 +37,22 @@ - (instancetype)initWithFile:(OCFile *)file item:(OCItem *)item @end +@implementation ZipFileItem + +- (instancetype)initWithFilepath:(NSString *)filepath isDirectory:(BOOL)isDirectory absolutePath:(NSString *)absolutePath +{ + if ((self = [super init]) != nil) + { + self.filepath = filepath; + self.isDirectory = isDirectory; + self.absolutePath = absolutePath; + } + + return(self); +} + +@end + @implementation ZIPArchive + (NSError *)compressContentsOf:(NSURL *)sourceDirectory asZipFile:(NSURL *)zipFileURL @@ -160,7 +177,7 @@ + (NSError *)compressContentsOf:(NSURL *)sourceDirectory asZipFile:(NSURL *)zipF return (error); } -+ (NSError *)compressContentsOfItems:(NSArray *)sourceItems fromBasePath:(NSString *)basePath asZipFile:(NSURL *)zipFileURL withPassword:(nullable NSString *)password ++ (nullable NSError *)compressContentsOfItems:(NSArray *)sourceItems fromBasePath:(NSString *)basePath asZipFile:(NSURL *)zipFileURL withPassword:(nullable NSString *)password { zip_t *zipArchive = NULL; int zipError = ZIP_ER_OK; @@ -242,6 +259,97 @@ + (NSError *)compressContentsOfItems:(NSArray *)sourceItems from return (error); } ++ (NSArray *)uncompressContentsOfZipFile:(NSURL *)zipFileURL parentItem:(OCItem *)parentItem withPassword:(nullable NSString *)password withCore:(OCCore *)core +{ + NSMutableArray *zipItems = [NSMutableArray new]; + zip_t *zipArchive = NULL; + int zipError = ZIP_ER_OK; + struct zip_stat sb; + struct zip_file *zf; + + char buf[100]; + long long sum; + NSInteger len; + NSError *error = nil; + + NSError *(^ErrorFromZipArchive)(zip_t *zipArchive) = ^(zip_t *zipArchive) { + zip_error_t *zipError = zip_get_error(zipArchive); + + return ([NSError errorWithDomain:LibZipErrorDomain code:zipError->zip_err userInfo:nil]); + }; + + if ((zipArchive = zip_open(zipFileURL.path.UTF8String, ZIP_RDONLY, &zipError)) != NULL) + { + NSURL *tmpURL = [zipFileURL URLByDeletingPathExtension]; + + [NSFileManager.defaultManager createDirectoryAtURL:tmpURL withIntermediateDirectories:NO attributes:nil error:nil]; + + for (NSInteger i = 0; i < zip_get_num_entries(zipArchive, 0); i++) { + if (zip_stat_index(zipArchive, i, 0, &sb) == 0) { + printf("==================/n"); + len = strlen(sb.name); + printf("Name: [%s], ", sb.name); + printf("Size: [%llu], ", sb.size); + printf("mtime: [%u]/n", (unsigned int)sb.mtime); + + + NSString *filePath = [tmpURL.path stringByAppendingPathComponent:[NSString stringWithUTF8String:sb.name]]; + + if (sb.name[len - 1] == '/') { + //safe_create_dir(sb.name); + ZipFileItem *item = [[ZipFileItem alloc] initWithFilepath:[NSString stringWithUTF8String:sb.name] isDirectory:YES absolutePath:filePath]; + [zipItems addObject:item]; + NSLog(@"--> create dir: %s", sb.name); + [NSFileManager.defaultManager createDirectoryAtURL:[tmpURL URLByAppendingPathComponent:[NSString stringWithUTF8String:sb.name]] withIntermediateDirectories:NO attributes:nil error:nil]; + } else { + NSLog(@"--> read file: %s", sb.name); + zf = zip_fopen_index(zipArchive, i, 0); + if (!zf) { + error = ErrorFromZipArchive(zipArchive); + OCLogError(@"Error opening zip file %@", error); + } + + NSMutableData *data = [NSMutableData new]; + sum = 0; + while (sum != sb.size) { + len = zip_fread(zf, buf, 100); + if (len < 0) { + error = ErrorFromZipArchive(zipArchive); + OCLogError(@"Error reading zip file %@", error); + } + [data appendBytes:buf length:len]; + sum += len; + } + + [NSFileManager.defaultManager createFileAtPath:filePath contents:data attributes:nil]; + + ZipFileItem *item = [[ZipFileItem alloc] initWithFilepath:[NSString stringWithUTF8String:sb.name] isDirectory:NO absolutePath:filePath]; + [zipItems addObject:item]; + + NSLog(@"--> zipitems %@", zipItems); + + zip_fclose(zf); + } + } else { + printf("File[%s] Line[%d]/n", __FILE__, __LINE__); + } + } + + } + + if (zip_close(zipArchive) < 0) + { + // Error + error = ErrorFromZipArchive(zipArchive); + OCLogError(@"Error closing zip archive %@: %@", zipFileURL.path, error); + + zip_discard(zipArchive); + } + + return zipItems; + //return (error); +} + @end NSErrorDomain LibZipErrorDomain = @"LibZIPErrorDomain"; From 1e1b966c42d6bcb2a1387623612968a1a40f39c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Mon, 11 May 2020 16:33:10 +0200 Subject: [PATCH 5/6] - added encryption (passsord) support - code cleanup - added localization strings - added icon for iOS < 13 --- .../libzip/libzip.xcodeproj/project.pbxproj | 10 ++ ownCloud.xcodeproj/project.pbxproj | 2 +- .../Actions+Extensions/CompressAction.swift | 28 ++-- .../Actions+Extensions/UncompressAction.swift | 122 +++++++++++------- .../Actions/CompressViewController.swift | 107 +++++++++++++-- .../Client/Actions/NamingViewController.swift | 11 +- .../cube.imageset/Contents.json | 21 +++ .../cube.imageset/cube.box.png | Bin 0 -> 1372 bytes .../Resources/en.lproj/Localizable.strings | 9 ++ ownCloudAppFramework/ZIP Archive/ZIPArchive.h | 2 + ownCloudAppFramework/ZIP Archive/ZIPArchive.m | 90 ++++++++++--- .../Base.lproj/Intents.intentdefinition | 50 ++++++- .../CompressPathItemsIntentHandler .swift | 11 +- 13 files changed, 361 insertions(+), 102 deletions(-) create mode 100644 ownCloud/Resources/Assets.xcassets/cube.imageset/Contents.json create mode 100644 ownCloud/Resources/Assets.xcassets/cube.imageset/cube.box.png diff --git a/external/libzip/libzip.xcodeproj/project.pbxproj b/external/libzip/libzip.xcodeproj/project.pbxproj index f03b4736f..246da5d96 100644 --- a/external/libzip/libzip.xcodeproj/project.pbxproj +++ b/external/libzip/libzip.xcodeproj/project.pbxproj @@ -741,6 +741,12 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + "HAVE_CONFIG_H=1", + "HAVE_CRYPTO=1", + ); INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( @@ -765,6 +771,10 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "HAVE_CONFIG_H=1", + "HAVE_CRYPTO=1", + ); INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 1afd821f5..7a4958154 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -1748,7 +1748,6 @@ 6E586CF52199A70100F680C4 /* Actions+Extensions */ = { isa = PBXGroup; children = ( - 39999DF32462D57800880D45 /* UncompressAction.swift */, 39265BB123D9987500B0C4CA /* MediaEditingAction.swift */, 39CD755123D787E400193950 /* DocumentEditingAction.swift */, 397754F22327A33500119FCB /* OpenSceneAction.swift */, @@ -1771,6 +1770,7 @@ DC625147225CEB2C00736874 /* UploadFileAction.swift */, DC625149225CEB4300736874 /* UploadMediaAction.swift */, 39BCDD79246006AC00FE3D23 /* CompressAction.swift */, + 39999DF32462D57800880D45 /* UncompressAction.swift */, ); path = "Actions+Extensions"; sourceTree = ""; diff --git a/ownCloud/Client/Actions/Actions+Extensions/CompressAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CompressAction.swift index a1c4d9462..70f74e72b 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CompressAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CompressAction.swift @@ -112,27 +112,26 @@ class CompressAction: Action { zipName = String(format: "%@.zip", item.name ?? self.defaultZipName) } - let renameViewController = NamingViewController(with: nil, core: self.core, defaultName: zipName, stringValidator: { name in + let renameViewController = CompressViewController(with: nil, core: self.core, defaultName: zipName, stringValidator: { name in if name.contains("/") || name.contains("\\") { return (false, "File name cannot contain / or \\".localized) } else { return (true, nil) } - }, completion: { newName, _ in - + }, completion: { newName, password, _ in OnBackgroundQueue { if let newName = newName, error == nil, let fileItem = self.context.items.first, let parentItem = fileItem.parentItem(from: core) { let zipURL = FileManager.default.temporaryDirectory.appendingPathComponent(newName) - let error = ZIPArchive.compressContents(of: unifiedItems, fromBasePath: parentItem.path ?? "", asZipFile: zipURL, withPassword: nil) - - if !self.upload(itemURL: zipURL, to: parentItem, name: zipURL.lastPathComponent) { - self.removeFiles(url: zipURL) - self.completed(with: NSError(ocError: .internal)) - return - } else { - self.removeFiles(url: zipURL) - self.completed() - } + let error = ZIPArchive.compressContents(of: unifiedItems, fromBasePath: parentItem.path ?? "", asZipFile: zipURL, withPassword: password) + + if !self.upload(itemURL: zipURL, to: parentItem, name: zipURL.lastPathComponent) { + self.removeFiles(url: zipURL) + self.completed(with: NSError(ocError: .internal)) + return + } else { + self.removeFiles(url: zipURL) + self.completed() + } } } }) @@ -161,7 +160,7 @@ class CompressAction: Action { if #available(iOS 13.0, *) { return UIImage(systemName: "cube.box")?.tinted(with: Theme.shared.activeCollection.tintColor) } else { - // Fallback on earlier versions + return UIImage(named: "cube")?.tinted(with: Theme.shared.activeCollection.tintColor) } } @@ -169,7 +168,6 @@ class CompressAction: Action { } internal func upload(itemURL: URL, to rootItem: OCItem, name: String) -> Bool { - if core != nil, let progress = itemURL.upload(with: core, at: rootItem) { self.publish(progress: progress) return true diff --git a/ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift index 3c0db8918..40e5357ab 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift @@ -55,64 +55,98 @@ class UncompressAction: Action { let hudViewController = DownloadItemsHUDViewController(core: core, downloadItems: context.items as [OCItem]) { [weak hostViewController] (error, downloadedItems) in - if let downloadedItems = downloadedItems, let downloadedItem = downloadedItems.first, error == nil, let fileItem = self.context.items.first, let parentItem = fileItem.parentItem(from: core), let fileURL = downloadedItem.file.url, let parentPath = parentItem.path, let fileName = fileItem.path { + if let downloadedItems = downloadedItems, let downloadedItem = downloadedItems.first, error == nil, let fileItem = self.context.items.first, let parentItem = fileItem.parentItem(from: core), let fileURL = downloadedItem.file.url { - print("--> uncompress \(fileURL)") + if ZIPArchive.isZipFileEncrypted(fileURL) { + let alertController = UIAlertController(title: "Enter Password".localized, message: "The file is protected with a password.\nPlease enter the password to uncompress the file.".localized, preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "Password".localized + textField.isSecureTextEntry = true + } + let confirmAction = UIAlertAction(title: "OK".localized, style: .default) { [weak alertController] _ in + guard let alertController = alertController, let textField = alertController.textFields?.first, let password = textField.text else { return } - let zipItems = ZIPArchive.uncompressContents(ofZipFile: fileURL, parentItem: parentItem, withPassword: nil, with: core) -/* - for item in zipItems { - print("--> \(item.filepath) \(item.isDirectory) \(item.absolutePath)") - }*/ + if ZIPArchive.checkPassword(password, forZipFile: fileURL) == true { + self.uncompressContents(of: fileURL, fileItem: fileItem, parentItem: parentItem, password: password, core: core) + } else { + let alert = UIAlertController(title: "Wrong Password".localized, message: "The entered password was not correct!".localized, preferredStyle: .alert) - let collectionItems = zipItems.filter { (item) -> Bool in - return item.isDirectory - } + alert.addAction(UIAlertAction(title: "OK".localized, style: .default, handler: { _ in + self.completed() + })) - let fileItems = zipItems.filter { (item) -> Bool in - return !item.isDirectory + hostViewController?.present(alert, animated: true, completion: nil) + } + } + alertController.addAction(confirmAction) + let cancelAction = UIAlertAction(title: "Cancel".localized, style: .cancel, handler: { _ in + self.completed() + }) + alertController.addAction(cancelAction) + + hostViewController?.present(alertController, animated: true, completion: nil) + } else { + self.uncompressContents(of: fileURL, fileItem: fileItem, parentItem: parentItem, password: nil, core: core) } - let fileName = ((fileName as NSString).lastPathComponent as NSString).deletingPathExtension + } + } - let dispatchGroup = DispatchGroup() + hudViewController.presentHUDOn(viewController: hostViewController) + } - core.createFolder(fileName, inside: parentItem, options: [ - .returnImmediatelyIfOfflineOrUnavailable : true, - .addTemporaryClaimForPurpose : OCCoreClaimPurpose.view.rawValue - ]) { (error, subcore, containerItem, _) in + func uncompressContents(of zipFile: URL, fileItem: OCItem, parentItem: OCItem, password: String?, core: OCCore) { + if let parentPath = parentItem.path, let fileName = fileItem.path { + let zipItems = ZIPArchive.uncompressContents(ofZipFile: zipFile, parentItem: parentItem, withPassword: nil, with: core) + /* + for item in zipItems { + print("--> \(item.filepath) \(item.isDirectory) \(item.absolutePath)") + }*/ - guard let containerItem = containerItem else { return } - var lastItem = containerItem - for collectionItem in collectionItems { - OnMainThread { - dispatchGroup.enter() - let newFolderPath = (collectionItem.filepath as NSString).lastPathComponent + let collectionItems = zipItems.filter { (item) -> Bool in + return item.isDirectory + } - var insideItem = containerItem - if let lastpath = lastItem.path, lastpath.hasSuffix(collectionItem.filepath) { - insideItem = lastItem - } - print("--> create folder \(newFolderPath) in \(insideItem.path) \(collectionItem.filepath)") - - subcore.createFolder(newFolderPath, inside: insideItem, options: [ - .returnImmediatelyIfOfflineOrUnavailable : true, - .addTemporaryClaimForPurpose : OCCoreClaimPurpose.view.rawValue - ]) { (error, core, item, _) in - print("create folder finished \(item?.path)") - if let item = item { - lastItem = item - } - dispatchGroup.leave() + let fileItems = zipItems.filter { (item) -> Bool in + return !item.isDirectory + } + let fileName = ((fileName as NSString).lastPathComponent as NSString).deletingPathExtension + + let dispatchGroup = DispatchGroup() + // Todo: Should be repleaced by SDK function! + core.createFolder(fileName, inside: parentItem, options: [ + .returnImmediatelyIfOfflineOrUnavailable : true, + .addTemporaryClaimForPurpose : OCCoreClaimPurpose.view.rawValue + ]) { (error, subcore, containerItem, _) in + + guard let containerItem = containerItem else { return } + var lastItem = containerItem + for collectionItem in collectionItems { + OnMainThread { + dispatchGroup.enter() + let newFolderPath = (collectionItem.filepath as NSString).lastPathComponent + + var insideItem = containerItem + if let lastpath = lastItem.path, lastpath.hasSuffix(collectionItem.filepath) { + insideItem = lastItem + } + print("--> create folder \(newFolderPath) in \(insideItem.path) \(collectionItem.filepath)") + + subcore.createFolder(newFolderPath, inside: insideItem, options: [ + .returnImmediatelyIfOfflineOrUnavailable : true, + .addTemporaryClaimForPurpose : OCCoreClaimPurpose.view.rawValue + ]) { (error, core, item, _) in + print("create folder finished \(item?.path)") + if let item = item { + lastItem = item } - dispatchGroup.wait() + dispatchGroup.leave() } + dispatchGroup.wait() + self.completed() } } } - } - - hudViewController.presentHUDOn(viewController: hostViewController) } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { @@ -120,7 +154,7 @@ class UncompressAction: Action { if #available(iOS 13.0, *) { return UIImage(systemName: "cube.box")?.tinted(with: Theme.shared.activeCollection.tintColor) } else { - // Fallback on earlier versions + return UIImage(named: "cube")?.tinted(with: Theme.shared.activeCollection.tintColor) } } diff --git a/ownCloud/Client/Actions/CompressViewController.swift b/ownCloud/Client/Actions/CompressViewController.swift index 1e6955ff8..1f9ab0090 100644 --- a/ownCloud/Client/Actions/CompressViewController.swift +++ b/ownCloud/Client/Actions/CompressViewController.swift @@ -17,39 +17,122 @@ */ import UIKit +import ownCloudSDK class CompressViewController: NamingViewController { + private var passwordTextField: UITextField + private var passwordSwitch: UISwitch + private var passwordLabel: UILabel + var completionHandler: (String?, String?, NamingViewController) -> Void + private let zipExtension = ".zip" + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - deinit { - NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidShowNotification, object: nil) - Theme.shared.unregister(client: self) + public init(with item: OCItem? = nil, core: OCCore? = nil, defaultName: String? = nil, stringValidator: StringValidatorHandler? = nil, completion: @escaping (String?, String?, NamingViewController) -> Void) { + + passwordTextField = UITextField(frame: .zero) + passwordTextField.accessibilityIdentifier = "pasword-text-field" + passwordSwitch = UISwitch(frame: .zero) + passwordSwitch.accessibilityIdentifier = "password-switch" + passwordLabel = UILabel(frame: .zero) + completionHandler = completion + + super.init(with: item, core: core, defaultName: defaultName, stringValidator: stringValidator, completion: { _, _ in + }) } override func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { - //super.applyThemeCollection(theme: theme, collection: collection, event: ThemeEvent) + super.applyThemeCollection(theme: theme, collection: collection, event: event) + + passwordTextField.backgroundColor = collection.tableBackgroundColor + passwordTextField.textColor = collection.tableRowColors.labelColor + passwordTextField.keyboardAppearance = collection.keyboardAppearance + passwordLabel.textColor = collection.tableRowColors.labelColor } override func viewDidLoad() { super.viewDidLoad() - } + completion = {name, controller in + self.passwordTextField.resignFirstResponder() + if self.passwordSwitch.isOn { + self.completionHandler(name, self.passwordTextField.text, controller) + } else { + self.completionHandler(name, nil, controller) + } + } - private func render(newTraitCollection: UITraitCollection) { + // Password switch + passwordSwitch.translatesAutoresizingMaskIntoConstraints = false + nameContainer.addSubview(passwordSwitch) - } + NSLayoutConstraint.activate([ + passwordSwitch.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 30), + passwordSwitch.leftAnchor.constraint(equalTo: nameContainer.leftAnchor, constant: 30) + ]) + + // Password label + passwordLabel.translatesAutoresizingMaskIntoConstraints = false + nameContainer.addSubview(passwordLabel) + + passwordLabel.text = "Protect file with password".localized - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) + NSLayoutConstraint.activate([ + passwordLabel.centerYAnchor.constraint(equalTo: passwordSwitch.centerYAnchor), + passwordLabel.heightAnchor.constraint(equalToConstant: 40), + passwordLabel.leftAnchor.constraint(equalTo: passwordSwitch.rightAnchor, constant: 15), + passwordLabel.rightAnchor.constraint(equalTo: nameContainer.rightAnchor, constant: -20) + ]) - render(newTraitCollection: traitCollection) + // Password textfield + passwordTextField.translatesAutoresizingMaskIntoConstraints = false + nameContainer.addSubview(passwordTextField) + NSLayoutConstraint.activate([ + passwordTextField.topAnchor.constraint(equalTo: passwordLabel.bottomAnchor, constant: 15), + passwordTextField.heightAnchor.constraint(equalToConstant: 40), + passwordTextField.leftAnchor.constraint(equalTo: nameContainer.leftAnchor, constant: 30), + passwordTextField.rightAnchor.constraint(equalTo: nameContainer.rightAnchor, constant: -20) + ]) + + passwordSwitch.isOn = false + + passwordTextField.delegate = self + passwordTextField.textAlignment = .center + passwordTextField.becomeFirstResponder() + passwordTextField.addTarget(self, action: #selector(textfieldDidChange(_:)), for: .editingChanged) + passwordTextField.enablesReturnKeyAutomatically = true + passwordTextField.autocorrectionType = .no + passwordTextField.isSecureTextEntry = true + passwordTextField.borderStyle = .roundedRect + passwordTextField.clearButtonMode = .always + passwordTextField.accessibilityLabel = "Password".localized + passwordTextField.placeholder = "Password".localized } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) + override func textfieldDidChange(_ sender: UITextField) { + if sender.isEqual(passwordTextField), sender.text?.count ?? 0 > 0 { + passwordSwitch.setOn(true, animated: true) + } } +} + +extension CompressViewController { + override func textFieldDidBeginEditing(_ textField: UITextField) { + + if textField.isEqual(nameTextField) { + if let name = nameTextField.text, + let range = name.range(of: zipExtension), + let position: UITextPosition = nameTextField.position(from: nameTextField.beginningOfDocument, offset: range.lowerBound.utf16Offset(in: name)) { + + textField.selectedTextRange = nameTextField.textRange(from: nameTextField.beginningOfDocument, to:position) + + } else { + textField.selectedTextRange = nameTextField.textRange(from: nameTextField.beginningOfDocument, to: nameTextField.endOfDocument) + } + } + } } diff --git a/ownCloud/Client/Actions/NamingViewController.swift b/ownCloud/Client/Actions/NamingViewController.swift index c9432b067..9617c3731 100644 --- a/ownCloud/Client/Actions/NamingViewController.swift +++ b/ownCloud/Client/Actions/NamingViewController.swift @@ -25,19 +25,20 @@ typealias StringValidatorHandler = (String) -> StringValidatorResult class NamingViewController: UIViewController, Themeable { weak var item: OCItem? weak var core: OCCore? - var completion: (String?, NamingViewController) -> Void + typealias NamingCompletionHandler = (String?, NamingViewController) -> Void + var completion: NamingCompletionHandler var stringValidator: StringValidatorHandler? var defaultName: String? private var blurView: UIVisualEffectView - private var stackView: UIStackView + var stackView: UIStackView private var thumbnailContainer: UIView private var thumbnailImageView: UIImageView - private var nameContainer: UIView - private var nameTextField: UITextField + var nameContainer: UIView + var nameTextField: UITextField private var textfieldTopAnchorConstraint: NSLayoutConstraint private var textfieldCenterYAnchorConstraint: NSLayoutConstraint @@ -52,7 +53,7 @@ class NamingViewController: UIViewController, Themeable { private let thumbnailSize = CGSize(width: 150.0, height: 150.0) - init(with item: OCItem? = nil, core: OCCore? = nil, defaultName: String? = nil, stringValidator: StringValidatorHandler? = nil, completion: @escaping (String?, NamingViewController) -> Void) { + public init(with item: OCItem? = nil, core: OCCore? = nil, defaultName: String? = nil, stringValidator: StringValidatorHandler? = nil, completion: @escaping (String?, NamingViewController) -> Void) { self.item = item self.core = core self.completion = completion diff --git a/ownCloud/Resources/Assets.xcassets/cube.imageset/Contents.json b/ownCloud/Resources/Assets.xcassets/cube.imageset/Contents.json new file mode 100644 index 000000000..071e0c843 --- /dev/null +++ b/ownCloud/Resources/Assets.xcassets/cube.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "cube.box.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ownCloud/Resources/Assets.xcassets/cube.imageset/cube.box.png b/ownCloud/Resources/Assets.xcassets/cube.imageset/cube.box.png new file mode 100644 index 0000000000000000000000000000000000000000..17dfada08265457c958a26667edd9a774af85297 GIT binary patch literal 1372 zcmX|>dpOg39LIk%x3E)@(wGWqJQc^~Bttc38;>6oVxgQ)OsM2?kn0%9Oh=3~YwlAf zG%>fBh(x1gj--xUHe|WQ$?2RqPfwrcd4JyT@8@}azJGqwT|g{M39bYH01S__bCtTe zR47GxX&;;COFIAwbH$>8>H#g0^bvK@6YuZj1Q<%QA|MNa19Dr4R5lRJAK4y)1fc)V zWdI;85Rm=OfKuNooKz4%4yFbGJGlUq1^G@(|6$4epv_s(9~rf^)}&Y4BeiW;ab95n zpe)^1wt}dpmPP=8Y#iS1lp9HIL3o`6`T@^Esr?u8dHTV^X;-u#<;9V5NmlcEr?)b1 zhNy!!+x*~5^fb`RE9TT3Jp96%XLReJGqaeL%G>eWo*hwrOZ}>dLM6ha3kZnzRZ= z$UN|fyKk0jP*L|~I~5rWc4-&$g5~{Ayjk((eezj0V{Wt?g+I0;` z1-_VEf<%4ssy;@~Zd!lIZ)~k?xps%FyHQ2+K|9=;n2(+Bci0mO8SHRl9iK!+wu>2R z?0CfLUV%ZhYT@}~1!E=>=xNCrv!VPl$T=}`xGS#8&}aG{C-x$z5SK{vJZd6P@+b+4 zhJHe?R%+KTKU|;7Q4+r`OtitJrb@inkmik^n?bj3{Q8!!KO6of4;k+Xggiks*v&dpfGN)@WZRc_tg5MZ$N@#4` z?N{Z*Y|Wp`lXML4Qr*0tAym@5yytQVJn+_TvWy4DOzZ9pUql&xHbo-DW0YenQq54r}X~UY>yvRBmeg ziHCo%>4;=V0g2;o|03lA+vHx?b>H>aO@x#BgCaanKm67G4BrMcZPKHHno1HPGLkd^ z7!n?@%bF!JV7YTQee+Y$jdXGK@JayY%m$kVBZCziJ?&;KeB4JJy>0Fl)NcRUTbaE= zsC5DYjhkE(1-$Wu4Z`DdC!c(ytX;u`8*;6UE63+@dckdd4_kEA>1kc1gm-_Kj2ifD zi*s{R*Y@6(}+pbYvasIknwl0C`DpA;nVo(mn*VG*4L=Pt-n{ zHY$v4caNQLWE7WbD{{KQitNfwt(&ao*vUIo%ojDM+?bS+lh*(a!Ivz``P_zf?nq)X z`^L&S6T^aq^&1?LN *)sourceDirectorie fromBasePath:(NSString *)basePath asZipFile:(NSURL *)zipFileURL withPassword:(nullable NSString *)password; + (NSArray *)uncompressContentsOfZipFile:(NSURL *)zipFileURL parentItem:(OCItem *)parentItem withPassword:(nullable NSString *)password withCore:(OCCore *)core; ++ (BOOL)isZipFileEncrypted:(NSURL *)zipFileURL; ++ (BOOL)checkPassword:(NSString *)password forZipFile:(NSURL *)zipFileURL; @end diff --git a/ownCloudAppFramework/ZIP Archive/ZIPArchive.m b/ownCloudAppFramework/ZIP Archive/ZIPArchive.m index 26dcff242..7ef6ddc19 100644 --- a/ownCloudAppFramework/ZIP Archive/ZIPArchive.m +++ b/ownCloudAppFramework/ZIP Archive/ZIPArchive.m @@ -280,6 +280,9 @@ + (nullable NSError *)compressContentsOfItems:(NSArray *)sourceI if ((zipArchive = zip_open(zipFileURL.path.UTF8String, ZIP_RDONLY, &zipError)) != NULL) { + if (password != nil) { + zip_set_default_password(zipArchive, (const char*)[password UTF8String]); + } NSURL *tmpURL = [zipFileURL URLByDeletingPathExtension]; [NSFileManager.defaultManager createDirectoryAtURL:tmpURL withIntermediateDirectories:NO attributes:nil error:nil]; @@ -303,32 +306,33 @@ + (nullable NSError *)compressContentsOfItems:(NSArray *)sourceI [NSFileManager.defaultManager createDirectoryAtURL:[tmpURL URLByAppendingPathComponent:[NSString stringWithUTF8String:sb.name]] withIntermediateDirectories:NO attributes:nil error:nil]; } else { NSLog(@"--> read file: %s", sb.name); + zf = zip_fopen_index(zipArchive, i, 0); if (!zf) { error = ErrorFromZipArchive(zipArchive); OCLogError(@"Error opening zip file %@", error); - } - - NSMutableData *data = [NSMutableData new]; - sum = 0; - while (sum != sb.size) { - len = zip_fread(zf, buf, 100); - if (len < 0) { - error = ErrorFromZipArchive(zipArchive); - OCLogError(@"Error reading zip file %@", error); + } else { + NSMutableData *data = [NSMutableData new]; + sum = 0; + while (sum != sb.size) { + len = zip_fread(zf, buf, 100); + if (len < 0) { + error = ErrorFromZipArchive(zipArchive); + OCLogError(@"Error reading zip file %@", error); + } + [data appendBytes:buf length:len]; + sum += len; } - [data appendBytes:buf length:len]; - sum += len; - } - [NSFileManager.defaultManager createFileAtPath:filePath contents:data attributes:nil]; + [NSFileManager.defaultManager createFileAtPath:filePath contents:data attributes:nil]; - ZipFileItem *item = [[ZipFileItem alloc] initWithFilepath:[NSString stringWithUTF8String:sb.name] isDirectory:NO absolutePath:filePath]; - [zipItems addObject:item]; + ZipFileItem *item = [[ZipFileItem alloc] initWithFilepath:[NSString stringWithUTF8String:sb.name] isDirectory:NO absolutePath:filePath]; + [zipItems addObject:item]; - NSLog(@"--> zipitems %@", zipItems); + NSLog(@"--> zipitems %@", zipItems); - zip_fclose(zf); + zip_fclose(zf); + } } } else { printf("File[%s] Line[%d]/n", __FILE__, __LINE__); @@ -350,6 +354,58 @@ + (nullable NSError *)compressContentsOfItems:(NSArray *)sourceI //return (error); } ++ (BOOL)isZipFileEncrypted:(NSURL *)zipFileURL +{ + zip_t *zipArchive = NULL; + int zipError = ZIP_ER_OK; + + if ((zipArchive = zip_open(zipFileURL.path.UTF8String, ZIP_RDONLY, &zipError)) != NULL) + { + struct zip_file *zf; + zf = zip_fopen_index(zipArchive, 0, 0); + if (!zf) { + return YES; + } + + if (zip_close(zipArchive) < 0) + { + // Error + zip_discard(zipArchive); + } + } + + return NO; +} + ++ (BOOL)checkPassword:(NSString *)password forZipFile:(NSURL *)zipFileURL +{ + zip_t *zipArchive = NULL; + int zipError = ZIP_ER_OK; + + if ((zipArchive = zip_open(zipFileURL.path.UTF8String, ZIP_RDONLY, &zipError)) != NULL) + { + struct zip_file *zf; + zf = zip_fopen_index_encrypted(zipArchive, 0, 0, (const char*)[password UTF8String]); + if (zf) { + if (zip_close(zipArchive) < 0) + { + // Error + zip_discard(zipArchive); + } + + return YES; + } + + if (zip_close(zipArchive) < 0) + { + // Error + zip_discard(zipArchive); + } + } + + return NO; +} + @end NSErrorDomain LibZipErrorDomain = @"LibZIPErrorDomain"; diff --git a/ownCloudAppShared/Intent/Base.lproj/Intents.intentdefinition b/ownCloudAppShared/Intent/Base.lproj/Intents.intentdefinition index ab49c9606..e04c348b0 100644 --- a/ownCloudAppShared/Intent/Base.lproj/Intents.intentdefinition +++ b/ownCloudAppShared/Intent/Base.lproj/Intents.intentdefinition @@ -2516,15 +2516,15 @@ INIntentDescriptionID hgRmHs INIntentLastParameterTag - 8 + 9 INIntentManagedParameterCombinations - pathItems,account,filename + pathItems,account,filename,password INIntentParameterCombinationSupportsBackgroundExecution INIntentParameterCombinationTitle - Compress ${pathItems} of ${account}to ${filename} + Compress ${pathItems} of ${account} INIntentParameterCombinationTitleID oBIFKF INIntentParameterCombinationUpdatesLinked @@ -2535,16 +2535,18 @@ CompressPathItems INIntentParameterCombinations - pathItems,account,filename + pathItems,account,filename,password INIntentParameterCombinationIsLinked + INIntentParameterCombinationIsPrimary + INIntentParameterCombinationSupportsBackgroundExecution INIntentParameterCombinationTitle - Compress ${pathItems} of ${account}to ${filename} + Compress ${pathItems} of ${account} INIntentParameterCombinationTitleID - yaob48 + x0Cl4V INIntentParameters @@ -2646,13 +2648,47 @@ INIntentParameterType Object + + INIntentParameterDisplayName + Password (optional) + INIntentParameterDisplayNameID + n2gH1U + INIntentParameterDisplayPriority + 3 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + + INIntentParameterName + password + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Enter a password for the file, if needed + INIntentParameterPromptDialogFormatStringID + P4MF3Y + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsResolution + + INIntentParameterTag + 9 + INIntentParameterType + String + INIntentParameterDisplayName Filename (optional) INIntentParameterDisplayNameID dPSUsQ INIntentParameterDisplayPriority - 3 + 4 INIntentParameterMetadata INIntentParameterMetadataCapitalization diff --git a/ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift b/ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift index 82bfdbdcd..421e564a8 100644 --- a/ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift +++ b/ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift @@ -103,9 +103,13 @@ public class CompressPathItemsIntentHandler: NSObject, CompressPathItemsIntentHa } else if unifiedItems.count == 1, let item = unifiedItems.first?.item { zipFileName = String(format: "%@.zip", item.name ?? defaultZipName) } + var password: String? = nil + if let intentPassword = intent.password, intentPassword.count > 0 { + password = intentPassword + } let zipURL = FileManager.default.temporaryDirectory.appendingPathComponent(zipFileName) - let error = ZIPArchive.compressContents(of: unifiedItems, fromBasePath: "/", asZipFile: zipURL, withPassword: nil) + let error = ZIPArchive.compressContents(of: unifiedItems, fromBasePath: "/", asZipFile: zipURL, withPassword: password) let file = INFile(fileURL: zipURL, filename: zipFileName, typeIdentifier: nil) completion(CompressPathItemsIntentResponse.success(file: file)) @@ -146,6 +150,11 @@ public class CompressPathItemsIntentHandler: NSObject, CompressPathItemsIntentHa public func resolveFilename(for intent: CompressPathItemsIntent, with completion: @escaping (INStringResolutionResult) -> Void) { completion(INStringResolutionResult.success(with: intent.filename ?? "")) } + + public func resolvePassword(for intent: CompressPathItemsIntent, with completion: @escaping (INStringResolutionResult) -> Void) { + completion(INStringResolutionResult.success(with: intent.password ?? "")) + } + } @available(iOS 13.0, *) From 695f3a4d127bd54bebaf7bc66a6b4ee6b2148034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Mon, 11 May 2020 22:45:59 +0200 Subject: [PATCH 6/6] changed localization strings --- .../Actions/Actions+Extensions/UncompressAction.swift | 6 +++--- ownCloud/Resources/en.lproj/Localizable.strings | 4 ++-- .../Intent/CompressPathItemsIntentHandler .swift | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift index 40e5357ab..c1fc92955 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UncompressAction.swift @@ -55,10 +55,10 @@ class UncompressAction: Action { let hudViewController = DownloadItemsHUDViewController(core: core, downloadItems: context.items as [OCItem]) { [weak hostViewController] (error, downloadedItems) in - if let downloadedItems = downloadedItems, let downloadedItem = downloadedItems.first, error == nil, let fileItem = self.context.items.first, let parentItem = fileItem.parentItem(from: core), let fileURL = downloadedItem.file.url { + if let downloadedItems = downloadedItems, let downloadedItem = downloadedItems.first, error == nil, let fileItem = self.context.items.first, let filename = fileItem.name, let parentItem = fileItem.parentItem(from: core), let fileURL = downloadedItem.file.url { if ZIPArchive.isZipFileEncrypted(fileURL) { - let alertController = UIAlertController(title: "Enter Password".localized, message: "The file is protected with a password.\nPlease enter the password to uncompress the file.".localized, preferredStyle: .alert) + let alertController = UIAlertController(title: "Enter Password".localized, message: String(format: "The document \"%@\" is password protected.\nPlease enter the password to uncompress the document.".localized, filename), preferredStyle: .alert) alertController.addTextField { textField in textField.placeholder = "Password".localized textField.isSecureTextEntry = true @@ -69,7 +69,7 @@ class UncompressAction: Action { if ZIPArchive.checkPassword(password, forZipFile: fileURL) == true { self.uncompressContents(of: fileURL, fileItem: fileItem, parentItem: parentItem, password: password, core: core) } else { - let alert = UIAlertController(title: "Wrong Password".localized, message: "The entered password was not correct!".localized, preferredStyle: .alert) + let alert = UIAlertController(title: "Wrong Password".localized, message: "The archive could not be uncompressed with the provided password.".localized, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK".localized, style: .default, handler: { _ in self.completed() diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index 5776da8a1..44aba024e 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -596,9 +596,9 @@ /* Compress / Uncompress */ "Enter Password" = "Enter Password"; -"The file is protected with a password.\nPlease enter the password to uncompress the file." = "The file is protected with a password.\nPlease enter the password to uncompress the file."; +"The document \"%@\" is password protected.\nPlease enter the password to uncompress the document." = "The document \"%@\" is password protected.\nPlease enter the password to uncompress the document."; "Wrong Password" = "Wrong Password"; -"The entered password was not correct!" = "The entered password was not correct!"; +"The archive could not be uncompressed with the provided password." = "The archive could not be uncompressed with the provided password."; "Compress" = "Compress"; "Uncompress" = "Uncompress"; "Protect file with password" = "Protect file with password"; diff --git a/ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift b/ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift index 421e564a8..86d778415 100644 --- a/ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift +++ b/ownCloudAppShared/Intent/CompressPathItemsIntentHandler .swift @@ -103,7 +103,7 @@ public class CompressPathItemsIntentHandler: NSObject, CompressPathItemsIntentHa } else if unifiedItems.count == 1, let item = unifiedItems.first?.item { zipFileName = String(format: "%@.zip", item.name ?? defaultZipName) } - var password: String? = nil + var password: String? if let intentPassword = intent.password, intentPassword.count > 0 { password = intentPassword }