From 5368da7df7247295a18d14e38d66df35d5599eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Fri, 27 Sep 2024 15:15:42 +0200 Subject: [PATCH 01/13] feat: Multiselect behaviour --- kDrive/AppRouter.swift | 17 +++++++- .../File List/FileListViewController.swift | 39 ++++++++++-------- .../Files/File List/FileListViewModel.swift | 3 ++ .../MultipleSelectionFileListViewModel.swift | 2 + .../UI/Controller/Files/FilePresenter.swift | 14 ++++++- ...atingPanelSelectOptionViewController.swift | 6 +++ .../Menu/Share/PublicShareViewModel.swift | 41 +++++++++++-------- 7 files changed, 85 insertions(+), 37 deletions(-) diff --git a/kDrive/AppRouter.swift b/kDrive/AppRouter.swift index 5bef6867c..1fda140e0 100644 --- a/kDrive/AppRouter.swift +++ b/kDrive/AppRouter.swift @@ -614,12 +614,27 @@ public struct AppRouter: AppNavigable { return } + // TODO: i18n + let configuration = FileListViewModel.Configuration(selectAllSupported: true, + rootTitle: "public share", + emptyViewType: .emptyFolder, + supportsDrop: false, + leftBarButtons: [.cancel], + rightBarButtons: [.downloadAll], + matomoViewPath: [ + MatomoUtils.Views.menu.displayName, + "publicShare" + ]) + let viewModel = PublicShareViewModel(publicShareProxy: publicShareProxy, sortType: .nameAZ, driveFileManager: driveFileManager, currentDirectory: frozenRootFolder, - apiFetcher: apiFetcher) + apiFetcher: apiFetcher, + configuration: configuration) let viewController = FileListViewController(viewModel: viewModel) + viewModel.viewControllerDismissable = viewController + let publicShareNavigationController = UINavigationController(rootViewController: viewController) publicShareNavigationController.modalPresentationStyle = .fullScreen publicShareNavigationController.modalTransitionStyle = .coverVertical diff --git a/kDrive/UI/Controller/Files/File List/FileListViewController.swift b/kDrive/UI/Controller/Files/File List/FileListViewController.swift index e1a604cdf..4781d7566 100644 --- a/kDrive/UI/Controller/Files/File List/FileListViewController.swift +++ b/kDrive/UI/Controller/Files/File List/FileListViewController.swift @@ -49,7 +49,7 @@ extension SortType: Selectable { } class FileListViewController: UICollectionViewController, SwipeActionCollectionViewDelegate, - SwipeActionCollectionViewDataSource, FilesHeaderViewDelegate, SceneStateRestorable { + SwipeActionCollectionViewDataSource, FilesHeaderViewDelegate, SceneStateRestorable, ViewControllerDismissable { @LazyInjectService var accountManager: AccountManageable // MARK: - Constants @@ -60,6 +60,7 @@ class FileListViewController: UICollectionViewController, SwipeActionCollectionV private let leftRightInset = 12.0 private let gridInnerSpacing = 16.0 private let headerViewIdentifier = "FilesHeaderView" + private var addToKDriveButton: IKButton? // MARK: - Properties @@ -257,33 +258,35 @@ class FileListViewController: UICollectionViewController, SwipeActionCollectionV return } - let addToKDriveButton = IKButton(type: .custom) - addToKDriveButton.setTitle("Add to My kDrive", for: .normal) - addToKDriveButton.addTarget(self, action: #selector(addToMyDriveButtonTapped(_:)), for: .touchUpInside) - addToKDriveButton.setBackgroundColors(normal: .systemBlue, highlighted: .darkGray) - addToKDriveButton.translatesAutoresizingMaskIntoConstraints = false - addToKDriveButton.cornerRadius = 8.0 - addToKDriveButton.clipsToBounds = true + let addToKDrive = IKButton(type: .custom) + addToKDriveButton = addToKDrive - view.addSubview(addToKDriveButton) - view.bringSubviewToFront(addToKDriveButton) + addToKDrive.setTitle("Add to My kDrive", for: .normal) + addToKDrive.addTarget(self, action: #selector(addToMyDriveButtonTapped(_:)), for: .touchUpInside) + addToKDrive.setBackgroundColors(normal: .systemBlue, highlighted: .darkGray) + addToKDrive.translatesAutoresizingMaskIntoConstraints = false + addToKDrive.cornerRadius = 8.0 + addToKDrive.clipsToBounds = true - let leadingConstraint = addToKDriveButton.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, - constant: 16) + view.addSubview(addToKDrive) + view.bringSubviewToFront(addToKDrive) + + let leadingConstraint = addToKDrive.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, + constant: 16) leadingConstraint.priority = .defaultHigh - let trailingConstraint = addToKDriveButton.trailingAnchor.constraint( + let trailingConstraint = addToKDrive.trailingAnchor.constraint( greaterThanOrEqualTo: view.trailingAnchor, constant: -16 ) trailingConstraint.priority = .defaultHigh - let widthConstraint = addToKDriveButton.widthAnchor.constraint(lessThanOrEqualToConstant: 360) + let widthConstraint = addToKDrive.widthAnchor.constraint(lessThanOrEqualToConstant: 360) NSLayoutConstraint.activate([ - addToKDriveButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + addToKDrive.centerXAnchor.constraint(equalTo: view.centerXAnchor), leadingConstraint, trailingConstraint, - addToKDriveButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16), - addToKDriveButton.heightAnchor.constraint(equalToConstant: 60), + addToKDrive.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16), + addToKDrive.heightAnchor.constraint(equalToConstant: 60), widthConstraint ]) } @@ -614,6 +617,7 @@ class FileListViewController: UICollectionViewController, SwipeActionCollectionV func toggleMultipleSelection(_ on: Bool) { if on { + addToKDriveButton?.isHidden = true navigationItem.title = nil headerView?.selectView.isHidden = false headerView?.selectView.setActions(viewModel.multipleSelectionViewModel?.multipleSelectionActions ?? []) @@ -623,6 +627,7 @@ class FileListViewController: UICollectionViewController, SwipeActionCollectionV generator.prepare() generator.impactOccurred() } else { + addToKDriveButton?.isHidden = false headerView?.selectView.isHidden = true collectionView.allowsMultipleSelection = false navigationController?.navigationBar.prefersLargeTitles = true diff --git a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift index cf1db00fc..20220be63 100644 --- a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift @@ -93,6 +93,9 @@ class FileListViewModel: SelectDelegate { var matomoViewPath = ["FileList"] } + /// Tracking a way to dismiss the current stack + weak var viewControllerDismissable: ViewControllerDismissable? + var realmObservationToken: NotificationToken? var currentDirectoryObservationToken: NotificationToken? diff --git a/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift b/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift index e8e854b7c..65ee438b3 100644 --- a/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift @@ -67,6 +67,8 @@ class MultipleSelectionFileListViewModel { leftBarButtons = [.cancel] if configuration.selectAllSupported { rightBarButtons = [.selectAll] + } else { + rightBarButtons = [] } } else { leftBarButtons = nil diff --git a/kDrive/UI/Controller/Files/FilePresenter.swift b/kDrive/UI/Controller/Files/FilePresenter.swift index be01bcbeb..820812773 100644 --- a/kDrive/UI/Controller/Files/FilePresenter.swift +++ b/kDrive/UI/Controller/Files/FilePresenter.swift @@ -146,11 +146,23 @@ final class FilePresenter { if driveFileManager.drive.sharedWithMe { viewModel = SharedWithMeViewModel(driveFileManager: driveFileManager, currentDirectory: file) } else if let publicShareProxy = driveFileManager.publicShareProxy { + // TODO: i18n + let configuration = FileListViewModel.Configuration(selectAllSupported: true, + rootTitle: "public share", + emptyViewType: .emptyFolder, + supportsDrop: false, + rightBarButtons: [.downloadAll], + matomoViewPath: [ + MatomoUtils.Views.menu.displayName, + "publicShare" + ]) + viewModel = PublicShareViewModel(publicShareProxy: publicShareProxy, sortType: .nameAZ, driveFileManager: driveFileManager, currentDirectory: file, - apiFetcher: PublicShareApiFetcher()) + apiFetcher: PublicShareApiFetcher(), + configuration: configuration) } else if file.isTrashed || file.deletedAt != nil { viewModel = TrashListViewModel(driveFileManager: driveFileManager, currentDirectory: file) } else { diff --git a/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift b/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift index 88bcb3c7b..1097e3a76 100644 --- a/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift +++ b/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift @@ -37,6 +37,12 @@ protocol SelectDelegate: AnyObject { func didSelect(option: Selectable) } +/// Something that can dismiss the current VC if presented +@MainActor +public protocol ViewControllerDismissable: AnyObject { + func dismiss(animated flag: Bool, completion: (() -> Void)?) +} + class FloatingPanelSelectOptionViewController: UITableViewController { var headerTitle = "" var selectedOption: T? diff --git a/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift index 1a6818b52..4d50ce094 100644 --- a/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift +++ b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift @@ -30,32 +30,26 @@ final class PublicShareViewModel: InMemoryFileListViewModel { let rootProxy: ProxyFile var publicShareApiFetcher: PublicShareApiFetcher? - required init(driveFileManager: DriveFileManager, currentDirectory: File? = nil) { - guard let currentDirectory else { - fatalError("PublicShareViewModel requires a currentDirectory to work") - } - - // TODO: i18n - let configuration = Configuration(selectAllSupported: false, - rootTitle: "public share", - emptyViewType: .emptyFolder, - supportsDrop: false, - rightBarButtons: [.downloadAll], - matomoViewPath: [MatomoUtils.Views.menu.displayName, "publicShare"]) - + override init(configuration: Configuration, driveFileManager: DriveFileManager, currentDirectory: File) { rootProxy = currentDirectory.proxify() super.init(configuration: configuration, driveFileManager: driveFileManager, currentDirectory: currentDirectory) observedFiles = AnyRealmCollection(currentDirectory.children) } + required init(driveFileManager: DriveFileManager, currentDirectory: File? = nil) { + fatalError("unsupported initializer") + } + convenience init( publicShareProxy: PublicShareProxy, sortType: SortType, driveFileManager: DriveFileManager, currentDirectory: File, - apiFetcher: PublicShareApiFetcher + apiFetcher: PublicShareApiFetcher, + configuration: Configuration ) { - self.init(driveFileManager: driveFileManager, currentDirectory: currentDirectory) + self.init(configuration: configuration, driveFileManager: driveFileManager, currentDirectory: currentDirectory) + self.publicShareProxy = publicShareProxy self.sortType = sortType publicShareApiFetcher = apiFetcher @@ -85,14 +79,25 @@ final class PublicShareViewModel: InMemoryFileListViewModel { } } - // TODO: Move away from view model override func barButtonPressed(sender: Any?, type: FileListBarButtonType) { + guard type == .downloadAll else { + // We try to close the "Public Share screen" + if type == .cancel, + !(multipleSelectionViewModel?.isMultipleSelectionEnabled ?? true), + let viewControllerDismissable = viewControllerDismissable { + viewControllerDismissable.dismiss(animated: true, completion: nil) + return + } + + super.barButtonPressed(sender: sender, type: type) + return + } + guard downloadObserver == nil else { return } - guard type == .downloadAll, - let publicShareProxy = publicShareProxy else { + guard let publicShareProxy = publicShareProxy else { return } From 1861f13eb95ffa5120151c87e603e4b057fbc9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 30 Oct 2024 13:16:56 +0100 Subject: [PATCH 02/13] feat: Public Share multi selection actions are set to download only --- .../File List/FileListViewController.swift | 6 ++- .../Files/File List/FileListViewModel.swift | 2 +- .../MultipleSelectionFileListViewModel.swift | 29 ++++++++--- ...nFloatingPanelViewController+Actions.swift | 44 ++++++++++++---- ...SelectionFloatingPanelViewController.swift | 51 ++++++++++++++++++- .../DownloadArchiveOperation.swift | 25 ++++++++- .../Data/DownloadQueue/DownloadQueue.swift | 26 ++++++++++ 7 files changed, 160 insertions(+), 23 deletions(-) diff --git a/kDrive/UI/Controller/Files/File List/FileListViewController.swift b/kDrive/UI/Controller/Files/File List/FileListViewController.swift index 2f06f81ec..f3352d37d 100644 --- a/kDrive/UI/Controller/Files/File List/FileListViewController.swift +++ b/kDrive/UI/Controller/Files/File List/FileListViewController.swift @@ -441,7 +441,7 @@ class FileListViewController: UICollectionViewController, SwipeActionCollectionV floatingPanelViewController.set(contentViewController: trashFloatingPanelTableViewController) (floatingPanelViewController as? AdaptiveDriveFloatingPanelController)? .trackAndObserve(scrollView: trashFloatingPanelTableViewController.tableView) - case .multipleSelection: + case .multipleSelection(let downloadOnly): let allItemsSelected: Bool let exceptFileIds: [Int]? let selectedFiles: [File] @@ -467,6 +467,10 @@ class FileListViewController: UICollectionViewController, SwipeActionCollectionV presentingParent: self ) + if downloadOnly { + selectViewController.actions = [.download] + } + floatingPanelViewController = AdaptiveDriveFloatingPanelController() floatingPanelViewController.set(contentViewController: selectViewController) (floatingPanelViewController as? AdaptiveDriveFloatingPanelController)? diff --git a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift index 10b8c0333..d329a4f2f 100644 --- a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift @@ -40,7 +40,7 @@ enum FileListBarButtonType { enum FileListQuickActionType { case file case trash - case multipleSelection + case multipleSelection(onlyDownload: Bool) } enum ControllerPresentationType { diff --git a/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift b/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift index 65ee438b3..c54d0bef5 100644 --- a/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift @@ -23,35 +23,48 @@ import kDriveCore import kDriveResources struct MultipleSelectionAction: Equatable { - let id: Int + private let id: MultipleSelectionActionId let name: String let icon: KDriveResourcesImages var enabled = true + private enum MultipleSelectionActionId: Equatable { + case move + case delete + case more + case deletePermanently + case download + } + static func == (lhs: MultipleSelectionAction, rhs: MultipleSelectionAction) -> Bool { return lhs.id == rhs.id } static let move = MultipleSelectionAction( - id: 0, + id: MultipleSelectionActionId.move, name: KDriveResourcesStrings.Localizable.buttonMove, icon: KDriveResourcesAsset.folderSelect ) static let delete = MultipleSelectionAction( - id: 1, + id: MultipleSelectionActionId.delete, name: KDriveResourcesStrings.Localizable.buttonDelete, icon: KDriveResourcesAsset.delete ) static let more = MultipleSelectionAction( - id: 2, + id: MultipleSelectionActionId.more, name: KDriveResourcesStrings.Localizable.buttonMenu, icon: KDriveResourcesAsset.menu ) static let deletePermanently = MultipleSelectionAction( - id: 3, + id: MultipleSelectionActionId.deletePermanently, name: KDriveResourcesStrings.Localizable.buttonDelete, icon: KDriveResourcesAsset.delete ) + static let download = MultipleSelectionAction( + id: MultipleSelectionActionId.download, + name: KDriveResourcesStrings.Localizable.buttonDownload, + icon: KDriveResourcesAsset.menu + ) } @MainActor @@ -113,7 +126,7 @@ class MultipleSelectionFileListViewModel { self.driveFileManager = driveFileManager if driveFileManager.isPublicShare { - multipleSelectionActions = [] + multipleSelectionActions = [.download] } else { multipleSelectionActions = [.move, .delete, .more] } @@ -170,7 +183,9 @@ class MultipleSelectionFileListViewModel { } onPresentViewController?(.modal, alert, true) case .more: - onPresentQuickActionPanel?(Array(selectedItems), .multipleSelection) + onPresentQuickActionPanel?(Array(selectedItems), .multipleSelection(onlyDownload: false)) + case .download: + onPresentQuickActionPanel?(Array(selectedItems), .multipleSelection(onlyDownload: true)) default: break } diff --git a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift index 951c30288..4747d4b48 100644 --- a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift +++ b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift @@ -122,7 +122,14 @@ extension MultipleSelectionFloatingPanelViewController { } group.leave() } - DownloadQueue.instance.addToQueue(file: file, userId: accountManager.currentUserId) + + if let publicShareProxy = driveFileManager.publicShareProxy { + DownloadQueue.instance.addPublicShareToQueue(file: file, + driveFileManager: driveFileManager, + publicShareProxy: publicShareProxy) + } else { + DownloadQueue.instance.addToQueue(file: file, userId: accountManager.currentUserId) + } } } } else { @@ -147,16 +154,33 @@ extension MultipleSelectionFloatingPanelViewController { downloadInProgress = true collectionView.reloadItems(at: [indexPath]) group.enter() - downloadArchivedFiles(downloadCellPath: indexPath) { result in - switch result { - case .success(let archiveUrl): - self.downloadedArchiveUrl = archiveUrl - self.success = true - case .failure(let error): - self.downloadError = error - self.success = false + + if let publicShareProxy = driveFileManager.publicShareProxy { + downloadPublicShareArchivedFiles(downloadCellPath: indexPath, + driveFileManager: driveFileManager, + publicShareProxy: publicShareProxy) { result in + switch result { + case .success(let archiveUrl): + self.downloadedArchiveUrl = archiveUrl + self.success = true + case .failure(let error): + self.downloadError = error + self.success = false + } + group.leave() + } + } else { + downloadArchivedFiles(downloadCellPath: indexPath) { result in + switch result { + case .success(let archiveUrl): + self.downloadedArchiveUrl = archiveUrl + self.success = true + case .failure(let error): + self.downloadError = error + self.success = false + } + group.leave() } - group.leave() } } } diff --git a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift index 631b203c8..8c85850b6 100644 --- a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift +++ b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift @@ -48,7 +48,7 @@ final class MultipleSelectionFloatingPanelViewController: UICollectionViewContro return currentDirectory.visibility == .isInSharedSpace || currentDirectory.visibility == .isSharedSpace } - var actions = FloatingPanelAction.listActions + var actions: [FloatingPanelAction] = [] init( driveFileManager: DriveFileManager, @@ -83,6 +83,8 @@ final class MultipleSelectionFloatingPanelViewController: UICollectionViewContro } func setupContent() { + guard actions.isEmpty else { return } + if sharedWithMe { actions = FloatingPanelAction.multipleSelectionSharedWithMeActions } else if allItemsSelected { @@ -139,7 +141,10 @@ final class MultipleSelectionFloatingPanelViewController: UICollectionViewContro } } - func downloadArchivedFiles(downloadCellPath: IndexPath, completion: @escaping (Result) -> Void) { + func downloadPublicShareArchivedFiles(downloadCellPath: IndexPath, + driveFileManager: DriveFileManager, + publicShareProxy: PublicShareProxy, + completion: @escaping (Result) -> Void) { Task { [proxyFiles = files.map { $0.proxify() }, currentProxyDirectory = currentDirectory.proxify()] in do { let archiveBody: ArchiveBody @@ -172,6 +177,48 @@ final class MultipleSelectionFloatingPanelViewController: UICollectionViewContro } } + func downloadArchivedFiles(downloadCellPath: IndexPath, + completion: @escaping (Result) -> Void) { + Task { [proxyFiles = files.map { $0.proxify() }, currentProxyDirectory = currentDirectory.proxify()] in + do { + let archiveBody: ArchiveBody + if allItemsSelected { + archiveBody = .init(parentId: currentProxyDirectory.id, exceptFileIds: exceptFileIds) + } else { + archiveBody = .init(files: proxyFiles) + } + let response = try await self.driveFileManager.apiFetcher.buildArchive( + drive: driveFileManager.drive, + body: archiveBody + ) + currentArchiveId = response.uuid + guard let rootViewController = view.window?.rootViewController else { return } + DownloadQueue.instance + .observeArchiveDownloaded(rootViewController, archiveId: response.uuid) { _, archiveUrl, error in + if let archiveUrl { + completion(.success(archiveUrl)) + } else { + completion(.failure(error ?? .unknownError)) + } + } + + if let publicShareProxy = self.driveFileManager.publicShareProxy { + DownloadQueue.instance.addPublicShareArchiveToQueue(archiveId: response.uuid, + driveFileManager: driveFileManager, + publicShareProxy: publicShareProxy) + } else { + DownloadQueue.instance.addToQueue(archiveId: response.uuid, + driveId: self.driveFileManager.drive.id, + userId: accountManager.currentUserId) + } + + self.collectionView.reloadItems(at: [downloadCellPath]) + } catch { + completion(.failure(error as? DriveError ?? .unknownError)) + } + } + } + private static func createLayout() -> UICollectionViewLayout { return UICollectionViewCompositionalLayout { _, _ in let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(53)) diff --git a/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift b/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift index a49585b40..b2b2769e9 100644 --- a/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift +++ b/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift @@ -31,6 +31,7 @@ public class DownloadArchiveOperation: Operation { private let archiveId: String private let driveFileManager: DriveFileManager private let urlSession: FileDownloadSession + private let publicShareProxy: PublicShareProxy? private var progressObservation: NSKeyValueObservation? private var backgroundTaskIdentifier: UIBackgroundTaskIdentifier = .invalid @@ -68,10 +69,14 @@ public class DownloadArchiveOperation: Operation { return true } - public init(archiveId: String, driveFileManager: DriveFileManager, urlSession: FileDownloadSession) { + public init(archiveId: String, + driveFileManager: DriveFileManager, + urlSession: FileDownloadSession, + publicShareProxy: PublicShareProxy? = nil) { self.archiveId = archiveId self.driveFileManager = driveFileManager self.urlSession = urlSession + self.publicShareProxy = publicShareProxy } // MARK: - Public methods @@ -103,7 +108,17 @@ public class DownloadArchiveOperation: Operation { } override public func main() { - DDLogInfo("[DownloadOperation] Downloading Archive of files \(archiveId) with session \(urlSession.identifier)") + if publicShareProxy == nil { + authenticatedDownload() + } else { + publicShareDownload() + } + } + + func publicShareDownload() { + DDLogInfo( + "[DownloadOperation] Downloading Archive of public share files \(archiveId) with session \(urlSession.identifier)" + ) let url = Endpoint.getArchive(drive: driveFileManager.drive, uuid: archiveId).url @@ -131,6 +146,12 @@ public class DownloadArchiveOperation: Operation { } } + func authenticatedDownload() { + DDLogInfo("[DownloadOperation] Downloading Archive of files \(archiveId) with session \(urlSession.identifier)") + + // TODO: missing imp + } + func downloadCompletion(url: URL?, response: URLResponse?, error: Error?) { let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1 diff --git a/kDriveCore/Data/DownloadQueue/DownloadQueue.swift b/kDriveCore/Data/DownloadQueue/DownloadQueue.swift index 0fd65e28b..11e711834 100644 --- a/kDriveCore/Data/DownloadQueue/DownloadQueue.swift +++ b/kDriveCore/Data/DownloadQueue/DownloadQueue.swift @@ -188,6 +188,32 @@ public final class DownloadQueue: ParallelismHeuristicDelegate { } } + public func addPublicShareArchiveToQueue(archiveId: String, + driveFileManager: DriveFileManager, + publicShareProxy: PublicShareProxy) { + Log.downloadQueue("addPublicShareArchiveToQueue archiveId:\(archiveId)") + dispatchQueue.async { + OperationQueueHelper.disableIdleTimer(true) + + let operation = DownloadArchiveOperation( + archiveId: archiveId, + driveFileManager: driveFileManager, + urlSession: self.bestSession, + publicShareProxy: publicShareProxy + ) + + operation.completionBlock = { + self.dispatchQueue.async { + self.archiveOperationsInQueue.removeValue(forKey: archiveId) + self.publishArchiveDownloaded(archiveId: archiveId, archiveUrl: operation.archiveUrl, error: operation.error) + OperationQueueHelper.disableIdleTimer(false, hasOperationsInQueue: !self.operationsInQueue.isEmpty) + } + } + self.operationQueue.addOperation(operation) + self.archiveOperationsInQueue[archiveId] = operation + } + } + public func addToQueue(archiveId: String, driveId: Int, userId: Int) { Log.downloadQueue("addToQueue archiveId:\(archiveId)") dispatchQueue.async { From ced6c1949cc7f4f4a1ecccbffa5b74a425e25d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 27 Nov 2024 07:35:25 +0100 Subject: [PATCH 03/13] chore: Removed unnecessary title --- kDrive/AppRouter.swift | 3 +-- kDrive/UI/Controller/Files/FilePresenter.swift | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/kDrive/AppRouter.swift b/kDrive/AppRouter.swift index 7b2b4ad38..38685cb79 100644 --- a/kDrive/AppRouter.swift +++ b/kDrive/AppRouter.swift @@ -657,9 +657,8 @@ public struct AppRouter: AppNavigable { return } - // TODO: i18n let configuration = FileListViewModel.Configuration(selectAllSupported: true, - rootTitle: "public share", + rootTitle: nil, emptyViewType: .emptyFolder, supportsDrop: false, leftBarButtons: [.cancel], diff --git a/kDrive/UI/Controller/Files/FilePresenter.swift b/kDrive/UI/Controller/Files/FilePresenter.swift index 820812773..ec133a8d4 100644 --- a/kDrive/UI/Controller/Files/FilePresenter.swift +++ b/kDrive/UI/Controller/Files/FilePresenter.swift @@ -146,9 +146,8 @@ final class FilePresenter { if driveFileManager.drive.sharedWithMe { viewModel = SharedWithMeViewModel(driveFileManager: driveFileManager, currentDirectory: file) } else if let publicShareProxy = driveFileManager.publicShareProxy { - // TODO: i18n let configuration = FileListViewModel.Configuration(selectAllSupported: true, - rootTitle: "public share", + rootTitle: nil, emptyViewType: .emptyFolder, supportsDrop: false, rightBarButtons: [.downloadAll], From 0513dfbbfc7c2a120c1738822007327737dd1f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 27 Nov 2024 08:21:19 +0100 Subject: [PATCH 04/13] refactor: Split download action in manageable bits --- ...nFloatingPanelViewController+Actions.swift | 154 ++++++++++-------- 1 file changed, 83 insertions(+), 71 deletions(-) diff --git a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift index 4747d4b48..f4772a1b3 100644 --- a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift +++ b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift @@ -100,87 +100,99 @@ extension MultipleSelectionFloatingPanelViewController { } private func downloadAction(group: DispatchGroup, at indexPath: IndexPath) { - if !allItemsSelected && - (files.allSatisfy { $0.convertedType == .image || $0.convertedType == .video } || files.count <= 1) { - for file in files { - if file.isDownloaded { - FileActionsHelper.save(file: file, from: self, showSuccessSnackBar: false) - } else { - guard let observerViewController = view.window?.rootViewController else { return } - downloadInProgress = true - collectionView.reloadItems(at: [indexPath]) - group.enter() - DownloadQueue.instance - .observeFileDownloaded(observerViewController, fileId: file.id) { [weak self] _, error in - guard let self else { return } - if error == nil { - Task { @MainActor in - FileActionsHelper.save(file: file, from: self, showSuccessSnackBar: false) - } - } else { - success = false - } - group.leave() - } + if !allItemsSelected, + files.allSatisfy { $0.convertedType == .image || $0.convertedType == .video } || files.count <= 1 { + downloadActionMediaOrSingleFile(group: group, at: indexPath) + } else { + downloadActionArchive(group: group, at: indexPath) + } + } + + private func downloadActionMediaOrSingleFile(group: DispatchGroup, at indexPath: IndexPath) { + for file in files { + guard !file.isDownloaded else { + FileActionsHelper.save(file: file, from: self, showSuccessSnackBar: false) + return + } - if let publicShareProxy = driveFileManager.publicShareProxy { - DownloadQueue.instance.addPublicShareToQueue(file: file, - driveFileManager: driveFileManager, - publicShareProxy: publicShareProxy) + guard let observerViewController = view.window?.rootViewController else { + return + } + + downloadInProgress = true + collectionView.reloadItems(at: [indexPath]) + group.enter() + DownloadQueue.instance + .observeFileDownloaded(observerViewController, fileId: file.id) { [weak self] _, error in + guard let self else { return } + if error == nil { + Task { @MainActor in + FileActionsHelper.save(file: file, from: self, showSuccessSnackBar: false) + } } else { - DownloadQueue.instance.addToQueue(file: file, userId: accountManager.currentUserId) + success = false } + group.leave() } + + if let publicShareProxy = driveFileManager.publicShareProxy { + DownloadQueue.instance.addPublicShareToQueue(file: file, + driveFileManager: driveFileManager, + publicShareProxy: publicShareProxy) + } else { + DownloadQueue.instance.addToQueue(file: file, userId: accountManager.currentUserId) } + } + } + + private func downloadActionArchive(group: DispatchGroup, at indexPath: IndexPath) { + if downloadInProgress, + let currentArchiveId, + let operation = DownloadQueue.instance.archiveOperationsInQueue[currentArchiveId] { + group.enter() + let alert = AlertTextViewController( + title: KDriveResourcesStrings.Localizable.cancelDownloadTitle, + message: KDriveResourcesStrings.Localizable.cancelDownloadDescription, + action: KDriveResourcesStrings.Localizable.buttonYes, + destructive: true + ) { + operation.cancel() + self.downloadError = .taskCancelled + self.success = false + group.leave() + } + present(alert, animated: true) } else { - if downloadInProgress, - let currentArchiveId, - let operation = DownloadQueue.instance.archiveOperationsInQueue[currentArchiveId] { - group.enter() - let alert = AlertTextViewController( - title: KDriveResourcesStrings.Localizable.cancelDownloadTitle, - message: KDriveResourcesStrings.Localizable.cancelDownloadDescription, - action: KDriveResourcesStrings.Localizable.buttonYes, - destructive: true - ) { - operation.cancel() - self.downloadError = .taskCancelled - self.success = false + downloadedArchiveUrl = nil + downloadInProgress = true + collectionView.reloadItems(at: [indexPath]) + group.enter() + + if let publicShareProxy = driveFileManager.publicShareProxy { + downloadPublicShareArchivedFiles(downloadCellPath: indexPath, + driveFileManager: driveFileManager, + publicShareProxy: publicShareProxy) { result in + switch result { + case .success(let archiveUrl): + self.downloadedArchiveUrl = archiveUrl + self.success = true + case .failure(let error): + self.downloadError = error + self.success = false + } group.leave() } - present(alert, animated: true) } else { - downloadedArchiveUrl = nil - downloadInProgress = true - collectionView.reloadItems(at: [indexPath]) - group.enter() - - if let publicShareProxy = driveFileManager.publicShareProxy { - downloadPublicShareArchivedFiles(downloadCellPath: indexPath, - driveFileManager: driveFileManager, - publicShareProxy: publicShareProxy) { result in - switch result { - case .success(let archiveUrl): - self.downloadedArchiveUrl = archiveUrl - self.success = true - case .failure(let error): - self.downloadError = error - self.success = false - } - group.leave() - } - } else { - downloadArchivedFiles(downloadCellPath: indexPath) { result in - switch result { - case .success(let archiveUrl): - self.downloadedArchiveUrl = archiveUrl - self.success = true - case .failure(let error): - self.downloadError = error - self.success = false - } - group.leave() + downloadArchivedFiles(downloadCellPath: indexPath) { result in + switch result { + case .success(let archiveUrl): + self.downloadedArchiveUrl = archiveUrl + self.success = true + case .failure(let error): + self.downloadError = error + self.success = false } + group.leave() } } } From 7c48ef29fcc593c038ae612ed5f606f1d718dd97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 27 Nov 2024 09:05:27 +0100 Subject: [PATCH 05/13] chore: PR notes --- kDrive/AppDelegate.swift | 10 ++++++---- .../MultipleSelectionFloatingPanelViewController.swift | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/kDrive/AppDelegate.swift b/kDrive/AppDelegate.swift index 54195f04a..d82f00173 100644 --- a/kDrive/AppDelegate.swift +++ b/kDrive/AppDelegate.swift @@ -105,13 +105,15 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { // swiftlint:disable force_try Task { - try! await Task.sleep(nanoseconds:5_000_000_000) + try! await Task.sleep(nanoseconds: 5_000_000_000) print("coucou") // a public share expired - let somePublicShare = URL(string: "https://kdrive.infomaniak.com/app/share/140946/81de098a-3156-4ae6-93df-be7f9ae78ddd") +// let somePublicShare = URL(string:"https://kdrive.infomaniak.com/app/share/140946/81de098a-3156-4ae6-93df-be7f9ae78ddd") // a public share password protected -// let somePublicShare = URL(string: "https://kdrive.infomaniak.com/app/share/140946/34844cea-db8d-4d87-b66f-e944e9759a2e") - +// let somePublicShare = URL(string:"https://kdrive.infomaniak.com/app/share/140946/34844cea-db8d-4d87-b66f-e944e9759a2e") + // a valid public share + let somePublicShare = + URL(string: "https://kdrive.infomaniak.com/app/share/140946/01953831-16d3-4df6-8b48-33c8001c7981") await UniversalLinksHelper.handleURL(somePublicShare!) } diff --git a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift index 8c85850b6..3284d1369 100644 --- a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift +++ b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift @@ -141,6 +141,7 @@ final class MultipleSelectionFloatingPanelViewController: UICollectionViewContro } } + // TODO: make it work func downloadPublicShareArchivedFiles(downloadCellPath: IndexPath, driveFileManager: DriveFileManager, publicShareProxy: PublicShareProxy, From 424a60c5849583a94c6f62a88eb079b23d308be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Thu, 28 Nov 2024 15:55:40 +0100 Subject: [PATCH 06/13] feat: Public share archive request working --- ...tionFloatingPanelViewController+Actions.swift | 1 - ...pleSelectionFloatingPanelViewController.swift | 16 +++++++++------- kDriveCore/Data/Api/Endpoint+Share.swift | 5 +++++ kDriveCore/Data/Api/PublicShareApiFetcher.swift | 12 ++++++++++++ 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift index f4772a1b3..c79d1bf20 100644 --- a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift +++ b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift @@ -170,7 +170,6 @@ extension MultipleSelectionFloatingPanelViewController { if let publicShareProxy = driveFileManager.publicShareProxy { downloadPublicShareArchivedFiles(downloadCellPath: indexPath, - driveFileManager: driveFileManager, publicShareProxy: publicShareProxy) { result in switch result { case .success(let archiveUrl): diff --git a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift index 3284d1369..7f5ebec9b 100644 --- a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift +++ b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift @@ -141,9 +141,8 @@ final class MultipleSelectionFloatingPanelViewController: UICollectionViewContro } } - // TODO: make it work + // TODO:  make it work func downloadPublicShareArchivedFiles(downloadCellPath: IndexPath, - driveFileManager: DriveFileManager, publicShareProxy: PublicShareProxy, completion: @escaping (Result) -> Void) { Task { [proxyFiles = files.map { $0.proxify() }, currentProxyDirectory = currentDirectory.proxify()] in @@ -154,8 +153,10 @@ final class MultipleSelectionFloatingPanelViewController: UICollectionViewContro } else { archiveBody = .init(files: proxyFiles) } - let response = try await driveFileManager.apiFetcher.buildArchive( - drive: driveFileManager.drive, + + let response = try await PublicShareApiFetcher().buildPublicShareArchive( + driveId: publicShareProxy.driveId, + linkUuid: publicShareProxy.shareLinkUid, body: archiveBody ) currentArchiveId = response.uuid @@ -168,9 +169,10 @@ final class MultipleSelectionFloatingPanelViewController: UICollectionViewContro completion(.failure(error ?? .unknownError)) } } - DownloadQueue.instance.addToQueue(archiveId: response.uuid, - driveId: self.driveFileManager.drive.id, - userId: accountManager.currentUserId) + DownloadQueue.instance.addPublicShareArchiveToQueue(archiveId: response.uuid, + driveFileManager: driveFileManager, + publicShareProxy: publicShareProxy) + self.collectionView.reloadItems(at: [downloadCellPath]) } catch { completion(.failure(error as? DriveError ?? .unknownError)) diff --git a/kDriveCore/Data/Api/Endpoint+Share.swift b/kDriveCore/Data/Api/Endpoint+Share.swift index ee80590b4..ef06c45ba 100644 --- a/kDriveCore/Data/Api/Endpoint+Share.swift +++ b/kDriveCore/Data/Api/Endpoint+Share.swift @@ -84,6 +84,11 @@ public extension Endpoint { return shareLinkFileV2(driveId: driveId, linkUuid: linkUuid, fileId: fileId).appending(path: "/download") } + /// Archive files from a share link + static func publicShareArchive(driveId: Int, linkUuid: String) -> Endpoint { + return shareUrlV2.appending(path: "/\(driveId)/share/\(linkUuid)/archive") + } + func showOfficeShareLinkFile(driveId: Int, linkUuid: String, fileId: Int) -> Endpoint { return Self.shareUrlV1.appending(path: "/share/\(driveId)/\(linkUuid)/preview/text/\(fileId)") } diff --git a/kDriveCore/Data/Api/PublicShareApiFetcher.swift b/kDriveCore/Data/Api/PublicShareApiFetcher.swift index 8005dc1f2..7fb3fd4d9 100644 --- a/kDriveCore/Data/Api/PublicShareApiFetcher.swift +++ b/kDriveCore/Data/Api/PublicShareApiFetcher.swift @@ -90,4 +90,16 @@ public extension PublicShareApiFetcher { let shareLinkFiles: ValidServerResponse<[File]> = try await perform(request: request) return shareLinkFiles } + + func buildPublicShareArchive(driveId: Int, + linkUuid: String, + body: ArchiveBody) async throws -> DownloadArchiveResponse { + let shareLinkArchiveUrl = Endpoint.publicShareArchive(driveId: driveId, linkUuid: linkUuid).url + let request = Session.default.request(shareLinkArchiveUrl, + method: .post, + parameters: body, + encoder: JSONParameterEncoder.convertToSnakeCase) + let archiveResponse: ValidServerResponse = try await perform(request: request) + return archiveResponse.validApiResponse.data + } } From 6c88f1562fd2f9c79e7f91ee59359968dfcd97c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Fri, 29 Nov 2024 17:15:14 +0100 Subject: [PATCH 07/13] feat: Download public share --- .../Files/File List/FileListViewModel.swift | 1 - ...SelectionFloatingPanelViewController.swift | 1 - ...atingPanelSelectOptionViewController.swift | 1 - .../DownloadArchiveOperation.swift | 26 ++++++++++++++----- .../Data/DownloadQueue/DownloadQueue.swift | 3 +++ kDriveCore/Data/Models/File.swift | 4 +++ 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift index d329a4f2f..9c15a79fe 100644 --- a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift @@ -93,7 +93,6 @@ class FileListViewModel: SelectDelegate { var matomoViewPath = ["FileList"] } - /// Tracking a way to dismiss the current stack weak var viewControllerDismissable: ViewControllerDismissable? var realmObservationToken: NotificationToken? diff --git a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift index 7f5ebec9b..345be1e1e 100644 --- a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift +++ b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift @@ -141,7 +141,6 @@ final class MultipleSelectionFloatingPanelViewController: UICollectionViewContro } } - // TODO:  make it work func downloadPublicShareArchivedFiles(downloadCellPath: IndexPath, publicShareProxy: PublicShareProxy, completion: @escaping (Result) -> Void) { diff --git a/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift b/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift index 1097e3a76..9a34c195a 100644 --- a/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift +++ b/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift @@ -37,7 +37,6 @@ protocol SelectDelegate: AnyObject { func didSelect(option: Selectable) } -/// Something that can dismiss the current VC if presented @MainActor public protocol ViewControllerDismissable: AnyObject { func dismiss(animated flag: Bool, completion: (() -> Void)?) diff --git a/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift b/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift index b2b2769e9..7df1e755d 100644 --- a/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift +++ b/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift @@ -16,6 +16,7 @@ along with this program. If not, see . */ +import Alamofire import CocoaLumberjackSwift import FileProvider import Foundation @@ -29,6 +30,7 @@ public class DownloadArchiveOperation: Operation { @LazyInjectService var appContextService: AppContextServiceable private let archiveId: String + private let shareDrive: AbstractDrive private let driveFileManager: DriveFileManager private let urlSession: FileDownloadSession private let publicShareProxy: PublicShareProxy? @@ -70,10 +72,12 @@ public class DownloadArchiveOperation: Operation { } public init(archiveId: String, + shareDrive: AbstractDrive, driveFileManager: DriveFileManager, urlSession: FileDownloadSession, publicShareProxy: PublicShareProxy? = nil) { self.archiveId = archiveId + self.shareDrive = shareDrive self.driveFileManager = driveFileManager self.urlSession = urlSession self.publicShareProxy = publicShareProxy @@ -120,6 +124,22 @@ public class DownloadArchiveOperation: Operation { "[DownloadOperation] Downloading Archive of public share files \(archiveId) with session \(urlSession.identifier)" ) + let url = Endpoint.getArchive(drive: shareDrive, uuid: archiveId).url + let request = URLRequest(url: url) + + task = urlSession.downloadTask(with: request, completionHandler: downloadCompletion) + progressObservation = task?.progress.observe(\.fractionCompleted, options: .new) { _, value in + guard let newValue = value.newValue else { + return + } + DownloadQueue.instance.publishProgress(newValue, for: self.archiveId) + } + task?.resume() + } + + func authenticatedDownload() { + DDLogInfo("[DownloadOperation] Downloading Archive of files \(archiveId) with session \(urlSession.identifier)") + let url = Endpoint.getArchive(drive: driveFileManager.drive, uuid: archiveId).url if let userToken = accountManager.getTokenForUserId(driveFileManager.drive.userId) { @@ -146,12 +166,6 @@ public class DownloadArchiveOperation: Operation { } } - func authenticatedDownload() { - DDLogInfo("[DownloadOperation] Downloading Archive of files \(archiveId) with session \(urlSession.identifier)") - - // TODO: missing imp - } - func downloadCompletion(url: URL?, response: URLResponse?, error: Error?) { let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1 diff --git a/kDriveCore/Data/DownloadQueue/DownloadQueue.swift b/kDriveCore/Data/DownloadQueue/DownloadQueue.swift index 11e711834..f6e41dee5 100644 --- a/kDriveCore/Data/DownloadQueue/DownloadQueue.swift +++ b/kDriveCore/Data/DownloadQueue/DownloadQueue.swift @@ -197,6 +197,7 @@ public final class DownloadQueue: ParallelismHeuristicDelegate { let operation = DownloadArchiveOperation( archiveId: archiveId, + shareDrive: publicShareProxy.proxyDrive, driveFileManager: driveFileManager, urlSession: self.bestSession, publicShareProxy: publicShareProxy @@ -209,6 +210,7 @@ public final class DownloadQueue: ParallelismHeuristicDelegate { OperationQueueHelper.disableIdleTimer(false, hasOperationsInQueue: !self.operationsInQueue.isEmpty) } } + self.operationQueue.addOperation(operation) self.archiveOperationsInQueue[archiveId] = operation } @@ -226,6 +228,7 @@ public final class DownloadQueue: ParallelismHeuristicDelegate { let operation = DownloadArchiveOperation( archiveId: archiveId, + shareDrive: drive, driveFileManager: driveFileManager, urlSession: self.bestSession ) diff --git a/kDriveCore/Data/Models/File.swift b/kDriveCore/Data/Models/File.swift index db5690028..c973d6848 100644 --- a/kDriveCore/Data/Models/File.swift +++ b/kDriveCore/Data/Models/File.swift @@ -193,6 +193,10 @@ public struct PublicShareProxy { self.fileId = fileId self.shareLinkUid = shareLinkUid } + + public var proxyDrive: ProxyDrive { + ProxyDrive(id: driveId) + } } public enum SortType: String { From 744fc93b5a9b69f7a70502f873936a883bc9a723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Tue, 3 Dec 2024 11:34:25 +0100 Subject: [PATCH 08/13] feat: Dedicated public share archive download endpoint --- kDriveCore/Data/Api/Endpoint+Share.swift | 5 +++++ .../DownloadQueue/DownloadArchiveOperation.swift | 15 ++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/kDriveCore/Data/Api/Endpoint+Share.swift b/kDriveCore/Data/Api/Endpoint+Share.swift index ef06c45ba..2581f9d57 100644 --- a/kDriveCore/Data/Api/Endpoint+Share.swift +++ b/kDriveCore/Data/Api/Endpoint+Share.swift @@ -89,6 +89,11 @@ public extension Endpoint { return shareUrlV2.appending(path: "/\(driveId)/share/\(linkUuid)/archive") } + /// Downloads a public share archive + static func downloadPublicShareArchive(drive: AbstractDrive, linkUuid: String, archiveUuid: String) -> Endpoint { + return publicShareArchive(driveId: drive.id, linkUuid: linkUuid).appending(path: "/\(archiveUuid)/download") + } + func showOfficeShareLinkFile(driveId: Int, linkUuid: String, fileId: Int) -> Endpoint { return Self.shareUrlV1.appending(path: "/share/\(driveId)/\(linkUuid)/preview/text/\(fileId)") } diff --git a/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift b/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift index 7df1e755d..1877cad09 100644 --- a/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift +++ b/kDriveCore/Data/DownloadQueue/DownloadArchiveOperation.swift @@ -112,19 +112,24 @@ public class DownloadArchiveOperation: Operation { } override public func main() { - if publicShareProxy == nil { + guard let publicShareProxy else { authenticatedDownload() - } else { - publicShareDownload() + return } + + publicShareDownload(proxy: publicShareProxy) } - func publicShareDownload() { + func publicShareDownload(proxy: PublicShareProxy) { DDLogInfo( "[DownloadOperation] Downloading Archive of public share files \(archiveId) with session \(urlSession.identifier)" ) - let url = Endpoint.getArchive(drive: shareDrive, uuid: archiveId).url + let url = Endpoint.downloadPublicShareArchive( + drive: shareDrive, + linkUuid: proxy.shareLinkUid, + archiveUuid: archiveId + ).url let request = URLRequest(url: url) task = urlSession.downloadTask(with: request, completionHandler: downloadCompletion) From c53c4c128cc91b0d4d99f7f117338ebd4ae94a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Tue, 3 Dec 2024 11:53:57 +0100 Subject: [PATCH 09/13] chore: Self assessment --- kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift index 9c6ce10a3..7f0939534 100644 --- a/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift +++ b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift @@ -84,7 +84,7 @@ final class PublicShareViewModel: InMemoryFileListViewModel { // We try to close the "Public Share screen" if type == .cancel, !(multipleSelectionViewModel?.isMultipleSelectionEnabled ?? true), - let viewControllerDismissable = viewControllerDismissable { + let viewControllerDismissable { viewControllerDismissable.dismiss(animated: true, completion: nil) return } @@ -97,7 +97,7 @@ final class PublicShareViewModel: InMemoryFileListViewModel { return } - guard let publicShareProxy = publicShareProxy else { + guard let publicShareProxy else { return } From c5644a6b908f9914cbaa3bedb198b3ca599248c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Tue, 3 Dec 2024 12:16:43 +0100 Subject: [PATCH 10/13] refactor: Removed action from ViewController and moved to the view in order to not risk breaking the inheritance stack. --- .../File List/MultipleSelectionFileListViewModel.swift | 8 +------- .../Files/FileActionsFloatingPanelViewController.swift | 4 ++++ .../MultipleSelectionFloatingPanelViewController.swift | 8 ++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift b/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift index c54d0bef5..26396e5de 100644 --- a/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift @@ -122,15 +122,9 @@ class MultipleSelectionFileListViewModel { init(configuration: FileListViewModel.Configuration, driveFileManager: DriveFileManager, currentDirectory: File) { isMultipleSelectionEnabled = false selectedCount = 0 + multipleSelectionActions = [.move, .delete, .more] self.driveFileManager = driveFileManager - - if driveFileManager.isPublicShare { - multipleSelectionActions = [.download] - } else { - multipleSelectionActions = [.move, .delete, .more] - } - self.currentDirectory = currentDirectory self.configuration = configuration } diff --git a/kDrive/UI/Controller/Files/FileActionsFloatingPanelViewController.swift b/kDrive/UI/Controller/Files/FileActionsFloatingPanelViewController.swift index 33a74e12c..8b3eefa56 100644 --- a/kDrive/UI/Controller/Files/FileActionsFloatingPanelViewController.swift +++ b/kDrive/UI/Controller/Files/FileActionsFloatingPanelViewController.swift @@ -228,6 +228,10 @@ public class FloatingPanelAction: Equatable { return [manageCategories, favorite, upsaleColor, folderColor, offline, download, move, duplicate].map { $0.reset() } } + static var multipleSelectionPublicShareActions: [FloatingPanelAction] { + return [download].map { $0.reset() } + } + static var multipleSelectionSharedWithMeActions: [FloatingPanelAction] { return [download].map { $0.reset() } } diff --git a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift index 345be1e1e..92d346a7d 100644 --- a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift +++ b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift @@ -48,7 +48,7 @@ final class MultipleSelectionFloatingPanelViewController: UICollectionViewContro return currentDirectory.visibility == .isInSharedSpace || currentDirectory.visibility == .isSharedSpace } - var actions: [FloatingPanelAction] = [] + var actions = FloatingPanelAction.listActions init( driveFileManager: DriveFileManager, @@ -83,9 +83,9 @@ final class MultipleSelectionFloatingPanelViewController: UICollectionViewContro } func setupContent() { - guard actions.isEmpty else { return } - - if sharedWithMe { + if driveFileManager.isPublicShare { + actions = FloatingPanelAction.multipleSelectionPublicShareActions + } else if sharedWithMe { actions = FloatingPanelAction.multipleSelectionSharedWithMeActions } else if allItemsSelected { actions = FloatingPanelAction.selectAllActions From 8918aa6d5ae183bce855ed15ad1a3bd52749249a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Tue, 3 Dec 2024 20:15:57 +0100 Subject: [PATCH 11/13] feat: Public share file count for select all --- .../File List/MultipleSelectionFileListViewModel.swift | 10 +++++++++- kDriveCore/Data/Api/Endpoint+Share.swift | 5 +++++ kDriveCore/Data/Api/PublicShareApiFetcher.swift | 7 +++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift b/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift index 26396e5de..a2b8565a1 100644 --- a/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift @@ -219,7 +219,15 @@ class MultipleSelectionFileListViewModel { onSelectAll?() Task { [proxyCurrentDirectory = currentDirectory.proxify()] in do { - let directoryCount = try await driveFileManager.apiFetcher.count(of: proxyCurrentDirectory) + let directoryCount: FileCount + if let publicShareProxy = driveFileManager.publicShareProxy { + directoryCount = try await PublicShareApiFetcher() + .countPublicShare(drive: publicShareProxy.proxyDrive, + linkUuid: publicShareProxy.shareLinkUid) + } else { + directoryCount = try await driveFileManager.apiFetcher.count(of: proxyCurrentDirectory) + } + selectedCount = directoryCount.count rightBarButtons = [.deselectAll] } catch { diff --git a/kDriveCore/Data/Api/Endpoint+Share.swift b/kDriveCore/Data/Api/Endpoint+Share.swift index 2581f9d57..8fc76c170 100644 --- a/kDriveCore/Data/Api/Endpoint+Share.swift +++ b/kDriveCore/Data/Api/Endpoint+Share.swift @@ -94,6 +94,11 @@ public extension Endpoint { return publicShareArchive(driveId: drive.id, linkUuid: linkUuid).appending(path: "/\(archiveUuid)/download") } + /// Count files of a public share folder + static func countPublicShare(drive: AbstractDrive, linkUuid: String) -> Endpoint { + return publicShareArchive(driveId: drive.id, linkUuid: linkUuid).appending(path: "/count") + } + func showOfficeShareLinkFile(driveId: Int, linkUuid: String, fileId: Int) -> Endpoint { return Self.shareUrlV1.appending(path: "/share/\(driveId)/\(linkUuid)/preview/text/\(fileId)") } diff --git a/kDriveCore/Data/Api/PublicShareApiFetcher.swift b/kDriveCore/Data/Api/PublicShareApiFetcher.swift index 7fb3fd4d9..e59c86686 100644 --- a/kDriveCore/Data/Api/PublicShareApiFetcher.swift +++ b/kDriveCore/Data/Api/PublicShareApiFetcher.swift @@ -102,4 +102,11 @@ public extension PublicShareApiFetcher { let archiveResponse: ValidServerResponse = try await perform(request: request) return archiveResponse.validApiResponse.data } + + func countPublicShare(drive: AbstractDrive, linkUuid: String) async throws -> FileCount { + let countUrl = Endpoint.countPublicShare(drive: drive, linkUuid: linkUuid).url + let request = Session.default.request(countUrl) + let countResponse: ValidServerResponse = try await perform(request: request) + return countResponse.validApiResponse.data + } } From b7cfac708e2cbdc640ca5d0ba4792aa80f52ea80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 4 Dec 2024 09:27:32 +0100 Subject: [PATCH 12/13] fix: Working select all --- .../File List/MultipleSelectionFileListViewModel.swift | 10 ++++++++-- kDriveCore/Data/Api/Endpoint+Share.swift | 4 ++-- kDriveCore/Data/Api/PublicShareApiFetcher.swift | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift b/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift index a2b8565a1..c09a7cf79 100644 --- a/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/MultipleSelectionFileListViewModel.swift @@ -122,7 +122,12 @@ class MultipleSelectionFileListViewModel { init(configuration: FileListViewModel.Configuration, driveFileManager: DriveFileManager, currentDirectory: File) { isMultipleSelectionEnabled = false selectedCount = 0 - multipleSelectionActions = [.move, .delete, .more] + + if driveFileManager.isPublicShare { + multipleSelectionActions = [.more] + } else { + multipleSelectionActions = [.move, .delete, .more] + } self.driveFileManager = driveFileManager self.currentDirectory = currentDirectory @@ -223,7 +228,8 @@ class MultipleSelectionFileListViewModel { if let publicShareProxy = driveFileManager.publicShareProxy { directoryCount = try await PublicShareApiFetcher() .countPublicShare(drive: publicShareProxy.proxyDrive, - linkUuid: publicShareProxy.shareLinkUid) + linkUuid: publicShareProxy.shareLinkUid, + fileId: publicShareProxy.fileId) } else { directoryCount = try await driveFileManager.apiFetcher.count(of: proxyCurrentDirectory) } diff --git a/kDriveCore/Data/Api/Endpoint+Share.swift b/kDriveCore/Data/Api/Endpoint+Share.swift index 8fc76c170..a07eb050d 100644 --- a/kDriveCore/Data/Api/Endpoint+Share.swift +++ b/kDriveCore/Data/Api/Endpoint+Share.swift @@ -95,8 +95,8 @@ public extension Endpoint { } /// Count files of a public share folder - static func countPublicShare(drive: AbstractDrive, linkUuid: String) -> Endpoint { - return publicShareArchive(driveId: drive.id, linkUuid: linkUuid).appending(path: "/count") + static func countPublicShare(driveId: Int, linkUuid: String, fileId: Int) -> Endpoint { + return shareLinkFileV2(driveId: driveId, linkUuid: linkUuid, fileId: fileId).appending(path: "/count") } func showOfficeShareLinkFile(driveId: Int, linkUuid: String, fileId: Int) -> Endpoint { diff --git a/kDriveCore/Data/Api/PublicShareApiFetcher.swift b/kDriveCore/Data/Api/PublicShareApiFetcher.swift index e59c86686..b97c52e47 100644 --- a/kDriveCore/Data/Api/PublicShareApiFetcher.swift +++ b/kDriveCore/Data/Api/PublicShareApiFetcher.swift @@ -103,8 +103,8 @@ public extension PublicShareApiFetcher { return archiveResponse.validApiResponse.data } - func countPublicShare(drive: AbstractDrive, linkUuid: String) async throws -> FileCount { - let countUrl = Endpoint.countPublicShare(drive: drive, linkUuid: linkUuid).url + func countPublicShare(drive: AbstractDrive, linkUuid: String, fileId: Int) async throws -> FileCount { + let countUrl = Endpoint.countPublicShare(driveId: drive.id, linkUuid: linkUuid, fileId: fileId).url let request = Session.default.request(countUrl) let countResponse: ValidServerResponse = try await perform(request: request) return countResponse.validApiResponse.data From 31051668d57d7d3ad0eb6805ca09d156b2e44efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 11 Dec 2024 09:22:22 +0100 Subject: [PATCH 13/13] refactor: Substitute ViewControllerDismissable for a simple closure. --- kDrive/AppRouter.swift | 4 +++- .../Controller/Files/File List/FileListViewController.swift | 2 +- kDrive/UI/Controller/Files/File List/FileListViewModel.swift | 3 +-- .../Controller/FloatingPanelSelectOptionViewController.swift | 5 ----- kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift | 5 ++--- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/kDrive/AppRouter.swift b/kDrive/AppRouter.swift index 38685cb79..70fa70cb5 100644 --- a/kDrive/AppRouter.swift +++ b/kDrive/AppRouter.swift @@ -675,7 +675,9 @@ public struct AppRouter: AppNavigable { apiFetcher: apiFetcher, configuration: configuration) let viewController = FileListViewController(viewModel: viewModel) - viewModel.viewControllerDismissable = viewController + viewModel.dismissClosure = { [weak viewController] in + viewController?.dismiss(animated: true) + } let publicShareNavigationController = UINavigationController(rootViewController: viewController) publicShareNavigationController.modalPresentationStyle = .fullScreen diff --git a/kDrive/UI/Controller/Files/File List/FileListViewController.swift b/kDrive/UI/Controller/Files/File List/FileListViewController.swift index 046acbcc3..394091956 100644 --- a/kDrive/UI/Controller/Files/File List/FileListViewController.swift +++ b/kDrive/UI/Controller/Files/File List/FileListViewController.swift @@ -49,7 +49,7 @@ extension SortType: Selectable { } class FileListViewController: UICollectionViewController, SwipeActionCollectionViewDelegate, - SwipeActionCollectionViewDataSource, FilesHeaderViewDelegate, SceneStateRestorable, ViewControllerDismissable { + SwipeActionCollectionViewDataSource, FilesHeaderViewDelegate, SceneStateRestorable { @LazyInjectService var accountManager: AccountManageable // MARK: - Constants diff --git a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift index 9c15a79fe..93cc3086c 100644 --- a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift @@ -93,8 +93,7 @@ class FileListViewModel: SelectDelegate { var matomoViewPath = ["FileList"] } - weak var viewControllerDismissable: ViewControllerDismissable? - + var dismissClosure: (() -> Void)? var realmObservationToken: NotificationToken? var currentDirectoryObservationToken: NotificationToken? diff --git a/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift b/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift index 9a34c195a..88bcb3c7b 100644 --- a/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift +++ b/kDrive/UI/Controller/FloatingPanelSelectOptionViewController.swift @@ -37,11 +37,6 @@ protocol SelectDelegate: AnyObject { func didSelect(option: Selectable) } -@MainActor -public protocol ViewControllerDismissable: AnyObject { - func dismiss(animated flag: Bool, completion: (() -> Void)?) -} - class FloatingPanelSelectOptionViewController: UITableViewController { var headerTitle = "" var selectedOption: T? diff --git a/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift index 7f0939534..22408ce7c 100644 --- a/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift +++ b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift @@ -83,9 +83,8 @@ final class PublicShareViewModel: InMemoryFileListViewModel { guard type == .downloadAll else { // We try to close the "Public Share screen" if type == .cancel, - !(multipleSelectionViewModel?.isMultipleSelectionEnabled ?? true), - let viewControllerDismissable { - viewControllerDismissable.dismiss(animated: true, completion: nil) + !(multipleSelectionViewModel?.isMultipleSelectionEnabled ?? true) { + dismissClosure?() return }