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.
This commit is contained in:
Aryan 2026-03-08 17:56:09 +05:30 committed by GitHub
parent 76780bff31
commit 6ea414c9b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 426 additions and 298 deletions

View file

@ -184,6 +184,71 @@ object LocalSyncUtils {
}
}
suspend fun preloadAnnotationSidecars(
context: Context,
rootTree: DocumentFile
): Map<String, Pair<Long, String>> = withContext(Dispatchers.IO) {
val results = mutableMapOf<String, Pair<Long, String>>()
try {
val allFiles = rootTree.listFiles()
val annotationFiles = allFiles.filter { file ->
val name = file.name ?: ""
name.contains(ANNOTATION_SUFFIX) && name.endsWith(".json") && !name.endsWith(".tmp")
}
val filesByBookId = annotationFiles.groupBy { file ->
val name = file.name ?: ""
var temp = name.substringBeforeLast(".json")
if (temp.contains(".sync-conflict")) {
temp = temp.substringBefore(".sync-conflict")
}
if (temp.endsWith(ANNOTATION_SUFFIX)) {
temp = temp.substring(0, temp.length - ANNOTATION_SUFFIX.length)
}
if (temp.startsWith(".")) {
temp = temp.substring(1)
}
temp
}
filesByBookId.forEach { (bookId, files) ->
if (bookId.isNotBlank()) {
var bestTs = -1L
var bestData: String? = null
for (file in files) {
try {
val content = context.contentResolver.openInputStream(file.uri)?.use {
it.bufferedReader().readText()
} ?: continue
val json = JSONObject(content)
val ts = json.optLong("timestamp", 0L)
val data = json.optJSONObject("data")?.toString()
if (data != null && ts > bestTs) {
bestTs = ts
bestData = data
}
} catch (e: Exception) {
Timber.tag("FolderAnnotationSync").e(e, "Error parsing preloaded file: ${file.name}")
}
}
if (bestData != null) {
results[bookId] = Pair(bestTs, bestData)
}
}
}
} catch (e: Exception) {
Timber.tag("FolderAnnotationSync").e(e, "Error preloading annotation sidecars")
}
return@withContext results
}
suspend fun getAnnotationSidecar(
context: Context,
sourceFolderUri: Uri,