Refactored folder synchronization logic and improved migration handling (#21)
- Updated `MainViewModel` to skip synchronization until folder migration is completed and added metadata syncing when opening books. - Modified folder migration to detach existing folder books (converting them to standard local files) before starting a fresh scan. - Added `detachAllFolderBooks` to `RecentFileDao` and `RecentFilesRepository` to facilitate the migration process. - Updated `MetadataExtractionWorker` to sync local metadata to the folder immediately after extraction. - Simplified `FolderSyncWorker` by removing legacy migration lookups and streamlining the reconciliation of local and remote metadata. - Updated `FolderMigrationDialog` UI text to reflect that books are now read directly from folders without duplication. - Fixed various indentation and formatting issues across several files.
This commit is contained in:
parent
34c6ec3fef
commit
0885b6949e
6 changed files with 105 additions and 196 deletions
|
|
@ -114,116 +114,52 @@ class FolderSyncWorker(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val activeDbBooks = recentFilesRepository.getFilesBySourceFolder(folderUriString)
|
|
||||||
|
|
||||||
val legacyLookup = activeDbBooks.associateBy { it.displayName }
|
|
||||||
|
|
||||||
val foundBookIds = mutableSetOf<String>()
|
val foundBookIds = mutableSetOf<String>()
|
||||||
|
|
||||||
for (file in currentDiskFiles) {
|
for (file in currentDiskFiles) {
|
||||||
val stableId = "local_${file.name}_${file.length()}"
|
val stableId = "local_${file.name}_${file.length()}"
|
||||||
|
foundBookIds.add(stableId)
|
||||||
|
|
||||||
var existingItem = recentFilesRepository.getFileByBookId(stableId)
|
val existingItem = recentFilesRepository.getFileByBookId(stableId)
|
||||||
var bookIdToUse = stableId
|
|
||||||
var isMigration = false
|
|
||||||
|
|
||||||
if (existingItem == null) {
|
|
||||||
val legacyMatch = legacyLookup[file.name]
|
|
||||||
if (legacyMatch != null) {
|
|
||||||
Timber.tag("FolderSync").i("Migration: Found legacy match for ${file.name}. ID: ${legacyMatch.bookId}")
|
|
||||||
|
|
||||||
existingItem = legacyMatch
|
|
||||||
bookIdToUse = legacyMatch.bookId
|
|
||||||
isMigration = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foundBookIds.add(bookIdToUse)
|
|
||||||
|
|
||||||
if (existingItem == null) {
|
if (existingItem == null) {
|
||||||
val remoteMeta = folderMetadataMap[stableId]
|
val remoteMeta = folderMetadataMap[stableId]
|
||||||
val type = getFileType(file.name ?: "", file.type) ?: FileType.EPUB
|
val type = getFileType(file.name ?: "", file.type) ?: FileType.EPUB
|
||||||
|
|
||||||
// --- CHANGED: Removed extractFileInfo() call ---
|
|
||||||
// We use placeholders. The MetadataExtractionWorker will fix this later.
|
|
||||||
val placeholderTitle = file.name ?: "Unknown"
|
val placeholderTitle = file.name ?: "Unknown"
|
||||||
val placeholderAuthor = null
|
|
||||||
val placeholderCover = null
|
|
||||||
|
|
||||||
if (remoteMeta != null) {
|
|
||||||
Timber.tag("FolderSync").d("Worker: Importing existing book from Metadata + File: ${file.name}")
|
|
||||||
// We prefer remoteMeta if available because it might have the correct title/author from a previous sync
|
|
||||||
val tempItem = RecentFileItem(
|
|
||||||
bookId = stableId,
|
|
||||||
uriString = file.uri.toString(),
|
|
||||||
type = type,
|
|
||||||
displayName = file.name ?: "Unknown",
|
|
||||||
timestamp = remoteMeta.lastModifiedTimestamp,
|
|
||||||
lastModifiedTimestamp = remoteMeta.lastModifiedTimestamp,
|
|
||||||
coverImagePath = null, // Will be fetched by MetadataWorker if needed
|
|
||||||
title = remoteMeta.title ?: placeholderTitle,
|
|
||||||
author = remoteMeta.author,
|
|
||||||
isAvailable = true,
|
|
||||||
isDeleted = false,
|
|
||||||
isRecent = false,
|
|
||||||
sourceFolderUri = folderUriString,
|
|
||||||
lastChapterIndex = remoteMeta.lastChapterIndex,
|
|
||||||
lastPage = remoteMeta.lastPage,
|
|
||||||
lastPositionCfi = remoteMeta.lastPositionCfi,
|
|
||||||
progressPercentage = remoteMeta.progressPercentage,
|
|
||||||
bookmarksJson = remoteMeta.bookmarksJson
|
|
||||||
)
|
|
||||||
recentFilesRepository.addRecentFile(tempItem)
|
|
||||||
} else {
|
|
||||||
// FAST PATH: Insert barebones item
|
|
||||||
val newItem = RecentFileItem(
|
val newItem = RecentFileItem(
|
||||||
bookId = stableId,
|
bookId = stableId,
|
||||||
uriString = file.uri.toString(),
|
uriString = file.uri.toString(),
|
||||||
type = type,
|
type = type,
|
||||||
displayName = file.name ?: "Unknown",
|
displayName = file.name ?: "Unknown",
|
||||||
timestamp = System.currentTimeMillis(),
|
timestamp = remoteMeta?.lastModifiedTimestamp ?: System.currentTimeMillis(),
|
||||||
coverImagePath = null, // Background worker will fill this
|
lastModifiedTimestamp = remoteMeta?.lastModifiedTimestamp ?: System.currentTimeMillis(),
|
||||||
title = placeholderTitle,
|
coverImagePath = null,
|
||||||
author = null,
|
title = remoteMeta?.title ?: placeholderTitle,
|
||||||
|
author = remoteMeta?.author,
|
||||||
isAvailable = true,
|
isAvailable = true,
|
||||||
lastModifiedTimestamp = System.currentTimeMillis(),
|
|
||||||
isDeleted = false,
|
isDeleted = false,
|
||||||
isRecent = false,
|
isRecent = false,
|
||||||
sourceFolderUri = folderUriString
|
sourceFolderUri = folderUriString,
|
||||||
|
lastChapterIndex = remoteMeta?.lastChapterIndex,
|
||||||
|
lastPage = remoteMeta?.lastPage,
|
||||||
|
lastPositionCfi = remoteMeta?.lastPositionCfi,
|
||||||
|
progressPercentage = remoteMeta?.progressPercentage,
|
||||||
|
bookmarksJson = remoteMeta?.bookmarksJson,
|
||||||
|
locatorBlockIndex = remoteMeta?.locatorBlockIndex,
|
||||||
|
locatorCharOffset = remoteMeta?.locatorCharOffset
|
||||||
)
|
)
|
||||||
recentFilesRepository.addRecentFile(newItem)
|
recentFilesRepository.addRecentFile(newItem)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (isMigration) {
|
var itemToUpdate = existingItem
|
||||||
val oldUriString = existingItem.uriString
|
|
||||||
val newUriString = file.uri.toString()
|
|
||||||
|
|
||||||
if (oldUriString != newUriString) {
|
if (existingItem.isDeleted) {
|
||||||
Timber.tag("FolderSync").i("Migration: Updating URI and cleaning up internal storage for $bookIdToUse")
|
itemToUpdate = itemToUpdate.copy(isDeleted = false, isAvailable = true)
|
||||||
|
|
||||||
if (oldUriString != null) {
|
|
||||||
bookImporter.deleteBookByUriString(oldUriString)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
existingItem = existingItem.copy(
|
val remoteMeta = folderMetadataMap[stableId]
|
||||||
uriString = newUriString,
|
if (remoteMeta != null && remoteMeta.lastModifiedTimestamp > itemToUpdate.lastModifiedTimestamp) {
|
||||||
isAvailable = true
|
itemToUpdate = itemToUpdate.copy(
|
||||||
)
|
|
||||||
recentFilesRepository.addRecentFile(existingItem)
|
|
||||||
}
|
|
||||||
} else if (existingItem.isDeleted) {
|
|
||||||
val resurrected = existingItem.copy(isDeleted = false, isAvailable = true)
|
|
||||||
recentFilesRepository.addRecentFile(resurrected)
|
|
||||||
}
|
|
||||||
|
|
||||||
val remoteMeta = folderMetadataMap[bookIdToUse]
|
|
||||||
|
|
||||||
if (remoteMeta == null) {
|
|
||||||
recentFilesRepository.syncLocalMetadataToFolder(bookIdToUse)
|
|
||||||
} else {
|
|
||||||
if (remoteMeta.lastModifiedTimestamp > existingItem.lastModifiedTimestamp) {
|
|
||||||
Timber.tag("FolderSync").d("Worker: Remote metadata newer for ${file.name}")
|
|
||||||
val updatedItem = existingItem.copy(
|
|
||||||
lastChapterIndex = remoteMeta.lastChapterIndex,
|
lastChapterIndex = remoteMeta.lastChapterIndex,
|
||||||
lastPage = remoteMeta.lastPage,
|
lastPage = remoteMeta.lastPage,
|
||||||
lastPositionCfi = remoteMeta.lastPositionCfi,
|
lastPositionCfi = remoteMeta.lastPositionCfi,
|
||||||
|
|
@ -234,10 +170,12 @@ class FolderSyncWorker(
|
||||||
lastModifiedTimestamp = remoteMeta.lastModifiedTimestamp,
|
lastModifiedTimestamp = remoteMeta.lastModifiedTimestamp,
|
||||||
timestamp = remoteMeta.lastModifiedTimestamp
|
timestamp = remoteMeta.lastModifiedTimestamp
|
||||||
)
|
)
|
||||||
recentFilesRepository.addRecentFile(updatedItem)
|
|
||||||
} else if (existingItem.lastModifiedTimestamp > remoteMeta.lastModifiedTimestamp) {
|
|
||||||
recentFilesRepository.syncLocalMetadataToFolder(bookIdToUse)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recentFilesRepository.addRecentFile(itemToUpdate)
|
||||||
|
|
||||||
|
if (remoteMeta == null || itemToUpdate.lastModifiedTimestamp > remoteMeta.lastModifiedTimestamp) {
|
||||||
|
recentFilesRepository.syncLocalMetadataToFolder(stableId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -249,79 +187,6 @@ class FolderSyncWorker(
|
||||||
Timber.tag("FolderSync").i("Cleaning up ${idsToRemove.size} missing folder books.")
|
Timber.tag("FolderSync").i("Cleaning up ${idsToRemove.size} missing folder books.")
|
||||||
recentFilesRepository.deleteFilePermanently(idsToRemove)
|
recentFilesRepository.deleteFilePermanently(idsToRemove)
|
||||||
}
|
}
|
||||||
|
|
||||||
val orphanedMetadataIds = folderMetadataMap.keys.filter { !foundBookIds.contains(it) }
|
|
||||||
|
|
||||||
if (orphanedMetadataIds.isNotEmpty()) {
|
|
||||||
Timber.tag("FolderSync").i("Cleaning up ${orphanedMetadataIds.size} orphaned metadata files.")
|
|
||||||
|
|
||||||
try {
|
|
||||||
val docTree = DocumentFile.fromTreeUri(appContext, folderUri)
|
|
||||||
val syncDir = docTree?.findFile("episteme")
|
|
||||||
|
|
||||||
if (syncDir != null) {
|
|
||||||
val allFiles = syncDir.listFiles()
|
|
||||||
orphanedMetadataIds.forEach { orphanId ->
|
|
||||||
allFiles.filter {
|
|
||||||
val name = it.name ?: ""
|
|
||||||
name.contains(orphanId) && (name.endsWith(".json") || name.contains(".sync-conflict"))
|
|
||||||
}.forEach { fileToDelete ->
|
|
||||||
try {
|
|
||||||
fileToDelete.delete()
|
|
||||||
} catch (_: Exception) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.tag("FolderSync").e(e, "Error during orphan cleanup")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reconcile Metadata (Write-back)
|
|
||||||
val activeDbBooks = recentFilesRepository.getFilesBySourceFolder(folderUriString)
|
|
||||||
|
|
||||||
val booksToDelete = mutableListOf<String>()
|
|
||||||
|
|
||||||
for (localBook in activeDbBooks) {
|
|
||||||
val remoteMeta = folderMetadataMap[localBook.bookId]
|
|
||||||
if (remoteMeta == null) {
|
|
||||||
val exists = try {
|
|
||||||
val uri = localBook.uriString?.toUri()
|
|
||||||
if (uri != null) {
|
|
||||||
DocumentFile.fromSingleUri(appContext, uri)?.exists() == true
|
|
||||||
} else false
|
|
||||||
} catch (e: Exception) { false }
|
|
||||||
|
|
||||||
if (exists) {
|
|
||||||
recentFilesRepository.syncLocalMetadataToFolder(localBook.bookId)
|
|
||||||
} else {
|
|
||||||
Timber.tag("FolderSync").i("Metadata Sync: Book ${localBook.displayName} missing from disk. Scheduling removal.")
|
|
||||||
booksToDelete.add(localBook.bookId)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (remoteMeta.lastModifiedTimestamp > localBook.lastModifiedTimestamp) {
|
|
||||||
Timber.tag("FolderSync").d("SyncDecision: Remote NEWER for ${localBook.displayName}. Updating local DB.")
|
|
||||||
val updatedItem = localBook.copy(
|
|
||||||
lastChapterIndex = remoteMeta.lastChapterIndex,
|
|
||||||
lastPage = remoteMeta.lastPage,
|
|
||||||
lastPositionCfi = remoteMeta.lastPositionCfi,
|
|
||||||
progressPercentage = remoteMeta.progressPercentage,
|
|
||||||
bookmarksJson = remoteMeta.bookmarksJson,
|
|
||||||
locatorBlockIndex = remoteMeta.locatorBlockIndex,
|
|
||||||
locatorCharOffset = remoteMeta.locatorCharOffset,
|
|
||||||
lastModifiedTimestamp = remoteMeta.lastModifiedTimestamp,
|
|
||||||
timestamp = remoteMeta.lastModifiedTimestamp
|
|
||||||
)
|
|
||||||
recentFilesRepository.addRecentFile(updatedItem)
|
|
||||||
} else if (localBook.lastModifiedTimestamp > remoteMeta.lastModifiedTimestamp) {
|
|
||||||
recentFilesRepository.syncLocalMetadataToFolder(localBook.bookId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (booksToDelete.isNotEmpty()) {
|
|
||||||
recentFilesRepository.deleteFilePermanently(booksToDelete)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prefs.edit { putLong(MainViewModel.KEY_LAST_FOLDER_SCAN_TIME, System.currentTimeMillis()) }
|
prefs.edit { putLong(MainViewModel.KEY_LAST_FOLDER_SCAN_TIME, System.currentTimeMillis()) }
|
||||||
|
|
@ -335,6 +200,7 @@ class FolderSyncWorker(
|
||||||
)
|
)
|
||||||
|
|
||||||
return Result.success()
|
return Result.success()
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.tag("FolderSync").e(e, "Error during folder sync worker execution.")
|
Timber.tag("FolderSync").e(e, "Error during folder sync worker execution.")
|
||||||
return Result.failure()
|
return Result.failure()
|
||||||
|
|
|
||||||
|
|
@ -1140,17 +1140,17 @@ fun FpsMonitor(modifier: Modifier = Modifier) {
|
||||||
@Composable
|
@Composable
|
||||||
private fun FolderMigrationDialog(onConfirm: () -> Unit) {
|
private fun FolderMigrationDialog(onConfirm: () -> Unit) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { }, // Force acknowledgment
|
onDismissRequest = { },
|
||||||
icon = { Icon(Icons.Default.FolderSpecial, contentDescription = null) },
|
icon = { Icon(Icons.Default.FolderSpecial, contentDescription = null) },
|
||||||
title = { Text("Folder Sync Refactored") },
|
title = { Text("Folder Sync Update") },
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
"We've completely rebuilt how Folder Sync works! Books from the folder are now read directly from your folder instead of being copied to app storage."
|
"We've improved Folder Sync! Books are now read directly from your folder without duplicating files."
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text(
|
Text(
|
||||||
"We'll now perform a one-time scan to migrate your existing progress and bookmarks. This will also free up internal storage space on your device.",
|
"To keep your reading progress safe, your previously synced books have been converted to standard local books. You may see duplicates once the folder resyncs; you can safely delete the old copies at your convenience.",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
@ -1158,7 +1158,7 @@ private fun FolderMigrationDialog(onConfirm: () -> Unit) {
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onConfirm) {
|
TextButton(onClick = onConfirm) {
|
||||||
Text("Start Migration")
|
Text("Got it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -529,9 +529,15 @@ open class MainViewModel(application: Application) : AndroidViewModel(applicatio
|
||||||
|
|
||||||
remoteConfigRepository.init()
|
remoteConfigRepository.init()
|
||||||
|
|
||||||
|
val isMigrationCompleted = prefs.getBoolean(KEY_FOLDER_MIGRATION_COMPLETED, false)
|
||||||
|
|
||||||
if (_internalState.value.syncedFolderUri != null) {
|
if (_internalState.value.syncedFolderUri != null) {
|
||||||
|
if (isMigrationCompleted) {
|
||||||
Timber.d("App Start: Triggering local folder metadata-only sync.")
|
Timber.d("App Start: Triggering local folder metadata-only sync.")
|
||||||
syncFolderMetadata()
|
syncFolderMetadata()
|
||||||
|
} else {
|
||||||
|
Timber.d("App Start: Skipping sync. Waiting for migration/detachment logic.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch { billingClientWrapper.initializeConnection() }
|
viewModelScope.launch { billingClientWrapper.initializeConnection() }
|
||||||
|
|
@ -593,12 +599,17 @@ open class MainViewModel(application: Application) : AndroidViewModel(applicatio
|
||||||
}
|
}
|
||||||
|
|
||||||
fun completeFolderMigration() {
|
fun completeFolderMigration() {
|
||||||
Timber.tag("FolderSync").d("User accepted migration. Marking completed and starting scan.")
|
Timber.tag("FolderSync").d("User acknowledged update. Detaching old books and starting fresh scan.")
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
recentFilesRepository.detachAllFolderBooks()
|
||||||
|
|
||||||
prefs.edit { putBoolean(KEY_FOLDER_MIGRATION_COMPLETED, true) }
|
prefs.edit { putBoolean(KEY_FOLDER_MIGRATION_COMPLETED, true) }
|
||||||
_internalState.update { it.copy(showFolderMigrationDialog = false) }
|
_internalState.update { it.copy(showFolderMigrationDialog = false) }
|
||||||
|
|
||||||
scanSyncedFolder()
|
scanSyncedFolder()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val fontsRepository = FontsRepository(appContext)
|
private val fontsRepository = FontsRepository(appContext)
|
||||||
|
|
||||||
|
|
@ -1330,8 +1341,12 @@ open class MainViewModel(application: Application) : AndroidViewModel(applicatio
|
||||||
if (workInfo != null) {
|
if (workInfo != null) {
|
||||||
when (workInfo.state) {
|
when (workInfo.state) {
|
||||||
WorkInfo.State.RUNNING, WorkInfo.State.ENQUEUED -> {
|
WorkInfo.State.RUNNING, WorkInfo.State.ENQUEUED -> {
|
||||||
val msg = if (metadataOnly) "Folder Sync: Updating metadata..." else "Folder Sync: Scanning files..."
|
val msg = if (metadataOnly) "Folder Sync: Updating metadata..." else "Scanning folder for new books..."
|
||||||
_internalState.update { it.copy(isLoading = true, bannerMessage = BannerMessage(msg)) }
|
_internalState.update { it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
isRefreshing = true,
|
||||||
|
bannerMessage = BannerMessage(msg)
|
||||||
|
) }
|
||||||
}
|
}
|
||||||
WorkInfo.State.SUCCEEDED -> {
|
WorkInfo.State.SUCCEEDED -> {
|
||||||
_internalState.update { it.copy(
|
_internalState.update { it.copy(
|
||||||
|
|
@ -1342,7 +1357,11 @@ open class MainViewModel(application: Application) : AndroidViewModel(applicatio
|
||||||
) }
|
) }
|
||||||
}
|
}
|
||||||
WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
|
WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
|
||||||
_internalState.update { it.copy(isLoading = false, isRefreshing = false, errorMessage = "Sync failed.") } // ADD isRefreshing = false
|
_internalState.update { it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
isRefreshing = false,
|
||||||
|
errorMessage = "Sync failed."
|
||||||
|
) }
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
|
|
@ -2255,9 +2274,14 @@ open class MainViewModel(application: Application) : AndroidViewModel(applicatio
|
||||||
if (type == FileType.PDF) {
|
if (type == FileType.PDF) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val recentItem = recentFilesRepository.getFileByBookId(bookId)
|
val recentItem = recentFilesRepository.getFileByBookId(bookId)
|
||||||
Timber.d(
|
|
||||||
"openBook: Loading PDF. bookId=$bookId, initialBookmarksJson from DB: ${recentItem?.bookmarksJson}"
|
if (recentItem?.sourceFolderUri != null) {
|
||||||
)
|
launch(Dispatchers.IO) {
|
||||||
|
recentFilesRepository.syncLocalMetadataToFolder(bookId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.d("openBook: Loading PDF. bookId=$bookId ...")
|
||||||
_internalState.update {
|
_internalState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
selectedPdfUri = uri,
|
selectedPdfUri = uri,
|
||||||
|
|
@ -2278,6 +2302,11 @@ open class MainViewModel(application: Application) : AndroidViewModel(applicatio
|
||||||
} else if (type == FileType.EPUB || type == FileType.MOBI || type == FileType.MD || type == FileType.TXT || type == FileType.HTML) {
|
} else if (type == FileType.EPUB || type == FileType.MOBI || type == FileType.MD || type == FileType.TXT || type == FileType.HTML) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val recentItem = recentFilesRepository.getFileByBookId(bookId)
|
val recentItem = recentFilesRepository.getFileByBookId(bookId)
|
||||||
|
if (recentItem?.sourceFolderUri != null) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
recentFilesRepository.syncLocalMetadataToFolder(bookId)
|
||||||
|
}
|
||||||
|
}
|
||||||
val locator =
|
val locator =
|
||||||
if (recentItem?.lastChapterIndex != null && recentItem.locatorBlockIndex != null && recentItem.locatorCharOffset != null) {
|
if (recentItem?.lastChapterIndex != null && recentItem.locatorBlockIndex != null && recentItem.locatorCharOffset != null) {
|
||||||
Locator(
|
Locator(
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,6 @@ class MetadataExtractionWorker(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only update if we actually found something useful
|
|
||||||
if (coverPath != null || title != null || author != null) {
|
if (coverPath != null || title != null || author != null) {
|
||||||
val updatedItem = item.copy(
|
val updatedItem = item.copy(
|
||||||
coverImagePath = coverPath ?: item.coverImagePath,
|
coverImagePath = coverPath ?: item.coverImagePath,
|
||||||
|
|
@ -99,6 +98,13 @@ class MetadataExtractionWorker(
|
||||||
)
|
)
|
||||||
recentFilesRepository.addRecentFile(updatedItem)
|
recentFilesRepository.addRecentFile(updatedItem)
|
||||||
Timber.tag("MetadataWorker").d("Updated metadata for: ${item.displayName}")
|
Timber.tag("MetadataWorker").d("Updated metadata for: ${item.displayName}")
|
||||||
|
|
||||||
|
try {
|
||||||
|
recentFilesRepository.syncLocalMetadataToFolder(updatedItem.bookId)
|
||||||
|
Timber.tag("MetadataWorker").d("Created/Updated JSON for: ${item.displayName}")
|
||||||
|
} catch (_: Exception) {
|
||||||
|
Timber.tag("MetadataWorker").w("Failed to save JSON during extraction for ${item.displayName}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
|
||||||
|
|
@ -87,4 +87,7 @@ interface RecentFileDao {
|
||||||
|
|
||||||
@Query("SELECT * FROM recent_files WHERE sourceFolderUri IS NOT NULL AND coverImagePath IS NULL AND isDeleted = 0")
|
@Query("SELECT * FROM recent_files WHERE sourceFolderUri IS NOT NULL AND coverImagePath IS NULL AND isDeleted = 0")
|
||||||
suspend fun getFolderBooksWithoutCovers(): List<RecentFileEntity>
|
suspend fun getFolderBooksWithoutCovers(): List<RecentFileEntity>
|
||||||
|
|
||||||
|
@Query("UPDATE recent_files SET sourceFolderUri = NULL WHERE sourceFolderUri IS NOT NULL")
|
||||||
|
suspend fun detachAllFolderBooks()
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +172,11 @@ class RecentFilesRepository(private val context: Context) {
|
||||||
return@withContext recentFileDao.getFolderBooksWithoutCovers().map { it.toRecentFileItem() }
|
return@withContext recentFileDao.getFolderBooksWithoutCovers().map { it.toRecentFileItem() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun detachAllFolderBooks() = withContext(Dispatchers.IO) {
|
||||||
|
recentFileDao.detachAllFolderBooks()
|
||||||
|
Timber.d("Detached all folder books. They are now standard local files.")
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun updateBookmarks(bookId: String, bookmarksJson: String) = withContext(Dispatchers.IO) {
|
suspend fun updateBookmarks(bookId: String, bookmarksJson: String) = withContext(Dispatchers.IO) {
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
recentFileDao.updateBookmarks(bookId, bookmarksJson, currentTime)
|
recentFileDao.updateBookmarks(bookId, bookmarksJson, currentTime)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue