2 // DownloadManager.swift
5 // Created by Pawel Dabrowski on 22/06/2018.
6 // Copyright © 2018 Fundacja Nowoczesna Polska. All rights reserved.
10 import MZDownloadManager
22 private var destinationPath: String{
25 return Constants.ebookPath
27 return Constants.audiobookPath
31 func destinationPathWithSlug(bookSlug: String) -> String {
32 return destinationPath + "/" + bookSlug + "/"
35 func pathForFileName(filename: String, bookSlug: String) -> String{
36 return self.destinationPathWithSlug(bookSlug: bookSlug) + filename
40 protocol DownloadManagerDelegate: class{
41 func downloadManagerDownloadProgressChanged(model: MZDownloadModel, allProgress: Float, bookSlug: String)
42 func downloadManagerDownloadFinished(model: MZDownloadModel, bookSlug: String)
43 func downloadManagerDownloadFailed(model: MZDownloadModel, bookSlug: String)
46 extension MZDownloadModel {
47 func isAudiobook() -> Bool {
48 return self.destinationPath.starts(with: Constants.audiobookPath)
50 func isEbook() -> Bool {
51 return self.destinationPath.starts(with: Constants.ebookPath)
56 class DownloadingAudiobook{
57 var allUrlsCount: Float = 0
58 var waitingToDownloadUrls: [String]
59 var downloadedUrls: [String]
61 init(bookDetailsModel: BookDetailsModel) {
63 waitingToDownloadUrls = [String]()
64 downloadedUrls = [String]()
66 let audiobookUrls = bookDetailsModel.getAudiobookFilesUrls()
67 bookSlug = bookDetailsModel.slug
68 allUrlsCount = Float(audiobookUrls.count)
70 for url in audiobookUrls{
71 if NSObject.audiobookExists(audioBookUrlString: url, bookSlug: bookSlug){
72 downloadedUrls.append(url)
75 waitingToDownloadUrls.append(url)
80 func getProgress() -> Float{
81 let allCount = waitingToDownloadUrls.count + downloadedUrls.count
83 if downloadedUrls.count > 0{
84 return Float(downloadedUrls.count) / Float(allCount)
91 class DownloadManager: NSObject, MZDownloadManagerDelegate{
93 weak var delegate: DownloadManagerDelegate?
94 var downloadingAudiobooks = [DownloadingAudiobook]()
96 //Shared instance of DownloadManager
97 static let sharedInstance : DownloadManager = {
98 return DownloadManager()
102 lazy var downloadManager: MZDownloadManager = {
104 let sessionIdentifer: String = "com.moiseum.WolneLektury.BackgroundSession"
106 let appDelegate = UIApplication.shared.delegate as! AppDelegate
107 var completion = appDelegate.backgroundSessionCompletionHandler
109 let downloadmanager = MZDownloadManager(session: sessionIdentifer, delegate: self, completion: completion)
110 return downloadmanager
113 func checkEbookStatus(bookSlug: String) -> FileStatus{
114 if getEbookProgress(bookSlug: bookSlug) != nil{
118 if ebookExists(bookSlug: bookSlug){
122 return .notDownloaded
125 func getDownloadingAudiobook(bookSlug: String) -> DownloadingAudiobook? {
126 for downloadingAudiobook in downloadingAudiobooks {
127 if downloadingAudiobook.bookSlug == bookSlug {
128 return downloadingAudiobook
134 func clearDownloadingAudiobookFromQueue(bookSlug: String){
138 for downloadingAudiobook in downloadingAudiobooks {
139 if downloadingAudiobook.bookSlug == bookSlug {
147 downloadingAudiobooks.remove(at: i)
150 if let index = downloadManager.downloadingArray.index(where: {$0.destinationPath == FileType.audiobook.destinationPathWithSlug(bookSlug: bookSlug)}) {
151 downloadManager.cancelTaskAtIndex(index)
155 func checkAudiobookStatus(bookDetailsModel: BookDetailsModel) -> FileStatus{
157 guard bookDetailsModel.slug.count > 0 else { return .notDownloaded}
159 if getDownloadingAudiobook(bookSlug: bookDetailsModel.slug) != nil {
163 if bookDetailsModel.checkIfAllAudiobookFilesAreDownloaded() {
167 return .notDownloaded
170 func deleteEbook(bookSlug: String){
172 let fileType = FileType.ebook
174 if let index = downloadManager.downloadingArray.index(where: {$0.fileName == bookSlug && $0.destinationPath == fileType.destinationPathWithSlug(bookSlug: bookSlug)}) {
175 downloadManager.cancelTaskAtIndex(index)
178 let path = fileType.destinationPathWithSlug(bookSlug: bookSlug)// pathForFileName(filename: bookSlug,bookSlug: bookSlug)
179 if FileManager.default.fileExists(atPath: path) {
180 try! FileManager.default.removeItem(atPath: path)
183 UserDefaults.standard.removeObject(forKey: bookSlug)
186 func deleteAudiobook(bookSlug: String){
188 let fileType = FileType.audiobook
190 try? FileManager.default.removeItem(atPath: fileType.destinationPathWithSlug(bookSlug: bookSlug))
193 func getEbookProgress(bookSlug: String) -> Float? {
194 if let model = downloadManager.downloadingArray.first(where: {$0.fileName == bookSlug && $0.destinationPath == FileType.ebook.destinationPathWithSlug(bookSlug: bookSlug)}){
195 return model.progress
200 func getAudiobookProgress(bookDetailsModel: BookDetailsModel) -> Float? {
202 guard let downloadingAudiobook = getDownloadingAudiobook(bookSlug: bookDetailsModel.slug) else {return nil}
204 return downloadingAudiobook.getProgress()
207 func downloadEbook(bookDetailsModel: BookDetailsModel) {
209 guard bookDetailsModel.epub.count > 0, bookDetailsModel.slug.count > 0 else {
213 downloadManager.addDownloadTask(bookDetailsModel.slug, fileURL: bookDetailsModel.epub, destinationPath: FileType.ebook.destinationPathWithSlug(bookSlug: bookDetailsModel.slug))
216 func downloadAudiobooks(bookDetailsModel: BookDetailsModel) {
218 let bookSlug = bookDetailsModel.slug
219 guard bookDetailsModel.getAudiobookFilesUrls().count > 0, bookSlug.count > 0 else {
223 if let downloadingAudiobook = getDownloadingAudiobook(bookSlug:bookSlug){
225 if let firstUrl = downloadingAudiobook.waitingToDownloadUrls.first{
226 addDownloadAudiobookTask(bookSlug: bookSlug, fileUrl: firstUrl)
230 clearDownloadingAudiobookFromQueue(bookSlug: bookSlug)
234 let downloadingAudiobook = DownloadingAudiobook(bookDetailsModel: bookDetailsModel)
235 if let firstUrl = downloadingAudiobook.waitingToDownloadUrls.first {
236 downloadingAudiobooks.append(downloadingAudiobook)
237 addDownloadAudiobookTask(bookSlug: bookSlug, fileUrl: firstUrl)
241 func addDownloadAudiobookTask(bookSlug: String, fileUrl: String){
242 downloadManager.addDownloadTask((fileUrl as NSString).lastPathComponent, fileURL: fileUrl, destinationPath: FileType.audiobook.destinationPathWithSlug(bookSlug: bookSlug))
245 func notifyDelegateThatProgressChanged(downloadModel: MZDownloadModel){
246 guard let delegate = delegate else { return }
248 if downloadModel.isAudiobook(){
249 for downloadAudiobook in downloadingAudiobooks {
250 if downloadAudiobook.waitingToDownloadUrls.index(of: downloadModel.fileURL) != nil {
251 delegate.downloadManagerDownloadProgressChanged(model: downloadModel, allProgress: downloadAudiobook.getProgress() + downloadModel.progress/downloadAudiobook.allUrlsCount, bookSlug: downloadAudiobook.bookSlug)
256 delegate.downloadManagerDownloadProgressChanged(model: downloadModel, allProgress: downloadModel.progress, bookSlug: downloadModel.fileName)
259 func downloadRequestStarted(_ downloadModel: MZDownloadModel, index: Int) {
261 notifyDelegateThatProgressChanged(downloadModel: downloadModel)
264 func downloadRequestDidPopulatedInterruptedTasks(_ downloadModels: [MZDownloadModel]) {
267 func downloadRequestDidUpdateProgress(_ downloadModel: MZDownloadModel, index: Int) {
268 notifyDelegateThatProgressChanged(downloadModel: downloadModel)
271 func downloadRequestDidPaused(_ downloadModel: MZDownloadModel, index: Int) {
274 func downloadRequestDidResumed(_ downloadModel: MZDownloadModel, index: Int) {
277 func downloadRequestCanceled(_ downloadModel: MZDownloadModel, index: Int) {
278 if downloadModel.isAudiobook(){
279 for downloadAudiobook in downloadingAudiobooks {
280 if downloadAudiobook.waitingToDownloadUrls.index(of: downloadModel.fileURL) != nil {
281 clearDownloadingAudiobookFromQueue(bookSlug: downloadAudiobook.bookSlug)
283 delegate?.downloadManagerDownloadFailed(model: downloadModel, bookSlug:downloadAudiobook.bookSlug )
289 delegate?.downloadManagerDownloadFailed(model: downloadModel, bookSlug: downloadModel.fileName)
292 func downloadRequestFinished(_ downloadModel: MZDownloadModel, index: Int) {
295 if downloadModel.isAudiobook() {
296 for downloadAudiobook in downloadingAudiobooks {
297 if let index = downloadAudiobook.waitingToDownloadUrls.index(of: downloadModel.fileURL) {
298 downloadAudiobook.waitingToDownloadUrls.remove(at: index)
299 downloadAudiobook.downloadedUrls.append(downloadModel.fileURL)
300 let slug = downloadAudiobook.bookSlug
301 // check if there is any waiting to download url, and start downloading
302 if let firstUrl = downloadAudiobook.waitingToDownloadUrls.first(where: {$0.count > 0}) {
303 addDownloadAudiobookTask(bookSlug: downloadAudiobook.bookSlug, fileUrl: firstUrl)
305 else { // otherwise, downloading is finished, notify delegates
306 clearDownloadingAudiobookFromQueue(bookSlug: slug)
307 delegate?.downloadManagerDownloadFinished(model: downloadModel, bookSlug: slug)
313 delegate?.downloadManagerDownloadFinished(model: downloadModel, bookSlug: downloadModel.fileName)
317 func downloadRequestDidFailedWithError(_ error: NSError, downloadModel: MZDownloadModel, index: Int) {
318 var bookSlug = downloadModel.fileName
319 if downloadModel.isAudiobook() {
320 for downloadAudiobook in downloadingAudiobooks {
321 if downloadAudiobook.waitingToDownloadUrls.index(of: downloadModel.fileURL) != nil {
322 bookSlug = downloadAudiobook.bookSlug
323 clearDownloadingAudiobookFromQueue(bookSlug: bookSlug ?? "")
328 delegate?.downloadManagerDownloadFailed(model: downloadModel, bookSlug: downloadModel.fileName)
331 //Oppotunity to handle destination does not exists error
332 //This delegate will be called on the session queue so handle it appropriately
333 func downloadRequestDestinationDoestNotExists(_ downloadModel: MZDownloadModel, index: Int, location: URL) {
334 let myDownloadPath = downloadModel.destinationPath
335 if !FileManager.default.fileExists(atPath: myDownloadPath) {
336 try! FileManager.default.createDirectory(atPath: myDownloadPath, withIntermediateDirectories: true, attributes: nil)
338 let filePath = myDownloadPath + "/" + downloadModel.fileName
339 if FileManager.default.fileExists(atPath: filePath){
340 try! FileManager.default.removeItem(atPath: filePath)
342 try! FileManager.default.moveItem(at: location, to: URL(fileURLWithPath: filePath))