support for downloads in history

This commit is contained in:
Fabian Freund
2025-09-26 16:31:07 +02:00
parent 9a0c00f842
commit e20cb336b1
6 changed files with 128 additions and 10 deletions
@@ -45,6 +45,11 @@ object GlobalComponents {
.whenSessionsChange() .whenSessionsChange()
} }
@DelicateCoroutinesApi
private fun restoreDownloads(newComponents: Components) = GlobalScope.launch(Dispatchers.Main) {
newComponents.useCases.downloadsUseCases.restoreDownloads()
}
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
fun setUp( fun setUp(
applicationContext: Context, applicationContext: Context,
@@ -82,8 +87,7 @@ object GlobalComponents {
newComponents.core.engine.warmUp() newComponents.core.engine.warmUp()
restoreBrowserState(newComponents) restoreBrowserState(newComponents)
restoreDownloads(newComponents)
//newComponents.useCases.downloadsUseCases.restoreDownloads()
try { try {
GlobalPlacesDependencyProvider.initialize(newComponents.core.historyStorage) GlobalPlacesDependencyProvider.initialize(newComponents.core.historyStorage)
@@ -1,7 +1,6 @@
package eu.weblibre.flutter_mozilla_components.api package eu.weblibre.flutter_mozilla_components.api
import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.pigeons.AutocompleteResult
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoHistoryApi import eu.weblibre.flutter_mozilla_components.pigeons.GeckoHistoryApi
import eu.weblibre.flutter_mozilla_components.pigeons.VisitInfo import eu.weblibre.flutter_mozilla_components.pigeons.VisitInfo
import eu.weblibre.flutter_mozilla_components.pigeons.VisitType import eu.weblibre.flutter_mozilla_components.pigeons.VisitType
@@ -10,6 +9,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import mozilla.components.browser.state.state.content.DownloadState
import kotlin.time.Duration.Companion.milliseconds
class GeckoHistoryApiImpl() : GeckoHistoryApi { class GeckoHistoryApiImpl() : GeckoHistoryApi {
companion object { companion object {
@@ -20,6 +21,32 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi {
requireNotNull(GlobalComponents.components) { "Components not initialized" } requireNotNull(GlobalComponents.components) { "Components not initialized" }
} }
private fun Map<String, DownloadState>.toVisitInfoList(
startMillis: Long,
endMillis: Long,
): List<VisitInfo> =
values
.filter { isDisplayableItem(it.status) &&
it.createdTime >= startMillis && it.createdTime <= endMillis
}
.distinctBy { Pair(it.fileName, it.status) }
.sortedByDescending { it.createdTime } // sort from newest to oldest
.map { it.toVisitInfo() }
private fun isDisplayableItem(status: DownloadState.Status) =
status != DownloadState.Status.CANCELLED
private fun DownloadState.toVisitInfo() =
VisitInfo(
url = url,
visitType = VisitType.DOWNLOAD,
visitTime = createdTime,
title = filePath,
previewImageUrl = contentType,
isRemote = false,
contentId = id
)
override fun getDetailedVisits( override fun getDetailedVisits(
startMillis: Long, startMillis: Long,
endMillis: Long, endMillis: Long,
@@ -28,7 +55,7 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi {
) { ) {
coroutineScope.launch { coroutineScope.launch {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val visits = components.core.historyStorage.getDetailedVisits( var visits = components.core.historyStorage.getDetailedVisits(
startMillis, startMillis,
endMillis, endMillis,
excludeTypes.map { excludeTypes.map {
@@ -43,9 +70,7 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi {
VisitType.FRAMED_LINK -> mozilla.components.concept.storage.VisitType.FRAMED_LINK VisitType.FRAMED_LINK -> mozilla.components.concept.storage.VisitType.FRAMED_LINK
VisitType.RELOAD -> mozilla.components.concept.storage.VisitType.RELOAD VisitType.RELOAD -> mozilla.components.concept.storage.VisitType.RELOAD
} }
}) }).map {
callback(Result.success(visits.map {
VisitInfo( VisitInfo(
url = it.url, url = it.url,
title = it.title, title = it.title,
@@ -65,6 +90,12 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi {
isRemote = it.isRemote isRemote = it.isRemote
) )
} }
if (!excludeTypes.contains(VisitType.DOWNLOAD)) {
visits = visits + components.core.store.state.downloads.toVisitInfoList(startMillis, endMillis)
}
callback(Result.success(visits
)) ))
} }
} }
@@ -84,6 +115,19 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi {
} }
} }
override fun deleteDownload(
id: String,
callback: (Result<Unit>) -> Unit
) {
coroutineScope.launch {
withContext(Dispatchers.Main) {
components.useCases.downloadsUseCases.removeDownload(id)
callback(Result.success(Unit))
}
}
}
override fun deleteVisitsBetween( override fun deleteVisitsBetween(
startMillis: Long, startMillis: Long,
endMillis: Long, endMillis: Long,
@@ -1146,7 +1146,8 @@ data class VisitInfo (
val visitTime: Long, val visitTime: Long,
val visitType: VisitType, val visitType: VisitType,
val previewImageUrl: String? = null, val previewImageUrl: String? = null,
val isRemote: Boolean val isRemote: Boolean,
val contentId: String? = null
) )
{ {
companion object { companion object {
@@ -1157,7 +1158,8 @@ data class VisitInfo (
val visitType = pigeonVar_list[3] as VisitType val visitType = pigeonVar_list[3] as VisitType
val previewImageUrl = pigeonVar_list[4] as String? val previewImageUrl = pigeonVar_list[4] as String?
val isRemote = pigeonVar_list[5] as Boolean val isRemote = pigeonVar_list[5] as Boolean
return VisitInfo(url, title, visitTime, visitType, previewImageUrl, isRemote) val contentId = pigeonVar_list[6] as String?
return VisitInfo(url, title, visitTime, visitType, previewImageUrl, isRemote, contentId)
} }
} }
fun toList(): List<Any?> { fun toList(): List<Any?> {
@@ -1168,6 +1170,7 @@ data class VisitInfo (
visitType, visitType,
previewImageUrl, previewImageUrl,
isRemote, isRemote,
contentId,
) )
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@@ -4767,6 +4770,7 @@ interface GeckoDeleteBrowsingDataController {
interface GeckoHistoryApi { interface GeckoHistoryApi {
fun getDetailedVisits(startMillis: Long, endMillis: Long, excludeTypes: List<VisitType>, callback: (Result<List<VisitInfo>>) -> Unit) fun getDetailedVisits(startMillis: Long, endMillis: Long, excludeTypes: List<VisitType>, callback: (Result<List<VisitInfo>>) -> Unit)
fun deleteVisit(url: String, timestamp: Long, callback: (Result<Unit>) -> Unit) fun deleteVisit(url: String, timestamp: Long, callback: (Result<Unit>) -> Unit)
fun deleteDownload(id: String, callback: (Result<Unit>) -> Unit)
fun deleteVisitsBetween(startMillis: Long, endMillis: Long, callback: (Result<Unit>) -> Unit) fun deleteVisitsBetween(startMillis: Long, endMillis: Long, callback: (Result<Unit>) -> Unit)
companion object { companion object {
@@ -4820,6 +4824,25 @@ interface GeckoHistoryApi {
channel.setMessageHandler(null) channel.setMessageHandler(null)
} }
} }
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteDownload$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val idArg = args[0] as String
api.deleteDownload(idArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(GeckoPigeonUtils.wrapError(error))
} else {
reply.reply(GeckoPigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run { run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteVisitsBetween$separatedMessageChannelSuffix", codec) val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteVisitsBetween$separatedMessageChannelSuffix", codec)
if (api != null) { if (api != null) {
@@ -26,7 +26,20 @@ class GeckoHistoryService {
} }
Future<void> deleteVisit(VisitInfo info) { Future<void> deleteVisit(VisitInfo info) {
return _api.deleteVisit(info.url, info.visitTime); switch (info.visitType) {
case VisitType.link:
case VisitType.typed:
case VisitType.embed:
case VisitType.redirectPermanent:
case VisitType.redirectTemporary:
case VisitType.framedLink:
case VisitType.reload:
return _api.deleteVisit(info.url, info.visitTime);
case VisitType.download:
return _api.deleteDownload(info.contentId!);
case VisitType.bookmark:
throw UnimplementedError();
}
} }
Future<void> deleteVisitsBetween(DateTime start, DateTime end) { Future<void> deleteVisitsBetween(DateTime start, DateTime end) {
@@ -1257,6 +1257,7 @@ class VisitInfo {
required this.visitType, required this.visitType,
this.previewImageUrl, this.previewImageUrl,
required this.isRemote, required this.isRemote,
this.contentId,
}); });
String url; String url;
@@ -1271,6 +1272,8 @@ class VisitInfo {
bool isRemote; bool isRemote;
String? contentId;
List<Object?> _toList() { List<Object?> _toList() {
return <Object?>[ return <Object?>[
url, url,
@@ -1279,6 +1282,7 @@ class VisitInfo {
visitType, visitType,
previewImageUrl, previewImageUrl,
isRemote, isRemote,
contentId,
]; ];
} }
@@ -1294,6 +1298,7 @@ class VisitInfo {
visitType: result[3]! as VisitType, visitType: result[3]! as VisitType,
previewImageUrl: result[4] as String?, previewImageUrl: result[4] as String?,
isRemote: result[5]! as bool, isRemote: result[5]! as bool,
contentId: result[6] as String?,
); );
} }
@@ -5783,6 +5788,29 @@ class GeckoHistoryApi {
} }
} }
Future<void> deleteDownload(String id) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteDownload$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[id]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
Future<void> deleteVisitsBetween(int startMillis, int endMillis) async { Future<void> deleteVisitsBetween(int startMillis, int endMillis) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteVisitsBetween$pigeonVar_messageChannelSuffix'; final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteVisitsBetween$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
@@ -425,6 +425,8 @@ class VisitInfo {
final String? previewImageUrl; final String? previewImageUrl;
final bool isRemote; final bool isRemote;
final String? contentId;
VisitInfo( VisitInfo(
this.url, this.url,
this.title, this.title,
@@ -432,6 +434,7 @@ class VisitInfo {
this.visitType, this.visitType,
this.previewImageUrl, this.previewImageUrl,
this.isRemote, this.isRemote,
this.contentId,
); );
} }
@@ -1253,6 +1256,9 @@ abstract class GeckoHistoryApi {
@async @async
void deleteVisit(String url, int timestamp); void deleteVisit(String url, int timestamp);
@async
void deleteDownload(String id);
@async @async
void deleteVisitsBetween(int startMillis, int endMillis); void deleteVisitsBetween(int startMillis, int endMillis);
} }