Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Don't delete downloaded videos when the user logs out #289

Merged
merged 4 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
358 changes: 318 additions & 40 deletions Authorization/AuthorizationTests/AuthorizationMock.generated.swift

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="22G91" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="CDDownloadData" representedClassName="CDDownloadData" syncable="YES" codeGenerationType="class">
<attribute name="blockId" optional="YES" attributeType="String"/>
<attribute name="courseId" optional="YES" attributeType="String"/>
<attribute name="displayName" optional="YES" attributeType="String"/>
<attribute name="fileName" optional="YES" attributeType="String"/>
Expand All @@ -11,6 +12,7 @@
<attribute name="state" optional="YES" attributeType="String"/>
<attribute name="type" optional="YES" attributeType="String"/>
<attribute name="url" optional="YES" attributeType="String"/>
<attribute name="userId" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
Expand Down
6 changes: 4 additions & 2 deletions Core/Core/Data/Persistence/CorePersistenceProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import CoreData
import Combine

public protocol CorePersistenceProtocol {
func set(userId: Int)
func getUserID() -> Int?
func publisher() -> AnyPublisher<Int, Never>
func addToDownloadQueue(blocks: [CourseBlock], downloadQuality: DownloadQuality)
func getNextBlockForDownloading() -> DownloadDataTask?
func nextBlockForDownloading() -> DownloadDataTask?
func updateDownloadState(id: String, state: DownloadState, resumeData: Data?)
func deleteDownloadDataTask(id: String) throws
func saveDownloadDataTask(data: DownloadDataTask)
func saveDownloadDataTask(_ task: DownloadDataTask)
func downloadDataTask(for blockId: String) -> DownloadDataTask?
func downloadDataTask(for blockId: String, completion: @escaping (DownloadDataTask?) -> Void)
func getDownloadDataTasks(completion: @escaping ([DownloadDataTask]) -> Void)
Expand Down
97 changes: 76 additions & 21 deletions Core/Core/Network/DownloadManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public enum DownloadType: String {
public struct DownloadDataTask: Identifiable, Hashable {
public let id: String
public let courseId: String
public let blockId: String
public let userId: Int
public let url: String
public let fileName: String
public let displayName: String
Expand All @@ -52,7 +54,9 @@ public struct DownloadDataTask: Identifiable, Hashable {

public init(
id: String,
blockId: String,
courseId: String,
userId: Int,
url: String,
fileName: String,
displayName: String,
Expand All @@ -64,6 +68,8 @@ public struct DownloadDataTask: Identifiable, Hashable {
) {
self.id = id
self.courseId = courseId
self.blockId = blockId
self.userId = userId
self.url = url
self.fileName = fileName
self.displayName = displayName
Expand All @@ -73,6 +79,21 @@ public struct DownloadDataTask: Identifiable, Hashable {
self.type = type
self.fileSize = fileSize
}

public init(sourse: CDDownloadData) {
self.id = sourse.id ?? ""
self.blockId = sourse.blockId ?? ""
self.courseId = sourse.courseId ?? ""
self.userId = Int(sourse.userId)
self.url = sourse.url ?? ""
self.fileName = sourse.fileName ?? ""
self.displayName = sourse.displayName ?? ""
self.progress = sourse.progress
self.resumeData = sourse.resumeData
self.state = DownloadState(rawValue: sourse.state ?? "") ?? .waiting
self.type = DownloadType(rawValue: sourse.type ?? "") ?? .video
self.fileSize = Int(sourse.fileSize)
}
}

public class NoWiFiError: LocalizedError {
Expand All @@ -85,19 +106,24 @@ public protocol DownloadManagerProtocol {
func publisher() -> AnyPublisher<Int, Never>
func eventPublisher() -> AnyPublisher<DownloadManagerEvent, Never>

func addToDownloadQueue(blocks: [CourseBlock]) throws

func getDownloadTasks() async -> [DownloadDataTask]
func getDownloadTasksForCourse(_ courseId: String) async -> [DownloadDataTask]

func cancelDownloading(courseId: String, blocks: [CourseBlock]) async throws
func cancelDownloading(task: DownloadDataTask) async throws
func cancelDownloading(courseId: String) async throws
func cancelAllDownloading() async throws

func deleteFile(blocks: [CourseBlock]) async
func deleteAllFiles() async

func fileUrl(for blockId: String) async -> URL?

func addToDownloadQueue(blocks: [CourseBlock]) throws
func isLargeVideosSize(blocks: [CourseBlock]) -> Bool
func resumeDownloading() throws
func fileUrl(for blockId: String) -> URL?
func isLargeVideosSize(blocks: [CourseBlock]) -> Bool
}

public enum DownloadManagerEvent {
Expand All @@ -106,8 +132,9 @@ public enum DownloadManagerEvent {
case progress(Double, DownloadDataTask)
case paused(DownloadDataTask)
case canceled(DownloadDataTask)
case finished(DownloadDataTask)
case courseCanceled(String)
case allCanceled
case finished(DownloadDataTask)
case deletedFile(String)
case clearedAll
}
Expand Down Expand Up @@ -138,6 +165,9 @@ public class DownloadManager: DownloadManagerProtocol {
connectivity: ConnectivityProtocol
) {
self.persistence = persistence
if let userId = appStorage.user?.id {
self.persistence.set(userId: userId)
}
self.appStorage = appStorage
self.connectivity = connectivity
self.backgroundTask()
Expand Down Expand Up @@ -200,10 +230,10 @@ public class DownloadManager: DownloadManagerProtocol {

public func cancelDownloading(courseId: String, blocks: [CourseBlock]) async throws {
downloadRequest?.cancel()

let downloaded = await getDownloadTasksForCourse(courseId).filter { $0.state == .finished }
let blocksForDelete = blocks.filter { block in downloaded.first(where: { $0.id == block.id }) == nil }

let blocksForDelete = blocks.filter { block in
downloaded.first(where: { $0.blockId == block.id }) == nil
}
await deleteFile(blocks: blocksForDelete)
downloaded.forEach {
currentDownloadEventPublisher.send(.canceled($0))
Expand All @@ -226,22 +256,21 @@ public class DownloadManager: DownloadManagerProtocol {
}

public func cancelDownloading(courseId: String) async throws {
let downloads = await getDownloadTasksForCourse(courseId)
for downloadData in downloads {
do {
try persistence.deleteDownloadDataTask(id: downloadData.id)
if let fileUrl = await fileUrl(for: downloadData.id) {
try FileManager.default.removeItem(at: fileUrl)
}
} catch {
debugLog("Error deleting file: \(error.localizedDescription)")
}
}
let tasks = await getDownloadTasksForCourse(courseId)
await cancel(tasks: tasks)
currentDownloadEventPublisher.send(.courseCanceled(courseId))
downloadRequest?.cancel()
try newDownload()
}

public func cancelAllDownloading() async throws {
let tasks = await getDownloadTasks().filter { $0.state != .finished }
await cancel(tasks: tasks)
currentDownloadEventPublisher.send(.allCanceled)
downloadRequest?.cancel()
try newDownload()
}

public func deleteFile(blocks: [CourseBlock]) async {
for block in blocks {
do {
Expand Down Expand Up @@ -293,11 +322,13 @@ public class DownloadManager: DownloadManagerProtocol {
return path?.appendingPathComponent(fileName)
}

// MARK: - Private Intents

private func newDownload() throws {
guard userCanDownload() else {
throw NoWiFiError()
}
guard let downloadTask = persistence.getNextBlockForDownloading() else {
guard let downloadTask = persistence.nextBlockForDownloading() else {
isDownloadingInProgress = false
return
}
Expand Down Expand Up @@ -378,7 +409,18 @@ public class DownloadManager: DownloadManagerProtocol {
}
}

// MARK: - Private Intents
private func cancel(tasks: [DownloadDataTask]) async {
for task in tasks {
do {
try persistence.deleteDownloadDataTask(id: task.id)
if let fileUrl = await fileUrl(for: task.id) {
try FileManager.default.removeItem(at: fileUrl)
}
} catch {
debugLog("Error deleting file: \(error.localizedDescription)")
}
}
}

private func backgroundTask() {
backgroundTaskProvider.eventPublisher()
Expand All @@ -394,8 +436,8 @@ public class DownloadManager: DownloadManagerProtocol {

lazy var videosFolderUrl: URL? = {
let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryURL = documentDirectoryURL.appendingPathComponent("Files", isDirectory: true)
let directoryURL = documentDirectoryURL.appendingPathComponent(folderPathComponent, isDirectory: true)

if FileManager.default.fileExists(atPath: directoryURL.path) {
return URL(fileURLWithPath: directoryURL.path)
} else {
Expand All @@ -413,6 +455,13 @@ public class DownloadManager: DownloadManagerProtocol {
}
}()

private var folderPathComponent: String {
if let id = appStorage.user?.id {
return "\(id)_Files"
}
return "Files"
}

private func saveFile(fileName: String, data: Data, folderURL: URL) {
let fileURL = folderURL.appendingPathComponent(fileName)
do {
Expand Down Expand Up @@ -522,7 +571,9 @@ public class DownloadManagerMock: DownloadManagerProtocol {
.canceled(
.init(
id: "",
blockId: "",
courseId: "",
userId: 0,
url: "",
fileName: "",
displayName: "",
Expand Down Expand Up @@ -562,6 +613,10 @@ public class DownloadManagerMock: DownloadManagerProtocol {

}

public func cancelAllDownloading() async throws {

}

public func resumeDownloading() {

}
Expand Down
19 changes: 1 addition & 18 deletions Course/Course/Presentation/Container/CourseContainerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public struct CourseContainerView: View {
}
.navigationBarHidden(false)
.navigationBarBackButtonHidden(false)
.navigationTitle(titleBar())
.navigationTitle(title)
.onChange(of: selection, perform: didSelect)
.background(Theme.Colors.background)
}
Expand Down Expand Up @@ -211,23 +211,6 @@ public struct CourseContainerView: View {
)
}
}

private func titleBar() -> String {
switch CourseTab(rawValue: selection) {
case .course:
return self.title
case .videos:
return self.title
case .dates:
return CourseLocalization.CourseContainer.dates
case .discussion:
return DiscussionLocalization.title
case .handounds:
return CourseLocalization.CourseContainer.handouts
default:
return ""
}
}
}

#if DEBUG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ public class CourseContainerViewModel: BaseCourseViewModel {
for vertical in sequential.childs where vertical.isDownloadable {
var verticalsChilds: [DownloadViewState] = []
for block in vertical.childs where block.isDownloadable {
if let download = courseDownloadTasks.first(where: { $0.id == block.id }) {
if let download = courseDownloadTasks.first(where: { $0.blockId == block.id }) {
switch download.state {
case .waiting, .inProgress:
sequentialsChilds.append(.downloading)
Expand Down
Loading
Loading