Skip to content

Commit

Permalink
Fix detection of iCloud files not being found
Browse files Browse the repository at this point in the history
  • Loading branch information
bradhowes committed Oct 25, 2024
1 parent 8828053 commit d3fc1ba
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 29 deletions.
4 changes: 4 additions & 0 deletions SoundFontsApp/SoundFontsApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,8 @@
DEVELOPMENT_TEAM = UP6SS5ES7E;
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
INFOPLIST_FILE = SoundFontsApp/App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SoundFonts;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down Expand Up @@ -1122,6 +1124,8 @@
DEVELOPMENT_TEAM = UP6SS5ES7E;
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
INFOPLIST_FILE = SoundFontsApp/App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SoundFonts;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
BD1C22E925CDC6AE007C114F /* TutorialContentPager.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BD1C22E825CDC6AE007C114F /* TutorialContentPager.storyboard */; };
BD1C22ED25CDC701007C114F /* TutorialContentPagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1C22EC25CDC701007C114F /* TutorialContentPagerViewController.swift */; };
BD1C22F125CDC781007C114F /* TutorialViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1C22F025CDC781007C114F /* TutorialViewController.swift */; };
BD1DE29F2CCC56A6004BA803 /* FontStateQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1DE29E2CCC5691004BA803 /* FontStateQuery.swift */; };
BD1F11B823FE61E300AE9F3D /* GuideViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1F11B723FE61E300AE9F3D /* GuideViewController.swift */; };
BD1F11BC23FE690300AE9F3D /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1F11BB23FE690300AE9F3D /* UIViewController.swift */; };
BD239486255213CB004E5E0D /* KeyParamSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD239485255213CB004E5E0D /* KeyParamSequence.swift */; };
Expand Down Expand Up @@ -364,6 +365,7 @@
BD1C22E825CDC6AE007C114F /* TutorialContentPager.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = TutorialContentPager.storyboard; sourceTree = "<group>"; };
BD1C22EC25CDC701007C114F /* TutorialContentPagerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TutorialContentPagerViewController.swift; sourceTree = "<group>"; };
BD1C22F025CDC781007C114F /* TutorialViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TutorialViewController.swift; sourceTree = "<group>"; };
BD1DE29E2CCC5691004BA803 /* FontStateQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontStateQuery.swift; sourceTree = "<group>"; };
BD1F11B723FE61E300AE9F3D /* GuideViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideViewController.swift; sourceTree = "<group>"; };
BD1F11BB23FE690300AE9F3D /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; };
BD239485255213CB004E5E0D /* KeyParamSequence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyParamSequence.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -830,6 +832,7 @@
BD5B5C3828C14C1E000BC5A4 /* FontsEditorTableViewController.swift */,
BD28D73827958CE4000A57DE /* FontsTableViewController.swift */,
BD074C2923CDAF51001987E9 /* SelectedSoundFontManager.swift */,
BD1DE29E2CCC5691004BA803 /* FontStateQuery.swift */,
);
path = Fonts;
sourceTree = "<group>";
Expand Down Expand Up @@ -1378,6 +1381,7 @@
BD074C7B23CDAF51001987E9 /* Foundation.swift in Sources */,
BD1C22F125CDC781007C114F /* TutorialViewController.swift in Sources */,
BD62CF3E253DB4CE00DBDC1B /* PresetChangeManager.swift in Sources */,
BD1DE29F2CCC56A6004BA803 /* FontStateQuery.swift in Sources */,
BDEB12642620939B003CE8EE /* ChangesPageViewController.swift in Sources */,
BDFD3E3029E3FA5B0010E3CB /* MIDIActionsTableCell.swift in Sources */,
BD074C8923CDAF51001987E9 /* UpperViewSwipingActivity.swift in Sources */,
Expand Down
25 changes: 19 additions & 6 deletions SoundFontsFramework/SoundFontsFramework/Audio/Bookmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,20 @@ extension Bookmark {

/// Determine the availability state for a bookmarked URL
public var isAvailable: Bool {
let secured = url.startAccessingSecurityScopedResource()
let value = try? url.checkResourceIsReachable()
if secured { url.stopAccessingSecurityScopedResource() }
return value ?? false
if url.startAccessingSecurityScopedResource() {
defer { url.stopAccessingSecurityScopedResource() }
return (try? url.checkResourceIsReachable()) ?? false
}
return false
}

/// Determine if the file is located in an iCloud container
public var isUbiquitous: Bool { FileManager.default.isUbiquitousItem(at: url) }

/// The various iCloud states a bookmark item may be in.
public enum CloudState {
/// Item is local and not synced to iCloud
case local
/// Item is on iCloud but not available locally.
case inCloud
/// Item is queue to be downloaded to the device
Expand All @@ -120,22 +123,32 @@ extension Bookmark {

/// Obtain the current iCloud state of the bookmark item
public var cloudState: CloudState {
let secured = url.startAccessingSecurityScopedResource()
defer { if secured { url.stopAccessingSecurityScopedResource() } }

guard
let values = try? url.resourceValues(forKeys: [
.isUbiquitousItemKey,
.ubiquitousItemDownloadRequestedKey,
.ubiquitousItemDownloadingStatusKey,
.ubiquitousItemIsDownloadingKey,
.ubiquitousItemDownloadingErrorKey
])
else {
return .unknown
}

let isUbiquitous = (values.isUbiquitousItem ?? false)
if !isUbiquitous { return .local }

guard values.ubiquitousItemDownloadingError == nil else { return .downloadError }
guard let status = values.ubiquitousItemDownloadingStatus else { return .unknown }

guard let status = values.ubiquitousItemDownloadingStatus else { return .inCloud }
switch status {
case .current: return .downloaded
case .downloaded: return .downloading
case .notDownloaded: return .inCloud
default: return .unknown
default: return .inCloud
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
class FontStateQuery {
let query = NSMetadataQuery()
let queue: OperationQueue

init(queue: OperationQueue = .main) {
self.queue = queue
}

func searchMetadataItems(paths: [URL]) -> AsyncStream<[MetadataItemWrapper]> {
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
query.sortDescriptors = []
query.predicate = NSPredicate(value: true)
query.searchItems = paths
return AsyncStream { continuation in
NotificationCenter.default.addObserver(
forName: .NSMetadataQueryDidFinishGathering,
object: query,
queue: queue
) { _ in
let result = self.query.results.compactMap { item -> MetadataItemWrapper? in
guard let metadataItem = item as? NSMetadataItem else {
return nil
}
return MetadataItemWrapper(metadataItem: metadataItem)
}
continuation.yield(result)
}

NotificationCenter.default.addObserver(
forName: .NSMetadataQueryDidUpdate,
object: query,
queue: queue
) { _ in
let result = self.query.results.compactMap { item -> MetadataItemWrapper? in
guard let metadataItem = item as? NSMetadataItem else {
return nil
}
return MetadataItemWrapper(metadataItem: metadataItem)
}
continuation.yield(result)
}

query.start()

continuation.onTermination = { @Sendable _ in
self.query.stop()
NotificationCenter.default.removeObserver(self, name: .NSMetadataQueryDidFinishGathering, object: self.query)
NotificationCenter.default.removeObserver(self, name: .NSMetadataQueryDidUpdate, object: self.query)
}
}
}
}

struct MetadataItemWrapper: Sendable {
let path: String?
let isPlaceholder: Bool
let isDownloading: Bool
let isUploaded: Bool

init(metadataItem: NSMetadataItem) {
path = metadataItem.value(forAttribute: NSMetadataItemPathKey) as? String
isPlaceholder = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? Bool ?? false

let downloadStatus = metadataItem.value(forAttribute: NSMetadataUbiquitousItemIsDownloadingKey) as? String
isDownloading = downloadStatus == NSMetadataUbiquitousItemDownloadingStatusCurrent

// Check whether the file has been uploaded successfully or saved in the cloud
let uploaded = metadataItem.value(forAttribute: NSMetadataUbiquitousItemIsUploadedKey) as? Bool ?? false
let uploading = metadataItem.value(forAttribute: NSMetadataUbiquitousItemIsUploadingKey) as? Bool ?? true

isUploaded = uploaded && !uploading
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,9 @@ private extension FontsTableViewController {
for index in 0..<soundFonts.count {
let soundFont = soundFonts.getBy(index: index)
if soundFont.kind.reference {
guard let cell: TableCell = tableView.cellForRow(at: IndexPath(row: index, section: 0)) else { continue }
cell.updateBookmarkButton()
if let cell: TableCell = tableView.cellForRow(at: IndexPath(row: index, section: 0)) {
cell.updateBookmarkButton()
}
}
}
}
Expand All @@ -182,7 +183,7 @@ private extension FontsTableViewController {

func startBookmarkMonitor() {
stopBookmarkMonitor()
self.bookmarkMonitor = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self.bookmarkMonitor = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { [weak self] _ in
guard let self = self else { return }
self.updateBookmarkButtons()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,16 @@ public final class TableCell: UITableViewCell, ReusableView, NibLoadableView {
return nil
}

if bookmark.isAvailable {
return nil
}

if !bookmark.isUbiquitous {
return missingFileButton
let cloudState = bookmark.cloudState
if cloudState == .local {
if bookmark.isAvailable {
return nil
} else {
return missingFileButton
}
}

return downloadableFileButton
return cloudState == .downloaded ? nil : downloadableFileButton
}

private var downloadableFileButton: UIButton {
Expand All @@ -202,6 +203,16 @@ public final class TableCell: UITableViewCell, ReusableView, NibLoadableView {
activeAlert = alert
alert.addAction(UIAlertAction(title: "OK", style: .cancel) { [weak self] _ in self?.activeAlert = nil })
viewController?.present(alert, animated: true)
do {
if bookmark.url.startAccessingSecurityScopedResource() {
try FileManager.default.startDownloadingUbiquitousItem(at: bookmark.url)
bookmark.url.stopAccessingSecurityScopedResource()
} else {
try FileManager.default.startDownloadingUbiquitousItem(at: bookmark.url)
}
} catch {
print("failed to start downloading \(bookmark.url)")
}
}

private var missingFileButton: UIButton {
Expand Down
Loading

0 comments on commit d3fc1ba

Please sign in to comment.