Skip to content

Commit

Permalink
Merge pull request #46 from Infomaniak/kDriveShareZip
Browse files Browse the repository at this point in the history
chore: moved kDrive code related to file sharing and management
  • Loading branch information
adrien-coye committed Jul 12, 2023
2 parents 5a36a9a + 6fa467f commit 3e858f4
Show file tree
Hide file tree
Showing 3 changed files with 723 additions and 0 deletions.
129 changes: 129 additions & 0 deletions Sources/InfomaniakCore/FileManagement/AppGroupPathProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
Infomaniak kDrive - iOS App
Copyright (C) 2023 Infomaniak Network SA
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#if canImport(MobileCoreServices)

import CocoaLumberjackSwift
import Foundation

/// Something that can provide a set of common URLs within the app group
///
/// This is shared between all apps of the group initialised with
public protocol AppGroupPathProvidable: AnyObject {
/// Failable init if app group is not found
init?(realmRootPath: String, appGroupIdentifier: String)

/// The directory of the current app group, exists in FS.
/// Uses the `.completeUntilFirstUserAuthentication` protection policy
var groupDirectoryURL: URL { get }

/// The root directory to store the App's Realm files, exists in FS
var realmRootURL: URL { get }

/// The import directory, exists in FS
var importDirectoryURL: URL { get }

/// The cache directory within the app group, exists in FS
var cacheDirectoryURL: URL { get }

/// The temporary directory within the app group, exists in FS
var tmpDirectoryURL: URL { get }

/// Open In Place directory if available
var openInPlaceDirectoryURL: URL? { get }
}

public final class AppGroupPathProvider: AppGroupPathProvidable {
private let fileManager = FileManager.default

private let realmRootPath: String

// MARK: public var

public let groupDirectoryURL: URL

public lazy var realmRootURL: URL = {
let drivesURL = groupDirectoryURL.appendingPathComponent(self.realmRootPath, isDirectory: true)
try? fileManager.createDirectory(
atPath: drivesURL.path,
withIntermediateDirectories: true,
attributes: nil
)
return drivesURL
}()

public lazy var importDirectoryURL: URL = {
let importURL = groupDirectoryURL.appendingPathComponent("import", isDirectory: true)
try? fileManager.createDirectory(
atPath: importURL.path,
withIntermediateDirectories: true,
attributes: nil
)
return importURL
}()

public lazy var cacheDirectoryURL: URL = {
let cacheURL = groupDirectoryURL.appendingPathComponent("Library/Caches", isDirectory: true)
try? fileManager.createDirectory(
atPath: cacheURL.path,
withIntermediateDirectories: true,
attributes: nil
)
return cacheURL
}()

public lazy var tmpDirectoryURL: URL = {
let tmpURL = groupDirectoryURL.appendingPathComponent("tmp", isDirectory: true)
try? fileManager.createDirectory(
atPath: tmpURL.path,
withIntermediateDirectories: true,
attributes: nil
)
return tmpURL
}()

public lazy var openInPlaceDirectoryURL: URL? = {
let openInPlaceURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first?
.appendingPathComponent(".shared", isDirectory: true)
return openInPlaceURL
}()

// MARK: init

public init?(realmRootPath: String, appGroupIdentifier: String) {
guard let groupDirectoryURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
else {
return nil
}

do {
try fileManager.setAttributes(
[FileAttributeKey.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
ofItemAtPath: groupDirectoryURL.path
)
} catch {
DDLogError("[AppGroupPathProvider] failed to protect mandatory path :\(error)")
return nil
}

self.groupDirectoryURL = groupDirectoryURL
self.realmRootPath = realmRootPath
}
}

#endif
124 changes: 124 additions & 0 deletions Sources/InfomaniakCore/FileManagement/NSItemProvider+Detect.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
Infomaniak kDrive - iOS App
Copyright (C) 2023 Infomaniak Network SA
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#if canImport(MobileCoreServices)

import Foundation
import InfomaniakDI

/// Extending NSItemProvider for detecting file type, business logic.
extension NSItemProvider {
public enum ItemUnderlyingType: Equatable {
/// The item is an URL
case isURL
/// The item is Text
case isText
/// The item is an UIImage
case isUIImage
/// The item is image Data (heic or jpg)
case isImageData
/// The item is a Directory
case isDirectory
/// The item is a compressed file
case isCompressedData(identifier: String)
/// The item is of a miscellaneous type
case isMiscellaneous(identifier: String)
/// This should not happen, no type identifier was found
case none
}

/// Wrapping business logic of supported types by the apps.
public var underlyingType: ItemUnderlyingType {
if hasItemConformingToTypeIdentifier(UTI.url.identifier) && registeredTypeIdentifiers.count == 1 {
return .isURL
} else if hasItemConformingToTypeIdentifier(UTI.plainText.identifier)
&& !hasItemConformingToTypeIdentifier(UTI.fileURL.identifier)
&& canLoadObject(ofClass: String.self) {
return .isText
} else if hasItemConformingToTypeIdentifier(UTI.directory.identifier)
|| hasItemConformingToTypeIdentifier(UTI.folder.identifier)
|| hasItemConformingToTypeIdentifier(UTI.filesAppFolder.identifier) {
return .isDirectory
} else if hasItemConformingToTypeIdentifier(UTI.zip.identifier)
|| hasItemConformingToTypeIdentifier(UTI.bz2.identifier)
|| hasItemConformingToTypeIdentifier(UTI.gzip.identifier)
|| hasItemConformingToTypeIdentifier(UTI.archive.identifier),
let typeIdentifier = registeredTypeIdentifiers.first {
return .isCompressedData(identifier: typeIdentifier)
} else if registeredTypeIdentifiers.count == 1 &&
registeredTypeIdentifiers.first == UTI.image.identifier {
return .isUIImage
} else if hasItemConformingToTypeIdentifier(UTI.heic.identifier) ||
hasItemConformingToTypeIdentifier(UTI.jpeg.identifier) {
return .isImageData
} else if let typeIdentifier = registeredTypeIdentifiers.first {
return .isMiscellaneous(identifier: typeIdentifier)
} else {
return .none
}
}
}

public extension NSItemProvider {
enum ErrorDomain: Error {
case unableToLoadURLForObject
case notADirectory
case wrapping(error: Error)
}

/// Provide a zip representation of the item, if the`ItemUnderlyingType` is `.isDirectory`
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public var zippedRepresentation: Result<URL, Error> {
get async {
guard underlyingType == ItemUnderlyingType.isDirectory else {
return .failure(ErrorDomain.notADirectory)
}

@InjectService var pathProvider: AppGroupPathProvidable

let fileManager = FileManager.default
let coordinator = NSFileCoordinator()
let tmpDirectoryURL = pathProvider.tmpDirectoryURL
let tempURL = tmpDirectoryURL.appendingPathComponent("\(UUID().uuidString).zip")

let result: Result<URL, Error> = await withCheckedContinuation { continuation in
_ = loadObject(ofClass: URL.self) { path, error in
guard error == nil, let path: URL = path else {
continuation.resume(returning: .failure(ErrorDomain.unableToLoadURLForObject))
return
}

// compress content of folder and move it somewhere we can safely store it for upload
var error: NSError?
coordinator.coordinate(readingItemAt: path, options: [.forUploading], error: &error) { zipURL in
do {
try fileManager.moveItem(at: zipURL, to: tempURL)
continuation.resume(returning: .success(tempURL))
} catch {
continuation.resume(returning: .failure(ErrorDomain.wrapping(error: error)))
}
}
}
}

return result
}
}
}

#endif
Loading

0 comments on commit 3e858f4

Please sign in to comment.