From 275370d7d8260fa57e9b5a94fddf590305467278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Neto?= Date: Mon, 29 May 2023 08:58:32 +0100 Subject: [PATCH 1/2] bkp --- Package.swift | 4 +- .../Controllers/ApodManagerController.swift | 55 ++++++++++++++++--- .../Controllers/LoginManagerController.swift | 7 ++- Sources/manager/Models/Apod/Apod.swift | 22 +++----- .../Models/Timeline/TimelineMonth.swift | 18 +++--- Tests/managerTests/managerTests.swift | 5 +- 6 files changed, 73 insertions(+), 38 deletions(-) diff --git a/Package.swift b/Package.swift index 88208ac..8596420 100644 --- a/Package.swift +++ b/Package.swift @@ -14,8 +14,8 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/SwifterSwift/SwifterSwift.git", exact: "5.2.0"), - .package(url: "https://github.com/joselinoneto/apiclient", from: "1.0.0"), - .package(url: "https://github.com/joselinoneto/storageclient", from: "1.0.0"), + .package(path: "../apiclient"), + .package(path: "../storageclient") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. diff --git a/Sources/manager/Controllers/ApodManagerController.swift b/Sources/manager/Controllers/ApodManagerController.swift index 8c855d0..96df2d2 100644 --- a/Sources/manager/Controllers/ApodManagerController.swift +++ b/Sources/manager/Controllers/ApodManagerController.swift @@ -11,6 +11,7 @@ import storageclient import tools import Combine import GRDB +import ToolboxAPIClient public class ApodManagerController { // MARK: Published Items @@ -50,15 +51,20 @@ public class ApodManagerController { /// Will emit saved data. /// - Parameter currentMonth: Month to search data public func getMonthData(currentMonth: TimelineMonth) async throws { - let response = try? await apiController.getMonthsApods(startDate: currentMonth.startMonth, endDate: currentMonth.endMonth) - - if TimelineMonth.currentMonth == currentMonth { - try await saveItems(response?.items) - } else { - try await saveItemsBatch(response?.items) + Task.retrying { [weak self] in + let response = try await self?.apiController.getMonthsApods(startDate: currentMonth.startMonth, endDate: currentMonth.endMonth) + if currentMonth == TimelineMonth.currentMonth { + try await self?.saveItems(response?.items) + } else { + try await self?.saveItemsBatch(response?.items, currentMonth: currentMonth) + } } } + private func handleError(_ error: Error) async throws { + + } + /// Get All Data /// - Returns: Returns an array of Apods public func getAll() throws -> [Apod]? { @@ -113,20 +119,51 @@ public class ApodManagerController { for item in itemsAdd { guard let id = item.id else { return } if try storageController.getApod(id: id) == nil { - try await storageController.asyncSaveItem(item) + try storageController.saveItemsSql([item]) } } } /// Save remote data locally /// - Parameter items: Remote Items - private func saveItemsBatch(_ items: [NasaApodDto]?) async throws { + private func saveItemsBatch(_ items: [NasaApodDto]?, currentMonth: TimelineMonth) async throws { let itemsAdd: [ApodStorage] = items?.map { ApodStorage($0) } ?? [] try storageController.saveItemsSql(itemsAdd) - let returnItems = try getAll() + let returnItems = try searchLocalData(currentMonth: currentMonth) DispatchQueue.main.async { [weak self] in self?.items = returnItems } } } + +extension Task where Failure == Error { + @discardableResult + static func retrying( + priority: TaskPriority? = nil, + maxRetryCount: Int = 3, + operation: @Sendable @escaping () async throws -> Success + ) -> Task { + Task(priority: priority) { + for _ in 0...checkCancellation() + + do { + return try await operation() + } catch { + guard let apiError = error as? APIErrors else { throw error } + switch apiError { + case .authenticationError: + await LoginManagerController.shared.loginDevice() + default: + throw error + } + continue + } + } + + try Task.checkCancellation() + return try await operation() + } + } +} diff --git a/Sources/manager/Controllers/LoginManagerController.swift b/Sources/manager/Controllers/LoginManagerController.swift index 886bb13..83291ea 100644 --- a/Sources/manager/Controllers/LoginManagerController.swift +++ b/Sources/manager/Controllers/LoginManagerController.swift @@ -6,6 +6,7 @@ // import Foundation +import UIKit import apiclient import tools @@ -13,11 +14,11 @@ public class LoginManagerController { public static let shared: LoginManagerController = LoginManagerController() private let loginController: LoginManagerAPI = LoginManagerAPI() - public func loginDevice(deviceId: String) async -> String? { + public func loginDevice() async { + guard let deviceId: String = await UIDevice.current.identifierForVendor?.uuidString else { return } let login = try? await loginController.deviceLogin(deviceId: deviceId) - guard let token = login?.token else { return nil } + guard let token = login?.token else { return } let key: String = ConfigLoader.shared.appConfig.token try? KeychainStorage.shared.set(newValue: token, forKey: key) - return token } } diff --git a/Sources/manager/Models/Apod/Apod.swift b/Sources/manager/Models/Apod/Apod.swift index 66df671..886eb4b 100644 --- a/Sources/manager/Models/Apod/Apod.swift +++ b/Sources/manager/Models/Apod/Apod.swift @@ -25,7 +25,10 @@ public struct Apod: Identifiable, Hashable { public var url: String? public var hdurl: String? public var copyright: String? - + + public init() { + } + init(_ item: NasaApodDto) { self.id = item.id self.date = item.date @@ -50,18 +53,6 @@ public struct Apod: Identifiable, Hashable { self.copyright = item.copyright } - init() { - self.id = nil - self.date = nil - self.explanation = nil - self.mediaType = nil - self.thumbnailUrl = nil - self.title = nil - self.url = nil - self.hdurl = nil - self.copyright = nil - } - //MARK: Computed Properties private let hdSufix: String = "hd" @@ -100,6 +91,11 @@ public struct Apod: Identifiable, Hashable { return URL(string: thumbnailUrl) } } + + public var videoUrl: URL? { + guard let url = url else { return nil } + return URL(string: url) + } public var imageHdUrl: URL? { // Local copy diff --git a/Sources/manager/Models/Timeline/TimelineMonth.swift b/Sources/manager/Models/Timeline/TimelineMonth.swift index 7551cce..f0ce546 100644 --- a/Sources/manager/Models/Timeline/TimelineMonth.swift +++ b/Sources/manager/Models/Timeline/TimelineMonth.swift @@ -32,12 +32,12 @@ public class TimelineMonth: Timeline, Identifiable, Hashable { } public var startMonthDate: Date { - guard let date: Date = Date(year: year.value.int, month: value.int) else { return Date() } + guard let date: Date = Date(year: year.value.int, month: value.int, day: 1) else { return Date() } return date.beginning(of: .month) ?? Date() } public var startMonth: String { - guard let date: Date = Date(year: year.value.int, month: value.int) else { return "" } + guard let date: Date = Date(year: year.value.int, month: value.int, day: 1) else { return "" } let dateReturn = date.beginning(of: .month) let formatedString: String = dateReturn?.string(withFormat: "yyyy-MM-dd") ?? "" return formatedString @@ -49,7 +49,7 @@ public class TimelineMonth: Timeline, Identifiable, Hashable { formatter.locale = Locale(identifier: "UTC") formatter.timeZone = TimeZone(abbreviation: "UTC") - guard let date: Date = Date(year: year.value.int, month: value.int) else { return "" } + guard let date: Date = Date(year: year.value.int, month: value.int, day: 1) else { return "" } guard let dateReturn = date.end(of: .month) else { return "" } if dateReturn.isInFuture { @@ -63,24 +63,26 @@ public class TimelineMonth: Timeline, Identifiable, Hashable { } public var endMonthDate: Date { - guard let date: Date = Date(year: year.value.int, month: value.int) else { return Date() } + guard let date: Date = Date(year: year.value.int, month: value.int, day: 1) else { return Date() } return date.end(of: .month) ?? Date() } private var _title: String public var title: String { get { - guard let date: Date = Date(year: year.value.int, month: value.int) else { return "" } - return date.string(withFormat: "MMMM, yyyy") + guard let date: Date = Date(year: year.value.int, month: value.int, day: 1) else { return "" } + return date.string(withFormat: "MMMM, yyyy") } } - + public static var currentMonth: TimelineMonth { if let month = TimelineYear.years.first?.months.first { return month } else { let date: Date = Date() - return TimelineMonth(value: date.month.string, year: .init(value: date.year.string, months: []), apods: []) + return TimelineMonth(value: date.month.string, + year: .init(value: date.year.string, months: []), + apods: []) } } diff --git a/Tests/managerTests/managerTests.swift b/Tests/managerTests/managerTests.swift index e4c96b4..c3e2ef6 100644 --- a/Tests/managerTests/managerTests.swift +++ b/Tests/managerTests/managerTests.swift @@ -29,9 +29,8 @@ final class managerTests: XCTestCase { func testLoginController() async throws { let controller = LoginManagerController.shared - let deviceId: String = UUID().uuidString - let token = await controller.loginDevice(deviceId: deviceId) - XCTAssertNotNil(token) + await controller.loginDevice() + XCTAssertTrue(true) } func testSearch() throws { From fb3dd9bc8fc4312c6f1c56608b720e118335ea35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Neto?= Date: Tue, 30 May 2023 23:39:47 +0100 Subject: [PATCH 2/2] refactor saving --- .../Controllers/ApodManagerController.swift | 75 ++++--------------- .../Controllers/LoginManagerController.swift | 7 +- Tests/managerTests/managerTests.swift | 57 -------------- 3 files changed, 15 insertions(+), 124 deletions(-) diff --git a/Sources/manager/Controllers/ApodManagerController.swift b/Sources/manager/Controllers/ApodManagerController.swift index 96df2d2..c428fc2 100644 --- a/Sources/manager/Controllers/ApodManagerController.swift +++ b/Sources/manager/Controllers/ApodManagerController.swift @@ -51,40 +51,17 @@ public class ApodManagerController { /// Will emit saved data. /// - Parameter currentMonth: Month to search data public func getMonthData(currentMonth: TimelineMonth) async throws { - Task.retrying { [weak self] in - let response = try await self?.apiController.getMonthsApods(startDate: currentMonth.startMonth, endDate: currentMonth.endMonth) - if currentMonth == TimelineMonth.currentMonth { - try await self?.saveItems(response?.items) - } else { - try await self?.saveItemsBatch(response?.items, currentMonth: currentMonth) + let items = try storageController.searchApods(startMonth: currentMonth.startMonth, endMonth: currentMonth.endMonth) + if let apodItems = items?.mapToEntity() { + DispatchQueue.main.async { [weak self] in + self?.items = apodItems } } - } - - private func handleError(_ error: Error) async throws { - - } - /// Get All Data - /// - Returns: Returns an array of Apods - public func getAll() throws -> [Apod]? { - try storageController.getAllItems()?.mapToEntity() - } - - /// Search locally Apod using date - /// - Parameter currentMonth: TimelineMonth to search - /// - Returns: Returns a locally Apod array - public func searchLocalData(currentMonth: TimelineMonth) throws -> [Apod]? { - let items = try storageController.searchApods(startMonth: currentMonth.startMonth, endMonth: currentMonth.endMonth) - guard let items = items else { return nil } - return items.mapToEntity() - } - - /// Search locally Apod using ID - /// - Parameter id: ID to search - /// - Returns: Returns a locally Apod - public func getApod(id: UUID) throws -> Apod? { - try storageController.getApod(id: id)?.mapToEntity() + Task.retrying { [weak self] in + let response = try await self?.apiController.getMonthsApods(startDate: currentMonth.startMonth, endDate: currentMonth.endMonth) + try await self?.saveItems(response?.items) + } } /// Download images and save in Documents Folder @@ -110,51 +87,25 @@ public class ApodManagerController { /// - Parameter items: Remote Items private func saveItems(_ items: [NasaApodDto]?) async throws { let itemsAdd: [ApodStorage] = items?.map { ApodStorage($0) } ?? [] - - let tempItems = itemsAdd.mapToEntity() - DispatchQueue.main.async { [weak self] in - self?.items = tempItems - } - - for item in itemsAdd { - guard let id = item.id else { return } - if try storageController.getApod(id: id) == nil { - try storageController.saveItemsSql([item]) - } - } - } - - /// Save remote data locally - /// - Parameter items: Remote Items - private func saveItemsBatch(_ items: [NasaApodDto]?, currentMonth: TimelineMonth) async throws { - let itemsAdd: [ApodStorage] = items?.map { ApodStorage($0) } ?? [] - try storageController.saveItemsSql(itemsAdd) - let returnItems = try searchLocalData(currentMonth: currentMonth) - - DispatchQueue.main.async { [weak self] in - self?.items = returnItems - } + try await storageController.saveItemsSql(itemsAdd) } } extension Task where Failure == Error { @discardableResult - static func retrying( - priority: TaskPriority? = nil, - maxRetryCount: Int = 3, - operation: @Sendable @escaping () async throws -> Success - ) -> Task { + static func retrying(priority: TaskPriority? = nil, + maxRetryCount: Int = 3, + operation: @Sendable @escaping () async throws -> Success) -> Task { Task(priority: priority) { for _ in 0...checkCancellation() - do { return try await operation() } catch { guard let apiError = error as? APIErrors else { throw error } switch apiError { case .authenticationError: - await LoginManagerController.shared.loginDevice() + await LoginManagerController.shared.loginDevice(deviceId: UUID().uuidString) default: throw error } diff --git a/Sources/manager/Controllers/LoginManagerController.swift b/Sources/manager/Controllers/LoginManagerController.swift index 83291ea..ec1687d 100644 --- a/Sources/manager/Controllers/LoginManagerController.swift +++ b/Sources/manager/Controllers/LoginManagerController.swift @@ -6,7 +6,6 @@ // import Foundation -import UIKit import apiclient import tools @@ -14,11 +13,9 @@ public class LoginManagerController { public static let shared: LoginManagerController = LoginManagerController() private let loginController: LoginManagerAPI = LoginManagerAPI() - public func loginDevice() async { - guard let deviceId: String = await UIDevice.current.identifierForVendor?.uuidString else { return } + public func loginDevice(deviceId: String) async { let login = try? await loginController.deviceLogin(deviceId: deviceId) guard let token = login?.token else { return } - let key: String = ConfigLoader.shared.appConfig.token - try? KeychainStorage.shared.set(newValue: token, forKey: key) + KeychainStorage.shared.accessToken = token } } diff --git a/Tests/managerTests/managerTests.swift b/Tests/managerTests/managerTests.swift index c3e2ef6..1794d1e 100644 --- a/Tests/managerTests/managerTests.swift +++ b/Tests/managerTests/managerTests.swift @@ -13,63 +13,6 @@ final class managerTests: XCTestCase { cancellables = [] } - func testRemoteData() async throws { - let controller = ApodManagerController(currentMonth: TimelineMonth.currentMonth) - try await controller.getMonthData(currentMonth: TimelineMonth.currentMonth) - let items = try controller.getAll() - XCTAssertNotNil(items) - } - - func testDownloadContent() async throws { - let controller = ApodManagerController(currentMonth: TimelineMonth.currentMonth) - let items = Apod.mockItems - - try await controller.downloadContent(items: items) - } - - func testLoginController() async throws { - let controller = LoginManagerController.shared - await controller.loginDevice() - XCTAssertTrue(true) - } - - func testSearch() throws { - guard let month = TimelineYear.years.first?.months[1] else { return } - let controller = ApodManagerController(currentMonth: TimelineMonth.currentMonth) - - let allItems = try controller.getAll() - XCTAssertNotNil(allItems) - - let items = try controller.searchLocalData(currentMonth: month) - XCTAssertNotNil(items) - XCTAssertNotEqual(items?.count, 0) - } - - func testBatch() async throws { - guard let month = TimelineYear.years.first?.months[2] else { - XCTFail() - return - } - let controller = ApodManagerController(currentMonth: month) - - try await controller.getMonthData(currentMonth: month) - let allItems = try controller.searchLocalData(currentMonth: month) - XCTAssertNotNil(allItems) - } - - func testGetId() async throws { - let controller = ApodManagerController(currentMonth: TimelineMonth.currentMonth) - - try await controller.getMonthData(currentMonth: TimelineMonth.currentMonth) - let allItems = try controller.getAll() - guard let id = allItems?.first?.id else { - XCTFail() - return - } - let item = try controller.getApod(id: id) - XCTAssertEqual(allItems?.first?.id, item?.id) - } - func testTimelineController() throws { let startMonthDate: Date = TimelineMonth.currentMonth.startMonthDate XCTAssertEqual(Date().beginning(of: .month), startMonthDate)