Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: External links feature branch #1306

Draft
wants to merge 111 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
3b54164
chore: WIP endpoints
adrien-coye Jul 26, 2024
2046680
feat: In memory realm for external share context
adrien-coye Aug 16, 2024
001219f
fix: Project builds
adrien-coye Aug 16, 2024
31eafba
feat: Public share metadata WIP
adrien-coye Aug 19, 2024
98cffec
feat(Rights): Updated to be compatible with public share
adrien-coye Aug 20, 2024
fe5772f
feat(PublicShareMetadata): Parsing pass
adrien-coye Aug 20, 2024
1aff48b
refactor(Endpoint): Split files into dedicated extensiions
adrien-coye Aug 22, 2024
772ac0d
feat: Fetch root folder for a public share
adrien-coye Aug 29, 2024
e1c1644
feat: Cursored public share children query
adrien-coye Sep 2, 2024
84fa7fd
feat: Can natively show a public share within the app
adrien-coye Sep 3, 2024
130305f
fix(FilePresenter): Present public share in context
adrien-coye Sep 16, 2024
0b48b91
fix: Capabilities for Public Share
adrien-coye Sep 16, 2024
272968e
feat: Thumbnails on public share
adrien-coye Sep 17, 2024
4e48460
feat: Can navigate hierarchy of public share folders
adrien-coye Sep 18, 2024
3bdf94e
feat: Preview for public share
adrien-coye Sep 19, 2024
0b922d3
feat: Download file from public share
adrien-coye Sep 19, 2024
f9c608b
feat: Actions match spec on public share
adrien-coye Sep 20, 2024
8277702
fix: Tuist config update to build
adrien-coye Sep 23, 2024
fda286c
feat: Add to my drive button
adrien-coye Sep 23, 2024
3ba0f11
chore: Sample code to open public share
adrien-coye Sep 24, 2024
bc7b593
chore: Matomo for public share tasks
adrien-coye Sep 25, 2024
6ef3eda
chore: Align Matomo with android on what exists
adrien-coye Sep 26, 2024
d025759
feat: Download all files from current folder as a ZIP within a public…
adrien-coye Sep 26, 2024
630111f
feat: Add to my kDrive button
adrien-coye Sep 26, 2024
5368da7
feat: Multiselect behaviour
adrien-coye Sep 27, 2024
e1e77ca
chore: Merge branch 'master' into externalLinks-multiSelect
adrien-coye Oct 22, 2024
6269b10
chore: Merge branch 'master' into feature-externalLinks
adrien-coye Oct 23, 2024
df3f175
chore: Merge branch 'feature-externalLinks' into externalLinks-listFiles
adrien-coye Oct 23, 2024
a8f744c
chore: Merge branch 'feature-externalLinks' into externalLinks-downlo…
adrien-coye Oct 23, 2024
b25299f
chore: Merge branch 'externalLinks-listFiles' into externalLinks-down…
adrien-coye Oct 23, 2024
0b2fd4e
chore: Merge branch 'externalLinks-downloadFiles' into externalLinks-…
adrien-coye Oct 23, 2024
245e8bf
chore: PR Feedback
adrien-coye Oct 23, 2024
e41d291
chore: Merge branch 'externalLinks-listFiles' into externalLinks-down…
adrien-coye Oct 23, 2024
fbf4989
chore: PR Feedback
adrien-coye Oct 23, 2024
6c99ba0
chore: PR Feedback
adrien-coye Oct 23, 2024
0fe23be
chore: PR Feedback
adrien-coye Oct 23, 2024
2f33f7b
feat: LockedFolderViewController
adrien-coye Oct 23, 2024
7d8748f
feat: UnavaillableFolderViewController
adrien-coye Oct 23, 2024
95e4825
Merge pull request #1307 from Infomaniak/externalLinks-listFiles
adrien-coye Oct 23, 2024
3624e82
Merge pull request #1308 from Infomaniak/externalLinks-downloadFiles
adrien-coye Oct 23, 2024
fb8ba1c
feat: Error on fetch public share metadata brings the locked landing
adrien-coye Oct 23, 2024
4a92608
feat: Public Share limitations handling
adrien-coye Oct 24, 2024
70c38ac
feat: UFO asset
adrien-coye Oct 24, 2024
9889f33
feat: Public share locked UI match desing
adrien-coye Oct 24, 2024
424e218
feat: Latest UI
adrien-coye Oct 25, 2024
097e966
chore: New strings for public share
adrien-coye Oct 28, 2024
72ed359
chore: Merge branch 'master' into feature-externalLinks
adrien-coye Oct 29, 2024
60e1718
Merge pull request #1333 from Infomaniak/bump_i18n__
valentinperignon Oct 29, 2024
5b8f4c0
chore: Bump DB version as it was bumped on master
adrien-coye Oct 29, 2024
ffbb21c
chore: Merge branch 'feature-externalLinks' into externalLinks-locked…
adrien-coye Oct 29, 2024
a5aa0a1
chore: Making use of i18n
adrien-coye Oct 29, 2024
3cca05b
feat: Added last error type to trigger the correct screen
adrien-coye Oct 29, 2024
4b0f126
chore: Sonar feedback
adrien-coye Oct 30, 2024
ba63bfd
fix: Fix i18n keys
adrien-coye Oct 30, 2024
8ac9134
chore: Merge branch 'feature-externalLinks' into externalLinks-multiS…
adrien-coye Oct 30, 2024
1861f13
feat: Public Share multi selection actions are set to download only
adrien-coye Oct 30, 2024
709ed16
chore: Merge branch 'master' into feature-externalLinks
adrien-coye Nov 11, 2024
0bf3f9e
chore: Merge branch 'feature-externalLinks' into externalLinks-locked…
adrien-coye Nov 11, 2024
a79706e
chore: PR Feedback
adrien-coye Nov 11, 2024
5ad6e99
chore: PR Feedback
adrien-coye Nov 11, 2024
176ce72
chore: Remove VisualFormat
adrien-coye Nov 14, 2024
b744190
chore: Merge branch 'master' into feature-externalLinks
adrien-coye Nov 19, 2024
183b683
chore: Merge branch 'feature-externalLinks' into externalLinks-locked…
adrien-coye Nov 19, 2024
0b1a88e
Merge pull request #1328 from Infomaniak/externalLinks-lockedFolder
adrien-coye Nov 19, 2024
7054054
chore: Merge branch 'feature-externalLinks' into externalLinks-multiS…
adrien-coye Nov 27, 2024
ced6c19
chore: Removed unnecessary title
adrien-coye Nov 27, 2024
0513dfb
refactor: Split download action in manageable bits
adrien-coye Nov 27, 2024
7c48ef2
chore: PR notes
adrien-coye Nov 27, 2024
38dce7f
chore: Merge branch 'master' into feature-externalLinks
adrien-coye Nov 28, 2024
de883e0
fix: Fix broken merge
adrien-coye Nov 28, 2024
66fa72f
chore: Merge branch 'feature-externalLinks' into externalLinks-multiS…
adrien-coye Nov 28, 2024
424a60c
feat: Public share archive request working
adrien-coye Nov 28, 2024
89e00af
feat: External links upsale sheet (#1309)
adrien-coye Nov 29, 2024
6c88f15
feat: Download public share
adrien-coye Nov 29, 2024
c38313c
chore: Merge branch 'feature-externalLinks' into externalLinks-multiS…
adrien-coye Nov 29, 2024
744fc93
feat: Dedicated public share archive download endpoint
adrien-coye Dec 3, 2024
c53c4c1
chore: Self assessment
adrien-coye Dec 3, 2024
c5644a6
refactor: Removed action from ViewController and moved to the view in…
adrien-coye Dec 3, 2024
8918aa6
feat: Public share file count for select all
adrien-coye Dec 3, 2024
b7cfac7
fix: Working select all
adrien-coye Dec 4, 2024
50ee035
chore: Merge branch 'master' into feature-externalLinks
adrien-coye Dec 5, 2024
a78e782
chore: Merge branch 'feature-externalLinks' into externalLinks-multiS…
adrien-coye Dec 5, 2024
f8932ca
fix(DriveFileManager): Fix pagination for public share.
adrien-coye Dec 5, 2024
4538f46
fix(DriveFileManager): Fix pagination for public share (#1348)
PhilippeWeidmann Dec 6, 2024
3105166
refactor: Substitute ViewControllerDismissable for a simple closure.
adrien-coye Dec 11, 2024
8eedeca
chore: Merge branch 'master' into feature-externalLinks
adrien-coye Dec 17, 2024
c35026c
chore: Merge branch 'feature-externalLinks' into externalLinks-multiS…
adrien-coye Dec 17, 2024
4d709db
chore: PR feedback
adrien-coye Dec 19, 2024
dfadb1d
refactor(DownloadOperation): Made a dedicated DownloadPublicShareOper…
adrien-coye Dec 30, 2024
0afa9b3
refactor(DownloadOperation): Factorised download request
adrien-coye Dec 30, 2024
42a8b05
chore: PR Feedback
adrien-coye Dec 30, 2024
f3c9853
refactor(DownloadOperation): Split into subclasses (#1367)
adrien-coye Dec 30, 2024
20ff0ef
chore: Merge branch 'feature-externalLinks' into externalLinks-multiS…
adrien-coye Dec 30, 2024
3ff747c
refactor(DownloadArchiveOperation): Split dedicated public share code
adrien-coye Dec 30, 2024
d96e98d
refactor(DownloadArchiveOperation): Factorised download request
adrien-coye Dec 30, 2024
17109b6
refactor(DownloadArchiveOperation): Split code related to publicShare…
adrien-coye Dec 30, 2024
62ddb92
refactor(DownloadOperation): Renamed DownloadAuthenticatedOperation
adrien-coye Jan 2, 2025
351a198
refactor(DownloadOperationable): Renamed DownloadFileOperationable
adrien-coye Jan 2, 2025
2d60725
chore(DownloadOperation): Explicit sendable requirements from base Op…
adrien-coye Jan 2, 2025
052442b
refactor(DownloadOperation): Factorise a base DownloadOperation.
adrien-coye Jan 2, 2025
e93a347
chore: Sonar naming conventions
adrien-coye Jan 2, 2025
b3fe62d
chore: Merge branch 'master' into feature-externalLinks
adrien-coye Jan 2, 2025
2825da0
chore: Merge branch 'feature-externalLinks' into externalLinks-multiS…
adrien-coye Jan 2, 2025
24b3fec
chore: Merge branch 'externalLinks-multiSelect' into externalLinks-co…
adrien-coye Jan 2, 2025
399acfb
refactor(DownloadOperation): Public accessor for progress value
adrien-coye Jan 6, 2025
cc16b7a
refactor: Common download operation (#1370)
adrien-coye Jan 6, 2025
d294a79
refactor(DownloadQueue): Use abstract type DownloadFileOperationable …
adrien-coye Jan 7, 2025
c1f7fb4
feat: Multiselect behaviour (#1311)
adrien-coye Jan 7, 2025
da394c9
chore: Sonar Feedback
adrien-coye Jan 7, 2025
7df9df3
chore: Merge branch 'master' into feature-externalLinks
adrien-coye Jan 7, 2025
09d4102
test(MCKRouter): Adding missing mocked signatures
adrien-coye Jan 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions kDrive/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
}
application.registerForRemoteNotifications()

// swiftlint:disable force_try
Task {
adrien-coye marked this conversation as resolved.
Show resolved Hide resolved
try! await Task.sleep(nanoseconds:5_000_000_000)
print("coucou")
let somePublicShare = URL(string: "")
//await UIApplication.shared.open(somePublicShare!) // opens safari

let components = URLComponents(url: somePublicShare!, resolvingAgainstBaseURL: true)
await UniversalLinksHelper.handlePath(components!.path)
}


return true
}

Expand Down
20 changes: 20 additions & 0 deletions kDrive/AppRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,26 @@ public struct AppRouter: AppNavigable {

// MARK: RouterFileNavigable

@MainActor public func presentPublicShare(
rootFolder: File,
publicShareProxy: PublicShareProxy,
driveFileManager: DriveFileManager,
apiFetcher: PublicShareApiFetcher
) {
// TODO: Present on top of existing views
guard let window,
let rootViewController = window.rootViewController else {
fatalError("TODO: lazy load a rootViewController")
}

let filePresenter = FilePresenter(viewController: rootViewController)
filePresenter.presentPublicShareDirectory(publicShareProxy: publicShareProxy,
rootFolder: rootFolder,
rootViewController: rootViewController,
driveFileManager: driveFileManager,
apiFetcher: apiFetcher)
}

@MainActor public func present(file: File, driveFileManager: DriveFileManager) {
present(file: file, driveFileManager: driveFileManager, office: false)
}
Expand Down
14 changes: 8 additions & 6 deletions kDrive/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,15 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate, AccountManagerDel

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
Log.sceneDelegate("scene continue userActivity")
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL,
let components = URLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else {
return
}
Task {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL,
let components = URLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else {
return
}

UniversalLinksHelper.handlePath(components.path)
await UniversalLinksHelper.handlePath(components.path)
}
}

func scene(_ scene: UIScene, didFailToContinueUserActivityWithType userActivityType: String, error: Error) {
Expand Down
25 changes: 25 additions & 0 deletions kDrive/UI/Controller/Files/FilePresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,31 @@ final class FilePresenter {
}
}

public func presentPublicShareDirectory(
publicShareProxy: PublicShareProxy,
rootFolder: File,
rootViewController: UIViewController,
driveFileManager: DriveFileManager,
apiFetcher: PublicShareApiFetcher
) {
let viewModel = PublicShareViewModel(publicShareProxy: publicShareProxy,
sortType: .nameAZ,
driveFileManager: driveFileManager,
currentDirectory: rootFolder,
apiFetcher: apiFetcher)

// TODO: Fix access right
// guard !rootFolder.isDisabled else {
// return
// }

let nextVC = FileListViewController(viewModel: viewModel)
print("nextVC:\(nextVC) viewModel:\(viewModel) navigationController:\(navigationController)")
// navigationController?.pushViewController(nextVC, animated: true)

rootViewController.present(nextVC, animated: true)
}

public func presentDirectory(
for file: File,
driveFileManager: DriveFileManager,
Expand Down
63 changes: 63 additions & 0 deletions kDrive/UI/Controller/Menu/SharedWithMe/SharedWithMeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,69 @@ import kDriveCore
import RealmSwift
import UIKit

/// Public share view model, loading content from memory realm
final class PublicShareViewModel: InMemoryFileListViewModel {
var publicShareProxy: PublicShareProxy?
let rootProxy: ProxyFile
var publicShareApiFetcher: PublicShareApiFetcher?

required init(driveFileManager: DriveFileManager, currentDirectory: File? = nil) {
guard let currentDirectory else {
fatalError("woops")
adrien-coye marked this conversation as resolved.
Show resolved Hide resolved
}

let configuration = Configuration(selectAllSupported: false,
rootTitle: "public share",
adrien-coye marked this conversation as resolved.
Show resolved Hide resolved
emptyViewType: .emptyFolder,
supportsDrop: false,
matomoViewPath: [MatomoUtils.Views.menu.displayName, "publicShare"])

rootProxy = currentDirectory.proxify()
super.init(configuration: configuration, driveFileManager: driveFileManager, currentDirectory: currentDirectory)
observedFiles = AnyRealmCollection(currentDirectory.children)
print("• observedFiles :\(observedFiles.count)")
}

convenience init(
publicShareProxy: PublicShareProxy,
sortType: SortType,
driveFileManager: DriveFileManager,
currentDirectory: File,
apiFetcher: PublicShareApiFetcher
) {
self.init(driveFileManager: driveFileManager, currentDirectory: currentDirectory)
self.publicShareProxy = publicShareProxy
self.sortType = sortType
publicShareApiFetcher = apiFetcher
}

override func loadFiles(cursor: String? = nil, forceRefresh: Bool = false) async throws {
print("• loadFiles:\(cursor):\(forceRefresh)")
guard !isLoading || cursor != nil,
let publicShareProxy,
let publicShareApiFetcher else {
return
}

// Only show loading indicator if we have nothing in cache
if !currentDirectory.canLoadChildrenFromCache {
startRefreshing(cursor: cursor)
}
defer {
endRefreshing()
}

let (_, nextCursor) = try await driveFileManager.publicShareFiles(rootProxy: rootProxy,
publicShareProxy: publicShareProxy,
publicShareApiFetcher: publicShareApiFetcher)
print("• nextCursor:\(nextCursor)")
endRefreshing()
if let nextCursor {
try await loadFiles(cursor: nextCursor)
}
}
}

class SharedWithMeViewModel: FileListViewModel {
required init(driveFileManager: DriveFileManager, currentDirectory: File? = nil) {
let sharedWithMeRootFile = driveFileManager.getManagedFile(from: DriveFileManager.sharedWithMeRootFile)
Expand Down
76 changes: 74 additions & 2 deletions kDrive/Utils/UniversalLinksHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,44 @@ enum UniversalLinksHelper {
regex: Regex(pattern: #"^/app/drive/([0-9]+)/redirect/([0-9]+)$"#)!,
displayMode: .file
)

/// Matches a public share link
static let publicShareLink = Link(
regex: Regex(pattern: #"^/app/share/([0-9]+)/([a-z0-9-]+)$"#)!,
displayMode: .file
)

/// Matches a directory list link
static let directoryLink = Link(regex: Regex(pattern: #"^/app/drive/([0-9]+)/files/([0-9]+)$"#)!, displayMode: .file)

/// Matches a file preview link
static let filePreview = Link(
regex: Regex(pattern: #"^/app/drive/([0-9]+)/files/([0-9]+/)?preview/[a-z]+/([0-9]+)$"#)!,
displayMode: .file
)

/// Matches an office file link
static let officeLink = Link(regex: Regex(pattern: #"^/app/office/([0-9]+)/([0-9]+)$"#)!, displayMode: .office)

static let all = [privateShareLink, directoryLink, filePreview, officeLink]
static let all = [privateShareLink, publicShareLink, directoryLink, filePreview, officeLink]
}

private enum DisplayMode {
case office, file
}

static func handlePath(_ path: String) -> Bool {
@discardableResult
static func handlePath(_ path: String) async -> Bool {
DDLogInfo("[UniversalLinksHelper] Trying to open link with path: \(path)")

// Public share link regex
let shareLink = Link.publicShareLink
let matches = shareLink.regex.matches(in: path)
if await processPublicShareLink(matches: matches, displayMode: shareLink.displayMode) {
return true
}

// Common regex
for link in Link.all {
let matches = link.regex.matches(in: path)
if processRegex(matches: matches, displayMode: link.displayMode) {
Expand All @@ -66,6 +84,33 @@ enum UniversalLinksHelper {
return false
}

private static func processPublicShareLink(matches: [[String]], displayMode: DisplayMode) async -> Bool {
@InjectService var accountManager: AccountManageable

guard let firstMatch = matches.first,
let driveId = firstMatch[safe: 1],
let driveIdInt = Int(driveId),
let shareLinkUid = firstMatch[safe: 2] else {
return false
}

// request metadata
let apiFetcher = PublicShareApiFetcher()
guard let metadata = try? await apiFetcher.getMetadata(driveId: driveIdInt, shareLinkUid: shareLinkUid)
else {
return false
}

// get file ID from metadata
let publicShareDriveFileManager = accountManager.getInMemoryDriveFileManager(for: shareLinkUid)
openPublicShare(driveId: driveIdInt,
linkUuid: shareLinkUid,
fileId: metadata.fileId,
driveFileManager: publicShareDriveFileManager,
apiFetcher: apiFetcher)
return true
}

private static func processRegex(matches: [[String]], displayMode: DisplayMode) -> Bool {
@InjectService var accountManager: AccountManageable

Expand All @@ -83,6 +128,33 @@ enum UniversalLinksHelper {
return true
}

private static func openPublicShare(driveId: Int,
linkUuid: String,
fileId: Int,
driveFileManager: DriveFileManager,
apiFetcher: PublicShareApiFetcher) {
Task {
do {
let rootFolder = try await apiFetcher.getShareLinkFile(driveId: driveId,
linkUuid: linkUuid,
fileId: fileId)
@InjectService var appNavigable: AppNavigable
let publicShareProxy = PublicShareProxy(driveId: driveId, fileId: fileId, shareLinkUid: linkUuid)
await appNavigable.presentPublicShare(
rootFolder: rootFolder,
publicShareProxy: publicShareProxy,
driveFileManager: driveFileManager,
apiFetcher: apiFetcher
)
} catch {
DDLogError(
"[UniversalLinksHelper] Failed to get public folder [driveId:\(driveId) linkUuid:\(linkUuid) fileId:\(fileId)]: \(error)"
)
await UIConstants.showSnackBarIfNeeded(error: error)
}
}
}

private static func openFile(id: Int, driveFileManager: DriveFileManager, office: Bool) {
Task {
do {
Expand Down
48 changes: 48 additions & 0 deletions kDriveCore/Data/Api/DriveApiFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,54 @@ public class AuthenticatedImageRequestModifier: ImageDownloadRequestModifier {
}
}

public struct PublicShareMetadata: Decodable {
public let url: URL
public let fileId: Int
public let right: String

public let validUntil: TimeInterval?
public let capabilities: Rights

public let createdBy: TimeInterval
public let createdAt: TimeInterval
public let updatedAt: TimeInterval
public let accessBlocked: Bool

enum CodingKeys: String, CodingKey {
case url
case fileId
case right
case validUntil
case capabilities
case createdBy
case createdAt
case updatedAt
case accessBlocked
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

do {
url = try container.decode(URL.self, forKey: .url)
fileId = try container.decode(Int.self, forKey: .fileId)
right = try container.decode(String.self, forKey: .right)

validUntil = try container.decodeIfPresent(TimeInterval.self, forKey: .validUntil)
capabilities = try container.decode(Rights.self, forKey: .capabilities)

createdBy = try container.decode(TimeInterval.self, forKey: .createdBy)
createdAt = try container.decode(TimeInterval.self, forKey: .createdAt)
updatedAt = try container.decode(TimeInterval.self, forKey: .updatedAt)

accessBlocked = try container.decode(Bool.self, forKey: .accessBlocked)
} catch {
// TODO: remove
fatalError("error:\(error)")
}
}
}

public class DriveApiFetcher: ApiFetcher {
@LazyInjectService var accountManager: AccountManageable
@LazyInjectService var tokenable: InfomaniakTokenable
Expand Down
Loading
Loading