Skip to content

Commit

Permalink
feat: Don't delete downloaded videos when the user logs out (#289)
Browse files Browse the repository at this point in the history
* chore: add new logic with multi users

* chore: change properties name

* chore: cancel downloading if log out and title fixes

* chore: resolve PR comments
  • Loading branch information
eyatsenkoperpetio authored Feb 23, 2024
1 parent ea0dfc2 commit 4f60316
Show file tree
Hide file tree
Showing 21 changed files with 2,257 additions and 433 deletions.
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 @@ -357,7 +357,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

0 comments on commit 4f60316

Please sign in to comment.