-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #46 from Infomaniak/kDriveShareZip
chore: moved kDrive code related to file sharing and management
- Loading branch information
Showing
3 changed files
with
723 additions
and
0 deletions.
There are no files selected for viewing
129 changes: 129 additions & 0 deletions
129
Sources/InfomaniakCore/FileManagement/AppGroupPathProvider.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
124
Sources/InfomaniakCore/FileManagement/NSItemProvider+Detect.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.