From 99c99d7785fc9f9d6fad75dbcbb6bdd2bc57214b Mon Sep 17 00:00:00 2001 From: Balazs Perlaki-Horvath Date: Sat, 7 Sep 2024 22:22:37 +0200 Subject: [PATCH] Library loading defered --- App/App_macOS.swift | 6 +++ Model/Utilities/Performance.swift | 36 +++++++++++++++ Model/Utilities/PerformanceObjC.h | 54 +++++++++++++++++++++++ Model/ZimFileService/ZimFileService.h | 3 +- Model/ZimFileService/ZimFileService.mm | 40 ++++++++++------- Model/ZimFileService/ZimFileService.swift | 5 +-- SwiftUI/Model/LibraryOperations.swift | 4 +- ViewModel/LibraryViewModel.swift | 1 - 8 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 Model/Utilities/Performance.swift create mode 100644 Model/Utilities/PerformanceObjC.h diff --git a/App/App_macOS.swift b/App/App_macOS.swift index 1bdfcad5..ffb58f12 100644 --- a/App/App_macOS.swift +++ b/App/App_macOS.swift @@ -216,12 +216,18 @@ struct RootView: View { }.task { switch AppType.current { case .kiwix: + let perfLib = Performance() LibraryOperations.reopen { navigation.currentItem = .reading + perfLib.measure("LibraryOperations.reopen") } + let perf = Performance() LibraryOperations.scanDirectory(URL.documentDirectory) + perf.measure("scanDirectory") LibraryOperations.applyFileBackupSetting() + perf.measure("applyFileBackupSetting") DownloadService.shared.restartHeartbeatIfNeeded() + perf.measure("restartHeartbeatIfNeeded") case let .custom(zimFileURL): LibraryOperations.open(url: zimFileURL) { ZimMigration.forCustomApps() diff --git a/Model/Utilities/Performance.swift b/Model/Utilities/Performance.swift new file mode 100644 index 00000000..f2ee9a88 --- /dev/null +++ b/Model/Utilities/Performance.swift @@ -0,0 +1,36 @@ +// 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 QuartzCore + +final class Performance { + + private let id: UUID + private var start: CFTimeInterval + + init(id: UUID = UUID()) { + self.id = id + start = CACurrentMediaTime() + } + + func measure(_ msg: String) { + print("\(msg) \(id): \((CACurrentMediaTime() - start) * 1000) ms") + } + + func reset() { + start = CACurrentMediaTime() + } +} diff --git a/Model/Utilities/PerformanceObjC.h b/Model/Utilities/PerformanceObjC.h new file mode 100644 index 00000000..3c5a54f4 --- /dev/null +++ b/Model/Utilities/PerformanceObjC.h @@ -0,0 +1,54 @@ +// 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 +#import + +@interface PerformanceObjC : NSObject + +@property (nonatomic, strong, readonly) NSUUID *id; +@property (nonatomic, assign) CFTimeInterval start; + +- (instancetype)initWithId:(NSUUID *)id; +- (void)measure:(NSString *)msg; +- (void)reset; + +@end + +@implementation PerformanceObjC + +- (instancetype)init { + return [self initWithId:[NSUUID UUID]]; +} + +- (instancetype)initWithId:(NSUUID *)id { + self = [super init]; + if (self) { + _id = id; + _start = CACurrentMediaTime(); + } + return self; +} + +- (void)measure:(NSString *)msg { + CFTimeInterval elapsedTime = (CACurrentMediaTime() - _start) * 1000; + NSLog(@"%@ %@: %.2f ms", msg, _id.UUIDString, elapsedTime); +} + +- (void)reset { + _start = CACurrentMediaTime(); +} + +@end diff --git a/Model/ZimFileService/ZimFileService.h b/Model/ZimFileService/ZimFileService.h index dddcd900..c5d7a307 100644 --- a/Model/ZimFileService/ZimFileService.h +++ b/Model/ZimFileService/ZimFileService.h @@ -26,7 +26,8 @@ #pragma mark - Reader Management -- (void)open:(NSURL *_Nonnull)url NS_REFINED_FOR_SWIFT; +//- (void)open:(NSURL *_Nonnull)url NS_REFINED_FOR_SWIFT; +- (void)store:(NSURL *_Nonnull)url with: (NSUUID *_Nonnull)zimFileID NS_REFINED_FOR_SWIFT; - (void)close:(NSUUID *_Nonnull)zimFileID NS_REFINED_FOR_SWIFT; - (NSArray *_Nonnull)getReaderIdentifiers NS_REFINED_FOR_SWIFT; - (nonnull void *) getArchives; diff --git a/Model/ZimFileService/ZimFileService.mm b/Model/ZimFileService/ZimFileService.mm index 8ba2b7fc..ae1a6a8d 100644 --- a/Model/ZimFileService/ZimFileService.mm +++ b/Model/ZimFileService/ZimFileService.mm @@ -14,7 +14,7 @@ // along with Kiwix; If not, see https://www.gnu.org/licenses/. #include - +#import #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdocumentation" #include "kiwix/book.h" @@ -27,6 +27,7 @@ #import "ZimFileService.h" #import "ZimFileMetaData.h" +#import "PerformanceObjC.h" @interface ZimFileService () @@ -68,26 +69,13 @@ #pragma mark - Reader Management -- (void)open:(NSURL *)url { +- (void)store:(NSURL *)url with:(NSUUID *)zimFileID { try { // if url does not ends with "zim", skip it NSString *pathExtension = [[url pathExtension] lowercaseString]; if (![pathExtension isEqualToString:@"zim"]) { return; } - - // if we have previously added this url, skip it - if ([[self.fileURLs allKeysForObject:url] count] > 0) { - return; - } - - // add the archive - [url startAccessingSecurityScopedResource]; - zim::Archive archive = zim::Archive([url fileSystemRepresentation]); - self.archives->insert(std::make_pair(std::string(archive.getUuid()), archive)); - - // store file URL - NSUUID *zimFileID = [[NSUUID alloc] initWithUUIDBytes:(unsigned char *)archive.getUuid().data]; self.fileURLs[zimFileID] = url; } catch (std::exception) { NSLog(@"Error opening zim file."); @@ -230,6 +218,18 @@ } - (zim::Archive *_Nullable) archiveBy: (NSUUID *_Nonnull) zimFileID { + zim::Archive *found = [self findArchiveBy:zimFileID]; + if(found == nil) { + NSURL *url = self.fileURLs[zimFileID]; + if (url == nil) { + return nil; + } + [self insertIntoArchives:url with:zimFileID]; + } + return [self findArchiveBy: zimFileID]; +} + +- (zim::Archive *_Nullable) findArchiveBy: (NSUUID *_Nonnull) zimFileID { std::string zimFileID_C = [self zimfileID_C: zimFileID]; auto found = self.archives->find(zimFileID_C); if (found == self.archives->end()) { @@ -238,6 +238,16 @@ return &(found->second); } +- (void) insertIntoArchives: (NSURL *_Nonnull) url with: (NSUUID *_Nonnull) zimFileID { + try { + [url startAccessingSecurityScopedResource]; + zim::Archive archive = zim::Archive([url fileSystemRepresentation]); // takes the longest time + self.archives->insert(std::make_pair(std::string(archive.getUuid()), archive)); + } catch (std::exception) { + NSLog(@"cannot insert archive with: %@, %@", url.absoluteString, zimFileID.UUIDString); + } +} + - (zim::Item) itemIn: (NSUUID *)zimFileID contentPath:(NSString *)contentPath { if ([contentPath hasPrefix:@"/"]) { contentPath = [contentPath substringFromIndex:1]; diff --git a/Model/ZimFileService/ZimFileService.swift b/Model/ZimFileService/ZimFileService.swift index dad1a5ea..0482460e 100644 --- a/Model/ZimFileService/ZimFileService.swift +++ b/Model/ZimFileService/ZimFileService.swift @@ -27,7 +27,7 @@ extension ZimFileService { /// - Parameter bookmark: url bookmark data of the zim file to open /// - Returns: new url bookmark data if the one used to open the zim file is stale @discardableResult - func open(fileURLBookmark data: Data) throws -> Data? { + func open(fileURLBookmark data: Data, for uuid: UUID) throws -> Data? { // resolve url var isStale: Bool = false #if os(macOS) @@ -41,8 +41,7 @@ extension ZimFileService { throw ZimFileOpenError.missing } #endif - - __open(url) + __store(url, with: uuid) return isStale ? ZimFileService.getFileURLBookmarkData(for: url) : nil } diff --git a/SwiftUI/Model/LibraryOperations.swift b/SwiftUI/Model/LibraryOperations.swift index 91dc5afb..3b7664f3 100644 --- a/SwiftUI/Model/LibraryOperations.swift +++ b/SwiftUI/Model/LibraryOperations.swift @@ -35,7 +35,7 @@ struct LibraryOperations { // open the file do { - try ZimFileService.shared.open(fileURLBookmark: fileURLBookmark) + try ZimFileService.shared.open(fileURLBookmark: fileURLBookmark, for: metadata.fileID) } catch { return nil } @@ -73,7 +73,7 @@ struct LibraryOperations { zimFiles.forEach { zimFile in guard let data = zimFile.fileURLBookmark else { return } do { - if let data = try ZimFileService.shared.open(fileURLBookmark: data) { + if let data = try ZimFileService.shared.open(fileURLBookmark: data, for: zimFile.fileID) { zimFile.fileURLBookmark = data } zimFile.isMissing = false diff --git a/ViewModel/LibraryViewModel.swift b/ViewModel/LibraryViewModel.swift index d0001afa..6e88b0b3 100644 --- a/ViewModel/LibraryViewModel.swift +++ b/ViewModel/LibraryViewModel.swift @@ -76,7 +76,6 @@ final class LibraryViewModel: ObservableObject { @MainActor func start(isUserInitiated: Bool) async { guard process.state != .inProgress else { return } - let oldState = process.state do { // decide if refresh should proceed let lastRefresh: Date? = Defaults[.libraryLastRefresh]