From 3ff5c22fb45f12123e03e8e55815ce8a996a7870 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 15 Jul 2025 08:26:43 -0400 Subject: [PATCH] Added only updating individual comics in the list on file events [#126] --- iosVariant/iosVariant/Data/ImageLoader.swift | 1 + .../variant/adaptor/ArchiveAPI.kt | 2 +- .../variant/model/cache/ImageCache.kt | 26 ------- .../variant/viewmodel/VariantViewModel.kt | 73 +++++++++++++------ 4 files changed, 51 insertions(+), 51 deletions(-) delete mode 100644 shared/src/commonMain/kotlin/org/comixedproject/variant/model/cache/ImageCache.kt diff --git a/iosVariant/iosVariant/Data/ImageLoader.swift b/iosVariant/iosVariant/Data/ImageLoader.swift index 4d358ec..dcf53ae 100644 --- a/iosVariant/iosVariant/Data/ImageLoader.swift +++ b/iosVariant/iosVariant/Data/ImageLoader.swift @@ -22,6 +22,7 @@ import shared private let TAG = "ImageLoader" +@MainActor class ImageLoader: ObservableObject { private var comicFilename: String = "" private var pageFilename: String = "" diff --git a/shared/src/commonMain/kotlin/org/comixedproject/variant/adaptor/ArchiveAPI.kt b/shared/src/commonMain/kotlin/org/comixedproject/variant/adaptor/ArchiveAPI.kt index b4ade01..9c74549 100644 --- a/shared/src/commonMain/kotlin/org/comixedproject/variant/adaptor/ArchiveAPI.kt +++ b/shared/src/commonMain/kotlin/org/comixedproject/variant/adaptor/ArchiveAPI.kt @@ -38,7 +38,7 @@ public object ArchiveAPI { val pages = mutableListOf() var metadata = ComicBookMetadata() - Log.debug(TAG, "Loading comic archive entries") + Log.debug(TAG, "Loading comic archive entries: ${archive.path}") try { ZipFile(archive).use { zip -> zip.map diff --git a/shared/src/commonMain/kotlin/org/comixedproject/variant/model/cache/ImageCache.kt b/shared/src/commonMain/kotlin/org/comixedproject/variant/model/cache/ImageCache.kt deleted file mode 100644 index 1f76cbd..0000000 --- a/shared/src/commonMain/kotlin/org/comixedproject/variant/model/cache/ImageCache.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Variant - A digital comic book reading application for the iPad and Android tablets. - * Copyright (C) 2025, The ComiXed Project - * - * This program 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 - * (at your option) any later version. - * - * This program 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 this program. If not, see - */ - -package org.comixedproject.variant.model.cache - -import kotlinx.serialization.Serializable - -@Serializable -data class ImageCache( - val entries: MutableMap = HashMap() -) diff --git a/shared/src/commonMain/kotlin/org/comixedproject/variant/viewmodel/VariantViewModel.kt b/shared/src/commonMain/kotlin/org/comixedproject/variant/viewmodel/VariantViewModel.kt index 6fc4797..17d2834 100644 --- a/shared/src/commonMain/kotlin/org/comixedproject/variant/viewmodel/VariantViewModel.kt +++ b/shared/src/commonMain/kotlin/org/comixedproject/variant/viewmodel/VariantViewModel.kt @@ -29,10 +29,12 @@ import com.rickclephas.kmp.observableviewmodel.launch import com.russhwolf.settings.Settings import io.github.irgaly.kfswatch.KfsDirectoryWatcher import io.github.irgaly.kfswatch.KfsDirectoryWatcherEvent +import io.github.irgaly.kfswatch.KfsEvent import io.github.irgaly.kfswatch.KfsLogger import io.ktor.http.URLBuilder import io.ktor.http.takeFrom import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @@ -62,7 +64,7 @@ open class VariantViewModel( fun setLibraryDirectory(directory: String) { Log.debug(TAG, "_libraryDirectory=${directory}") _libraryDirectory = directory - viewModelScope.coroutineScope.launch { + viewModelScope.coroutineScope.launch(Dispatchers.Main) { if (!File(_libraryDirectory).exists) { Log.info(TAG, "Creating library directory: ${_libraryDirectory}") File(_libraryDirectory).makeDirectory() @@ -90,9 +92,14 @@ open class VariantViewModel( }) libraryWatcher?.add(_libraryDirectory) libraryWatcher?.onEventFlow?.collect { event: KfsDirectoryWatcherEvent -> - viewModelScope.coroutineScope.launch { - Log.debug(TAG, "Received file watcher event: ${event}") - loadLibraryContents() + viewModelScope.coroutineScope.launch(Dispatchers.Main) { + Log.debug( + TAG, + "Received file watcher event: target directory=${event.targetDirectory} path=${event.path}" + ) + val path = "${event.targetDirectory}/${event.path}" + val file = File(path) + onLibraryFileEvent(file, event.event) } } } @@ -151,7 +158,7 @@ open class VariantViewModel( Log.debug(TAG, "Loading directory: ${path}") var contents = directoryRepository.loadDirectoryContents(path) if (contents.isEmpty() || reload) { - _loading.emit(true) + _loading.tryEmit(true) try { val url = URLBuilder(address).takeFrom(path) @@ -186,7 +193,7 @@ open class VariantViewModel( val directory = directoryRepository.findDirectory(path) Log.debug(TAG, "Updating browsing state: currentPath=${path}") - _browsingState.emit( + _browsingState.tryEmit( BrowsingState( path, directory?.parent ?: "", @@ -195,7 +202,7 @@ open class VariantViewModel( _browsingState.value.downloadingState ) ) - _loading.emit(false) + _loading.tryEmit(false) } } @@ -205,7 +212,7 @@ open class VariantViewModel( val username = this.username val password = this.password - viewModelScope.launch(Dispatchers.Main) { + viewModelScope.launch(Dispatchers.IO) { val downloadingState = DownloadingState(path, filename, 0, 0) val state = mutableListOf() state.addAll(_browsingState.value.downloadingState) @@ -254,29 +261,29 @@ open class VariantViewModel( } fun readComicBook(comicBook: ComicBook?) { - viewModelScope.launch(Dispatchers.Main) { + viewModelScope.launch(Dispatchers.Default) { if (comicBook != null) { Log.info(TAG, "Reading comic book: ${comicBook.filename}") } else { Log.info(TAG, "Stopped reading comic book") } - _comicBook.emit(comicBook) + _comicBook.tryEmit(comicBook) } } fun setSelectMode(enable: Boolean) { - viewModelScope.launch(Dispatchers.Main) { + viewModelScope.launch(Dispatchers.Default) { Log.debug(TAG, "Setting selection mode: ${enable}") - _selectionMode.emit(enable) + _selectionMode.tryEmit(enable) if (!enable) { Log.debug(TAG, "Clearing selections") - _selectionList.emit(emptyList()) + _selectionList.tryEmit(emptyList()) } } } fun updateSelectionList(filename: String) { - viewModelScope.launch(Dispatchers.Main) { + viewModelScope.launch(Dispatchers.Default) { val selections = mutableListOf() if (_selectionList.value.contains(filename)) { Log.debug(TAG, "Removing selection: ${filename}") @@ -286,7 +293,7 @@ open class VariantViewModel( selections.addAll(_selectionList.value) selections.add(filename) } - _selectionList.emit(selections) + _selectionList.tryEmit(selections) } } @@ -297,15 +304,28 @@ open class VariantViewModel( file.delete() } - _selectionList.emit(emptyList()) - _selectionMode.emit(false) + _selectionList.tryEmit(emptyList()) + _selectionMode.tryEmit(false) } - private suspend fun loadLibraryContents() { - Log.debug(TAG, "Loading library contents: ${_libraryDirectory}") + private suspend fun onLibraryFileEvent(file: File, event: KfsEvent) { + Log.debug(TAG, "Processing library event: ${event} for ${file.fullPath}") + when (event) { + KfsEvent.Create -> loadLibraryContents() + KfsEvent.Modify -> loadLibraryContents() + KfsEvent.Delete -> { + val contents = _comicBookList.value.filter { + !it.path.equals(file.fullPath) + } + _comicBookList.tryEmit(contents) + } + } + } + private suspend fun loadLibraryContents() { val path = File(_libraryDirectory) val ignored = this._browsingState.value.downloadingState.map { it.filename }.toList() + val entries = _comicBookList.value.associate { it.path to it } val contents = path.directoryFiles() .filter { @@ -316,18 +336,23 @@ open class VariantViewModel( } } .filter { !it.isDirectory } + .filter { it.extension.equals("cbz") } .filter { it.size.toLong() > 0L } - .filter { it.extension.equals("cbz") || it.extension.equals("cbr") } - .map { entry -> - Log.debug(TAG, "Found file: ${entry.path}") - ArchiveAPI.loadComicBook(entry) + .map { + if (entries.contains(it.fullPath)) { + Log.debug(TAG, "File already loaded: ${it.fullPath}") + entries.getValue(it.fullPath) + } else { + Log.debug(TAG, "Found file: ${it.path}") + ArchiveAPI.loadComicBook(it) + } }.toList() _comicBookList.tryEmit(contents) } private suspend fun doUpdateDownloadingState(state: List) { val browsingState = _browsingState.value - _browsingState.emit( + _browsingState.tryEmit( BrowsingState( browsingState.currentPath, browsingState.parentPath,