X-Git-Url: https://git.mdrn.pl/wl-app.git/blobdiff_plain/53b27422d140022594fc241cca91c3183be57bca..48b2fe9f7c2dc3d9aeaaa6dbfb27c7da4f3235ff:/iOS/WolneLektury/Connection/DownloadManager.swift diff --git a/iOS/WolneLektury/Connection/DownloadManager.swift b/iOS/WolneLektury/Connection/DownloadManager.swift new file mode 100644 index 0000000..f72e81f --- /dev/null +++ b/iOS/WolneLektury/Connection/DownloadManager.swift @@ -0,0 +1,344 @@ +// +// DownloadManager.swift +// WolneLektury +// +// Created by Pawel Dabrowski on 22/06/2018. +// Copyright © 2018 Fundacja Nowoczesna Polska. All rights reserved. +// + +import UIKit +import MZDownloadManager + +enum FileStatus{ + case notDownloaded + case downloading + case downloaded +} + +enum FileType{ + case ebook + case audiobook + + private var destinationPath: String{ + switch self { + case .ebook: + return Constants.ebookPath + case .audiobook: + return Constants.audiobookPath + } + } + + func destinationPathWithSlug(bookSlug: String) -> String { + return destinationPath + "/" + bookSlug + "/" + } + + func pathForFileName(filename: String, bookSlug: String) -> String{ + return self.destinationPathWithSlug(bookSlug: bookSlug) + filename + } +} + +protocol DownloadManagerDelegate: class{ + func downloadManagerDownloadProgressChanged(model: MZDownloadModel, allProgress: Float, bookSlug: String) + func downloadManagerDownloadFinished(model: MZDownloadModel, bookSlug: String) + func downloadManagerDownloadFailed(model: MZDownloadModel, bookSlug: String) +} + +extension MZDownloadModel { + func isAudiobook() -> Bool { + return self.destinationPath.starts(with: Constants.audiobookPath) + } + func isEbook() -> Bool { + return self.destinationPath.starts(with: Constants.ebookPath) + } + +} + +class DownloadingAudiobook{ + var allUrlsCount: Float = 0 + var waitingToDownloadUrls: [String] + var downloadedUrls: [String] + var bookSlug: String + init(bookDetailsModel: BookDetailsModel) { + + waitingToDownloadUrls = [String]() + downloadedUrls = [String]() + + let audiobookUrls = bookDetailsModel.getAudiobookFilesUrls() + bookSlug = bookDetailsModel.slug + allUrlsCount = Float(audiobookUrls.count) + + for url in audiobookUrls{ + if NSObject.audiobookExists(audioBookUrlString: url, bookSlug: bookSlug){ + downloadedUrls.append(url) + } + else{ + waitingToDownloadUrls.append(url) + } + } + } + + func getProgress() -> Float{ + let allCount = waitingToDownloadUrls.count + downloadedUrls.count + if allCount > 0{ + if downloadedUrls.count > 0{ + return Float(downloadedUrls.count) / Float(allCount) + } + } + return 0.0 + } +} + +class DownloadManager: NSObject, MZDownloadManagerDelegate{ + + weak var delegate: DownloadManagerDelegate? + var downloadingAudiobooks = [DownloadingAudiobook]() + + //Shared instance of DownloadManager + static let sharedInstance : DownloadManager = { + return DownloadManager() + }() + + + lazy var downloadManager: MZDownloadManager = { + [unowned self] in + let sessionIdentifer: String = "com.moiseum.WolneLektury.BackgroundSession" + + let appDelegate = UIApplication.shared.delegate as! AppDelegate + var completion = appDelegate.backgroundSessionCompletionHandler + + let downloadmanager = MZDownloadManager(session: sessionIdentifer, delegate: self, completion: completion) + return downloadmanager + }() + + func checkEbookStatus(bookSlug: String) -> FileStatus{ + if getEbookProgress(bookSlug: bookSlug) != nil{ + return .downloading + } + + if ebookExists(bookSlug: bookSlug){ + return .downloaded + } + + return .notDownloaded + } + + func getDownloadingAudiobook(bookSlug: String) -> DownloadingAudiobook? { + for downloadingAudiobook in downloadingAudiobooks { + if downloadingAudiobook.bookSlug == bookSlug { + return downloadingAudiobook + } + } + return nil + } + + func clearDownloadingAudiobookFromQueue(bookSlug: String){ + + var i = 0 + var found = false + for downloadingAudiobook in downloadingAudiobooks { + if downloadingAudiobook.bookSlug == bookSlug { + found = true + break + } + + i += 1 + } + if found { + downloadingAudiobooks.remove(at: i) + } + + if let index = downloadManager.downloadingArray.index(where: {$0.destinationPath == FileType.audiobook.destinationPathWithSlug(bookSlug: bookSlug)}) { + downloadManager.cancelTaskAtIndex(index) + } + } + + func checkAudiobookStatus(bookDetailsModel: BookDetailsModel) -> FileStatus{ + + guard bookDetailsModel.slug.count > 0 else { return .notDownloaded} + + if getDownloadingAudiobook(bookSlug: bookDetailsModel.slug) != nil { + return .downloading + } + + if bookDetailsModel.checkIfAllAudiobookFilesAreDownloaded() { + return .downloaded + } + + return .notDownloaded + } + + func deleteEbook(bookSlug: String){ + + let fileType = FileType.ebook + + if let index = downloadManager.downloadingArray.index(where: {$0.fileName == bookSlug && $0.destinationPath == fileType.destinationPathWithSlug(bookSlug: bookSlug)}) { + downloadManager.cancelTaskAtIndex(index) + } + else { + let path = fileType.destinationPathWithSlug(bookSlug: bookSlug)// pathForFileName(filename: bookSlug,bookSlug: bookSlug) + if FileManager.default.fileExists(atPath: path) { + try! FileManager.default.removeItem(atPath: path) + } + } + UserDefaults.standard.removeObject(forKey: bookSlug) + } + + func deleteAudiobook(bookSlug: String){ + + let fileType = FileType.audiobook + + try? FileManager.default.removeItem(atPath: fileType.destinationPathWithSlug(bookSlug: bookSlug)) + } + + func getEbookProgress(bookSlug: String) -> Float? { + if let model = downloadManager.downloadingArray.first(where: {$0.fileName == bookSlug && $0.destinationPath == FileType.ebook.destinationPathWithSlug(bookSlug: bookSlug)}){ + return model.progress + } + return nil + } + + func getAudiobookProgress(bookDetailsModel: BookDetailsModel) -> Float? { + + guard let downloadingAudiobook = getDownloadingAudiobook(bookSlug: bookDetailsModel.slug) else {return nil} + + return downloadingAudiobook.getProgress() + } + + func downloadEbook(bookDetailsModel: BookDetailsModel) { + + guard bookDetailsModel.epub.count > 0, bookDetailsModel.slug.count > 0 else { + return + } + + downloadManager.addDownloadTask(bookDetailsModel.slug, fileURL: bookDetailsModel.epub, destinationPath: FileType.ebook.destinationPathWithSlug(bookSlug: bookDetailsModel.slug)) + } + + func downloadAudiobooks(bookDetailsModel: BookDetailsModel) { + + let bookSlug = bookDetailsModel.slug + guard bookDetailsModel.getAudiobookFilesUrls().count > 0, bookSlug.count > 0 else { + return + } + + if let downloadingAudiobook = getDownloadingAudiobook(bookSlug:bookSlug){ + + if let firstUrl = downloadingAudiobook.waitingToDownloadUrls.first{ + addDownloadAudiobookTask(bookSlug: bookSlug, fileUrl: firstUrl) + return + } + else{ + clearDownloadingAudiobookFromQueue(bookSlug: bookSlug) + } + } + + let downloadingAudiobook = DownloadingAudiobook(bookDetailsModel: bookDetailsModel) + if let firstUrl = downloadingAudiobook.waitingToDownloadUrls.first { + downloadingAudiobooks.append(downloadingAudiobook) + addDownloadAudiobookTask(bookSlug: bookSlug, fileUrl: firstUrl) + } + } + + func addDownloadAudiobookTask(bookSlug: String, fileUrl: String){ + downloadManager.addDownloadTask((fileUrl as NSString).lastPathComponent, fileURL: fileUrl, destinationPath: FileType.audiobook.destinationPathWithSlug(bookSlug: bookSlug)) + } + + func notifyDelegateThatProgressChanged(downloadModel: MZDownloadModel){ + guard let delegate = delegate else { return } + + if downloadModel.isAudiobook(){ + for downloadAudiobook in downloadingAudiobooks { + if downloadAudiobook.waitingToDownloadUrls.index(of: downloadModel.fileURL) != nil { + delegate.downloadManagerDownloadProgressChanged(model: downloadModel, allProgress: downloadAudiobook.getProgress() + downloadModel.progress/downloadAudiobook.allUrlsCount, bookSlug: downloadAudiobook.bookSlug) + return + } + } + } + delegate.downloadManagerDownloadProgressChanged(model: downloadModel, allProgress: downloadModel.progress, bookSlug: downloadModel.fileName) + } + + func downloadRequestStarted(_ downloadModel: MZDownloadModel, index: Int) { + + notifyDelegateThatProgressChanged(downloadModel: downloadModel) + } + + func downloadRequestDidPopulatedInterruptedTasks(_ downloadModels: [MZDownloadModel]) { + } + + func downloadRequestDidUpdateProgress(_ downloadModel: MZDownloadModel, index: Int) { + notifyDelegateThatProgressChanged(downloadModel: downloadModel) + } + + func downloadRequestDidPaused(_ downloadModel: MZDownloadModel, index: Int) { + } + + func downloadRequestDidResumed(_ downloadModel: MZDownloadModel, index: Int) { + } + + func downloadRequestCanceled(_ downloadModel: MZDownloadModel, index: Int) { + if downloadModel.isAudiobook(){ + for downloadAudiobook in downloadingAudiobooks { + if downloadAudiobook.waitingToDownloadUrls.index(of: downloadModel.fileURL) != nil { + clearDownloadingAudiobookFromQueue(bookSlug: downloadAudiobook.bookSlug) + + delegate?.downloadManagerDownloadFailed(model: downloadModel, bookSlug:downloadAudiobook.bookSlug ) + return + } + } + } + + delegate?.downloadManagerDownloadFailed(model: downloadModel, bookSlug: downloadModel.fileName) + } + + func downloadRequestFinished(_ downloadModel: MZDownloadModel, index: Int) { + + // audiobook + if downloadModel.isAudiobook() { + for downloadAudiobook in downloadingAudiobooks { + if let index = downloadAudiobook.waitingToDownloadUrls.index(of: downloadModel.fileURL) { + downloadAudiobook.waitingToDownloadUrls.remove(at: index) + downloadAudiobook.downloadedUrls.append(downloadModel.fileURL) + let slug = downloadAudiobook.bookSlug + // check if there is any waiting to download url, and start downloading + if let firstUrl = downloadAudiobook.waitingToDownloadUrls.first(where: {$0.count > 0}) { + addDownloadAudiobookTask(bookSlug: downloadAudiobook.bookSlug, fileUrl: firstUrl) + } + else { // otherwise, downloading is finished, notify delegates + clearDownloadingAudiobookFromQueue(bookSlug: slug) + delegate?.downloadManagerDownloadFinished(model: downloadModel, bookSlug: slug) + } + } + } + } + else {//ebook + delegate?.downloadManagerDownloadFinished(model: downloadModel, bookSlug: downloadModel.fileName) + } + } + + func downloadRequestDidFailedWithError(_ error: NSError, downloadModel: MZDownloadModel, index: Int) { + var bookSlug = downloadModel.fileName + if downloadModel.isAudiobook() { + for downloadAudiobook in downloadingAudiobooks { + if downloadAudiobook.waitingToDownloadUrls.index(of: downloadModel.fileURL) != nil { + bookSlug = downloadAudiobook.bookSlug + clearDownloadingAudiobookFromQueue(bookSlug: bookSlug ?? "") + continue + } + } + } + delegate?.downloadManagerDownloadFailed(model: downloadModel, bookSlug: downloadModel.fileName) + } + + //Oppotunity to handle destination does not exists error + //This delegate will be called on the session queue so handle it appropriately + func downloadRequestDestinationDoestNotExists(_ downloadModel: MZDownloadModel, index: Int, location: URL) { + let myDownloadPath = downloadModel.destinationPath + if !FileManager.default.fileExists(atPath: myDownloadPath) { + try! FileManager.default.createDirectory(atPath: myDownloadPath, withIntermediateDirectories: true, attributes: nil) + } + let filePath = myDownloadPath + "/" + downloadModel.fileName + if FileManager.default.fileExists(atPath: filePath){ + try! FileManager.default.removeItem(atPath: filePath) + } + try! FileManager.default.moveItem(at: location, to: URL(fileURLWithPath: filePath)) + } +}