diff --git a/Core/Core/Data/Persistence/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/Core/Core/Data/Persistence/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index 79f74bbed..ed12a0c8a 100644 --- a/Core/Core/Data/Persistence/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/Core/Core/Data/Persistence/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -1,9 +1,10 @@ - + + diff --git a/Core/Core/Data/Persistence/CorePersistenceProtocol.swift b/Core/Core/Data/Persistence/CorePersistenceProtocol.swift index b93c8a428..e7256850c 100644 --- a/Core/Core/Data/Persistence/CorePersistenceProtocol.swift +++ b/Core/Core/Data/Persistence/CorePersistenceProtocol.swift @@ -10,11 +10,12 @@ import Combine public protocol CorePersistenceProtocol { func publisher() -> AnyPublisher + func getAllDownloadData() -> [DownloadData] func addToDownloadQueue(blocks: [CourseBlock]) func getNextBlockForDownloading() -> DownloadData? func getDownloadsForCourse(_ courseId: String) -> [DownloadData] func downloadData(by blockId: String) -> DownloadData? - func updateDownloadState(id: String, state: DownloadState, resumeData: Data?) + func updateDownloadState(id: String, state: DownloadState, path: String?, resumeData: Data?) func deleteDownloadData(id: String) throws func saveDownloadData(data: DownloadData) } diff --git a/Core/Core/Network/DownloadManager.swift b/Core/Core/Network/DownloadManager.swift index 3b5378b9e..4b8ed6df8 100644 --- a/Core/Core/Network/DownloadManager.swift +++ b/Core/Core/Network/DownloadManager.swift @@ -24,6 +24,7 @@ public struct DownloadData { public let id: String public let courseId: String public let url: String + public let path: String? public let fileName: String public let progress: Double public let resumeData: Data? @@ -34,6 +35,7 @@ public struct DownloadData { id: String, courseId: String, url: String, + path: String?, fileName: String, progress: Double, resumeData: Data?, @@ -43,6 +45,7 @@ public struct DownloadData { self.id = id self.courseId = courseId self.url = url + self.path = path self.fileName = fileName self.progress = progress self.resumeData = resumeData @@ -145,6 +148,7 @@ public class DownloadManager: DownloadManagerProtocol { persistence.updateDownloadState( id: download.id, state: .inProgress, + path: nil, resumeData: download.resumeData ) self.isDownloadingInProgress = true @@ -161,10 +165,11 @@ public class DownloadManager: DownloadManagerProtocol { downloadRequest?.responseData(completionHandler: { [weak self] data in guard let self else { return } if let data = data.value, let url = self.videosFolderUrl() { - self.saveFile(file: fileName, data: data, url: url) + let fileUrl = self.saveFile(fileName: fileName, data: data, folderURL: url) self.persistence.updateDownloadState( id: download.id, state: .finished, + path: fileUrl?.absoluteString, resumeData: nil ) try? self.newDownload() @@ -183,6 +188,7 @@ public class DownloadManager: DownloadManagerProtocol { self.persistence.updateDownloadState( id: currentDownload.id, state: .paused, + path: nil, resumeData: resumeData ) }) @@ -190,26 +196,29 @@ public class DownloadManager: DownloadManagerProtocol { public func deleteFile(blocks: [CourseBlock]) { for block in blocks { - if let url = block.videoUrl, - let fileName = URL(string: url)?.lastPathComponent, let folderUrl = videosFolderUrl() { - do { - let fileUrl = folderUrl.appendingPathComponent(fileName) - try persistence.deleteDownloadData(id: block.id) - try FileManager.default.removeItem(at: fileUrl) - print("File deleted successfully") - } catch { - print("Error deleting file: \(error.localizedDescription)") - } + let downloadData = persistence.downloadData(by: block.id) + guard let path = persistence.downloadData(by: block.id)?.path, + let fileUrl = URL(string: path) else { return } + + do { + try persistence.deleteDownloadData(id: block.id) + try FileManager.default.removeItem(at: fileUrl) + print("File deleted successfully") + } catch { + print("Error deleting file: \(error.localizedDescription)") } } } public func deleteAllFiles() { - if let folderUrl = videosFolderUrl() { - do { - try FileManager.default.removeItem(at: folderUrl) - } catch { - NSLog("Error deleting All files: \(error.localizedDescription)") + let downloadData = persistence.getAllDownloadData() + downloadData.forEach { + if let path = $0.path, let fileURL = URL(string: path) { + do { + try FileManager.default.removeItem(at: fileURL) + } catch { + NSLog("Error deleting All files: \(error.localizedDescription)") + } } } } @@ -219,9 +228,7 @@ public class DownloadManager: DownloadManagerProtocol { data.url.count > 0, data.state == .finished else { return nil } - let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] - let directoryURL = documentDirectoryURL.appendingPathComponent("Files", isDirectory: true) - return directoryURL.appendingPathComponent(data.fileName) + return URL(string: data.path ?? "") } private func videosFolderUrl() -> URL? { @@ -245,13 +252,15 @@ public class DownloadManager: DownloadManagerProtocol { } } - private func saveFile(file: String, data: Data, url: URL) { - let fileURL = url.appendingPathComponent(file) + private func saveFile(fileName: String, data: Data, folderURL: URL) -> URL? { + let fileURL = folderURL.appendingPathComponent(fileName) do { try data.write(to: fileURL) + return fileURL } catch { print("SaveFile Error", error.localizedDescription) } + return nil } } diff --git a/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift b/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift index 86c12aa7a..63632ac3b 100644 --- a/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift +++ b/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift @@ -631,6 +631,7 @@ final class CourseContainerViewModelTests: XCTestCase { id: "1", courseId: "course123", url: "https://example.com/file.mp4", + path: nil, fileName: "file.mp4", progress: 0, resumeData: nil, @@ -739,6 +740,7 @@ final class CourseContainerViewModelTests: XCTestCase { id: "1", courseId: "course123", url: "https://example.com/file.mp4", + path: "file://../file.mp4", fileName: "file.mp4", progress: 0, resumeData: nil, @@ -860,6 +862,7 @@ final class CourseContainerViewModelTests: XCTestCase { id: "1", courseId: "course123", url: "https://example.com/file.mp4", + path: nil, fileName: "file.mp4", progress: 0, resumeData: nil, diff --git a/OpenEdX/Data/CorePersistence.swift b/OpenEdX/Data/CorePersistence.swift index ce2a4e31e..38605c97b 100644 --- a/OpenEdX/Data/CorePersistence.swift +++ b/OpenEdX/Data/CorePersistence.swift @@ -41,6 +41,24 @@ public class CorePersistence: CorePersistenceProtocol { .eraseToAnyPublisher() } + public func getAllDownloadData() -> [DownloadData] { + let request = CDDownloadData.fetchRequest() + guard let downloadData = try? context.fetch(request) else { return [] } + return downloadData.map { + DownloadData( + id: $0.id ?? "", + courseId: $0.courseId ?? "", + url: $0.url ?? "", + path: $0.path, + fileName: $0.fileName ?? "", + progress: $0.progress, + resumeData: $0.resumeData, + state: DownloadState(rawValue: $0.state ?? "") ?? .waiting, + type: DownloadType(rawValue: $0.type ?? "") ?? .video + ) + } + } + public func addToDownloadQueue(blocks: [CourseBlock]) { for block in blocks { let request = CDDownloadData.fetchRequest() @@ -72,6 +90,7 @@ public class CorePersistence: CorePersistenceProtocol { id: data.id ?? "", courseId: data.courseId ?? "", url: data.url ?? "", + path: data.path, fileName: data.fileName ?? "", progress: data.progress, resumeData: data.resumeData, @@ -89,6 +108,7 @@ public class CorePersistence: CorePersistenceProtocol { id: $0.id ?? "", courseId: $0.courseId ?? "", url: $0.url ?? "", + path: $0.path, fileName: $0.fileName ?? "", progress: $0.progress, resumeData: $0.resumeData, @@ -106,6 +126,7 @@ public class CorePersistence: CorePersistenceProtocol { id: downloadData.id ?? "", courseId: downloadData.courseId ?? "", url: downloadData.url ?? "", + path: downloadData.path, fileName: downloadData.fileName ?? "", progress: downloadData.progress, resumeData: downloadData.resumeData, @@ -114,12 +135,13 @@ public class CorePersistence: CorePersistenceProtocol { ) } - public func updateDownloadState(id: String, state: DownloadState, resumeData: Data?) { + public func updateDownloadState(id: String, state: DownloadState, path: String?, resumeData: Data?) { context.performAndWait { let request = CDDownloadData.fetchRequest() request.predicate = NSPredicate(format: "id = %@", id) guard let downloadData = try? context.fetch(request).first else { return } downloadData.state = state.rawValue + downloadData.path = path downloadData.resumeData = resumeData do { try context.save() diff --git a/OpenEdX/Data/DatabaseManager.swift b/OpenEdX/Data/DatabaseManager.swift index 326f42a10..b8f71b346 100644 --- a/OpenEdX/Data/DatabaseManager.swift +++ b/OpenEdX/Data/DatabaseManager.swift @@ -74,7 +74,11 @@ class DatabaseManager: CoreDataHandlerProtocol { } // Re-create the persistent container - persistentContainer = createContainer() - context = createContext() + persistentContainer.loadPersistentStores { _, error in + if let error = error { + print("Unresolved error \(error)") + fatalError() + } + } } }