mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-25 21:05:09 -04:00
Merge pull request #678 from kiwix/601-bookmarks-intial-cleanup
601 bookmarks intial cleanup
This commit is contained in:
commit
4479f8298e
@ -148,7 +148,7 @@ struct URLContent {
|
||||
let size: UInt
|
||||
}
|
||||
|
||||
class ZimFile: NSManagedObject, Identifiable {
|
||||
final class ZimFile: NSManagedObject, Identifiable {
|
||||
var id: UUID { fileID }
|
||||
|
||||
@NSManaged var articleCount: Int64
|
||||
@ -159,6 +159,7 @@ class ZimFile: NSManagedObject, Identifiable {
|
||||
@NSManaged var faviconURL: URL?
|
||||
@NSManaged var fileDescription: String
|
||||
@NSManaged var fileID: UUID
|
||||
/// System file URL, if not nil, it means it's downloaded
|
||||
@NSManaged var fileURLBookmark: Data?
|
||||
@NSManaged var flavor: String?
|
||||
@NSManaged var hasDetails: Bool
|
||||
@ -184,11 +185,16 @@ class ZimFile: NSManagedObject, Identifiable {
|
||||
}.joined(separator: ",")
|
||||
}
|
||||
|
||||
enum Predicate {
|
||||
static let isDownloaded = NSPredicate(format: "fileURLBookmark != nil")
|
||||
static let notDownloaded = NSPredicate(format: "fileURLBookmark == nil")
|
||||
static let notMissing = NSPredicate(format: "isMissing == false")
|
||||
}
|
||||
|
||||
static var openedPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
NSPredicate(format: "fileURLBookmark != nil"),
|
||||
NSPredicate(format: "isMissing == false")
|
||||
Predicate.isDownloaded,
|
||||
Predicate.notMissing
|
||||
])
|
||||
static var withFileURLBookmarkPredicate = NSPredicate(format: "fileURLBookmark != nil")
|
||||
|
||||
class func fetchRequest(
|
||||
predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor] = []
|
||||
|
@ -26,8 +26,6 @@
|
||||
|
||||
# pragma mark - Metadata
|
||||
|
||||
- (nullable ZimFileMetaData *)getMetaData:(nonnull NSUUID *)zimFileID NS_REFINED_FOR_SWIFT;
|
||||
- (nullable NSData *)getFavicon:(nonnull NSUUID *)zimFileID NS_REFINED_FOR_SWIFT;
|
||||
+ (nullable ZimFileMetaData *)getMetaDataWithFileURL:(nonnull NSURL *)url NS_REFINED_FOR_SWIFT;
|
||||
|
||||
# pragma mark - URL Handling
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
@interface ZimFileService ()
|
||||
|
||||
@property (assign) std::unordered_map<std::string, zim::Archive> *archives;
|
||||
@property (assign) std::unordered_map<std::string, zim::Archive> *archives; // (NSUUID_c: Archive)
|
||||
@property (strong) NSMutableDictionary *fileURLs; // [NSUUID: URL]
|
||||
|
||||
@end
|
||||
@ -68,7 +68,7 @@
|
||||
if (![pathExtension isEqualToString:@"zim"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// if we have previously added this url, skip it
|
||||
if ([[self.fileURLs allKeysForObject:url] count] > 0) {
|
||||
return;
|
||||
@ -78,7 +78,7 @@
|
||||
[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;
|
||||
@ -88,7 +88,7 @@
|
||||
}
|
||||
|
||||
- (void)close:(NSUUID *)zimFileID {
|
||||
self.archives->erase([[[zimFileID UUIDString] lowercaseString] cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
self.archives->erase([self zimfileID_C: zimFileID]);
|
||||
[self.fileURLs[zimFileID] stopAccessingSecurityScopedResource];
|
||||
[self.fileURLs removeObjectForKey:zimFileID];
|
||||
}
|
||||
@ -103,33 +103,6 @@
|
||||
|
||||
# pragma mark - Metadata
|
||||
|
||||
- (ZimFileMetaData *)getMetaData:(NSUUID *)zimFileID {
|
||||
std::string zimFileID_C = [[[zimFileID UUIDString] lowercaseString] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
auto found = self.archives->find(zimFileID_C);
|
||||
if (found == self.archives->end()) {
|
||||
return nil;
|
||||
} else {
|
||||
kiwix::Book book = kiwix::Book();
|
||||
book.update(found->second);
|
||||
return [[ZimFileMetaData alloc] initWithBook: &book];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSData *)getFavicon:(NSUUID *)zimFileID {
|
||||
std::string zimFileID_C = [[[zimFileID UUIDString] lowercaseString] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
auto found = self.archives->find(zimFileID_C);
|
||||
if (found == self.archives->end()) {
|
||||
return nil;
|
||||
} else {
|
||||
try {
|
||||
zim::Blob blob = found->second.getIllustrationItem().getData();
|
||||
return [NSData dataWithBytes:blob.data() length:blob.size()];
|
||||
} catch (std::exception) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (ZimFileMetaData *)getMetaDataWithFileURL:(NSURL *)url {
|
||||
ZimFileMetaData *metaData = nil;
|
||||
[url startAccessingSecurityScopedResource];
|
||||
@ -148,29 +121,23 @@
|
||||
return self.fileURLs[zimFileID];
|
||||
}
|
||||
|
||||
- (NSString *_Nullable)getRedirectedPath:(NSUUID *_Nonnull)zimFileID contentPath:(NSString *_Nonnull)contentPath {
|
||||
std::string zimFileID_C = [[[zimFileID UUIDString] lowercaseString] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
auto found = self.archives->find(zimFileID_C);
|
||||
if (found == self.archives->end()) {
|
||||
return nil;
|
||||
}
|
||||
- (NSString *_Nullable) getRedirectedPath:(NSUUID *_Nonnull)zimFileID contentPath:(NSString *_Nonnull)contentPath {
|
||||
zim::Archive *archive = [self archiveBy: zimFileID];
|
||||
if (archive == nil) { return nil; }
|
||||
try {
|
||||
std::string contentPathC = [contentPath cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
zim::Item item = found->second.getEntryByPath(contentPathC).getRedirect();
|
||||
return [NSString stringWithUTF8String:item.getPath().c_str()];
|
||||
zim::Item item = archive->getEntryByPath(contentPathC).getRedirect();
|
||||
return [NSString stringWithUTF8String: item.getPath().c_str()];
|
||||
} catch (std::exception) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)getMainPagePath:(NSUUID *)zimFileID {
|
||||
std::string zimFileID_C = [[[zimFileID UUIDString] lowercaseString] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
auto found = self.archives->find(zimFileID_C);
|
||||
if (found == self.archives->end()) {
|
||||
return nil;
|
||||
}
|
||||
zim::Archive *archive = [self archiveBy: zimFileID];
|
||||
if (archive == nil) { return nil; }
|
||||
try {
|
||||
zim::Entry entry = found->second.getMainEntry();
|
||||
zim::Entry entry = archive->getMainEntry();
|
||||
zim::Item item = entry.getItem(entry.isRedirect());
|
||||
return [NSString stringWithCString:item.getPath().c_str() encoding:NSUTF8StringEncoding];
|
||||
} catch (std::exception) {
|
||||
@ -179,13 +146,10 @@
|
||||
}
|
||||
|
||||
- (NSString *)getRandomPagePath:(NSUUID *)zimFileID {
|
||||
std::string zimFileID_C = [[[zimFileID UUIDString] lowercaseString] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
auto found = self.archives->find(zimFileID_C);
|
||||
if (found == self.archives->end()) {
|
||||
return nil;
|
||||
}
|
||||
zim::Archive *archive = [self archiveBy: zimFileID];
|
||||
if (archive == nil) { return nil; }
|
||||
try {
|
||||
zim::Entry entry = found->second.getRandomEntry();
|
||||
zim::Entry entry = archive->getRandomEntry();
|
||||
zim::Item item = entry.getItem(entry.isRedirect());
|
||||
return [NSString stringWithCString:item.getPath().c_str() encoding:NSUTF8StringEncoding];
|
||||
} catch (std::exception) {
|
||||
@ -194,18 +158,15 @@
|
||||
}
|
||||
|
||||
- (NSDictionary *)getContent:(NSUUID *)zimFileID contentPath:(NSString *)contentPath
|
||||
start:(NSUInteger)start end:(NSUInteger)end {
|
||||
start:(NSUInteger)start end:(NSUInteger)end {
|
||||
if ([contentPath hasPrefix:@"/"]) {
|
||||
contentPath = [contentPath substringFromIndex:1];
|
||||
}
|
||||
|
||||
std::string zimFileID_C = [[[zimFileID UUIDString] lowercaseString] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
auto found = self.archives->find(zimFileID_C);
|
||||
if (found == self.archives->end()) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
zim::Archive *archive = [self archiveBy: zimFileID];
|
||||
if (archive == nil) { return nil; }
|
||||
try {
|
||||
zim::Entry entry = found->second.getEntryByPath([contentPath cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
zim::Entry entry = archive->getEntryByPath([contentPath cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
zim::Item item = entry.getItem(entry.isRedirect());
|
||||
zim::Blob blob;
|
||||
if (start == 0 && end == 0) {
|
||||
@ -227,4 +188,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
# pragma mark - private
|
||||
|
||||
/// Converts the UUID to a C representation
|
||||
- (std::string) zimfileID_C: (NSUUID *_Nonnull) zimFileID {
|
||||
return [[[zimFileID UUIDString] lowercaseString] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (zim::Archive *_Nullable) archiveBy: (NSUUID *_Nonnull) zimFileID {
|
||||
std::string zimFileID_C = [self zimfileID_C: zimFileID];
|
||||
auto found = self.archives->find(zimFileID_C);
|
||||
if (found == self.archives->end()) {
|
||||
return nil;
|
||||
}
|
||||
return &(found->second);
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -12,31 +12,31 @@ extension ZimFileService {
|
||||
static let shared = ZimFileService.__sharedInstance()
|
||||
|
||||
/// IDs of currently opened zim files
|
||||
var fileIDs: [UUID] { get { return __getReaderIdentifiers().compactMap({ $0 as? UUID }) } }
|
||||
|
||||
private var fileIDs: [UUID] { get { return __getReaderIdentifiers().compactMap({ $0 as? UUID }) } }
|
||||
|
||||
// MARK: - Reader Management
|
||||
|
||||
/// Open a zim file from bookmark data
|
||||
/// Open a zim file from system file URL bookmark data
|
||||
/// - 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(bookmark: Data) throws -> Data? {
|
||||
func open(fileURLBookmark data: Data) throws -> Data? {
|
||||
// resolve url
|
||||
var isStale: Bool = false
|
||||
#if os(macOS)
|
||||
guard let url = try? URL(
|
||||
resolvingBookmarkData: bookmark,
|
||||
resolvingBookmarkData: data,
|
||||
options: [.withSecurityScope],
|
||||
bookmarkDataIsStale: &isStale
|
||||
) else { throw ZimFileOpenError.missing }
|
||||
#else
|
||||
guard let url = try? URL(resolvingBookmarkData: bookmark, bookmarkDataIsStale: &isStale) else {
|
||||
guard let url = try? URL(resolvingBookmarkData: data, bookmarkDataIsStale: &isStale) else {
|
||||
throw ZimFileOpenError.missing
|
||||
}
|
||||
#endif
|
||||
|
||||
__open(url)
|
||||
return isStale ? ZimFileService.getBookmarkData(url: url) : nil
|
||||
return isStale ? ZimFileService.getFileURLBookmarkData(for: url) : nil
|
||||
}
|
||||
|
||||
/// Close a zim file
|
||||
@ -44,22 +44,20 @@ extension ZimFileService {
|
||||
func close(fileID: UUID) { __close(fileID) }
|
||||
|
||||
// MARK: - Metadata
|
||||
|
||||
func getMetaData(id: UUID) -> ZimFileMetaData? {
|
||||
__getMetaData(id)
|
||||
}
|
||||
|
||||
func getFavicon(id: UUID) -> Data? {
|
||||
__getFavicon(id)
|
||||
}
|
||||
|
||||
static func getMetaData(url: URL) -> ZimFileMetaData? {
|
||||
__getMetaData(withFileURL: url)
|
||||
}
|
||||
|
||||
// MARK: - URL Bookmark
|
||||
|
||||
static func getBookmarkData(url: URL) -> Data? {
|
||||
// MARK: - URL System Bookmark
|
||||
|
||||
/// System URL bookmark for the ZIM file itself
|
||||
/// "bookmark data that can later be resolved into a URL object for a file
|
||||
/// even if the user moves or renames it"
|
||||
/// Not to be confused with the article bookmarks
|
||||
/// - Parameter url: file system URL
|
||||
/// - Returns: data that can later be resolved into a URL object
|
||||
static func getFileURLBookmarkData(for url: URL) -> Data? {
|
||||
_ = url.startAccessingSecurityScopedResource()
|
||||
defer { url.stopAccessingSecurityScopedResource() }
|
||||
#if os(macOS)
|
||||
|
@ -15,7 +15,6 @@ NS_INLINE NSException * _Nullable objCTryBlock(void(^_Nonnull tryBlock)(void)) {
|
||||
return nil;
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSLog(@"Exception: %s", exception);
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
|
@ -26,11 +26,11 @@ struct LibraryOperations {
|
||||
@discardableResult
|
||||
static func open(url: URL, onComplete: (() -> Void)? = nil) -> ZimFileMetaData? {
|
||||
guard let metadata = ZimFileService.getMetaData(url: url),
|
||||
let fileURLBookmark = ZimFileService.getBookmarkData(url: url) else { return nil }
|
||||
let fileURLBookmark = ZimFileService.getFileURLBookmarkData(for: url) else { return nil }
|
||||
|
||||
// open the file
|
||||
do {
|
||||
try ZimFileService.shared.open(bookmark: fileURLBookmark)
|
||||
try ZimFileService.shared.open(fileURLBookmark: fileURLBookmark)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
@ -57,7 +57,7 @@ struct LibraryOperations {
|
||||
static func reopen(onComplete: (() -> Void)?) {
|
||||
var successCount = 0
|
||||
let context = Database.shared.container.viewContext
|
||||
let request = ZimFile.fetchRequest(predicate: ZimFile.withFileURLBookmarkPredicate)
|
||||
let request = ZimFile.fetchRequest(predicate: ZimFile.Predicate.isDownloaded)
|
||||
|
||||
guard let zimFiles = try? context.fetch(request) else {
|
||||
onComplete?()
|
||||
@ -66,7 +66,7 @@ struct LibraryOperations {
|
||||
zimFiles.forEach { zimFile in
|
||||
guard let data = zimFile.fileURLBookmark else { return }
|
||||
do {
|
||||
if let data = try ZimFileService.shared.open(bookmark: data) {
|
||||
if let data = try ZimFileService.shared.open(fileURLBookmark: data) {
|
||||
zimFile.fileURLBookmark = data
|
||||
}
|
||||
zimFile.isMissing = false
|
||||
|
@ -219,7 +219,7 @@ final class LibraryRefreshViewModelTest: XCTestCase {
|
||||
XCTAssertNotEqual(zimFile1.fileID, zimFile2.fileID)
|
||||
|
||||
// set fileURLBookmark of zim file 2
|
||||
zimFile2.fileURLBookmark = "some file url bookmark data".data(using: .utf8)
|
||||
zimFile2.fileURLBookmark = "/Users/tester/Downloads/file_url.zim".data(using: .utf8)
|
||||
try context.save()
|
||||
|
||||
// refresh library for the third time
|
||||
|
@ -195,7 +195,7 @@ public class LibraryViewModel: ObservableObject {
|
||||
// delete old zim entries not included in the feed
|
||||
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = ZimFile.fetchRequest()
|
||||
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
NSPredicate(format: "fileURLBookmark == nil"),
|
||||
ZimFile.Predicate.notDownloaded,
|
||||
NSPredicate(format: "NOT fileID IN %@", parser.zimFileIDs)
|
||||
])
|
||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
||||
|
@ -14,7 +14,7 @@ struct ZimFilesOpened: View {
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(keyPath: \ZimFile.size, ascending: false)],
|
||||
predicate: ZimFile.withFileURLBookmarkPredicate,
|
||||
predicate: ZimFile.Predicate.isDownloaded,
|
||||
animation: .easeInOut
|
||||
) private var zimFiles: FetchedResults<ZimFile>
|
||||
@State private var isFileImporterPresented = false
|
||||
|
@ -18,7 +18,7 @@ struct SearchResults: View {
|
||||
@EnvironmentObject private var viewModel: SearchViewModel
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(keyPath: \ZimFile.size, ascending: false)],
|
||||
predicate: ZimFile.withFileURLBookmarkPredicate,
|
||||
predicate: ZimFile.Predicate.isDownloaded,
|
||||
animation: .easeInOut
|
||||
) private var zimFiles: FetchedResults<ZimFile>
|
||||
@State private var isClearSearchConfirmationPresented = false
|
||||
|
Loading…
x
Reference in New Issue
Block a user