diff --git a/iphone/Maps/Core/iCloud/CloudStorageManager.swift b/iphone/Maps/Core/iCloud/CloudStorageManager.swift index 806ff74568ed8..ee88187a4e4f9 100644 --- a/iphone/Maps/Core/iCloud/CloudStorageManager.swift +++ b/iphone/Maps/Core/iCloud/CloudStorageManager.swift @@ -104,7 +104,7 @@ final class CloudStorageSynchronizationState: NSObject { private extension CloudStorageManager { // MARK: - Synchronization Lifecycle func startSynchronization() { - LOG(.debug, "Start synchronization...") + LOG(.info, "Start synchronization...") switch cloudDirectoryMonitor.state { case .started: LOG(.debug, "Synchronization is already started") @@ -135,7 +135,7 @@ private extension CloudStorageManager { } func stopSynchronization(withError error: Error? = nil) { - LOG(.debug, "Stop synchronization") + LOG(.info, "Stop synchronization") localDirectoryMonitor.stop() cloudDirectoryMonitor.stop() fileWriter = nil @@ -148,13 +148,13 @@ private extension CloudStorageManager { } func pauseSynchronization() { - LOG(.debug, "Pause synchronization") + LOG(.info, "Pause synchronization") localDirectoryMonitor.pause() cloudDirectoryMonitor.pause() } func resumeSynchronization() { - LOG(.debug, "Resume synchronization") + LOG(.info, "Resume synchronization") localDirectoryMonitor.resume() cloudDirectoryMonitor.resume() } @@ -230,10 +230,7 @@ private extension CloudStorageManager { synchronizationError = nil return } - - LOG(.debug, "Start processing events...") events.forEach { [weak self] event in - LOG(.debug, "Processing event: \(event)") guard let self, let fileWriter else { return } fileWriter.processEvent(event, completion: writingResultHandler(for: event)) } @@ -249,13 +246,9 @@ private extension CloudStorageManager { UserDefaults.standard.set(true, forKey: kUDDidFinishInitialCloudSynchronization) } case .reloadCategoriesAtURLs(let urls): - DispatchQueue.main.async { - urls.forEach { self.bookmarksManager.reloadCategory(atFilePath: $0.path) } - } + urls.forEach { self.bookmarksManager.reloadCategory(atFilePath: $0.path) } case .deleteCategoriesAtURLs(let urls): - DispatchQueue.main.async { - urls.forEach { self.bookmarksManager.deleteCategory(atFilePath: $0.path) } - } + urls.forEach { self.bookmarksManager.deleteCategory(atFilePath: $0.path) } case .failure(let error): self.processError(error) } @@ -284,7 +277,7 @@ private extension CloudStorageManager { stopSynchronization(withError: error) } default: - LOG(.debug, "Non-synchronization Error: \(error.localizedDescription)") + LOG(.error, "System Error: \(error.localizedDescription)") stopSynchronization(withError: error) } } diff --git a/iphone/Maps/Core/iCloud/DefaultLocalDirectoryMonitor.swift b/iphone/Maps/Core/iCloud/DefaultLocalDirectoryMonitor.swift index 250425b279c04..c6733e7cd50eb 100644 --- a/iphone/Maps/Core/iCloud/DefaultLocalDirectoryMonitor.swift +++ b/iphone/Maps/Core/iCloud/DefaultLocalDirectoryMonitor.swift @@ -63,10 +63,11 @@ final class DefaultLocalDirectoryMonitor: LocalDirectoryMonitor { guard state != .started else { return } let nowTimer = Timer.scheduledTimer(withTimeInterval: .zero, repeats: false) { [weak self] _ in - LOG(.debug, "LocalMonitor: Initial timer firing...") + LOG(.debug, "Initial timer firing...") self?.debounceTimerDidFire() } + LOG(.debug, "Start local monitor.") if let dispatchSource { dispatchSourceDebounceState = .debounce(source: dispatchSource, timer: nowTimer) resume() @@ -92,7 +93,7 @@ final class DefaultLocalDirectoryMonitor: LocalDirectoryMonitor { func stop() { guard state == .started else { return } - LOG(.debug, "LocalMonitor: Stop.") + LOG(.debug, "Stop.") suspendDispatchSource() didFinishGatheringIsCalled = false dispatchSourceDebounceState = .stopped @@ -101,21 +102,21 @@ final class DefaultLocalDirectoryMonitor: LocalDirectoryMonitor { func pause() { guard state == .started else { return } - LOG(.debug, "LocalMonitor: Pause.") + LOG(.debug, "Pause.") suspendDispatchSource() state = .paused } func resume() { guard state != .started else { return } - LOG(.debug, "LocalMonitor: Resume.") + LOG(.debug, "Resume.") resumeDispatchSource() state = .started } // MARK: - Private private func queueDidFire() { - LOG(.debug, "LocalMonitor: Queue did fire.") + LOG(.debug, "Queue did fire.") let debounceTimeInterval = 0.5 switch dispatchSourceDebounceState { case .started(let source): @@ -135,9 +136,9 @@ final class DefaultLocalDirectoryMonitor: LocalDirectoryMonitor { } private func debounceTimerDidFire() { - LOG(.debug, "LocalMonitor: Debounce timer did fire.") + LOG(.debug, "Debounce timer did fire.") guard state == .started else { - LOG(.debug, "LocalMonitor: State is not started. Skip iteration.") + LOG(.debug, "State is not started. Skip iteration.") return } guard case .debounce(let source, let timer) = dispatchSourceDebounceState else { fatalError() } @@ -148,16 +149,15 @@ final class DefaultLocalDirectoryMonitor: LocalDirectoryMonitor { let files = try fileManager .contentsOfDirectory(at: directory, includingPropertiesForKeys: [.contentModificationDateKey], options: [.skipsHiddenFiles]) .filter { $0.pathExtension == fileType.fileExtension } + LOG(.info, "Local directory content: \(files.map { $0.lastPathComponent }) ") let contents: LocalContents = try files.map { try LocalMetadataItem(fileUrl: $0) } if !didFinishGatheringIsCalled { didFinishGatheringIsCalled = true - LOG(.debug, "LocalMonitor: didFinishGathering called.") - LOG(.debug, "LocalMonitor: contentMetadataItems count: \(contents.count)") + LOG(.debug, "didFinishGathering will be called") delegate?.didFinishGathering(contents: contents) } else { - LOG(.debug, "LocalMonitor: didUpdate called.") - LOG(.debug, "LocalMonitor: contentMetadataItems count: \(contents.count)") + LOG(.debug, "didUpdate will be called") delegate?.didUpdate(contents: contents) } } catch { @@ -167,7 +167,7 @@ final class DefaultLocalDirectoryMonitor: LocalDirectoryMonitor { private func suspendDispatchSource() { if !dispatchSourceIsSuspended { - LOG(.debug, "LocalMonitor: Suspend dispatch source.") + LOG(.debug, "Suspend dispatch source.") dispatchSource?.suspend() dispatchSourceIsSuspended = true dispatchSourceIsResumed = false @@ -176,7 +176,7 @@ final class DefaultLocalDirectoryMonitor: LocalDirectoryMonitor { private func resumeDispatchSource() { if !dispatchSourceIsResumed { - LOG(.debug, "LocalMonitor: Resume dispatch source.") + LOG(.debug, "Resume dispatch source.") dispatchSource?.resume() dispatchSourceIsResumed = true dispatchSourceIsSuspended = false diff --git a/iphone/Maps/Core/iCloud/MetadataItem.swift b/iphone/Maps/Core/iCloud/MetadataItem.swift index 0eda9cdfa0713..9657ba220cbfc 100644 --- a/iphone/Maps/Core/iCloud/MetadataItem.swift +++ b/iphone/Maps/Core/iCloud/MetadataItem.swift @@ -91,6 +91,12 @@ extension CloudMetadataItem { } } +extension MetadataItem { + var shortDebugDescription: String { + "File path: \(fileUrl.path), lastModified: \(lastModificationDate)" + } +} + extension LocalMetadataItem { func relatedCloudItemUrl(to cloudContainer: URL) -> URL { cloudContainer.appendingPathComponent(fileName) @@ -104,6 +110,10 @@ extension Array where Element: MetadataItem { func firstByName(_ item: any MetadataItem) -> Element? { return first(where: { $0.fileName == item.fileName }) } + + var shortDebugDescription: String { + map { $0.shortDebugDescription }.joined(separator: "\n") + } } extension Array where Element == CloudMetadataItem { diff --git a/iphone/Maps/Core/iCloud/SynchronizationFileWriter.swift b/iphone/Maps/Core/iCloud/SynchronizationFileWriter.swift index 2ec7966a38067..94856f4413062 100644 --- a/iphone/Maps/Core/iCloud/SynchronizationFileWriter.swift +++ b/iphone/Maps/Core/iCloud/SynchronizationFileWriter.swift @@ -14,22 +14,25 @@ final class SynchronizationFileWriter { self.localDirectoryUrl = localDirectoryUrl self.cloudDirectoryUrl = cloudDirectoryUrl } - + func processEvent(_ event: OutgoingEvent, completion: @escaping WritingResultCompletionHandler) { + let resultCompletion: WritingResultCompletionHandler = { result in + DispatchQueue.main.sync { completion(result) } + } backgroundQueue.async { [weak self] in guard let self else { return } switch event { - case .createLocalItem(let cloudMetadataItem): self.createInLocalContainer(cloudMetadataItem, completion: completion) - case .updateLocalItem(let cloudMetadataItem): self.updateInLocalContainer(cloudMetadataItem, completion: completion) - case .removeLocalItem(let cloudMetadataItem): self.removeFromLocalContainer(cloudMetadataItem, completion: completion) - case .startDownloading(let cloudMetadataItem): self.startDownloading(cloudMetadataItem, completion: completion) - case .createCloudItem(let localMetadataItem): self.createInCloudContainer(localMetadataItem, completion: completion) - case .updateCloudItem(let localMetadataItem): self.updateInCloudContainer(localMetadataItem, completion: completion) - case .removeCloudItem(let localMetadataItem): self.removeFromCloudContainer(localMetadataItem, completion: completion) - case .resolveVersionsConflict(let cloudMetadataItem): self.resolveVersionsConflict(cloudMetadataItem, completion: completion) - case .resolveInitialSynchronizationConflict(let localMetadataItem): self.resolveInitialSynchronizationConflict(localMetadataItem, completion: completion) - case .didFinishInitialSynchronization: completion(.success) - case .didReceiveError(let error): completion(.failure(error)) + case .createLocalItem(let cloudMetadataItem): self.createInLocalContainer(cloudMetadataItem, completion: resultCompletion) + case .updateLocalItem(let cloudMetadataItem): self.updateInLocalContainer(cloudMetadataItem, completion: resultCompletion) + case .removeLocalItem(let cloudMetadataItem): self.removeFromLocalContainer(cloudMetadataItem, completion: resultCompletion) + case .startDownloading(let cloudMetadataItem): self.startDownloading(cloudMetadataItem, completion: resultCompletion) + case .createCloudItem(let localMetadataItem): self.createInCloudContainer(localMetadataItem, completion: resultCompletion) + case .updateCloudItem(let localMetadataItem): self.updateInCloudContainer(localMetadataItem, completion: resultCompletion) + case .removeCloudItem(let localMetadataItem): self.removeFromCloudContainer(localMetadataItem, completion: resultCompletion) + case .resolveVersionsConflict(let cloudMetadataItem): self.resolveVersionsConflict(cloudMetadataItem, completion: resultCompletion) + case .resolveInitialSynchronizationConflict(let localMetadataItem): self.resolveInitialSynchronizationConflict(localMetadataItem, completion: resultCompletion) + case .didFinishInitialSynchronization: resultCompletion(.success) + case .didReceiveError(let error): resultCompletion(.failure(error)) } } } @@ -37,7 +40,7 @@ final class SynchronizationFileWriter { // MARK: - Read/Write/Downloading/Uploading private func startDownloading(_ cloudMetadataItem: CloudMetadataItem, completion: WritingResultCompletionHandler) { do { - LOG(.debug, "Start downloading file: \(cloudMetadataItem.fileName)...") + LOG(.info, "Start downloading file: \(cloudMetadataItem.fileName)...") try fileManager.startDownloadingUbiquitousItem(at: cloudMetadataItem.fileUrl) completion(.success) } catch { @@ -48,7 +51,7 @@ final class SynchronizationFileWriter { private func createInLocalContainer(_ cloudMetadataItem: CloudMetadataItem, completion: @escaping WritingResultCompletionHandler) { let targetLocalFileUrl = cloudMetadataItem.relatedLocalItemUrl(to: localDirectoryUrl) guard !fileManager.fileExists(atPath: targetLocalFileUrl.path) else { - LOG(.debug, "File \(cloudMetadataItem.fileName) already exists in the local iCloud container.") + LOG(.info, "File \(cloudMetadataItem.fileName) already exists in the local iCloud container.") completion(.success) return } @@ -60,9 +63,9 @@ final class SynchronizationFileWriter { } private func writeToLocalContainer(_ cloudMetadataItem: CloudMetadataItem, completion: @escaping WritingResultCompletionHandler) { + LOG(.info, "Start writing file \(cloudMetadataItem.fileName) to the local directory...") var coordinationError: NSError? let targetLocalFileUrl = cloudMetadataItem.relatedLocalItemUrl(to: localDirectoryUrl) - LOG(.debug, "File \(cloudMetadataItem.fileName) is downloaded to the local iCloud container. Start coordinating and writing file...") fileCoordinator.coordinate(readingItemAt: cloudMetadataItem.fileUrl, writingItemAt: targetLocalFileUrl, error: &coordinationError) { readingUrl, writingUrl in do { try fileManager.replaceFileSafe(at: writingUrl, with: readingUrl) @@ -79,10 +82,10 @@ final class SynchronizationFileWriter { } private func removeFromLocalContainer(_ cloudMetadataItem: CloudMetadataItem, completion: @escaping WritingResultCompletionHandler) { - LOG(.debug, "Start removing file \(cloudMetadataItem.fileName) from the local directory...") + LOG(.info, "Start removing file \(cloudMetadataItem.fileName) from the local directory...") let targetLocalFileUrl = cloudMetadataItem.relatedLocalItemUrl(to: localDirectoryUrl) guard fileManager.fileExists(atPath: targetLocalFileUrl.path) else { - LOG(.debug, "File \(cloudMetadataItem.fileName) doesn't exist in the local directory and cannot be removed.") + LOG(.warning, "File \(cloudMetadataItem.fileName) doesn't exist in the local directory and cannot be removed.") completion(.success) return } @@ -93,7 +96,7 @@ final class SynchronizationFileWriter { private func createInCloudContainer(_ localMetadataItem: LocalMetadataItem, completion: @escaping WritingResultCompletionHandler) { let targetCloudFileUrl = localMetadataItem.relatedCloudItemUrl(to: cloudDirectoryUrl) guard !fileManager.fileExists(atPath: targetCloudFileUrl.path) else { - LOG(.debug, "File \(localMetadataItem.fileName) already exists in the cloud directory.") + LOG(.info, "File \(localMetadataItem.fileName) already exists in the cloud directory.") completion(.success) return } @@ -105,7 +108,7 @@ final class SynchronizationFileWriter { } private func writeToCloudContainer(_ localMetadataItem: LocalMetadataItem, completion: @escaping WritingResultCompletionHandler) { - LOG(.debug, "Start writing file \(localMetadataItem.fileName) to the cloud directory...") + LOG(.info, "Start writing file \(localMetadataItem.fileName) to the cloud directory...") let targetCloudFileUrl = localMetadataItem.relatedCloudItemUrl(to: cloudDirectoryUrl) var coordinationError: NSError? fileCoordinator.coordinate(readingItemAt: localMetadataItem.fileUrl, writingItemAt: targetCloudFileUrl, error: &coordinationError) { readingUrl, writingUrl in @@ -124,7 +127,7 @@ final class SynchronizationFileWriter { } private func removeFromCloudContainer(_ localMetadataItem: LocalMetadataItem, completion: @escaping WritingResultCompletionHandler) { - LOG(.debug, "Start trashing file \(localMetadataItem.fileName)...") + LOG(.info, "Start trashing file \(localMetadataItem.fileName)...") do { let targetCloudFileUrl = localMetadataItem.relatedCloudItemUrl(to: cloudDirectoryUrl) try removeDuplicatedFileFromTrashDirectoryIfNeeded(cloudDirectoryUrl: cloudDirectoryUrl, fileName: localMetadataItem.fileName) @@ -143,26 +146,26 @@ final class SynchronizationFileWriter { if #available(iOS 14.0, *), ProcessInfo.processInfo.isiOSAppOnMac { return } - LOG(.debug, "Checking if the file \(fileName) is already in the trash directory...") + LOG(.info, "Checking if the file \(fileName) is already in the trash directory...") let trashDirectoryUrl = try fileManager.trashDirectoryUrl(for: cloudDirectoryUrl) let fileInTrashDirectoryUrl = trashDirectoryUrl.appendingPathComponent(fileName) let trashDirectoryContent = try fileManager.contentsOfDirectory(at: trashDirectoryUrl, includingPropertiesForKeys: [], options: [.skipsPackageDescendants, .skipsSubdirectoryDescendants]) if trashDirectoryContent.contains(fileInTrashDirectoryUrl) { - LOG(.debug, "File \(fileName) is already in the trash directory. Removing it...") + LOG(.info, "File \(fileName) is already in the trash directory. Removing it...") try fileManager.removeItem(at: fileInTrashDirectoryUrl) - LOG(.debug, "File \(fileName) was removed from the trash directory successfully.") + LOG(.info, "File \(fileName) was removed from the trash directory successfully.") } } // MARK: - Merge conflicts resolving private func resolveVersionsConflict(_ cloudMetadataItem: CloudMetadataItem, completion: @escaping WritingResultCompletionHandler) { - LOG(.debug, "Start resolving version conflict for file \(cloudMetadataItem.fileName)...") + LOG(.info, "Start resolving version conflict for file \(cloudMetadataItem.fileName)...") guard let versionsInConflict = NSFileVersion.unresolvedConflictVersionsOfItem(at: cloudMetadataItem.fileUrl), !versionsInConflict.isEmpty, let currentVersion = NSFileVersion.currentVersionOfItem(at: cloudMetadataItem.fileUrl) else { - LOG(.debug, "No versions in conflict found for file \(cloudMetadataItem.fileName).") + LOG(.info, "No versions in conflict found for file \(cloudMetadataItem.fileName).") completion(.success) return } @@ -175,7 +178,7 @@ final class SynchronizationFileWriter { } guard let latestVersionInConflict = sortedVersions.first else { - LOG(.debug, "No latest version in conflict found for file \(cloudMetadataItem.fileName).") + LOG(.info, "No latest version in conflict found for file \(cloudMetadataItem.fileName).") completion(.success) return } @@ -189,27 +192,27 @@ final class SynchronizationFileWriter { error: &coordinationError) { currentVersionUrl, copyVersionUrl in // Check that during the coordination block, the current version of the file have not been already resolved by another process. guard let unresolvedVersions = NSFileVersion.unresolvedConflictVersionsOfItem(at: currentVersionUrl), !unresolvedVersions.isEmpty else { - LOG(.debug, "File \(cloudMetadataItem.fileName) was already resolved.") + LOG(.info, "File \(cloudMetadataItem.fileName) was already resolved.") completion(.success) return } do { // Check if the file was already resolved by another process. The in-memory versions should be marked as resolved. guard !fileManager.fileExists(atPath: copyVersionUrl.path) else { - LOG(.debug, "File \(cloudMetadataItem.fileName) was already resolved.") + LOG(.info, "File \(cloudMetadataItem.fileName) was already resolved.") try NSFileVersion.removeOtherVersionsOfItem(at: currentVersionUrl) completion(.success) return } - LOG(.debug, "Duplicate file \(cloudMetadataItem.fileName)...") + LOG(.info, "Duplicate file \(cloudMetadataItem.fileName)...") try latestVersionInConflict.replaceItem(at: copyVersionUrl) // The modification date should be updated to mark files that was involved into the resolving process. try currentVersionUrl.setResourceModificationDate(Date()) try copyVersionUrl.setResourceModificationDate(Date()) unresolvedVersions.forEach { $0.isResolved = true } try NSFileVersion.removeOtherVersionsOfItem(at: currentVersionUrl) - LOG(.debug, "File \(cloudMetadataItem.fileName) was successfully resolved.") + LOG(.info, "File \(cloudMetadataItem.fileName) was successfully resolved.") completion(.success) return } catch { @@ -224,11 +227,11 @@ final class SynchronizationFileWriter { } private func resolveInitialSynchronizationConflict(_ localMetadataItem: LocalMetadataItem, completion: @escaping WritingResultCompletionHandler) { - LOG(.debug, "Start resolving initial sync conflict for file \(localMetadataItem.fileName) by copying with a new name...") + LOG(.info, "Start resolving initial sync conflict for file \(localMetadataItem.fileName) by copying with a new name...") do { let newFileUrl = generateNewFileUrl(for: localMetadataItem.fileUrl, addDeviceName: true) try fileManager.copyItem(at: localMetadataItem.fileUrl, to: newFileUrl) - LOG(.debug, "File \(localMetadataItem.fileName) was successfully resolved.") + LOG(.info, "File \(localMetadataItem.fileName) was successfully resolved.") completion(.reloadCategoriesAtURLs([newFileUrl])) } catch { completion(.failure(error)) diff --git a/iphone/Maps/Core/iCloud/SynchronizationStateManager.swift b/iphone/Maps/Core/iCloud/SynchronizationStateManager.swift index b803422f235e0..23aa633b1406b 100644 --- a/iphone/Maps/Core/iCloud/SynchronizationStateManager.swift +++ b/iphone/Maps/Core/iCloud/SynchronizationStateManager.swift @@ -75,10 +75,14 @@ final class DefaultSynchronizationStateManager: SynchronizationStateManager { case .didUpdateCloudContents(let contents): outgoingEvents = resolveDidUpdateCloudContents(contents) } + LOG(.info, "Cloud content: \n\(currentCloudContents.shortDebugDescription)") + LOG(.info, "Local content: \n\(currentLocalContents.shortDebugDescription)") + LOG(.info, "Events to process: \n\(outgoingEvents)") return outgoingEvents } func resetState() { + LOG(.debug, "Resetting state") currentLocalContents.removeAll() currentCloudContents.removeAll() localContentsGatheringIsFinished = false diff --git a/iphone/Maps/Core/iCloud/iCloudDocumentsDirectoryMonitor.swift b/iphone/Maps/Core/iCloud/iCloudDocumentsDirectoryMonitor.swift index f8d25519337f6..825b1f1e7b007 100644 --- a/iphone/Maps/Core/iCloud/iCloudDocumentsDirectoryMonitor.swift +++ b/iphone/Maps/Core/iCloud/iCloudDocumentsDirectoryMonitor.swift @@ -59,7 +59,7 @@ class iCloudDocumentsDirectoryMonitor: NSObject, CloudDirectoryMonitor { case .failure(let error): completion?(.failure(error)) case .success(let url): - LOG(.debug, "iCloudMonitor: Start") + LOG(.debug, "Start cloud monitor.") self.startQuery() self.state = .started completion?(.success(url)) @@ -69,21 +69,21 @@ class iCloudDocumentsDirectoryMonitor: NSObject, CloudDirectoryMonitor { func stop() { guard state != .stopped else { return } - LOG(.debug, "iCloudMonitor: Stop") + LOG(.debug, "Stop cloud monitor.") stopQuery() state = .stopped } func resume() { guard state != .started else { return } - LOG(.debug, "iCloudMonitor: Resume") + LOG(.debug, "Resume cloud monitor.") metadataQuery?.enableUpdates() state = .started } func pause() { guard state != .paused else { return } - LOG(.debug, "iCloudMonitor: Pause") + LOG(.debug, "Pause cloud monitor.") metadataQuery?.disableUpdates() state = .paused } @@ -95,20 +95,20 @@ class iCloudDocumentsDirectoryMonitor: NSObject, CloudDirectoryMonitor { } DispatchQueue.global().async { guard let containerUrl = self.fileManager.url(forUbiquityContainerIdentifier: self.containerIdentifier) else { - LOG(.debug, "iCloudMonitor: Failed to retrieve container's URL for:\(self.containerIdentifier)") + LOG(.warning, "Failed to retrieve container's URL for:\(self.containerIdentifier)") completion?(.failure(SynchronizationError.containerNotFound)) return } let documentsContainerUrl = containerUrl.appendingPathComponent(kDocumentsDirectoryName) if !self.fileManager.fileExists(atPath: documentsContainerUrl.path) { - LOG(.debug, "iCloudMonitor: Creating directory at path: \(documentsContainerUrl.path)") + LOG(.debug, "Creating directory at path: \(documentsContainerUrl.path)...") do { try self.fileManager.createDirectory(at: documentsContainerUrl, withIntermediateDirectories: true) } catch { completion?(.failure(SynchronizationError.containerNotFound)) } } - LOG(.debug, "iCloudMonitor: Ubiquity directory URL: \(documentsContainerUrl)") + LOG(.debug, "Ubiquity directory URL: \(documentsContainerUrl)") self.ubiquitousDocumentsDirectory = documentsContainerUrl completion?(.success(documentsContainerUrl)) } @@ -118,7 +118,7 @@ class iCloudDocumentsDirectoryMonitor: NSObject, CloudDirectoryMonitor { let cloudToken = fileManager.ubiquityIdentityToken guard let cloudToken else { UserDefaults.standard.removeObject(forKey: kUDCloudIdentityKey) - LOG(.debug, "iCloudMonitor: Cloud is not available. Cloud token is nil.") + LOG(.warning, "Cloud is not available. Cloud token is nil.") return false } do { @@ -127,7 +127,7 @@ class iCloudDocumentsDirectoryMonitor: NSObject, CloudDirectoryMonitor { return true } catch { UserDefaults.standard.removeObject(forKey: kUDCloudIdentityKey) - LOG(.debug, "iCloudMonitor: Failed to archive cloud token: \(error)") + LOG(.warning, "Failed to archive cloud token: \(error)") return false } } @@ -154,20 +154,24 @@ class iCloudDocumentsDirectoryMonitor: NSObject, CloudDirectoryMonitor { // Due to didUpdate state we can get the list of deleted items on macOS from the userInfo property but cannot get their new url. class func getTrashContentsFromNotification(_ notification: Notification) throws -> CloudContents { guard let removedItems = notification.userInfo?[NSMetadataQueryUpdateRemovedItemsKey] as? [NSMetadataItem] else { + LOG(.warning, "userInfo[NSMetadataQueryUpdateRemovedItemsKey] is nil") return [] } + LOG(.info, "Removed from the cloud content: \n\(removedItems.shortDebugDescription)") return try removedItems.map { try CloudMetadataItem(metadataItem: $0, isRemoved: true) } } class func getTrashedContentsFromTrashDirectory(fileManager: FileManager, ubiquitousDocumentsDirectory: URL) throws -> CloudContents { // There are no ways to retrieve the content of iCloud's .Trash directory on macOS. if #available(iOS 14.0, *), ProcessInfo.processInfo.isiOSAppOnMac { + LOG(.warning, "Trashed content is not available on macOS.") return [] } let trashDirectoryUrl = try fileManager.trashDirectoryUrl(for: ubiquitousDocumentsDirectory) let removedItems = try fileManager.contentsOfDirectory(at: trashDirectoryUrl, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsPackageDescendants, .skipsSubdirectoryDescendants]) + LOG(.info, "Trashed cloud content: \n\(removedItems)") return try removedItems.map { try CloudMetadataItem(fileUrl: $0, isRemoved: true) } } } @@ -194,12 +198,12 @@ private extension iCloudDocumentsDirectoryMonitor { func startQuery() { metadataQuery = Self.buildMetadataQuery(for: fileType) guard let metadataQuery, !metadataQuery.isStarted else { return } - LOG(.debug, "iCloudMonitor: Start metadata query") + LOG(.debug, "Start metadata query") metadataQuery.start() } func stopQuery() { - LOG(.debug, "iCloudMonitor: Stop metadata query") + LOG(.debug, "Stop metadata query") metadataQuery?.stop() metadataQuery = nil } @@ -207,12 +211,10 @@ private extension iCloudDocumentsDirectoryMonitor { @objc func queryDidFinishGathering(_ notification: Notification) { guard isCloudAvailable(), let ubiquitousDocumentsDirectory else { return } metadataQuery?.disableUpdates() - LOG(.debug, "iCloudMonitor: Query did finish gathering") + LOG(.debug, "Query did finish gathering") do { let contents = try Self.getContentsFromNotification(notification) let trashedContents = try Self.getTrashedContentsFromTrashDirectory(fileManager: fileManager, ubiquitousDocumentsDirectory: ubiquitousDocumentsDirectory) - LOG(.debug, "iCloudMonitor: Cloud contents count: \(contents.count)") - LOG(.debug, "iCloudMonitor: Trashed contents count: \(trashedContents.count)") delegate?.didFinishGathering(contents: contents + trashedContents) } catch { delegate?.didReceiveCloudMonitorError(error) @@ -223,12 +225,10 @@ private extension iCloudDocumentsDirectoryMonitor { @objc func queryDidUpdate(_ notification: Notification) { guard isCloudAvailable() else { return } metadataQuery?.disableUpdates() - LOG(.debug, "iCloudMonitor: Query did update") + LOG(.debug, "Query did update") do { let contents = try Self.getContentsFromNotification(notification) let trashedContents = try Self.getTrashContentsFromNotification(notification) - LOG(.debug, "iCloudMonitor: Cloud contents count: \(contents.count)") - LOG(.debug, "iCloudMonitor: Trashed contents count: \(trashedContents.count)") delegate?.didUpdate(contents: contents + trashedContents) } catch { delegate?.didReceiveCloudMonitorError(error) @@ -236,3 +236,9 @@ private extension iCloudDocumentsDirectoryMonitor { metadataQuery?.enableUpdates() } } + +fileprivate extension Array where Element == NSMetadataItem { + var shortDebugDescription: String { + map { $0.value(forAttribute: NSMetadataItemFSNameKey) as! String }.joined(separator: "\n") + } +}