diff --git a/Amperfy/Api/Ampache/AmpacheXmlServerApi.swift b/Amperfy/Api/Ampache/AmpacheXmlServerApi.swift index b69ce568..735f5b90 100755 --- a/Amperfy/Api/Ampache/AmpacheXmlServerApi.swift +++ b/Amperfy/Api/Ampache/AmpacheXmlServerApi.swift @@ -177,7 +177,7 @@ class AmpacheXmlServerApi { authHandshake = nil os_log("Couldn't get a login token.", log: log, type: .error) if let apiError = curDelegate.error { - eventLogger.report(error: apiError) + eventLogger.report(error: apiError, displayPopup: true) } return nil } @@ -461,7 +461,7 @@ class AmpacheXmlServerApi { parser.delegate = parserDelegate parser.parse() if let error = parserDelegate.error, AmpacheError.shouldErrorBeDisplayedToUser(statusCode: error.statusCode) { - eventLogger.report(error: error) + eventLogger.report(error: error, displayPopup: true) } } @@ -491,9 +491,6 @@ class AmpacheXmlServerApi { let parser = XMLParser(data: data) parser.delegate = errorParser parser.parse() - if let error = errorParser.error, AmpacheError.shouldErrorBeDisplayedToUser(statusCode: error.statusCode) { - eventLogger.report(error: error) - } return errorParser.error } diff --git a/Amperfy/Api/EventLogger.swift b/Amperfy/Api/EventLogger.swift index 9e8164fc..db2a8ec2 100644 --- a/Amperfy/Api/EventLogger.swift +++ b/Amperfy/Api/EventLogger.swift @@ -25,15 +25,15 @@ class EventLogger { self.persistentContainer = persistentContainer } - func info(topic: String, statusCode: AmperfyLogStatusCode, message: String) { - report(topic: topic, statusCode: statusCode, message: message, logType: .info) + func info(topic: String, statusCode: AmperfyLogStatusCode, message: String, displayPopup: Bool) { + report(topic: topic, statusCode: statusCode, message: message, logType: .info, displayPopup: displayPopup) } - func error(topic: String, statusCode: AmperfyLogStatusCode, message: String) { - report(topic: topic, statusCode: statusCode, message: message, logType: .error) + func error(topic: String, statusCode: AmperfyLogStatusCode, message: String, displayPopup: Bool) { + report(topic: topic, statusCode: statusCode, message: message, logType: .error, displayPopup: displayPopup) } - private func report(topic: String, statusCode: AmperfyLogStatusCode, message: String, logType: LogEntryType) { + private func report(topic: String, statusCode: AmperfyLogStatusCode, message: String, logType: LogEntryType, displayPopup: Bool) { persistentContainer.performBackgroundTask { context in let library = LibraryStorage(context: context) let logEntry = library.createLogEntry() @@ -47,11 +47,13 @@ class EventLogger { if let sameEntry = sameStatusCodeEntries.first, sameEntry.creationDate.compare(Date() - Double(sameEntry.suppressionTimeInterval)) == .orderedDescending { return } - self.displayAlert(topic: topic, message: message, logEntry: logEntry) + if displayPopup { + self.displayAlert(topic: topic, message: message, logEntry: logEntry) + } } } - func report(error: ResponseError) { + func report(error: ResponseError, displayPopup: Bool) { persistentContainer.performBackgroundTask { context in let library = LibraryStorage(context: context) let logEntry = library.createLogEntry() @@ -72,7 +74,9 @@ class EventLogger { if let sameEntry = sameStatusCodeEntries.first, sameEntry.creationDate.compare(Date() - Double(sameEntry.suppressionTimeInterval)) == .orderedDescending { return } - self.displayAlert(topic: "API Error", message: alertMessage, logEntry: logEntry) + if displayPopup { + self.displayAlert(topic: "API Error", message: alertMessage, logEntry: logEntry) + } } } diff --git a/Amperfy/Api/Subsonic/SubsonicServerApi.swift b/Amperfy/Api/Subsonic/SubsonicServerApi.swift index e054f085..fed7814c 100644 --- a/Amperfy/Api/Subsonic/SubsonicServerApi.swift +++ b/Amperfy/Api/Subsonic/SubsonicServerApi.swift @@ -303,9 +303,6 @@ class SubsonicServerApi { let parser = XMLParser(data: data) parser.delegate = errorParser parser.parse() - if let error = errorParser.error { - eventLogger.report(error: error) - } return errorParser.error } @@ -366,7 +363,7 @@ class SubsonicServerApi { parser.delegate = parserDelegate parser.parse() if !ignoreErrorResponse, let error = parserDelegate.error { - eventLogger.report(error: error) + eventLogger.report(error: error, displayPopup: true) } } diff --git a/Amperfy/Common/AppDelegate.swift b/Amperfy/Common/AppDelegate.swift index 383d8881..992fa98c 100644 --- a/Amperfy/Common/AppDelegate.swift +++ b/Amperfy/Common/AppDelegate.swift @@ -76,6 +76,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let requestManager = DownloadRequestManager(persistentStorage: persistentStorage, downloadDelegate: dlDelegate) requestManager.clearAllDownloads() let dlManager = DownloadManager(name: "ArtworkDownloader", persistentStorage: persistentStorage, requestManager: requestManager, downloadDelegate: dlDelegate, eventLogger: eventLogger) + dlManager.isFailWithPopupError = false let configuration = URLSessionConfiguration.default var urlSession = URLSession(configuration: configuration, delegate: dlManager, delegateQueue: nil) diff --git a/Amperfy/Download/DownloadManager.swift b/Amperfy/Download/DownloadManager.swift index f1fb46c8..015605f9 100644 --- a/Amperfy/Download/DownloadManager.swift +++ b/Amperfy/Download/DownloadManager.swift @@ -10,6 +10,7 @@ class DownloadManager: NSObject, DownloadManageable { var urlSession: URLSession! var backgroundFetchCompletionHandler: CompleteHandlerBlock? let log: OSLog + var isFailWithPopupError: Bool = true private let eventLogger: EventLogger @@ -35,10 +36,18 @@ class DownloadManager: NSObject, DownloadManageable { func download(objects: [Downloadable]) { guard persistentStorage.settings.isOnlineMode else { return } let downloadObjects = objects.filter{ !$0.isCached } - if downloadObjects.count > 0 { + if !downloadObjects.isEmpty { self.requestManager.add(objects: downloadObjects) } } + + func removeFinishedDownload(for object: Downloadable) { + requestManager.removeFinishedDownload(for: object) + } + + func removeFinishedDownload(for objects: [Downloadable]) { + requestManager.removeFinishedDownload(for: objects) + } func start() { isRunning = true @@ -136,7 +145,7 @@ class DownloadManager: NSObject, DownloadManageable { download.error = error if error != .apiErrorResponse { os_log("Fetching %s FAILED: %s", log: self.log, type: .info, download.title, error.description) - eventLogger.error(topic: "Download Error", statusCode: .downloadError, message: "Error \"\(error.description)\" occured while downloading object \"\(download.title)\".") + eventLogger.error(topic: "Download Error", statusCode: .downloadError, message: "Error \"\(error.description)\" occured while downloading object \"\(download.title)\".", displayPopup: isFailWithPopupError) } } download.isDownloading = false @@ -148,6 +157,7 @@ class DownloadManager: NSObject, DownloadManageable { download.resumeData = data if let responseError = downloadDelegate.validateDownloadedData(download: download) { os_log("Fetching %s API-ERROR StatusCode: %d, Message: %s", log: log, type: .error, download.title, responseError.statusCode, responseError.message) + eventLogger.report(error: responseError, displayPopup: isFailWithPopupError) finishDownload(download: download, context: context, error: .apiErrorResponse) return } diff --git a/Amperfy/Download/DownloadProtocols.swift b/Amperfy/Download/DownloadProtocols.swift index c5cbbf97..bde77a4d 100644 --- a/Amperfy/Download/DownloadProtocols.swift +++ b/Amperfy/Download/DownloadProtocols.swift @@ -7,6 +7,8 @@ protocol DownloadManageable { var backgroundFetchCompletionHandler: CompleteHandlerBlock? { get set } func download(object: Downloadable) func download(objects: [Downloadable]) + func removeFinishedDownload(for object: Downloadable) + func removeFinishedDownload(for objects: [Downloadable]) func clearFinishedDownloads() func resetFailedDownloads() func cancelDownloads() diff --git a/Amperfy/Player/BackendAudioPlayer.swift b/Amperfy/Player/BackendAudioPlayer.swift index f62143c3..4410c1fd 100644 --- a/Amperfy/Player/BackendAudioPlayer.swift +++ b/Amperfy/Player/BackendAudioPlayer.swift @@ -97,7 +97,7 @@ class BackendAudioPlayer { semaphore.wait() if !playable.isPlayableOniOS, let contentType = playable.contentType { clearPlayer() - eventLogger.info(topic: "Player Info", statusCode: .playerError, message: "Content type \"\(contentType)\" of \"\(playable.displayString)\" is not playable via Amperfy.") + eventLogger.info(topic: "Player Info", statusCode: .playerError, message: "Content type \"\(contentType)\" of \"\(playable.displayString)\" is not playable via Amperfy.", displayPopup: true) } else if playable.isCached { insertCachedPlayable(playable: playable) } else if !isOfflineMode{ diff --git a/Amperfy/Screens/View/AlbumDetailTableHeader.swift b/Amperfy/Screens/View/AlbumDetailTableHeader.swift index 102456a7..ab2adb69 100644 --- a/Amperfy/Screens/View/AlbumDetailTableHeader.swift +++ b/Amperfy/Screens/View/AlbumDetailTableHeader.swift @@ -73,6 +73,7 @@ class AlbumDetailTableHeader: UIView { } if album.hasCachedPlayables { alert.addAction(UIAlertAction(title: "Remove from cache", style: .default, handler: { _ in + self.appDelegate.playableDownloadManager.removeFinishedDownload(for: album.playables) self.appDelegate.library.deleteCache(of: album) self.appDelegate.library.saveContext() if let rootView = self.rootView { diff --git a/Amperfy/Screens/View/ArtistDetailTableHeader.swift b/Amperfy/Screens/View/ArtistDetailTableHeader.swift index a785ea01..a6b72ae2 100644 --- a/Amperfy/Screens/View/ArtistDetailTableHeader.swift +++ b/Amperfy/Screens/View/ArtistDetailTableHeader.swift @@ -70,6 +70,7 @@ class ArtistDetailTableHeader: UIView { } if artist.hasCachedPlayables { alert.addAction(UIAlertAction(title: "Remove from cache", style: .default, handler: { _ in + self.appDelegate.playableDownloadManager.removeFinishedDownload(for: artist.playables) self.appDelegate.library.deleteCache(of: artist) self.appDelegate.library.saveContext() if let rootView = self.rootView { diff --git a/Amperfy/Screens/View/GenreDetailTableHeader.swift b/Amperfy/Screens/View/GenreDetailTableHeader.swift index 6f685da1..336cac94 100644 --- a/Amperfy/Screens/View/GenreDetailTableHeader.swift +++ b/Amperfy/Screens/View/GenreDetailTableHeader.swift @@ -68,6 +68,7 @@ class GenreDetailTableHeader: UIView { } if genre.hasCachedPlayables { alert.addAction(UIAlertAction(title: "Remove from cache", style: .default, handler: { _ in + self.appDelegate.playableDownloadManager.removeFinishedDownload(for: genre.playables) self.appDelegate.library.deleteCache(of: genre) self.appDelegate.library.saveContext() if let rootView = self.rootView { diff --git a/Amperfy/Screens/View/PlayableTableCell.swift b/Amperfy/Screens/View/PlayableTableCell.swift index 85ea46d9..597a7f99 100644 --- a/Amperfy/Screens/View/PlayableTableCell.swift +++ b/Amperfy/Screens/View/PlayableTableCell.swift @@ -133,6 +133,7 @@ class PlayableTableCell: BasicTableCell { } if playable.isCached { alert.addAction(UIAlertAction(title: "Remove from cache", style: .default, handler: { _ in + self.appDelegate.playableDownloadManager.removeFinishedDownload(for: playable) self.appDelegate.library.deleteCache(ofPlayable: playable) self.appDelegate.library.saveContext() self.refresh() diff --git a/Amperfy/Screens/View/PlaylistDetailTableHeader.swift b/Amperfy/Screens/View/PlaylistDetailTableHeader.swift index 4a80d67d..a4b5303d 100644 --- a/Amperfy/Screens/View/PlaylistDetailTableHeader.swift +++ b/Amperfy/Screens/View/PlaylistDetailTableHeader.swift @@ -98,6 +98,7 @@ class PlaylistDetailTableHeader: UIView { } if playlist.hasCachedPlayables { alert.addAction(UIAlertAction(title: "Remove from cache", style: .default, handler: { _ in + self.appDelegate.playableDownloadManager.removeFinishedDownload(for: playlist.playables) self.appDelegate.library.deleteCache(of: playlist) self.appDelegate.library.saveContext() if let rootView = self.rootView { diff --git a/Amperfy/Screens/View/PodcastDetailTableHeader.swift b/Amperfy/Screens/View/PodcastDetailTableHeader.swift index b9b4181e..74f03a92 100644 --- a/Amperfy/Screens/View/PodcastDetailTableHeader.swift +++ b/Amperfy/Screens/View/PodcastDetailTableHeader.swift @@ -61,6 +61,7 @@ class PodcastDetailTableHeader: UIView { } if podcast.hasCachedPlayables { alert.addAction(UIAlertAction(title: "Remove from cache", style: .default, handler: { _ in + self.appDelegate.playableDownloadManager.removeFinishedDownload(for: podcast.playables) self.appDelegate.library.deleteCache(of: podcast) self.appDelegate.library.saveContext() if let rootView = self.rootView { diff --git a/Amperfy/Screens/View/PodcastEpisodeTableCell.swift b/Amperfy/Screens/View/PodcastEpisodeTableCell.swift index 048c1049..7e1a6296 100644 --- a/Amperfy/Screens/View/PodcastEpisodeTableCell.swift +++ b/Amperfy/Screens/View/PodcastEpisodeTableCell.swift @@ -99,6 +99,7 @@ class PodcastEpisodeTableCell: BasicTableCell { } if episode.isCached { alert.addAction(UIAlertAction(title: "Remove from cache", style: .default, handler: { _ in + self.appDelegate.playableDownloadManager.removeFinishedDownload(for: episode) self.appDelegate.library.deleteCache(ofPlayable: episode) self.appDelegate.library.saveContext() self.refresh() diff --git a/Amperfy/Screens/View/SongTableCell.swift b/Amperfy/Screens/View/SongTableCell.swift index 1c5a4200..ddc4e91c 100644 --- a/Amperfy/Screens/View/SongTableCell.swift +++ b/Amperfy/Screens/View/SongTableCell.swift @@ -134,6 +134,7 @@ class SongTableCell: BasicTableCell { } if song.isCached { alert.addAction(UIAlertAction(title: "Remove from cache", style: .default, handler: { _ in + self.appDelegate.playableDownloadManager.removeFinishedDownload(for: song) self.appDelegate.library.deleteCache(ofPlayable: song) self.appDelegate.library.saveContext() self.refresh() diff --git a/Amperfy/Screens/ViewController/SupportVC.swift b/Amperfy/Screens/ViewController/SupportVC.swift index f8e68cf4..489b2e5d 100644 --- a/Amperfy/Screens/ViewController/SupportVC.swift +++ b/Amperfy/Screens/ViewController/SupportVC.swift @@ -36,7 +36,7 @@ class SupportVC: UITableViewController, MFMailComposeViewControllerDelegate { mailComposer.mailComposeDelegate = self self.present(mailComposer, animated: true, completion: nil) } else { - appDelegate.eventLogger.info(topic: "Email Info", statusCode: .emailError, message: "Email is not configured in settings app or Amperfy is not able to send an email.") + appDelegate.eventLogger.info(topic: "Email Info", statusCode: .emailError, message: "Email is not configured in settings app or Amperfy is not able to send an email.", displayPopup: true) } } diff --git a/Amperfy/Storage/DownloadRequestManager.swift b/Amperfy/Storage/DownloadRequestManager.swift index f639d640..5508575a 100644 --- a/Amperfy/Storage/DownloadRequestManager.swift +++ b/Amperfy/Storage/DownloadRequestManager.swift @@ -30,10 +30,34 @@ class DownloadRequestManager { } private func addLowPrio(object: Downloadable, library: LibraryStorage) { - if library.getDownload(id: object.uniqueID) == nil { + let existingDownload = library.getDownload(id: object.uniqueID) + + if existingDownload == nil { let download = library.createDownload() download.id = object.uniqueID download.element = object + } else if let existingDownload = existingDownload, existingDownload.errorDate != nil { + existingDownload.reset() + } + } + + func removeFinishedDownload(for object: Downloadable) { + persistentStorage.context.performAndWait { + let library = LibraryStorage(context: self.persistentStorage.context) + guard let existingDownload = library.getDownload(id: object.uniqueID), existingDownload.finishDate != nil else { return } + library.deleteDownload(existingDownload) + library.saveContext() + } + } + + func removeFinishedDownload(for objects: [Downloadable]) { + persistentStorage.context.performAndWait { + let library = LibraryStorage(context: self.persistentStorage.context) + for object in objects { + guard let existingDownload = library.getDownload(id: object.uniqueID), existingDownload.finishDate != nil else { continue } + library.deleteDownload(existingDownload) + } + library.saveContext() } } diff --git a/AmperfyTests/Cases/API/Ampache/PodcastsParserTest.swift b/AmperfyTests/Cases/API/Ampache/PodcastsParserTest.swift index a41e8bad..2c7de3cb 100644 --- a/AmperfyTests/Cases/API/Ampache/PodcastsParserTest.swift +++ b/AmperfyTests/Cases/API/Ampache/PodcastsParserTest.swift @@ -13,16 +13,6 @@ class PodcastsParserTest: AbstractAmpacheTest { parserDelegate = PodcastParserDelegate(library: library, syncWave: syncWave, parseNotifier: nil) } - func testLibraryContainsBeforeMorePodcastsThenAfter() { - for i in 10...20 { - let podcast = library.createPodcast() - podcast.id = i.description - podcast.title = i.description - } - recreateParserDelegate() - testParsing() - } - override func checkCorrectParsing() { let podcasts = library.getPodcasts() XCTAssertEqual(podcasts.count, 3) diff --git a/AmperfyTests/Cases/API/Subsonic/SsPodcastParserTest.swift b/AmperfyTests/Cases/API/Subsonic/SsPodcastParserTest.swift index e2024101..93407d66 100644 --- a/AmperfyTests/Cases/API/Subsonic/SsPodcastParserTest.swift +++ b/AmperfyTests/Cases/API/Subsonic/SsPodcastParserTest.swift @@ -12,17 +12,7 @@ class SsPodcastParserTest: AbstractSsParserTest { override func recreateParserDelegate() { ssParserDelegate = SsPodcastParserDelegate(library: library, syncWave: syncWave, subsonicUrlCreator: subsonicUrlCreator, parseNotifier: nil) } - - func testLibraryContainsBeforeMorePodcastsThenAfter() { - for i in 10...20 { - let podcast = library.createPodcast() - podcast.id = i.description - podcast.title = i.description - } - recreateParserDelegate() - testParsing() - } - + override func checkCorrectParsing() { let podcasts = library.getPodcasts().sorted(by: {Int($0.id)! < Int($1.id)!} ) XCTAssertEqual(podcasts.count, 2) diff --git a/AmperfyTests/Cases/Player/MusicPlayerTest.swift b/AmperfyTests/Cases/Player/MusicPlayerTest.swift index 2ee5c065..67e4af35 100644 --- a/AmperfyTests/Cases/Player/MusicPlayerTest.swift +++ b/AmperfyTests/Cases/Player/MusicPlayerTest.swift @@ -39,6 +39,8 @@ class MOCK_SongDownloader: DownloadManageable { var backgroundFetchCompletionHandler: CompleteHandlerBlock? { get {return nil} set {} } func download(object: Downloadable) { downloadables.append(object) } func download(objects: [Downloadable]) { downloadables.append(contentsOf: objects) } + func removeFinishedDownload(for object: Downloadable) {} + func removeFinishedDownload(for objects: [Downloadable]) {} func clearFinishedDownloads() {} func resetFailedDownloads() {} func cancelDownloads() {}