Skip to content

Commit 8de3bf4

Browse files
authored
Merge pull request #453 from synonymdev/feat/backup-bulk-upserts
feat: restore LSP data and use bulk upsert APIs
2 parents 94abebd + 16df3f9 commit 8de3bf4

File tree

16 files changed

+191
-67
lines changed

16 files changed

+191
-67
lines changed

app/src/main/java/to/bitkit/data/dao/TagMetadataDao.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ interface TagMetadataDao {
1616
suspend fun insert(tagMetadata: TagMetadataEntity)
1717

1818
@Upsert
19-
suspend fun upsert(tagMetadata: TagMetadataEntity)
19+
suspend fun upsert(entity: TagMetadataEntity)
20+
21+
@Upsert
22+
suspend fun upsert(entities: List<TagMetadataEntity>)
2023

2124
@Query("SELECT * FROM tag_metadata")
2225
fun observeAll(): Flow<List<TagMetadataEntity>>

app/src/main/java/to/bitkit/data/dao/TransferDao.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ interface TransferDao {
1717
@Upsert
1818
suspend fun upsert(transfer: TransferEntity)
1919

20+
@Upsert
21+
suspend fun upsert(transfers: List<TransferEntity>)
22+
2023
@Update
2124
suspend fun update(transfer: TransferEntity)
2225

app/src/main/java/to/bitkit/models/BackupPayloads.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package to.bitkit.models
22

33
import com.synonym.bitkitcore.Activity
4+
import com.synonym.bitkitcore.ClosedChannelDetails
45
import com.synonym.bitkitcore.IBtInfo
56
import com.synonym.bitkitcore.IBtOrder
67
import com.synonym.bitkitcore.IcJitEntry
@@ -38,4 +39,5 @@ data class ActivityBackupV1(
3839
val version: Int = 1,
3940
val createdAt: Long,
4041
val activities: List<Activity>,
42+
val closedChannels: List<ClosedChannelDetails>,
4143
)

app/src/main/java/to/bitkit/repositories/ActivityRepo.kt

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package to.bitkit.repositories
22

33
import com.synonym.bitkitcore.Activity
44
import com.synonym.bitkitcore.ActivityFilter
5+
import com.synonym.bitkitcore.ClosedChannelDetails
56
import com.synonym.bitkitcore.IcJitEntry
67
import com.synonym.bitkitcore.LightningActivity
78
import com.synonym.bitkitcore.PaymentState
@@ -29,6 +30,7 @@ import to.bitkit.ext.amountOnClose
2930
import to.bitkit.ext.matchesPaymentId
3031
import to.bitkit.ext.nowTimestamp
3132
import to.bitkit.ext.rawId
33+
import to.bitkit.models.ActivityBackupV1
3234
import to.bitkit.services.CoreService
3335
import to.bitkit.utils.AddressChecker
3436
import to.bitkit.utils.Logger
@@ -166,9 +168,6 @@ class ActivityRepo @Inject constructor(
166168
}
167169
}
168170

169-
/**
170-
* Gets activities with specified filters
171-
*/
172171
suspend fun getActivities(
173172
filter: ActivityFilter? = null,
174173
txType: PaymentType? = null,
@@ -198,9 +197,6 @@ class ActivityRepo @Inject constructor(
198197
}
199198
}
200199

201-
/**
202-
* Gets a specific activity by ID
203-
*/
204200
suspend fun getActivity(id: String): Result<Activity?> = withContext(bgDispatcher) {
205201
return@withContext runCatching {
206202
coreService.activity.getActivity(id)
@@ -209,6 +205,16 @@ class ActivityRepo @Inject constructor(
209205
}
210206
}
211207

208+
suspend fun getClosedChannels(
209+
sortDirection: SortDirection = SortDirection.ASC,
210+
): Result<List<ClosedChannelDetails>> = withContext(bgDispatcher) {
211+
runCatching {
212+
coreService.activity.closedChannels(sortDirection)
213+
}.onFailure { e ->
214+
Logger.error("Error getting closed channels (sortDirection=$sortDirection)", e, context = TAG)
215+
}
216+
}
217+
212218
/**
213219
* Updates an activity
214220
* @param forceUpdate use it if you want update a deleted activity
@@ -643,6 +649,13 @@ class ActivityRepo @Inject constructor(
643649
}
644650
}
645651

652+
suspend fun restoreFromBackup(backup: ActivityBackupV1): Result<Unit> = withContext(bgDispatcher) {
653+
return@withContext runCatching {
654+
coreService.activity.upsertList(backup.activities)
655+
coreService.activity.upsertClosedChannelList(backup.closedChannels)
656+
}
657+
}
658+
646659
// MARK: - Development/Testing Methods
647660

648661
/**

app/src/main/java/to/bitkit/repositories/BackupRepo.kt

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -371,10 +371,12 @@ class BackupRepo @Inject constructor(
371371

372372
BackupCategory.ACTIVITY -> {
373373
val activities = activityRepo.getActivities().getOrDefault(emptyList())
374+
val closedChannels = activityRepo.getClosedChannels().getOrDefault(emptyList())
374375

375376
val payload = ActivityBackupV1(
376377
createdAt = currentTimeMillis(),
377378
activities = activities,
379+
closedChannels = closedChannels,
378380
)
379381

380382
json.encodeToString(payload).toByteArray()
@@ -399,47 +401,29 @@ class BackupRepo @Inject constructor(
399401
}
400402
performRestore(BackupCategory.WALLET) { dataBytes ->
401403
val parsed = json.decodeFromString<WalletBackupV1>(String(dataBytes))
402-
403-
parsed.transfers.forEach { transfer ->
404-
db.transferDao().upsert(transfer)
405-
}
406-
404+
db.transferDao().upsert(parsed.transfers)
407405
Logger.debug("Restored ${parsed.transfers.size} transfers", context = TAG)
408406
}
409407
performRestore(BackupCategory.METADATA) { dataBytes ->
410408
val parsed = json.decodeFromString<MetadataBackupV1>(String(dataBytes))
411-
412-
parsed.tagMetadata.forEach { entity ->
413-
db.tagMetadataDao().upsert(entity)
414-
}
415-
409+
db.tagMetadataDao().upsert(parsed.tagMetadata)
416410
cacheStore.update { parsed.cache }
417-
418-
Logger.debug("Restored caches and ${parsed.tagMetadata.size} tags metadata", context = TAG)
411+
Logger.debug("Restored caches and ${parsed.tagMetadata.size} tags metadata records", TAG)
419412
}
420413
performRestore(BackupCategory.BLOCKTANK) { dataBytes ->
421414
val parsed = json.decodeFromString<BlocktankBackupV1>(String(dataBytes))
422-
423-
// TODO: Restore orders, CJIT entries, and info to bitkit-core using synonymdev/bitkit-core#46
424-
// For now, trigger a refresh from the server to sync the data
425-
blocktankRepo.refreshInfo()
426-
blocktankRepo.refreshOrders()
427-
428-
Logger.debug(
429-
"Triggered Blocktank refresh (${parsed.orders.size} orders," +
430-
"${parsed.cjitEntries.size} CJIT entries," +
431-
"info=${parsed.info != null} backed up)",
432-
context = TAG,
433-
)
415+
blocktankRepo.restoreFromBackup(parsed).onSuccess {
416+
Logger.debug("Restored ${parsed.orders.size} orders, ${parsed.cjitEntries.size} CJITs", TAG)
417+
}
434418
}
435419
performRestore(BackupCategory.ACTIVITY) { dataBytes ->
436420
val parsed = json.decodeFromString<ActivityBackupV1>(String(dataBytes))
437-
438-
parsed.activities.forEach { activity ->
439-
activityRepo.upsertActivity(activity)
421+
activityRepo.restoreFromBackup(parsed).onSuccess {
422+
Logger.debug(
423+
"Restored ${parsed.activities.size} activities, ${parsed.closedChannels.size} closed channels",
424+
context = TAG,
425+
)
440426
}
441-
442-
Logger.debug("Restored ${parsed.activities.size} activities", context = TAG)
443427
}
444428

445429
Logger.info("Full restore success", context = TAG)

app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import to.bitkit.data.CacheStore
3131
import to.bitkit.di.BgDispatcher
3232
import to.bitkit.env.Env
3333
import to.bitkit.ext.nowTimestamp
34+
import to.bitkit.models.BlocktankBackupV1
3435
import to.bitkit.models.EUR_CURRENCY
3536
import to.bitkit.services.CoreService
3637
import to.bitkit.services.LightningService
@@ -374,6 +375,27 @@ class BlocktankRepo @Inject constructor(
374375
_blocktankState.update { BlocktankState() }
375376
}
376377

378+
suspend fun restoreFromBackup(backup: BlocktankBackupV1): Result<Unit> = withContext(bgDispatcher) {
379+
return@withContext runCatching {
380+
coreService.blocktank.upsertOrderList(backup.orders)
381+
coreService.blocktank.upsertCjitList(backup.cjitEntries)
382+
backup.info?.let { info ->
383+
coreService.blocktank.setInfo(info)
384+
}
385+
386+
// We don't refresh orders here because we rely on the polling mechanism.
387+
// We also don't restore `paidOrders` the refresh interval uses restored paidOrderIds to rebuild the list.
388+
389+
_blocktankState.update {
390+
it.copy(
391+
orders = backup.orders,
392+
cjitEntries = backup.cjitEntries,
393+
info = backup.info,
394+
)
395+
}
396+
}
397+
}
398+
377399
companion object {
378400
private const val TAG = "BlocktankRepo"
379401
private const val DEFAULT_CHANNEL_EXPIRY_WEEKS = 6u

app/src/main/java/to/bitkit/services/CoreService.kt

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.synonym.bitkitcore.Activity
44
import com.synonym.bitkitcore.ActivityFilter
55
import com.synonym.bitkitcore.BtOrderState2
66
import com.synonym.bitkitcore.CJitStateEnum
7+
import com.synonym.bitkitcore.ClosedChannelDetails
78
import com.synonym.bitkitcore.CreateCjitOptions
89
import com.synonym.bitkitcore.CreateOrderOptions
910
import com.synonym.bitkitcore.FeeRates
@@ -26,6 +27,7 @@ import com.synonym.bitkitcore.deleteActivityById
2627
import com.synonym.bitkitcore.estimateOrderFeeFull
2728
import com.synonym.bitkitcore.getActivities
2829
import com.synonym.bitkitcore.getActivityById
30+
import com.synonym.bitkitcore.getAllClosedChannels
2931
import com.synonym.bitkitcore.getAllUniqueTags
3032
import com.synonym.bitkitcore.getCjitEntries
3133
import com.synonym.bitkitcore.getInfo
@@ -39,7 +41,13 @@ import com.synonym.bitkitcore.refreshActiveOrders
3941
import com.synonym.bitkitcore.removeTags
4042
import com.synonym.bitkitcore.updateActivity
4143
import com.synonym.bitkitcore.updateBlocktankUrl
44+
import com.synonym.bitkitcore.upsertActivities
4245
import com.synonym.bitkitcore.upsertActivity
46+
import com.synonym.bitkitcore.upsertCjitEntries
47+
import com.synonym.bitkitcore.upsertClosedChannel
48+
import com.synonym.bitkitcore.upsertClosedChannels
49+
import com.synonym.bitkitcore.upsertInfo
50+
import com.synonym.bitkitcore.upsertOrders
4351
import io.ktor.client.HttpClient
4452
import io.ktor.client.request.get
4553
import io.ktor.http.HttpStatusCode
@@ -199,10 +207,20 @@ class ActivityService(
199207
}
200208
}
201209

202-
suspend fun upsert(activity: Activity) {
203-
ServiceQueue.CORE.background {
204-
upsertActivity(activity)
205-
}
210+
suspend fun upsert(activity: Activity) = ServiceQueue.CORE.background {
211+
upsertActivity(activity)
212+
}
213+
214+
suspend fun upsertList(activities: List<Activity>) = ServiceQueue.CORE.background {
215+
upsertActivities(activities)
216+
}
217+
218+
suspend fun upsertClosedChannelItem(closedChannel: ClosedChannelDetails) = ServiceQueue.CORE.background {
219+
upsertClosedChannel(closedChannel)
220+
}
221+
222+
suspend fun upsertClosedChannelList(closedChannels: List<ClosedChannelDetails>) = ServiceQueue.CORE.background {
223+
upsertClosedChannels(closedChannels)
206224
}
207225

208226
suspend fun getActivity(id: String): Activity? {
@@ -267,6 +285,12 @@ class ActivityService(
267285
}
268286
}
269287

288+
suspend fun closedChannels(
289+
sortDirection: SortDirection,
290+
): List<ClosedChannelDetails> = ServiceQueue.CORE.background {
291+
getAllClosedChannels(sortDirection)
292+
}
293+
270294
/**
271295
* Maps all `PaymentDetails` from LDK Node to bitkit-core [Activity] records.
272296
*
@@ -325,7 +349,7 @@ class ActivityService(
325349
}
326350
}
327351

328-
private suspend fun processBolt11(
352+
private fun processBolt11(
329353
kind: PaymentKind.Bolt11,
330354
payment: PaymentDetails,
331355
state: PaymentState,
@@ -421,6 +445,7 @@ class ActivityService(
421445
confirmed = isConfirmed,
422446
timestamp = timestamp,
423447
isBoosted = false,
448+
boostTxIds = emptyList(),
424449
isTransfer = false,
425450
doesExist = true,
426451
confirmTimestamp = confirmedTimestamp,
@@ -471,11 +496,11 @@ class ActivityService(
471496
)
472497

473498
repeat(count) { i ->
474-
val isLightning = Random.Default.nextBoolean()
499+
val isLightning = Random.nextBoolean()
475500
val value = (1000..1_000_000).random().toULong()
476501
val txTimestamp =
477502
(timestamp.toLong() - (0..30L * 24 * 60 * 60).random()).toULong() // Random time in last 30 days
478-
val txType = if (Random.Default.nextBoolean()) PaymentType.SENT else PaymentType.RECEIVED
503+
val txType = if (Random.nextBoolean()) PaymentType.SENT else PaymentType.RECEIVED
479504
val status = when ((0..10).random()) {
480505
in 0..7 -> PaymentState.SUCCEEDED // 80% chance
481506
8 -> PaymentState.PENDING // 10% chance
@@ -497,7 +522,7 @@ class ActivityService(
497522
invoice = "lnbc$value",
498523
message = possibleMessages.random(),
499524
timestamp = txTimestamp,
500-
preimage = if (Random.Default.nextBoolean()) "preimage$i" else null,
525+
preimage = if (Random.nextBoolean()) "preimage$i" else null,
501526
createdAt = txTimestamp,
502527
updatedAt = txTimestamp
503528
)
@@ -513,16 +538,17 @@ class ActivityService(
513538
fee = (100..10_000).random().toULong(),
514539
feeRate = (1..100).random().toULong(),
515540
address = "bc1...$i",
516-
confirmed = Random.Default.nextBoolean(),
541+
confirmed = Random.nextBoolean(),
517542
timestamp = txTimestamp,
518-
isBoosted = Random.Default.nextBoolean(),
519-
isTransfer = Random.Default.nextBoolean(),
543+
isBoosted = Random.nextBoolean(),
544+
boostTxIds = emptyList(),
545+
isTransfer = Random.nextBoolean(),
520546
doesExist = true,
521-
confirmTimestamp = if (Random.Default.nextBoolean()) txTimestamp + 3600.toULong() else null,
522-
channelId = if (Random.Default.nextBoolean()) "channel$i" else null,
547+
confirmTimestamp = if (Random.nextBoolean()) txTimestamp + 3600.toULong() else null,
548+
channelId = if (Random.nextBoolean()) "channel$i" else null,
523549
transferTxId = null,
524550
createdAt = txTimestamp,
525-
updatedAt = txTimestamp
551+
updatedAt = txTimestamp,
526552
)
527553
)
528554
}
@@ -664,6 +690,18 @@ class BlocktankService(
664690
}
665691
}
666692

693+
suspend fun setInfo(info: IBtInfo) = ServiceQueue.CORE.background {
694+
upsertInfo(info)
695+
}
696+
697+
suspend fun upsertOrderList(orders: List<IBtOrder>) = ServiceQueue.CORE.background {
698+
upsertOrders(orders)
699+
}
700+
701+
suspend fun upsertCjitList(cjitEntries: List<IcJitEntry>) = ServiceQueue.CORE.background {
702+
upsertCjitEntries(cjitEntries)
703+
}
704+
667705
// MARK: - Regtest methods
668706
suspend fun regtestMine(count: UInt = 1u) {
669707
com.synonym.bitkitcore.regtestMine(count = count)

app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,7 @@ private fun PreviewOnchain() {
693693
confirmed = true,
694694
timestamp = (System.currentTimeMillis() / 1000 - 3600).toULong(),
695695
isBoosted = false,
696+
boostTxIds = emptyList(),
696697
isTransfer = false,
697698
doesExist = true,
698699
confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(),

app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ private fun PreviewOnchain() {
361361
confirmed = true,
362362
timestamp = (System.currentTimeMillis() / 1000 - 3600).toULong(),
363363
isBoosted = false,
364+
boostTxIds = emptyList(),
364365
isTransfer = false,
365366
doesExist = true,
366367
confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(),

0 commit comments

Comments
 (0)