diff --git a/kDrive/UI/Controller/Menu/StorageTableViewController.swift b/kDrive/UI/Controller/Menu/StorageTableViewController.swift index 454f76f9d..ea88efc6b 100644 --- a/kDrive/UI/Controller/Menu/StorageTableViewController.swift +++ b/kDrive/UI/Controller/Menu/StorageTableViewController.swift @@ -111,13 +111,9 @@ final class StorageTableViewController: UITableViewController { self.tableView.reloadData() } - // Check old size calculation and new one, send a sentry. Task { - let legacySize = cacheDirectories.reduce(0) { $0 + $1.size } - let metadata = [ "usedSize": usedSize, - "legacySize": legacySize, "appSize": appSize, "appGroupSize": appGroupSize ] diff --git a/kDriveCore/Data/UploadQueue/Operation/UploadOperation+UploadChunk.swift b/kDriveCore/Data/UploadQueue/Operation/UploadOperation+UploadChunk.swift index ed02699ab..10596ad6c 100644 --- a/kDriveCore/Data/UploadQueue/Operation/UploadOperation+UploadChunk.swift +++ b/kDriveCore/Data/UploadQueue/Operation/UploadOperation+UploadChunk.swift @@ -87,18 +87,16 @@ extension UploadOperation { chunksToGenerateCount: chunksToGenerateCount) } - func storeChunk(_ buffer: Data, number: Int64, uploadFileId: String, sessionToken: String, + /// Store a chunk at the root of NSTemporaryDirectory with a stable name + func storeChunk(_ buffer: Data, number: Int64, + uploadFileId: String, + sessionToken: String, hash: String) throws -> URL { - // Create subfolders if needed - let tempChunkFolder = buildFolderPath(fileId: uploadFileId, sessionToken: sessionToken) - Log.uploadOperation("using chunk folder:'\(tempChunkFolder)' ufid:\(uploadFileId)") - if !fileManager.fileExists(atPath: tempChunkFolder.path, isDirectory: nil) { - try fileManager.createDirectory(at: tempChunkFolder, withIntermediateDirectories: true, attributes: nil) - } + // Store chunks at the root of NSTemporaryDirectory + let tempRoot = FileManager.default.temporaryDirectory + let chunkName = chunkName(number: number, fileId: uploadFileId, sessionToken: sessionToken, hash: hash) + let chunkPath = tempRoot.appendingPathComponent(chunkName) - // Write buffer - let chunkName = chunkName(number: number, fileId: uploadFileId, hash: hash) - let chunkPath = tempChunkFolder.appendingPathExtension(chunkName) try buffer.write(to: chunkPath, options: [.atomic]) Log.uploadOperation("wrote chunk:\(chunkPath) ufid:\(uploadFileId)") @@ -217,23 +215,12 @@ extension UploadOperation { } } - private func chunkName(number: Int64, fileId: String, hash: String) -> String { + private func chunkName(number: Int64, fileId: String, sessionToken: String, hash: String) -> String { // Hashing name as it can break path building. Also it keeps it short - let fileName = "upload_\(fileId)_\(hash)_\(number)".SHA256DigestString + let fileName = "upload_\(fileId)_\(hash)_\(number)_\(sessionToken)".SHA256DigestString return fileName + ".part" } - private func buildFolderPath(fileId: String, sessionToken: String) -> URL { - // NSTemporaryDirectory is perfect for this use case. - // Cleaned after ≈ 3 days, our session is valid 12h. - // https://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html - - // fileId and sessionToken can break the URL path, hashing makes sure it works - let folderUrlString = NSTemporaryDirectory() + "/\(fileId.SHA256DigestString)_\(sessionToken.SHA256DigestString)" - let folderPath = URL(fileURLWithPath: folderUrlString) - return folderPath.standardizedFileURL - } - /// Make sure all `uploadTasks` canceled or completed are up to date in database. /// - Parameter iterator: A view on `uploadTasks` func cleanUploadSessionUploadTaskNotUploading(iterator: inout Dictionary.Iterator) throws { diff --git a/kDriveCore/Data/UploadQueue/Operation/UploadOperation.swift b/kDriveCore/Data/UploadQueue/Operation/UploadOperation.swift index 7b0ba81f0..69f00cec7 100644 --- a/kDriveCore/Data/UploadQueue/Operation/UploadOperation.swift +++ b/kDriveCore/Data/UploadQueue/Operation/UploadOperation.swift @@ -388,13 +388,15 @@ public final class UploadOperation: AsynchronousOperation, UploadOperationable { assertionFailure("unable to lookup chunk task id, ufid:\(self.uploadFileId)") } - // Some cleanup if we have the chance + // Cleanup chunks in storage if let path = chunkTask.path { let url = URL(fileURLWithPath: path, isDirectory: false) let chunkNumber = chunkTask.chunkNumber - DispatchQueue.global(qos: .background).async { - Log.uploadOperation("cleanup chunk:\(chunkNumber) ufid:\(self.uploadFileId)") - try? self.fileManager.removeItem(at: url) + Log.uploadOperation("cleanup chunk:\(chunkNumber) ufid:\(self.uploadFileId)") + do { + try self.fileManager.removeItem(at: url) + } catch { + Log.uploadOperation("failed to clean chunk \(error) ufid:\(self.uploadFileId)", level: .error) } } } notFound: { diff --git a/kDriveCore/Data/UploadQueue/Queue/UploadQueue+Queue.swift b/kDriveCore/Data/UploadQueue/Queue/UploadQueue+Queue.swift index de9cafcf4..e1ad324e8 100644 --- a/kDriveCore/Data/UploadQueue/Queue/UploadQueue+Queue.swift +++ b/kDriveCore/Data/UploadQueue/Queue/UploadQueue+Queue.swift @@ -20,6 +20,7 @@ import Algorithms import CocoaLumberjackSwift import Foundation import InfomaniakCore +import InfomaniakDI import RealmSwift // MARK: - Publish @@ -74,6 +75,10 @@ extension UploadQueue: UploadQueueable { public func rebuildUploadQueueFromObjectsInRealm(_ caller: StaticString = #function) { Log.uploadQueue("rebuildUploadQueueFromObjectsInRealm caller:\(caller)") concurrentQueue.sync { + // Clean cache if necessary before we try to restart the uploads. + @InjectService var freeSpaceService: FreeSpaceService + freeSpaceService.cleanCacheIfAlmostFull() + guard let uploadFileQuery else { Log.uploadQueue("\(#function) disabled in \(appContextService.context.rawValue)", level: .error) return diff --git a/kDriveCore/Data/UploadQueue/Servicies/FreeSpace/FreeSpaceService.swift b/kDriveCore/Data/UploadQueue/Servicies/FreeSpace/FreeSpaceService.swift index 550f5429b..33b45e770 100644 --- a/kDriveCore/Data/UploadQueue/Servicies/FreeSpace/FreeSpaceService.swift +++ b/kDriveCore/Data/UploadQueue/Servicies/FreeSpace/FreeSpaceService.swift @@ -174,8 +174,10 @@ public struct FreeSpaceService { } } - /// On devices with low free space, we clear the temporaryDirectory on exit - private func cleanCacheIfAlmostFull() { + /// On devices with low free space, we clear the temporaryDirectory + /// + /// Safe to call before rebuilding the upload queue + public func cleanCacheIfAlmostFull() { let freeSpaceInTemporaryDirectory: Int64 do { freeSpaceInTemporaryDirectory = try freeSpace(url: Self.temporaryDirectoryURL) @@ -185,7 +187,7 @@ public struct FreeSpaceService { } // Only clean if reaching the minimum space required for upload - guard freeSpaceInTemporaryDirectory < minimalSpaceRequiredForChunkUpload * 2 else { + guard freeSpaceInTemporaryDirectory < minimalSpaceRequiredForChunkUpload * 4 else { return }