Skip to content

Commit 35d0c93

Browse files
committed
Persist Gutenberg download markers
1 parent d6eff50 commit 35d0c93

7 files changed

Lines changed: 78 additions & 2 deletions

File tree

app/src/main/kotlin/com/fredapp/wbooks/MainActivity.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class MainActivity : ComponentActivity() {
8383
documentCache = app.documentCache,
8484
paceRepo = app.readingPaceRepository,
8585
statsRepo = app.readingStatsRepository,
86+
gutenbergDownloadsRepo = app.gutenbergDownloadsRepository,
8687
appScope = app.appScope,
8788
),
8889
)

app/src/main/kotlin/com/fredapp/wbooks/WBooksApp.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.fredapp.wbooks
22

33
import android.app.Application
44
import com.fredapp.wbooks.data.bookmarks.BookmarksRepository
5+
import com.fredapp.wbooks.data.gutenberg.GutenbergDownloadsRepository
56
import com.fredapp.wbooks.data.library.LibraryRepository
67
import com.fredapp.wbooks.data.pace.ReadingPaceRepository
78
import com.fredapp.wbooks.data.position.PositionsRepository
@@ -29,6 +30,7 @@ class WBooksApp : Application() {
2930
val bookmarksRepository: BookmarksRepository by lazy { BookmarksRepository(this) }
3031
val readingPaceRepository: ReadingPaceRepository by lazy { ReadingPaceRepository(this) }
3132
val readingStatsRepository: ReadingStatsRepository by lazy { ReadingStatsRepository(this) }
33+
val gutenbergDownloadsRepository: GutenbergDownloadsRepository by lazy { GutenbergDownloadsRepository(this) }
3234
val transferController: TransferController by lazy { TransferController(this) }
3335
val documentCache: DocumentCache by lazy { DocumentCache(File(cacheDir, "parsed")) }
3436
val crashReportingPref: CrashReportingPref by lazy { CrashReportingPref(this) }
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.fredapp.wbooks.data.gutenberg
2+
3+
import android.content.Context
4+
import androidx.datastore.core.DataStore
5+
import androidx.datastore.preferences.core.Preferences
6+
import androidx.datastore.preferences.core.edit
7+
import androidx.datastore.preferences.core.stringPreferencesKey
8+
import androidx.datastore.preferences.preferencesDataStore
9+
import kotlinx.coroutines.flow.Flow
10+
import kotlinx.coroutines.flow.map
11+
12+
private val Context.gutenbergDownloadsDataStore: DataStore<Preferences> by preferencesDataStore(name = "gutenberg_downloads")
13+
14+
/**
15+
* Project Gutenberg identity markers keyed by current book id.
16+
*
17+
* Filename/title matching breaks after a user renames a downloaded book. This
18+
* repository stores the Gutenberg entry identity separately and follows the
19+
* same book-id migrations as positions, bookmarks, stats, and parser cache.
20+
*/
21+
class GutenbergDownloadsRepository(context: Context) {
22+
23+
private val store: DataStore<Preferences> = context.applicationContext.gutenbergDownloadsDataStore
24+
25+
val downloadedKeysFlow: Flow<Set<String>> = store.data.map { prefs ->
26+
prefs.asMap().values
27+
.filterIsInstance<String>()
28+
.flatMap { decodeKeys(it) }
29+
.toSet()
30+
}
31+
32+
suspend fun markDownloaded(bookId: String, keys: Set<String>) {
33+
val clean = keys.map { it.trim() }.filter { it.isNotEmpty() }.distinct()
34+
if (clean.isEmpty()) return
35+
store.edit { prefs -> prefs[bucketKey(bookId)] = clean.joinToString("\n") }
36+
}
37+
38+
suspend fun clear(bookId: String) {
39+
store.edit { prefs -> prefs.remove(bucketKey(bookId)) }
40+
}
41+
42+
suspend fun moveBookId(fromBookId: String, toBookId: String) {
43+
if (fromBookId == toBookId) return
44+
val from = bucketKey(fromBookId)
45+
val to = bucketKey(toBookId)
46+
store.edit { prefs ->
47+
prefs[from]?.let { prefs[to] = it }
48+
prefs.remove(from)
49+
}
50+
}
51+
52+
private fun decodeKeys(raw: String): List<String> =
53+
raw.lines().map { it.trim() }.filter { it.isNotEmpty() }
54+
55+
private fun bucketKey(bookId: String) = stringPreferencesKey("gutenberg:$bookId")
56+
}

app/src/main/kotlin/com/fredapp/wbooks/transfer/BookReceiverService.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ class BookReceiverService : WearableListenerService() {
238238
app.readingPaceRepository.clear(id)
239239
app.positionsRepository.clear(id)
240240
app.bookmarksRepository.clear(id)
241+
app.gutenbergDownloadsRepository.clear(id)
241242
app.documentCache.invalidate(id)
242243
} else {
243244
// id may be a folder name — delete it and all books inside recursively
@@ -248,6 +249,7 @@ class BookReceiverService : WearableListenerService() {
248249
app.readingPaceRepository.clear(bookId)
249250
app.positionsRepository.clear(bookId)
250251
app.bookmarksRepository.clear(bookId)
252+
app.gutenbergDownloadsRepository.clear(bookId)
251253
app.documentCache.invalidate(bookId)
252254
}
253255
dir.deleteRecursively()
@@ -276,6 +278,7 @@ class BookReceiverService : WearableListenerService() {
276278
app.positionsRepository.moveBookId(oldId, newId)
277279
app.bookmarksRepository.moveBookId(oldId, newId)
278280
app.readingStatsRepository.moveBookId(oldId, newId)
281+
app.gutenbergDownloadsRepository.moveBookId(oldId, newId)
279282
app.documentCache.moveBookId(oldId, newId)
280283
}
281284
}
@@ -288,6 +291,7 @@ class BookReceiverService : WearableListenerService() {
288291
app.positionsRepository.moveBookId(id, newId)
289292
app.bookmarksRepository.moveBookId(id, newId)
290293
app.readingStatsRepository.moveBookId(id, newId)
294+
app.gutenbergDownloadsRepository.moveBookId(id, newId)
291295
app.documentCache.moveBookId(id, newId)
292296
}
293297

app/src/main/kotlin/com/fredapp/wbooks/transfer/UploadServerService.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class UploadServerService : Service() {
9191
app.readingPaceRepository.clear(bookId)
9292
app.positionsRepository.clear(bookId)
9393
app.bookmarksRepository.clear(bookId)
94+
app.gutenbergDownloadsRepository.clear(bookId)
9495
app.documentCache.invalidate(bookId)
9596
}
9697
},
@@ -100,6 +101,7 @@ class UploadServerService : Service() {
100101
app.positionsRepository.moveBookId(fromBookId, toBookId)
101102
app.bookmarksRepository.moveBookId(fromBookId, toBookId)
102103
app.readingStatsRepository.moveBookId(fromBookId, toBookId)
104+
app.gutenbergDownloadsRepository.moveBookId(fromBookId, toBookId)
103105
app.documentCache.moveBookId(fromBookId, toBookId)
104106
}
105107
},
@@ -117,6 +119,7 @@ class UploadServerService : Service() {
117119
app.positionsRepository.moveBookId(b.id, newId)
118120
app.bookmarksRepository.moveBookId(b.id, newId)
119121
app.readingStatsRepository.moveBookId(b.id, newId)
122+
app.gutenbergDownloadsRepository.moveBookId(b.id, newId)
120123
app.documentCache.moveBookId(b.id, newId)
121124
}
122125
}

app/src/main/kotlin/com/fredapp/wbooks/ui/ReaderViewModel.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.fredapp.wbooks.data.book.Book
88
import com.fredapp.wbooks.data.book.BookFormat
99
import com.fredapp.wbooks.data.bookmarks.Bookmark
1010
import com.fredapp.wbooks.data.bookmarks.BookmarksRepository
11+
import com.fredapp.wbooks.data.gutenberg.GutenbergDownloadsRepository
1112
import com.fredapp.wbooks.data.library.LibraryRepository
1213
import com.fredapp.wbooks.data.pace.ReadingPaceRepository
1314
import com.fredapp.wbooks.data.position.BookPosition
@@ -82,6 +83,7 @@ class ReaderViewModel(
8283
private val documentCache: DocumentCache,
8384
private val paceRepo: ReadingPaceRepository,
8485
private val statsRepo: ReadingStatsRepository,
86+
private val gutenbergDownloadsRepo: GutenbergDownloadsRepository,
8587
/** Application-level scope used for cache writes that must outlive this ViewModel. */
8688
private val appScope: CoroutineScope,
8789
) : ViewModel() {
@@ -142,6 +144,7 @@ class ReaderViewModel(
142144
paceRepo.clear(id)
143145
positionsRepo.clear(id)
144146
bookmarksRepo.clear(id)
147+
gutenbergDownloadsRepo.clear(id)
145148
documentCache.invalidate(id)
146149
}
147150
}
@@ -604,6 +607,7 @@ class ReaderViewModel(
604607
paceRepo.clear(bookId)
605608
positionsRepo.clear(bookId)
606609
bookmarksRepo.clear(bookId)
610+
gutenbergDownloadsRepo.clear(bookId)
607611
documentCache.invalidate(bookId)
608612
}
609613
}
@@ -615,6 +619,7 @@ class ReaderViewModel(
615619
positionsRepo.moveBookId(oldId, newId)
616620
bookmarksRepo.moveBookId(oldId, newId)
617621
statsRepo.moveBookId(oldId, newId)
622+
gutenbergDownloadsRepo.moveBookId(oldId, newId)
618623
documentCache.moveBookId(oldId, newId)
619624
}
620625

@@ -872,14 +877,15 @@ class ReaderViewModel(
872877
private val documentCache: DocumentCache,
873878
private val paceRepo: ReadingPaceRepository,
874879
private val statsRepo: ReadingStatsRepository,
880+
private val gutenbergDownloadsRepo: GutenbergDownloadsRepository,
875881
private val appScope: CoroutineScope,
876882
) : ViewModelProvider.Factory {
877883
@Suppress("UNCHECKED_CAST")
878884
override fun <T : ViewModel> create(modelClass: Class<T>): T {
879885
require(modelClass == ReaderViewModel::class.java)
880886
return ReaderViewModel(
881887
settingsRepo, libraryRepo, positionsRepo, bookmarksRepo, transferController,
882-
documentCache, paceRepo, statsRepo, appScope,
888+
documentCache, paceRepo, statsRepo, gutenbergDownloadsRepo, appScope,
883889
) as T
884890
}
885891
}

app/src/main/kotlin/com/fredapp/wbooks/ui/library/WatchGutenbergScreen.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import androidx.compose.foundation.text.KeyboardActions
2323
import androidx.compose.foundation.text.KeyboardOptions
2424
import androidx.compose.runtime.Composable
2525
import androidx.compose.runtime.LaunchedEffect
26+
import androidx.compose.runtime.collectAsState
2627
import androidx.compose.runtime.getValue
2728
import androidx.compose.runtime.mutableStateOf
2829
import androidx.compose.runtime.remember
@@ -104,6 +105,7 @@ fun WatchGutenbergScreen(onBack: () -> Unit, onLibraryChanged: () -> Unit) {
104105
var addedFilenames by remember { mutableStateOf(emptySet<String>()) }
105106
var addedTitleKeys by remember { mutableStateOf(emptySet<String>()) }
106107
var addedGutenbergKeys by remember { mutableStateOf(emptySet<String>()) }
108+
val persistedGutenbergKeys by app.gutenbergDownloadsRepository.downloadedKeysFlow.collectAsState(emptySet())
107109
var message by remember { mutableStateOf<String?>(null) }
108110
var error by remember { mutableStateOf<String?>(null) }
109111
val listState = rememberScalingLazyListState()
@@ -230,7 +232,7 @@ fun WatchGutenbergScreen(onBack: () -> Unit, onLibraryChanged: () -> Unit) {
230232
}
231233

232234
fun isPresent(book: GutenbergBook): Boolean =
233-
book.addedKeys().any { it in addedGutenbergKeys } ||
235+
book.addedKeys().any { it in addedGutenbergKeys || it in persistedGutenbergKeys } ||
234236
filenameFor(book).normalizedFilename() in addedFilenames ||
235237
book.gutenbergId()?.let { id ->
236238
SEED_GUTENBERG_FILES[id]?.normalizedFilename() in addedFilenames
@@ -292,8 +294,10 @@ fun WatchGutenbergScreen(onBack: () -> Unit, onLibraryChanged: () -> Unit) {
292294
app.libraryRepository.refresh()
293295
}
294296
destFile?.let {
297+
val bookId = it.relativeTo(app.booksDir).invariantSeparatorsPath
295298
addedFilenames = addedFilenames + it.name.normalizedFilename()
296299
addedTitleKeys = addedTitleKeys + it.nameWithoutExtension.normalizedTitleKey()
300+
app.gutenbergDownloadsRepository.markDownloaded(bookId, book.addedKeys())
297301
}
298302
addedGutenbergKeys = addedGutenbergKeys + book.addedKeys()
299303
message = null

0 commit comments

Comments
 (0)