book-reader/app/src/main/java/com/aryan/reader/MetadataExtractionWorker.kt
Aryan 6ea414c9b2
Expanded folder sync functionality to support multiple folders and improved performance. (#47)
- Added `SyncedFolder` data class and transitioned from a single `syncedFolderUri` to a list of `syncedFolders`.
- Implemented logic to add, remove, and persist multiple synced folders using JSON in shared preferences, including a migration path for legacy single-folder settings.
- Updated `FolderSyncWorker` to iterate through all registered folders and added an annotation sidecar preloading optimization to reduce file I/O during sync.
- Redesigned the "Folder Sync" UI in `LibraryScreen` to display a list of active folders with individual management options and a 3-folder limit.
- Refined permission handling to request both read and write access for synced folders.
2026-03-08 17:56:09 +05:30

115 lines
No EOL
4.8 KiB
Kotlin

// MetadataExtractionWorker.kt
package com.aryan.reader
import android.content.Context
import androidx.core.net.toUri
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.aryan.reader.data.RecentFilesRepository
import com.aryan.reader.epub.EpubParser
import com.aryan.reader.epub.MobiParser
import com.aryan.reader.pdf.PdfCoverGenerator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
class MetadataExtractionWorker(
private val appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
private val recentFilesRepository = RecentFilesRepository(appContext)
private val epubParser = EpubParser(appContext)
private val mobiParser = MobiParser(appContext)
private val pdfCoverGenerator = PdfCoverGenerator(appContext)
companion object {
const val WORK_NAME = "MetadataExtractionWorker"
}
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
val prefs = appContext.getSharedPreferences("reader_user_prefs", Context.MODE_PRIVATE)
val hasLegacy = prefs.contains("synced_folder_uri")
val hasNew = prefs.contains("synced_folders_list_json")
if (!hasLegacy && !hasNew) {
Timber.tag("MetadataWorker").w("No folders linked. Stopping.")
return@withContext Result.success()
}
try {
val filesToProcess = recentFilesRepository.getFolderBooksWithoutCovers()
if (filesToProcess.isEmpty()) {
return@withContext Result.success()
}
Timber.tag("MetadataWorker").i("Starting background metadata extraction for ${filesToProcess.size} books.")
filesToProcess.forEach { item ->
if (isStopped) return@forEach
if (item.sourceFolderUri == null) return@forEach
try {
val uri = item.uriString?.toUri() ?: return@forEach
val type = item.type
var coverPath: String? = null
var title: String? = null
var author: String? = null
appContext.contentResolver.openInputStream(uri)?.use { inputStream ->
when (type) {
FileType.EPUB -> {
val book = epubParser.createEpubBook(
inputStream = inputStream,
originalBookNameHint = item.displayName,
parseContent = false
)
title = book.title.takeIf { it.isNotBlank() }
author = book.author.takeIf { it.isNotBlank() }
book.coverImage?.let { coverPath = recentFilesRepository.saveCoverToCache(it, uri) }
}
FileType.MOBI -> {
val book = mobiParser.createMobiBook(inputStream, item.displayName)
book?.let {
title = it.title.takeIf { t -> t.isNotBlank() }
author = it.author.takeIf { a -> a.isNotBlank() }
it.coverImage?.let { img -> coverPath = recentFilesRepository.saveCoverToCache(img, uri) }
}
}
FileType.PDF -> {
pdfCoverGenerator.generateCover(uri)?.let {
coverPath = recentFilesRepository.saveCoverToCache(it, uri)
}
title = item.displayName.substringBeforeLast(".")
}
else -> {}
}
}
if (coverPath != null || title != null || author != null) {
val updatedItem = item.copy(
coverImagePath = coverPath ?: item.coverImagePath,
title = title ?: item.title ?: item.displayName,
author = author ?: item.author
)
recentFilesRepository.addRecentFile(updatedItem)
Timber.tag("MetadataWorker").d("Updated local metadata for: ${item.displayName}")
}
} catch (e: Exception) {
Timber.tag("MetadataWorker").e(e, "Failed to extract metadata for ${item.displayName}")
}
}
return@withContext Result.success()
} catch (e: Exception) {
Timber.tag("MetadataWorker").e(e, "Metadata extraction failed")
return@withContext Result.failure()
}
}
}