mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-14 14:49:50 -04:00
Library category view (#390)
* category view * build number * LibraryCategoryView * fix * FaviconDownloadService * remove old code * build number
This commit is contained in:
parent
98ca3b6473
commit
c865da5515
@ -38,6 +38,7 @@
|
||||
9768C23B1F4B7F6300FD499B /* Preference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9768C23A1F4B7F6300FD499B /* Preference.swift */; };
|
||||
976A65B32659489F009A97C6 /* SearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976A65B22659489F009A97C6 /* SearchResult.swift */; };
|
||||
976A65B42659489F009A97C6 /* SearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976A65B22659489F009A97C6 /* SearchResult.swift */; };
|
||||
977959C026D494C500D48E4A /* FaviconDownloadService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977959BF26D494C500D48E4A /* FaviconDownloadService.swift */; };
|
||||
9779A5B22456793600F6F6FF /* LibraryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9779A59C2456793500F6F6FF /* LibraryService.swift */; };
|
||||
9779A5B42456793600F6F6FF /* BookmarkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9779A59D2456793500F6F6FF /* BookmarkService.swift */; };
|
||||
9779A5B52456793600F6F6FF /* BookmarkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9779A59D2456793500F6F6FF /* BookmarkService.swift */; };
|
||||
@ -234,6 +235,7 @@
|
||||
9768C2341F4B7BAC00FD499B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
9768C23A1F4B7F6300FD499B /* Preference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preference.swift; sourceTree = "<group>"; };
|
||||
976A65B22659489F009A97C6 /* SearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResult.swift; sourceTree = "<group>"; };
|
||||
977959BF26D494C500D48E4A /* FaviconDownloadService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconDownloadService.swift; sourceTree = "<group>"; };
|
||||
9779A59C2456793500F6F6FF /* LibraryService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryService.swift; sourceTree = "<group>"; };
|
||||
9779A59D2456793500F6F6FF /* BookmarkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkService.swift; sourceTree = "<group>"; };
|
||||
9779A59F2456793500F6F6FF /* LibraryScanOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryScanOperation.swift; sourceTree = "<group>"; };
|
||||
@ -526,6 +528,7 @@
|
||||
children = (
|
||||
9779A5A62456793500F6F6FF /* ZimFileService */,
|
||||
9779A5D52456796A00F6F6FF /* DownloadService.swift */,
|
||||
977959BF26D494C500D48E4A /* FaviconDownloadService.swift */,
|
||||
9779A59C2456793500F6F6FF /* LibraryService.swift */,
|
||||
9779A59D2456793500F6F6FF /* BookmarkService.swift */,
|
||||
9793548A257D86370076E94A /* Queries.swift */,
|
||||
@ -1086,6 +1089,7 @@
|
||||
9779A7AF2456796B00F6F6FF /* Settings.swift in Sources */,
|
||||
97225A982618B2A200D8CB32 /* LibraryLanguageView.swift in Sources */,
|
||||
972521012009616B00B60A80 /* EmptyContentView.swift in Sources */,
|
||||
977959C026D494C500D48E4A /* FaviconDownloadService.swift in Sources */,
|
||||
9797435C257BF33600D30F03 /* WebViewController.swift in Sources */,
|
||||
97A36C571F8D5FCD0079B452 /* LibraryController.swift in Sources */,
|
||||
97A36C321F8C21210079B452 /* AppDelegate.swift in Sources */,
|
||||
@ -1424,7 +1428,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = iOS/Support/Kiwix.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 66;
|
||||
CURRENT_PROJECT_VERSION = 68;
|
||||
DEVELOPMENT_TEAM = L7HWM3SP3L;
|
||||
ENABLE_BITCODE = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = c11;
|
||||
@ -1464,7 +1468,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = iOS/Support/Kiwix.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 66;
|
||||
CURRENT_PROJECT_VERSION = 68;
|
||||
DEVELOPMENT_TEAM = L7HWM3SP3L;
|
||||
ENABLE_BITCODE = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = c11;
|
||||
@ -1556,7 +1560,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = iOS/BookmarksWidget/Bookmarks.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 66;
|
||||
CURRENT_PROJECT_VERSION = 68;
|
||||
DEVELOPMENT_TEAM = L7HWM3SP3L;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = iOS/BookmarksWidget/Info.plist;
|
||||
@ -1588,7 +1592,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = iOS/BookmarksWidget/Bookmarks.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 66;
|
||||
CURRENT_PROJECT_VERSION = 68;
|
||||
DEVELOPMENT_TEAM = L7HWM3SP3L;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = iOS/BookmarksWidget/Info.plist;
|
||||
|
@ -6,7 +6,6 @@
|
||||
// Copyright © 2020 Chris Li. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import os
|
||||
import Defaults
|
||||
import RealmSwift
|
||||
|
77
Model/Services/FaviconDownloadService.swift
Normal file
77
Model/Services/FaviconDownloadService.swift
Normal file
@ -0,0 +1,77 @@
|
||||
//
|
||||
// FaviconDownloadService.swift
|
||||
// Kiwix
|
||||
//
|
||||
// Created by Chris Li on 8/23/21.
|
||||
// Copyright © 2021 Chris Li. All rights reserved.
|
||||
//
|
||||
|
||||
import os
|
||||
import RealmSwift
|
||||
|
||||
class FaviconDownloadService: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDataDelegate {
|
||||
static let shared = FaviconDownloadService()
|
||||
|
||||
private let queue = DispatchQueue(label: "org.kiwix.faviconDownload")
|
||||
private var cache = [String: Data]()
|
||||
private var retryCounter = [String: Int]()
|
||||
|
||||
private lazy var session: URLSession = {
|
||||
var configuration = URLSessionConfiguration.default
|
||||
configuration.httpMaximumConnectionsPerHost = 1
|
||||
let operationQueue = OperationQueue()
|
||||
operationQueue.underlyingQueue = queue
|
||||
return URLSession(configuration: configuration, delegate: self, delegateQueue: operationQueue)
|
||||
}()
|
||||
|
||||
private override init() { }
|
||||
|
||||
func download(zimFile: ZimFile) {
|
||||
guard let faviconURL = zimFile.faviconURL, let url = URL(string: faviconURL) else { return }
|
||||
let task = session.dataTask(with: url)
|
||||
task.taskDescription = zimFile.fileID
|
||||
task.resume()
|
||||
}
|
||||
|
||||
private func retry(zimFileID: String) {
|
||||
guard retryCounter[zimFileID, default: 0] < 3 else { return }
|
||||
os_log("Retry downloading favicon data: %@.", log: Log.FaviconDownloadService, type: .info, zimFileID)
|
||||
retryCounter[zimFileID, default: 0] += 1
|
||||
queue.asyncAfter(deadline: DispatchTime.now() + 5) {
|
||||
guard let database = try? Realm(),
|
||||
let zimFile = database.object(ofType: ZimFile.self, forPrimaryKey: zimFileID) else { return }
|
||||
self.download(zimFile: zimFile)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Delegates
|
||||
|
||||
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
|
||||
guard let zimFileID = dataTask.taskDescription else { return }
|
||||
var faviconData = cache[zimFileID, default: Data()]
|
||||
faviconData.append(data)
|
||||
cache[zimFileID] = faviconData
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
guard let zimFileID = task.taskDescription else { return }
|
||||
defer { cache[zimFileID] = nil }
|
||||
|
||||
// retry download later if request was unsuccessful
|
||||
guard let response = task.response as? HTTPURLResponse, response.statusCode == 200 else {
|
||||
retry(zimFileID: zimFileID)
|
||||
return
|
||||
}
|
||||
|
||||
// save favicon data to database
|
||||
do {
|
||||
let database = try Realm()
|
||||
try database.write {
|
||||
guard let zimFile = database.object(ofType: ZimFile.self, forPrimaryKey: zimFileID) else { return }
|
||||
zimFile.faviconData = cache[zimFileID]
|
||||
}
|
||||
} catch {
|
||||
os_log("Failed to save favicon data.", log: Log.FaviconDownloadService, type: .error)
|
||||
}
|
||||
}
|
||||
}
|
@ -88,31 +88,4 @@ class LibraryService {
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// MARK: - Favicon Download
|
||||
|
||||
/// Download and save favicon data of a zim file.
|
||||
/// - Parameters:
|
||||
/// - zimFileID: ID of a zim file
|
||||
/// - url: URL of the favicon data
|
||||
@available(iOS 13.0, *)
|
||||
func downloadFavicons(zimFiles: [ZimFile]) {
|
||||
let urls = Set(zimFiles.compactMap { $0.faviconURL }.compactMap { URL(string: $0) })
|
||||
let publishers = urls.map { URLSession.shared.dataTaskPublisher(for: $0) }
|
||||
faviconDownloadPipeline = Combine.Publishers.MergeMany(publishers)
|
||||
.collect(5)
|
||||
.sink(receiveCompletion: { _ in }, receiveValue: { results in
|
||||
do {
|
||||
let database = try Realm()
|
||||
try database.write {
|
||||
for result in results {
|
||||
guard let url = result.response.url else { continue }
|
||||
database.objects(ZimFile.self)
|
||||
.filter("faviconURL = %@", url.absoluteString)
|
||||
.forEach { zimFile in zimFile.faviconData = result.data }
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ private let subsystem = "org.kiwix.kiwix"
|
||||
|
||||
struct Log {
|
||||
static let DownloadService = OSLog(subsystem: subsystem, category: "DownloadService")
|
||||
static let FaviconDownloadService = OSLog(subsystem: subsystem, category: "FaviconDownloadService")
|
||||
static let LibraryService = OSLog(subsystem: subsystem, category: "LibraryService")
|
||||
static let OPDS = OSLog(subsystem: subsystem, category: "OPDS")
|
||||
static let URLSchemeHandler = OSLog(subsystem: subsystem, category: "URLSchemeHandler")
|
||||
|
@ -64,26 +64,33 @@ struct LibraryCategoryView: View {
|
||||
@Published private(set) var zimFiles = [String: [ZimFile]]()
|
||||
|
||||
let category: ZimFile.Category
|
||||
private let database = try? Realm()
|
||||
private let queue = DispatchQueue(label: "org.kiwix.library.category", qos: .userInitiated)
|
||||
private var defaultsSubscriber: AnyCancellable?
|
||||
private var languageCodeObserver: Defaults.Observation?
|
||||
private var collectionSubscriber: AnyCancellable?
|
||||
|
||||
init(category: ZimFile.Category) {
|
||||
self.category = category
|
||||
defaultsSubscriber = Defaults.publisher(.libraryLanguageCodes)
|
||||
.sink(receiveValue: { languageCodes in
|
||||
self.loadData(languageCodes: languageCodes.newValue)
|
||||
self.downloadFavicon(languageCodes: languageCodes.newValue)
|
||||
})
|
||||
languageCodeObserver = Defaults.observe(.libraryLanguageCodes) { languageCodes in
|
||||
self.loadData(languageCodes: languageCodes.newValue)
|
||||
self.database?.objects(ZimFile.self)
|
||||
.filter(NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
NSPredicate(format: "categoryRaw = %@", category.rawValue),
|
||||
NSPredicate(format: "languageCode IN %@", languageCodes.newValue),
|
||||
NSPredicate(format: "faviconData = nil"),
|
||||
NSPredicate(format: "faviconURL != nil"),
|
||||
]))
|
||||
.forEach { FaviconDownloadService.shared.download(zimFile: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData(languageCodes: [String]) {
|
||||
let database = try? Realm()
|
||||
var predicates = [NSPredicate(format: "categoryRaw = %@", category.rawValue)]
|
||||
if !languageCodes.isEmpty {
|
||||
predicates.append(NSPredicate(format: "languageCode IN %@", languageCodes))
|
||||
}
|
||||
collectionSubscriber = database?.objects(ZimFile.self)
|
||||
.filter(NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
NSPredicate(format: "categoryRaw = %@", category.rawValue),
|
||||
NSPredicate(format: "languageCode IN %@", languageCodes),
|
||||
]))
|
||||
.filter(NSCompoundPredicate(andPredicateWithSubpredicates: predicates))
|
||||
.sorted(by: [
|
||||
SortDescriptor(keyPath: "title", ascending: true),
|
||||
SortDescriptor(keyPath: "size", ascending: false)
|
||||
@ -91,6 +98,7 @@ struct LibraryCategoryView: View {
|
||||
.collectionPublisher
|
||||
.subscribe(on: queue)
|
||||
.freeze()
|
||||
.throttle(for: 0.2, scheduler: queue, latest: true)
|
||||
.map { zimFiles in
|
||||
var results = [String: [ZimFile]]()
|
||||
for zimFile in zimFiles {
|
||||
@ -109,19 +117,5 @@ struct LibraryCategoryView: View {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func downloadFavicon(languageCodes: [String]) {
|
||||
do {
|
||||
let database = try Realm()
|
||||
let zimFiles = database.objects(ZimFile.self)
|
||||
.filter(NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
NSPredicate(format: "categoryRaw = %@", category.rawValue),
|
||||
NSPredicate(format: "languageCode IN %@", languageCodes),
|
||||
NSPredicate(format: "faviconData = nil"),
|
||||
NSPredicate(format: "faviconURL != nil"),
|
||||
]))
|
||||
LibraryService.shared.downloadFavicons(zimFiles: Array(zimFiles))
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,6 @@ struct LibrarySearchResultView: View {
|
||||
NSPredicate(format: "title CONTAINS[cd] %@", searchText),
|
||||
NSPredicate(format: "languageCode IN %@", Defaults[.libraryLanguageCodes]),
|
||||
])
|
||||
LibraryService.shared.downloadFavicons(zimFiles: zimFiles.filter { $0.faviconData == nil })
|
||||
// LibraryService.shared.downloadFavicons(zimFiles: zimFiles.filter { $0.faviconData == nil })
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user