diff --git a/Aerial/Source/Controllers/PreferencesWindowController.swift b/Aerial/Source/Controllers/PreferencesWindowController.swift index 1ba62de7..9b0eebc0 100644 --- a/Aerial/Source/Controllers/PreferencesWindowController.swift +++ b/Aerial/Source/Controllers/PreferencesWindowController.swift @@ -183,7 +183,10 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo @IBOutlet var lastCheckedVideosLabel: NSTextField! @IBOutlet var checkNowButton: NSButton! @IBOutlet var videoMenu: NSMenu! + @IBOutlet var videoVersionsLabel: NSTextField! + @IBOutlet var moveOldVideosButton: NSButton! + @IBOutlet var trashOldVideosButton: NSButton! var player: AVPlayer = AVPlayer() var videos: [AerialVideo]? @@ -1076,7 +1079,7 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo @IBAction func checkNowButtonClick(_ sender: NSButton) { checkNowButton.isEnabled = false - // TODO + ManifestLoader.instance.reloadFiles() } // MARK: - Time panel @@ -1380,6 +1383,35 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo showLogBottomClick.isHidden = false } + @IBAction func moveOldVideosClick(_ sender: Any) { + ManifestLoader.instance.moveOldVideos() + + let (description, total) = ManifestLoader.instance.getOldFilesEstimation() + videoVersionsLabel.stringValue = description + if total > 0 { + moveOldVideosButton.isEnabled = true + trashOldVideosButton.isEnabled = true + } else { + moveOldVideosButton.isEnabled = false + trashOldVideosButton.isEnabled = false + } + + } + + @IBAction func trashOldVideosClick(_ sender: Any) { + ManifestLoader.instance.trashOldVideos() + + let (description, total) = ManifestLoader.instance.getOldFilesEstimation() + videoVersionsLabel.stringValue = description + if total > 0 { + moveOldVideosButton.isEnabled = true + trashOldVideosButton.isEnabled = true + } else { + moveOldVideosButton.isEnabled = false + trashOldVideosButton.isEnabled = false + } + + } // MARK: - Menu @IBAction func outlineViewSettingsClick(_ button: NSButton) { let menu = NSMenu() @@ -1532,7 +1564,7 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo ManifestLoader.instance.addCallback { manifestVideos in self.loaded(manifestVideos: manifestVideos) - } + } } func reloadJson() { @@ -1570,6 +1602,15 @@ final class PreferencesWindowController: NSWindowController, NSOutlineViewDataSo self.outlineView.reloadData() self.outlineView.expandItem(nil, expandChildren: true) } + let (description, total) = ManifestLoader.instance.getOldFilesEstimation() + videoVersionsLabel.stringValue = description + if total > 0 { + moveOldVideosButton.isEnabled = true + trashOldVideosButton.isEnabled = true + } else { + moveOldVideosButton.isEnabled = false + trashOldVideosButton.isEnabled = false + } } // MARK: - Outline View Delegate & Data Source diff --git a/Aerial/Source/Models/ManifestLoader.swift b/Aerial/Source/Models/ManifestLoader.swift index cb6854c0..205e1a22 100644 --- a/Aerial/Source/Models/ManifestLoader.swift +++ b/Aerial/Source/Models/ManifestLoader.swift @@ -311,7 +311,44 @@ class ManifestLoader { } func reloadFiles() { - // TODO + moveOldManifests() + + // Ok then, we fetch them... + debugLog("Fetching missing manifests online") + let dateFormatter = DateFormatter() + let current = Date() + dateFormatter.dateFormat = "yyyy-MM-dd" + preferences.lastVideoCheck = dateFormatter.string(from: current) + + let downloadManager = DownloadManager() + + var urls: [URL] = [] + + // For tvOS12, json is now in a tar file + if !isManifestCached(manifest: .tvOS12) { + urls.append(URL(string: "https://sylvan.apple.com/Aerials/resources.tar")!) + } + + if !isManifestCached(manifest: .tvOS11) { + urls.append(URL(string: "https://sylvan.apple.com/Aerials/2x/entries.json")!) + } + + if !isManifestCached(manifest: .tvOS10) { + urls.append(URL(string: "http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/videos/entries.json")!) + } + + let completion = BlockOperation { + debugLog("Fetching manifests all done") + // We can now load from the newly cached files + self.loadCachedManifests() + } + + for url in urls { + let operation = downloadManager.queueDownload(url) + completion.addDependency(operation) + } + + OperationQueue.main.addOperation(completion) } func addCallback(_ callback:@escaping ManifestLoadCallback) { @@ -708,4 +745,220 @@ class ManifestLoader { return (false, nil) } + + // MARK: - Old video management + // Try to estimate how many old (unlinked) files we have + // swiftlint:disable:next cyclomatic_complexity + func getOldFilesEstimation() -> (String, Int) { + // loadedManifests contains the full deduplicated list of videos + debugLog("Looking for outdated files") + + if loadedManifest.isEmpty { + warnLog("We have no videos in the manifest") + return ("Can't estimate duplicates", 0) + } + guard let cacheDirectory = VideoCache.cacheDirectory else { + warnLog("No cache directory") + return ("Can't estimate duplicates", 0) + } + + var foundOldFiles = 0 + + let cacheDirectoryUrl = URL(fileURLWithPath: cacheDirectory as String) + let fileManager = FileManager.default + do { + let directoryContent = try fileManager.contentsOfDirectory(at: cacheDirectoryUrl, includingPropertiesForKeys: nil) + let videoFileURLs = directoryContent.filter { $0.pathExtension == "mov" } + + // We check the 3 fields + for fileURL in videoFileURLs { + var found = false + for video in loadedManifest { + if video.url1080pH264 != "" { + if fileURL.lastPathComponent == URL(string: video.url1080pH264)?.lastPathComponent { + found = true + break + } + } + if video.url1080pHEVC != "" { + if fileURL.lastPathComponent == URL(string: video.url1080pHEVC)?.lastPathComponent { + found = true + break + } + } + if video.url4KHEVC != "" { + if fileURL.lastPathComponent == URL(string: video.url4KHEVC)?.lastPathComponent { + found = true + break + } + } + } + + if !found { + debugLog("\(fileURL.lastPathComponent) NOT FOUND in manifest") + foundOldFiles += 1 + } + } + } catch { + errorLog("Error while enumerating files \(cacheDirectoryUrl.path): \(error.localizedDescription)") + } + + if foundOldFiles == 0 { + debugLog("No old files found") + return ("No old files found", 0) + } + debugLog("\(foundOldFiles) old files found") + return ("\(foundOldFiles) old files found", foundOldFiles) + } + + // swiftlint:disable:next cyclomatic_complexity + func moveOldVideos() { + debugLog("move old videos") + let cacheDirectory = VideoCache.cacheDirectory! + var cacheResourcesString = cacheDirectory + + let dateFormatter = DateFormatter() + let current = Date() + dateFormatter.dateFormat = "yyyy-MM-dd" + let today = dateFormatter.string(from: current) + + cacheResourcesString.append(contentsOf: "/oldvideos/"+today) + + let cacheUrl = URL(fileURLWithPath: cacheResourcesString) + if #available(OSX 10.11, *) { + if !cacheUrl.hasDirectoryPath { + do { + try FileManager.default.createDirectory(atPath: cacheResourcesString, withIntermediateDirectories: true, attributes: nil) + debugLog("creating dir \(cacheResourcesString)") + } catch { + errorLog("\(error.localizedDescription)") + } + } + } + + if loadedManifest.isEmpty { + warnLog("We have no videos in the manifest") + return + } + + let cacheDirectoryUrl = URL(fileURLWithPath: cacheDirectory as String) + let fileManager = FileManager.default + do { + let directoryContent = try fileManager.contentsOfDirectory(at: cacheDirectoryUrl, includingPropertiesForKeys: nil) + let videoFileURLs = directoryContent.filter { $0.pathExtension == "mov" } + + // We check the 3 fields + for fileURL in videoFileURLs { + var found = false + for video in loadedManifest { + if video.url1080pH264 != "" { + if fileURL.lastPathComponent == URL(string: video.url1080pH264)?.lastPathComponent { + found = true + break + } + } + if video.url1080pHEVC != "" { + if fileURL.lastPathComponent == URL(string: video.url1080pHEVC)?.lastPathComponent { + found = true + break + } + } + if video.url4KHEVC != "" { + if fileURL.lastPathComponent == URL(string: video.url4KHEVC)?.lastPathComponent { + found = true + break + } + } + } + + if !found { + do { + debugLog("moving \(fileURL.lastPathComponent)") + let new = URL(fileURLWithPath: cacheResourcesString.appending("/\(fileURL.lastPathComponent)")) + try FileManager.default.moveItem(at: fileURL, to: new) + } catch { + errorLog("\(error.localizedDescription)") + } + } + } + } catch { + errorLog("Error while enumerating files \(cacheDirectoryUrl.path): \(error.localizedDescription)") + } + } + + // swiftlint:disable:next cyclomatic_complexity + func trashOldVideos() { + debugLog("trash old videos") + let cacheDirectory = VideoCache.cacheDirectory! + var cacheResourcesString = cacheDirectory + + let dateFormatter = DateFormatter() + let current = Date() + dateFormatter.dateFormat = "yyyy-MM-dd" + let today = dateFormatter.string(from: current) + + cacheResourcesString.append(contentsOf: "/oldvideos/"+today) + + let cacheUrl = URL(fileURLWithPath: cacheResourcesString) + if #available(OSX 10.11, *) { + if !cacheUrl.hasDirectoryPath { + do { + try FileManager.default.createDirectory(atPath: cacheResourcesString, withIntermediateDirectories: true, attributes: nil) + debugLog("creating dir \(cacheResourcesString)") + } catch { + errorLog("\(error.localizedDescription)") + } + } + } + + if loadedManifest.isEmpty { + warnLog("We have no videos in the manifest") + return + } + + let cacheDirectoryUrl = URL(fileURLWithPath: cacheDirectory as String) + let fileManager = FileManager.default + do { + let directoryContent = try fileManager.contentsOfDirectory(at: cacheDirectoryUrl, includingPropertiesForKeys: nil) + let videoFileURLs = directoryContent.filter { $0.pathExtension == "mov" } + + // We check the 3 fields + for fileURL in videoFileURLs { + var found = false + for video in loadedManifest { + if video.url1080pH264 != "" { + if fileURL.lastPathComponent == URL(string: video.url1080pH264)?.lastPathComponent { + found = true + break + } + } + if video.url1080pHEVC != "" { + if fileURL.lastPathComponent == URL(string: video.url1080pHEVC)?.lastPathComponent { + found = true + break + } + } + if video.url4KHEVC != "" { + if fileURL.lastPathComponent == URL(string: video.url4KHEVC)?.lastPathComponent { + found = true + break + } + } + } + + if !found { + debugLog("trashing \(fileURL.lastPathComponent)") + + NSWorkspace.shared.recycle([fileURL]) { trashedFiles, error in + for file in [fileURL] where trashedFiles[file] == nil { + errorLog("\(file.relativePath) could not be moved to trash \(error!.localizedDescription)") + } + } + } + } + } catch { + errorLog("Error while enumerating files \(cacheDirectoryUrl.path): \(error.localizedDescription)") + } + } + } //swiftlint:disable:this file_length diff --git a/Aerial/Source/Views/CheckCellView.swift b/Aerial/Source/Views/CheckCellView.swift index 99d22380..b7a3a6ed 100644 --- a/Aerial/Source/Views/CheckCellView.swift +++ b/Aerial/Source/Views/CheckCellView.swift @@ -56,7 +56,6 @@ final class CheckCellView: NSTableCellView { if #available(OSX 10.12.2, *) { queuedImage.image = NSImage(named: NSImage.touchBarDownloadTemplateName) } - if video!.isAvailableOffline { status = .downloaded addButton.isHidden = true diff --git a/Resources/Info.plist b/Resources/Info.plist index 76046357..05c83be7 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.6test7 + 1.4.6beta7 CFBundleSignature ???? CFBundleVersion - 1.4.6test7 + 1.4.6beta7 LSApplicationCategoryType LSMinimumSystemVersion diff --git a/Resources/PreferencesWindow.xib b/Resources/PreferencesWindow.xib index d1411861..55f47059 100644 --- a/Resources/PreferencesWindow.xib +++ b/Resources/PreferencesWindow.xib @@ -72,6 +72,7 @@ + @@ -112,9 +113,11 @@ + + @@ -1476,7 +1479,7 @@ Shift, but macOS 10.12.4 or above and a compatible Mac are required) - +