mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-22 11:03:21 -04:00
Merge pull request #1072 from kiwix/fix-unittests
Fix "flaky" unit-tests
This commit is contained in:
commit
1a673e54b4
@ -13,17 +13,23 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
|
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
|
||||||
|
|
||||||
//
|
|
||||||
// CategoriesToLanguage.swift
|
|
||||||
// Kiwix
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Defaults
|
|
||||||
|
|
||||||
struct CategoriesToLanguages {
|
protocol CategoriesProtocol {
|
||||||
|
func has(category: Category, inLanguages langCodes: Set<String>) -> Bool
|
||||||
|
func save(_ dictionary: [Category: Set<String>])
|
||||||
|
func allCategories() -> [Category]
|
||||||
|
}
|
||||||
|
|
||||||
private let dictionary: [Category: Set<String>] = Defaults[.categoriesToLanguages]
|
struct CategoriesToLanguages: CategoriesProtocol {
|
||||||
|
|
||||||
|
private let defaults: Defaulting
|
||||||
|
private let dictionary: [Category: Set<String>]
|
||||||
|
|
||||||
|
init(withDefaults defaults: Defaulting = UDefaults()) {
|
||||||
|
self.defaults = defaults
|
||||||
|
self.dictionary = defaults[.categoriesToLanguages]
|
||||||
|
}
|
||||||
|
|
||||||
func has(category: Category, inLanguages langCodes: Set<String>) -> Bool {
|
func has(category: Category, inLanguages langCodes: Set<String>) -> Bool {
|
||||||
guard !langCodes.isEmpty, !dictionary.isEmpty else {
|
guard !langCodes.isEmpty, !dictionary.isEmpty else {
|
||||||
@ -35,15 +41,14 @@ struct CategoriesToLanguages {
|
|||||||
return !languages.isDisjoint(with: langCodes)
|
return !languages.isDisjoint(with: langCodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func save(_ dictionary: [Category: Set<String>]) {
|
func save(_ dictionary: [Category: Set<String>]) {
|
||||||
Defaults[.categoriesToLanguages] = dictionary
|
defaults[.categoriesToLanguages] = dictionary
|
||||||
}
|
}
|
||||||
|
|
||||||
static func allCategories() -> [Category] {
|
func allCategories() -> [Category] {
|
||||||
let categoriesToLanguages = CategoriesToLanguages()
|
let contentLanguages = defaults[.libraryLanguageCodes]
|
||||||
let contentLanguages = Defaults[.libraryLanguageCodes]
|
|
||||||
return Category.allCases.filter { (category: Category) in
|
return Category.allCases.filter { (category: Category) in
|
||||||
categoriesToLanguages.has(category: category, inLanguages: contentLanguages)
|
has(category: category, inLanguages: contentLanguages)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
Model/Defaulting.swift
Normal file
32
Model/Defaulting.swift
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// This file is part of Kiwix for iOS & macOS.
|
||||||
|
//
|
||||||
|
// Kiwix is free software; you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kiwix is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Defaults
|
||||||
|
|
||||||
|
public protocol Defaulting: NSObjectProtocol {
|
||||||
|
subscript<Value: Defaults.Serializable>(key: Defaults.Key<Value>) -> Value { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
final class UDefaults: NSObject, Defaulting {
|
||||||
|
subscript<Value>(key: Defaults.Key<Value>) -> Value where Value: DefaultsSerializable {
|
||||||
|
get {
|
||||||
|
Defaults[key]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
Defaults[key] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -36,7 +36,7 @@ enum ActiveSheet: Hashable, Identifiable {
|
|||||||
case safari(url: URL)
|
case safari(url: URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Category: String, CaseIterable, Identifiable, LosslessStringConvertible {
|
enum Category: String, CaseIterable, Identifiable, LosslessStringConvertible, Hashable {
|
||||||
var description: String { rawValue }
|
var description: String { rawValue }
|
||||||
|
|
||||||
var id: String { rawValue }
|
var id: String { rawValue }
|
||||||
|
@ -60,6 +60,7 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func makeOPDSData(zimFileID: UUID) -> String {
|
private func makeOPDSData(zimFileID: UUID) -> String {
|
||||||
|
// swiftlint:disable line_length
|
||||||
"""
|
"""
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||||
xmlns:dc="http://purl.org/dc/terms/"
|
xmlns:dc="http://purl.org/dc/terms/"
|
||||||
@ -88,6 +89,7 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
|||||||
</entry>
|
</entry>
|
||||||
</feed>
|
</feed>
|
||||||
"""
|
"""
|
||||||
|
// swiftlint:enable line_length
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test time out fetching library data.
|
/// Test time out fetching library data.
|
||||||
@ -96,9 +98,12 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
|||||||
HTTPTestingURLProtocol.handler = { urlProtocol in
|
HTTPTestingURLProtocol.handler = { urlProtocol in
|
||||||
urlProtocol.client?.urlProtocol(urlProtocol, didFailWithError: URLError(URLError.Code.timedOut))
|
urlProtocol.client?.urlProtocol(urlProtocol, didFailWithError: URLError(URLError.Code.timedOut))
|
||||||
}
|
}
|
||||||
|
let testDefaults = TestDefaults()
|
||||||
|
testDefaults.setup()
|
||||||
let viewModel = LibraryViewModel(urlSession: urlSession,
|
let viewModel = LibraryViewModel(urlSession: urlSession,
|
||||||
processFactory: { LibraryProcess() })
|
processFactory: { LibraryProcess(defaultState: .initial) },
|
||||||
|
defaults: testDefaults,
|
||||||
|
categories: CategoriesToLanguages(withDefaults: testDefaults))
|
||||||
await viewModel.start(isUserInitiated: true)
|
await viewModel.start(isUserInitiated: true)
|
||||||
XCTAssert(viewModel.error is LibraryRefreshError)
|
XCTAssert(viewModel.error is LibraryRefreshError)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@ -119,8 +124,12 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
|||||||
urlProtocol.client?.urlProtocolDidFinishLoading(urlProtocol)
|
urlProtocol.client?.urlProtocolDidFinishLoading(urlProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let testDefaults = TestDefaults()
|
||||||
|
testDefaults.setup()
|
||||||
let viewModel = LibraryViewModel(urlSession: urlSession,
|
let viewModel = LibraryViewModel(urlSession: urlSession,
|
||||||
processFactory: { LibraryProcess() })
|
processFactory: { LibraryProcess(defaultState: .initial) },
|
||||||
|
defaults: testDefaults,
|
||||||
|
categories: CategoriesToLanguages(withDefaults: testDefaults))
|
||||||
await viewModel.start(isUserInitiated: true)
|
await viewModel.start(isUserInitiated: true)
|
||||||
XCTAssert(viewModel.error is LibraryRefreshError)
|
XCTAssert(viewModel.error is LibraryRefreshError)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@ -137,13 +146,17 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
|||||||
url: URL.mock(),
|
url: URL.mock(),
|
||||||
statusCode: 200, httpVersion: nil, headerFields: [:]
|
statusCode: 200, httpVersion: nil, headerFields: [:]
|
||||||
)!
|
)!
|
||||||
urlProtocol.client?.urlProtocol(urlProtocol, didLoad: "Invalid OPDS Data".data(using: .utf8)!)
|
urlProtocol.client?.urlProtocol(urlProtocol, didLoad: Data("Invalid OPDS Data".utf8))
|
||||||
urlProtocol.client?.urlProtocol(urlProtocol, didReceive: response, cacheStoragePolicy: .notAllowed)
|
urlProtocol.client?.urlProtocol(urlProtocol, didReceive: response, cacheStoragePolicy: .notAllowed)
|
||||||
urlProtocol.client?.urlProtocolDidFinishLoading(urlProtocol)
|
urlProtocol.client?.urlProtocolDidFinishLoading(urlProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let testDefaults = TestDefaults()
|
||||||
|
testDefaults.setup()
|
||||||
let viewModel = LibraryViewModel(urlSession: urlSession,
|
let viewModel = LibraryViewModel(urlSession: urlSession,
|
||||||
processFactory: { LibraryProcess() })
|
processFactory: { LibraryProcess(defaultState: .initial) },
|
||||||
|
defaults: testDefaults,
|
||||||
|
categories: CategoriesToLanguages(withDefaults: testDefaults))
|
||||||
await viewModel.start(isUserInitiated: true)
|
await viewModel.start(isUserInitiated: true)
|
||||||
XCTExpectFailure("Requires work in dependency to resolve the issue.")
|
XCTExpectFailure("Requires work in dependency to resolve the issue.")
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@ -154,6 +167,7 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
|||||||
|
|
||||||
/// Test zim file entity is created, and metadata are saved when new zim file becomes available in online catalog.
|
/// Test zim file entity is created, and metadata are saved when new zim file becomes available in online catalog.
|
||||||
@MainActor
|
@MainActor
|
||||||
|
// swiftlint:disable:next function_body_length
|
||||||
func testNewZimFileAndProperties() async throws {
|
func testNewZimFileAndProperties() async throws {
|
||||||
let zimFileID = UUID()
|
let zimFileID = UUID()
|
||||||
HTTPTestingURLProtocol.handler = { urlProtocol in
|
HTTPTestingURLProtocol.handler = { urlProtocol in
|
||||||
@ -166,9 +180,12 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
|||||||
urlProtocol.client?.urlProtocol(urlProtocol, didReceive: response, cacheStoragePolicy: .notAllowed)
|
urlProtocol.client?.urlProtocol(urlProtocol, didReceive: response, cacheStoragePolicy: .notAllowed)
|
||||||
urlProtocol.client?.urlProtocolDidFinishLoading(urlProtocol)
|
urlProtocol.client?.urlProtocolDidFinishLoading(urlProtocol)
|
||||||
}
|
}
|
||||||
|
let testDefaults = TestDefaults()
|
||||||
|
testDefaults.setup()
|
||||||
let viewModel = LibraryViewModel(urlSession: urlSession,
|
let viewModel = LibraryViewModel(urlSession: urlSession,
|
||||||
processFactory: { LibraryProcess() })
|
processFactory: { LibraryProcess(defaultState: .initial) },
|
||||||
|
defaults: testDefaults,
|
||||||
|
categories: CategoriesToLanguages(withDefaults: testDefaults))
|
||||||
await viewModel.start(isUserInitiated: true)
|
await viewModel.start(isUserInitiated: true)
|
||||||
|
|
||||||
// check no error has happened
|
// check no error has happened
|
||||||
@ -185,6 +202,7 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
|||||||
XCTAssertEqual(zimFile.id, zimFileID)
|
XCTAssertEqual(zimFile.id, zimFileID)
|
||||||
XCTAssertEqual(zimFile.articleCount, 50001)
|
XCTAssertEqual(zimFile.articleCount, 50001)
|
||||||
XCTAssertEqual(zimFile.category, Category.wikipedia.rawValue)
|
XCTAssertEqual(zimFile.category, Category.wikipedia.rawValue)
|
||||||
|
// swiftlint:disable:next force_try
|
||||||
XCTAssertEqual(zimFile.created, try! Date("2023-01-07T00:00:00Z", strategy: .iso8601))
|
XCTAssertEqual(zimFile.created, try! Date("2023-01-07T00:00:00Z", strategy: .iso8601))
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
zimFile.downloadURL,
|
zimFile.downloadURL,
|
||||||
@ -211,14 +229,21 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
|||||||
XCTAssertEqual(zimFile.persistentID, "wikipedia_en_top")
|
XCTAssertEqual(zimFile.persistentID, "wikipedia_en_top")
|
||||||
XCTAssertEqual(zimFile.requiresServiceWorkers, false)
|
XCTAssertEqual(zimFile.requiresServiceWorkers, false)
|
||||||
XCTAssertEqual(zimFile.size, 6515656704)
|
XCTAssertEqual(zimFile.size, 6515656704)
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
context.delete(zimFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test zim file deprecation
|
/// Test zim file deprecation
|
||||||
@MainActor
|
@MainActor
|
||||||
func testZimFileDeprecation() async throws {
|
func testZimFileDeprecation() async throws {
|
||||||
|
let testDefaults = TestDefaults()
|
||||||
|
testDefaults.setup()
|
||||||
// refresh library for the first time, which should create one zim file
|
// refresh library for the first time, which should create one zim file
|
||||||
let viewModel = LibraryViewModel(urlSession: urlSession,
|
let viewModel = LibraryViewModel(urlSession: urlSession,
|
||||||
processFactory: { LibraryProcess() })
|
processFactory: { LibraryProcess(defaultState: .initial) },
|
||||||
|
defaults: testDefaults,
|
||||||
|
categories: CategoriesToLanguages(withDefaults: testDefaults))
|
||||||
await viewModel.start(isUserInitiated: true)
|
await viewModel.start(isUserInitiated: true)
|
||||||
let context = Database.shared.viewContext
|
let context = Database.shared.viewContext
|
||||||
let zimFile1 = try XCTUnwrap(try context.fetch(ZimFile.fetchRequest()).first)
|
let zimFile1 = try XCTUnwrap(try context.fetch(ZimFile.fetchRequest()).first)
|
||||||
@ -231,7 +256,7 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
|||||||
XCTAssertNotEqual(zimFile1.fileID, zimFile2.fileID)
|
XCTAssertNotEqual(zimFile1.fileID, zimFile2.fileID)
|
||||||
|
|
||||||
// set fileURLBookmark of zim file 2
|
// set fileURLBookmark of zim file 2
|
||||||
zimFile2.fileURLBookmark = "/Users/tester/Downloads/file_url.zim".data(using: .utf8)
|
zimFile2.fileURLBookmark = Data("/Users/tester/Downloads/file_url.zim".utf8)
|
||||||
try context.save()
|
try context.save()
|
||||||
|
|
||||||
// refresh library for the third time
|
// refresh library for the third time
|
||||||
@ -241,6 +266,10 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
|||||||
// check there are two zim files in the database, and zim file 2 is not deprecated
|
// check there are two zim files in the database, and zim file 2 is not deprecated
|
||||||
XCTAssertEqual(zimFiles.count, 2)
|
XCTAssertEqual(zimFiles.count, 2)
|
||||||
XCTAssertEqual(zimFiles.filter({ $0.fileID == zimFile2.fileID }).count, 1)
|
XCTAssertEqual(zimFiles.filter({ $0.fileID == zimFile2.fileID }).count, 1)
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
context.delete(zimFile1)
|
||||||
|
context.delete(zimFile2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
Tests/TestDefaults.swift
Normal file
41
Tests/TestDefaults.swift
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// This file is part of Kiwix for iOS & macOS.
|
||||||
|
//
|
||||||
|
// Kiwix is free software; you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kiwix is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Defaults
|
||||||
|
@testable import Kiwix
|
||||||
|
|
||||||
|
final class TestDefaults: NSObject, Defaulting {
|
||||||
|
|
||||||
|
var dict: [Defaults.AnyKey: any DefaultsSerializable] = [:]
|
||||||
|
|
||||||
|
func setup() {
|
||||||
|
self[.categoriesToLanguages] = [:]
|
||||||
|
self[.libraryAutoRefresh] = false
|
||||||
|
self[.libraryETag] = ""
|
||||||
|
self[.libraryUsingOldISOLangCodes] = false
|
||||||
|
self[.libraryLanguageCodes] = Set<String>()
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript<Value>(key: Defaults.Key<Value>) -> Value where Value: DefaultsSerializable {
|
||||||
|
get {
|
||||||
|
// swiftlint:disable:next force_cast
|
||||||
|
dict[key] as! Value
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
dict[key] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
import CoreData
|
import CoreData
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
enum LibraryState {
|
enum LibraryState {
|
||||||
@ -24,8 +23,8 @@ enum LibraryState {
|
|||||||
case complete
|
case complete
|
||||||
case error
|
case error
|
||||||
|
|
||||||
static func defaultState() -> LibraryState {
|
static func defaultState(defaults: Defaulting = UDefaults()) -> LibraryState {
|
||||||
if Defaults[.libraryLastRefresh] == nil {
|
if defaults[.libraryLastRefresh] == nil {
|
||||||
return .initial
|
return .initial
|
||||||
} else {
|
} else {
|
||||||
return .complete
|
return .complete
|
||||||
@ -52,6 +51,8 @@ final class LibraryViewModel: ObservableObject {
|
|||||||
@MainActor @Published var state: LibraryState
|
@MainActor @Published var state: LibraryState
|
||||||
@MainActor private let process: LibraryProcess
|
@MainActor private let process: LibraryProcess
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
private let defaults: Defaulting
|
||||||
|
private let categories: CategoriesProtocol
|
||||||
|
|
||||||
private let urlSession: URLSession
|
private let urlSession: URLSession
|
||||||
private var insertionCount = 0
|
private var insertionCount = 0
|
||||||
@ -60,9 +61,16 @@ final class LibraryViewModel: ObservableObject {
|
|||||||
private static let catalogURL = URL(string: "https://library.kiwix.org/catalog/v2/entries?count=-1")!
|
private static let catalogURL = URL(string: "https://library.kiwix.org/catalog/v2/entries?count=-1")!
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
init(urlSession: URLSession? = nil, processFactory: @MainActor () -> LibraryProcess = { .shared }) {
|
init(
|
||||||
|
urlSession: URLSession? = nil,
|
||||||
|
processFactory: @MainActor () -> LibraryProcess = { .shared },
|
||||||
|
defaults: Defaulting = UDefaults(),
|
||||||
|
categories: CategoriesProtocol = CategoriesToLanguages(withDefaults: UDefaults())
|
||||||
|
) {
|
||||||
self.urlSession = urlSession ?? URLSession.shared
|
self.urlSession = urlSession ?? URLSession.shared
|
||||||
self.process = processFactory()
|
self.process = processFactory()
|
||||||
|
self.defaults = defaults
|
||||||
|
self.categories = categories
|
||||||
state = process.state
|
state = process.state
|
||||||
process.$state.sink { [weak self] newState in
|
process.$state.sink { [weak self] newState in
|
||||||
self?.state = newState
|
self?.state = newState
|
||||||
@ -78,8 +86,8 @@ final class LibraryViewModel: ObservableObject {
|
|||||||
guard process.state != .inProgress else { return }
|
guard process.state != .inProgress else { return }
|
||||||
do {
|
do {
|
||||||
// decide if refresh should proceed
|
// decide if refresh should proceed
|
||||||
let lastRefresh: Date? = Defaults[.libraryLastRefresh]
|
let lastRefresh: Date? = defaults[.libraryLastRefresh]
|
||||||
let hasAutoRefresh: Bool = Defaults[.libraryAutoRefresh]
|
let hasAutoRefresh: Bool = defaults[.libraryAutoRefresh]
|
||||||
let isStale = (lastRefresh?.timeIntervalSinceNow ?? -3600) <= -3600
|
let isStale = (lastRefresh?.timeIntervalSinceNow ?? -3600) <= -3600
|
||||||
guard isUserInitiated || (hasAutoRefresh && isStale) else { return }
|
guard isUserInitiated || (hasAutoRefresh && isStale) else { return }
|
||||||
|
|
||||||
@ -90,7 +98,7 @@ final class LibraryViewModel: ObservableObject {
|
|||||||
// this is the case when we have no new data (304 http)
|
// this is the case when we have no new data (304 http)
|
||||||
// but we still need to refresh the memory only stored
|
// but we still need to refresh the memory only stored
|
||||||
// zimfile categories to languages dictionary
|
// zimfile categories to languages dictionary
|
||||||
if CategoriesToLanguages.allCategories().count < 2 {
|
if categories.allCategories().count < 2 {
|
||||||
let context = Database.shared.viewContext
|
let context = Database.shared.viewContext
|
||||||
if let zimFiles: [ZimFile] = try? context.fetch(ZimFile.fetchRequest()) {
|
if let zimFiles: [ZimFile] = try? context.fetch(ZimFile.fetchRequest()) {
|
||||||
saveCategoryAvailableInLanguages(fromDBZimFiles: zimFiles)
|
saveCategoryAvailableInLanguages(fromDBZimFiles: zimFiles)
|
||||||
@ -113,15 +121,15 @@ final class LibraryViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
let parser = try await parse(data: data)
|
let parser = try await parse(data: data)
|
||||||
// delete all old ISO Lang Code entries if needed, by passing in an empty parser
|
// delete all old ISO Lang Code entries if needed, by passing in an empty parser
|
||||||
if Defaults[.libraryUsingOldISOLangCodes] {
|
if defaults[.libraryUsingOldISOLangCodes] {
|
||||||
try await process(parser: DeletingParser())
|
try await process(parser: DeletingParser())
|
||||||
Defaults[.libraryUsingOldISOLangCodes] = false
|
defaults[.libraryUsingOldISOLangCodes] = false
|
||||||
}
|
}
|
||||||
// process the feed
|
// process the feed
|
||||||
try await process(parser: parser)
|
try await process(parser: parser)
|
||||||
|
|
||||||
// update library last refresh timestamp
|
// update library last refresh timestamp
|
||||||
Defaults[.libraryLastRefresh] = Date()
|
defaults[.libraryLastRefresh] = Date()
|
||||||
|
|
||||||
saveCategoryAvailableInLanguages(using: parser)
|
saveCategoryAvailableInLanguages(using: parser)
|
||||||
|
|
||||||
@ -156,7 +164,7 @@ final class LibraryViewModel: ObservableObject {
|
|||||||
dictionary[category] = allLanguagesForCategory
|
dictionary[category] = allLanguagesForCategory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CategoriesToLanguages.save(dictionary)
|
categories.save(dictionary)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func saveCategoryAvailableInLanguages(fromDBZimFiles zimFiles: [ZimFile]) {
|
private func saveCategoryAvailableInLanguages(fromDBZimFiles zimFiles: [ZimFile]) {
|
||||||
@ -172,7 +180,7 @@ final class LibraryViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
dictionary[category] = allLanguagesForCategory
|
dictionary[category] = allLanguagesForCategory
|
||||||
}
|
}
|
||||||
CategoriesToLanguages.save(dictionary)
|
categories.save(dictionary)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The fetched content is filtered by the languages set in settings.
|
/// The fetched content is filtered by the languages set in settings.
|
||||||
@ -183,10 +191,10 @@ final class LibraryViewModel: ObservableObject {
|
|||||||
let validCodes = Set<String>(languages.map { $0.code })
|
let validCodes = Set<String>(languages.map { $0.code })
|
||||||
// preserve only valid selections by:
|
// preserve only valid selections by:
|
||||||
// converting earlier user selections, and filtering out invalid ones
|
// converting earlier user selections, and filtering out invalid ones
|
||||||
Defaults[.libraryLanguageCodes] = LanguagesConverter.convert(codes: Defaults[.libraryLanguageCodes],
|
defaults[.libraryLanguageCodes] = LanguagesConverter.convert(codes: defaults[.libraryLanguageCodes],
|
||||||
validCodes: validCodes)
|
validCodes: validCodes)
|
||||||
|
|
||||||
guard Defaults[.libraryLanguageCodes].isEmpty else {
|
guard defaults[.libraryLanguageCodes].isEmpty else {
|
||||||
return // what was earlier set by the user or picked by default is valid
|
return // what was earlier set by the user or picked by default is valid
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,22 +209,22 @@ final class LibraryViewModel: ObservableObject {
|
|||||||
let deviceLangSet = Set<String>([deviceLang].compactMap { $0 })
|
let deviceLangSet = Set<String>([deviceLang].compactMap { $0 })
|
||||||
let validDefaults = LanguagesConverter.convert(codes: deviceLangSet, validCodes: validCodes)
|
let validDefaults = LanguagesConverter.convert(codes: deviceLangSet, validCodes: validCodes)
|
||||||
if validDefaults.isEmpty { // meaning the device language isn't valid (or nil)
|
if validDefaults.isEmpty { // meaning the device language isn't valid (or nil)
|
||||||
Defaults[.libraryLanguageCodes] = [fallbackToEnglish]
|
defaults[.libraryLanguageCodes] = [fallbackToEnglish]
|
||||||
} else {
|
} else {
|
||||||
Defaults[.libraryLanguageCodes] = validDefaults
|
defaults[.libraryLanguageCodes] = validDefaults
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchData() async throws -> Data? {
|
private func fetchData() async throws -> Data? {
|
||||||
do {
|
do {
|
||||||
var request = URLRequest(url: Self.catalogURL, timeoutInterval: 20)
|
var request = URLRequest(url: Self.catalogURL, timeoutInterval: 20)
|
||||||
request.allHTTPHeaderFields = ["If-None-Match": Defaults[.libraryETag]]
|
request.allHTTPHeaderFields = ["If-None-Match": defaults[.libraryETag]]
|
||||||
let (data, response) = try await self.urlSession.data(for: request)
|
let (data, response) = try await self.urlSession.data(for: request)
|
||||||
guard let response = response as? HTTPURLResponse else { return nil }
|
guard let response = response as? HTTPURLResponse else { return nil }
|
||||||
switch response.statusCode {
|
switch response.statusCode {
|
||||||
case 200:
|
case 200:
|
||||||
if let eTag = response.allHeaderFields["Etag"] as? String {
|
if let eTag = response.allHeaderFields["Etag"] as? String {
|
||||||
Defaults[.libraryETag] = eTag
|
defaults[.libraryETag] = eTag
|
||||||
}
|
}
|
||||||
// OK to process further
|
// OK to process further
|
||||||
os_log("Retrieved OPDS Data, size: %llu bytes", log: Log.OPDS, type: .info, data.count)
|
os_log("Retrieved OPDS Data, size: %llu bytes", log: Log.OPDS, type: .info, data.count)
|
||||||
|
@ -26,9 +26,12 @@ struct Library: View {
|
|||||||
private let categories: [Category]
|
private let categories: [Category]
|
||||||
let dismiss: (() -> Void)?
|
let dismiss: (() -> Void)?
|
||||||
|
|
||||||
init(dismiss: (() -> Void)?) {
|
init(
|
||||||
|
dismiss: (() -> Void)?,
|
||||||
|
categories: [Category] = CategoriesToLanguages().allCategories()
|
||||||
|
) {
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
categories = CategoriesToLanguages.allCategories()
|
self.categories = categories
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -24,8 +24,11 @@ struct ZimFilesCategories: View {
|
|||||||
private var categories: [Category]
|
private var categories: [Category]
|
||||||
private let dismiss: (() -> Void)?
|
private let dismiss: (() -> Void)?
|
||||||
|
|
||||||
init(dismiss: (() -> Void)?) {
|
init(
|
||||||
categories = CategoriesToLanguages.allCategories()
|
dismiss: (() -> Void)?,
|
||||||
|
categories: [Category] = CategoriesToLanguages().allCategories()
|
||||||
|
) {
|
||||||
|
self.categories = categories
|
||||||
selected = categories.first ?? .wikipedia
|
selected = categories.first ?? .wikipedia
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user