Skip to content

Commit

Permalink
EventLogger: display more errors to user
Browse files Browse the repository at this point in the history
  • Loading branch information
BLeeEZ committed Jun 2, 2021
1 parent 23b65bd commit 3cf94ad
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 27 deletions.
6 changes: 3 additions & 3 deletions Amperfy/Api/Ampache/AmpacheLibrarySyncer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,15 @@ class AmpacheLibrarySyncer: LibrarySyncer {
}

func syncMusicFolders(libraryStorage: LibraryStorage) {
ampacheXmlServerApi.eventLogger.info(message: "GetMusicFolders API function is not support by Ampache")
ampacheXmlServerApi.eventLogger.error(topic: "Internal Error", statusCode: .internalError, message: "GetMusicFolders API function is not support by Ampache")
}

func syncIndexes(musicFolder: MusicFolder, libraryStorage: LibraryStorage) {
ampacheXmlServerApi.eventLogger.info(message: "GetIndexes API function is not support by Ampache")
ampacheXmlServerApi.eventLogger.error(topic: "Internal Error", statusCode: .internalError, message: "GetIndexes API function is not support by Ampache")
}

func sync(directory: Directory, libraryStorage: LibraryStorage) {
ampacheXmlServerApi.eventLogger.info(message: "GetMusicDirectory API function is not support by Ampache")
ampacheXmlServerApi.eventLogger.error(topic: "Internal Error", statusCode: .internalError, message: "GetMusicDirectory API function is not support by Ampache")
}

func syncDownPlaylistsWithoutSongs(libraryStorage: LibraryStorage) {
Expand Down
54 changes: 40 additions & 14 deletions Amperfy/Api/EventLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ protocol AlertDisplayable {
func display(alert: UIAlertController) // Must be called from main thread
}

enum AmperfyLogStatusCode: Int {
case downloadError = 1
case playerError = 2
case emailError = 3
case internalError = 4
}

class EventLogger {

static private let errorReportOneDaySilentTimeInSec = 60*60*24
Expand All @@ -20,30 +27,47 @@ class EventLogger {
self.persistentContainer = persistentContainer
}

func info(message: String) {
os_log("Info: %s", log: log, type: .info, message)
displayAlert(message: message)
func info(topic: String, statusCode: AmperfyLogStatusCode, message: String) {
report(topic: topic, statusCode: statusCode, message: message, logType: .info)
}

func error(topic: String, statusCode: AmperfyLogStatusCode, message: String) {
report(topic: topic, statusCode: statusCode, message: message, logType: .error)
}

private func report(topic: String, statusCode: AmperfyLogStatusCode, message: String, logType: LogEntryType) {
persistentContainer.performBackgroundTask{ context in
let library = LibraryStorage(context: context)
let logEntries = library.getLogEntries()
let sameStatusCodeEntries = logEntries.lazy.filter{ $0.statusCode == statusCode.rawValue && $0.type == logType }
if let sameEntry = sameStatusCodeEntries.first, sameEntry.creationDate.compare(Date() - Double(sameEntry.suppressionTimeInterval)) == .orderedDescending {
return
}
self.displayAlert(topic: topic, statusCode: statusCode, message: message, logType: logType)
}
}

private func displayAlert(message: String) {
private func displayAlert(topic: String, statusCode: AmperfyLogStatusCode, message: String, logType: LogEntryType) {
DispatchQueue.main.async {
var alertMessage = ""
alertMessage += "\(message)"

let alert = UIAlertController(title: "Info", message: alertMessage, preferredStyle: .alert)
let alert = UIAlertController(title: topic, message: alertMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Suppress for \(Self.errorReportOneDaySilentTimeInSec.asDayString)", style: .destructive , handler: { _ in
self.saveMessagePersistent(message: message, statusCode: statusCode, logType: logType, suppressionTimeInterval: Self.errorReportOneDaySilentTimeInSec)
}))
alert.addAction(UIAlertAction(title: "OK", style: .default , handler: { _ in
self.saveInfoPersistent(message: message)
self.saveMessagePersistent(message: message, statusCode: statusCode, logType: logType)
}))
self.alertDisplayer.display(alert: alert)
}
}

func report(error: ResponseError) {
os_log("API-ERROR StatusCode: %d, Message: %s", log: log, type: .error, error.statusCode, error.message)
persistentContainer.performBackgroundTask{ context in
let library = LibraryStorage(context: context)
let logEntries = library.getLogEntries()
let sameStatusCodeEntries = logEntries.lazy.filter{ $0.statusCode == error.statusCode }
let sameStatusCodeEntries = logEntries.lazy.filter{ $0.statusCode == error.statusCode && $0.type == .apiError }
if let sameEntry = sameStatusCodeEntries.first, sameEntry.creationDate.compare(Date() - Double(sameEntry.suppressionTimeInterval)) == .orderedDescending {
return
}
Expand All @@ -60,35 +84,37 @@ class EventLogger {
alertMessage += "\nYou can find the event log at:"
alertMessage += "\nSettings -> Support -> Event Log"

let alert = UIAlertController(title: "API Error Occured", message: alertMessage, preferredStyle: .alert)
let alert = UIAlertController(title: "API Error", message: alertMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Suppress for \(Self.errorReportOneDaySilentTimeInSec.asDayString)", style: .destructive , handler: { _ in
self.saveErrorPersistent(error: error, suppressionTimeInterval: Self.errorReportOneDaySilentTimeInSec)
}))
alert.addAction(UIAlertAction(title: "OK", style: .default , handler: { _ in
self.saveErrorPersistent(error: error, suppressionTimeInterval: 0)
self.saveErrorPersistent(error: error)
}))
self.alertDisplayer.display(alert: alert)
}
}

private func saveErrorPersistent(error: ResponseError, suppressionTimeInterval: Int) {
private func saveErrorPersistent(error: ResponseError, suppressionTimeInterval: Int = 0) {
persistentContainer.performBackgroundTask{ context in
let library = LibraryStorage(context: context)
let errorLog = library.createLogEntry()
errorLog.type = .error
errorLog.type = .apiError
errorLog.statusCode = error.statusCode
errorLog.message = error.message
errorLog.suppressionTimeInterval = suppressionTimeInterval
library.saveContext()
}
}

private func saveInfoPersistent(message: String) {
private func saveMessagePersistent(message: String, statusCode: AmperfyLogStatusCode, logType: LogEntryType, suppressionTimeInterval: Int = 0) {
persistentContainer.performBackgroundTask{ context in
let library = LibraryStorage(context: context)
let errorLog = library.createLogEntry()
errorLog.type = .info
errorLog.type = logType
errorLog.statusCode = statusCode.rawValue
errorLog.message = message
errorLog.suppressionTimeInterval = suppressionTimeInterval
library.saveContext()
}
}
Expand Down
2 changes: 1 addition & 1 deletion Amperfy/Common/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
let requestManager = RequestManager()
let dlDelegate = DownloadDelegate(backendApi: backendApi)
let urlDownloader = UrlDownloader(requestManager: requestManager)
let dlManager = DownloadManager(storage: storage, requestManager: requestManager, urlDownloader: urlDownloader, downloadDelegate: dlDelegate)
let dlManager = DownloadManager(storage: storage, requestManager: requestManager, urlDownloader: urlDownloader, downloadDelegate: dlDelegate, eventLogger: eventLogger)
urlDownloader.urlDownloadNotifier = dlManager
return dlManager
}()
Expand Down
22 changes: 19 additions & 3 deletions Amperfy/Download/DownloadManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,19 @@ enum DownloadError: Error {
case noConnectivity
case alreadyDownloaded
case fetchFailed
case emptyFile
case apiErrorResponse

var description : String {
switch self {
case .urlInvalid: return "Invalid URL"
case .noConnectivity: return "No Connectivity"
case .alreadyDownloaded: return "Already Downloaded"
case .fetchFailed: return "Fetch Failed"
case .emptyFile: return "File is empty"
case .apiErrorResponse: return "API Error"
}
}
}

protocol SongDownloadable {
Expand Down Expand Up @@ -37,6 +49,7 @@ class DownloadManager: SongDownloadable {
private let requestManager: RequestManager
private let urlDownloader: UrlDownloader
private let downloadDelegate: DownloadManagerDelegate
private let eventLogger: EventLogger


private let downloadSlotCounter = DownloadSlotCounter(maximumActiveDownloads: 4)
Expand All @@ -49,11 +62,12 @@ class DownloadManager: SongDownloadable {
return requestManager.requestQueues
}

init(storage: PersistentStorage, requestManager: RequestManager, urlDownloader: UrlDownloader, downloadDelegate: DownloadManagerDelegate) {
init(storage: PersistentStorage, requestManager: RequestManager, urlDownloader: UrlDownloader, downloadDelegate: DownloadManagerDelegate, eventLogger: EventLogger) {
self.storage = storage
self.requestManager = requestManager
self.urlDownloader = urlDownloader
self.downloadDelegate = downloadDelegate
self.eventLogger = eventLogger
}

func download(song: Song, notifier: SongDownloadNotifiable? = nil, priority: Priority = .low) {
Expand Down Expand Up @@ -147,12 +161,14 @@ class DownloadManager: SongDownloadable {
os_log("Fetching %s SUCCESS (%{iec-bytes}d)", log: self.log, type: .info, request.title, request.download?.resumeData?.count ?? 0)
downloadDelegate.completedDownload(request: request, context: context)
} catch let fetchError as DownloadError {
os_log("Fetching %s FAILED", log: self.log, type: .info, request.title)
downloadError = fetchError
} catch {
os_log("Fetching %s FAILED", log: self.log, type: .info, request.title)
downloadError = DownloadError.fetchFailed
}
if let error = downloadError, error != .apiErrorResponse {
os_log("Fetching %s FAILED: %s", log: self.log, type: .info, request.title, error.description)
eventLogger.error(topic: "Download Error", statusCode: .downloadError, message: "Error \"\(error.description)\" occured while downloading song \"\(request.title)\".")
}
// remove data from request to free memory
request.download?.resumeData = nil
self.requestManager.informDownloadCompleted(request: request)
Expand Down
2 changes: 1 addition & 1 deletion Amperfy/Download/UrlDownloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class UrlDownloader: NSObject, URLSessionDownloadDelegate {
if data.count > 0 {
download.resumeData = data
} else {
download.error = .fetchFailed
download.error = .emptyFile
}

} catch let error {
Expand Down
2 changes: 1 addition & 1 deletion Amperfy/Player/BackendAudioPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class BackendAudioPlayer: SongDownloadNotifiable {
if !song.isPlayableOniOS, let contentType = song.contentType {
player.pause()
player.replaceCurrentItem(with: nil)
eventLogger.info(message: "Content type \"\(contentType)\" of song \"\(song.displayString)\" is not playable via Amperfy.")
eventLogger.info(topic: "Player Info", statusCode: .playerError, message: "Content type \"\(contentType)\" of song \"\(song.displayString)\" is not playable via Amperfy.")
} else {
if song.isCached {
insertCachedSong(playlistItem: playlistItem)
Expand Down
2 changes: 1 addition & 1 deletion Amperfy/Screens/ViewController/SupportVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class SupportVC: UITableViewController, MFMailComposeViewControllerDelegate {
mailComposer.mailComposeDelegate = self
self.present(mailComposer, animated: true, completion: nil)
} else {
appDelegate.eventLogger.info(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")
}
}

Expand Down
6 changes: 3 additions & 3 deletions Amperfy/Storage/EntityWrappers/LogEntry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import Foundation
import CoreData

enum LogEntryType: Int16 {
case error = 0
case warning = 1
case apiError = 0
case error = 1
case info = 2
case debug = 3

var description : String {
switch self {
case .apiError: return "API Error"
case .error: return "Error"
case .warning: return "Warning"
case .info: return "Info"
case .debug: return "Debug"
}
Expand Down

0 comments on commit 3cf94ad

Please sign in to comment.