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

fix: Cache cleanup fix #1337

Merged
merged 9 commits into from
Nov 7, 2024
4 changes: 0 additions & 4 deletions kDrive/UI/Controller/Menu/StorageTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = URL(fileURLWithPath: NSTemporaryDirectory())
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)")

Expand Down Expand Up @@ -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<String, URLSessionUploadTask>.Iterator) throws {
Expand Down
10 changes: 6 additions & 4 deletions kDriveCore/Data/UploadQueue/Operation/UploadOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
5 changes: 5 additions & 0 deletions kDriveCore/Data/UploadQueue/Queue/UploadQueue+Queue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Algorithms
import CocoaLumberjackSwift
import Foundation
import InfomaniakCore
import InfomaniakDI
import RealmSwift

// MARK: - Publish
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}

Expand Down
Loading