mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 10:29:58 +02:00
Merge pull request #25 from sudosylabs/feat/storage-accounting
fix(storage): reclaim transfer cache and track received files
This commit is contained in:
@@ -200,9 +200,10 @@
|
||||
<string name="status_stopped">Beendet</string>
|
||||
<string name="storage_calculating">Wird berechnet…</string>
|
||||
<string name="storage_delete_transfers">Alle Übertragungen löschen</string>
|
||||
<string name="storage_delete_transfers_description">Dies löscht alle Datensätze gesendeter und empfangener Übertragungen aus Ihrem Verlauf. Ihre empfangenen Dateien werden nicht gelöscht. Hinweis: Die Übertragungs-Engine behält ihre gespeicherten Inhalte, sodass die Übertragungsdaten möglicherweise nicht abnehmen. Dies kann nicht rückgängig gemacht werden.</string>
|
||||
<string name="storage_delete_transfers_description">Dadurch werden alle Datensätze gesendeter und empfangener Übertragungen aus Ihrem Verlauf gelöscht. Ihre empfangenen Dateien werden nicht gelöscht. Nicht mehr benötigte zwischengespeicherte freigegebene Inhalte werden automatisch bereinigt; dies kann etwas dauern. Dies kann nicht rückgängig gemacht werden.</string>
|
||||
<string name="storage_app_data">App-Daten</string>
|
||||
<string name="storage_deleting">Wird gelöscht…</string>
|
||||
<string name="storage_footer">Übertragungsdaten werden von der Übertragungs-Engine verwaltet – Ihr Verlauf sowie der Inhalt der von Ihnen geteilten Dateien. Die Schaltfläche unten löscht Ihre Übertragungsdatensätze; die Engine behält ihre gespeicherten Inhalte, sodass dieser Wert möglicherweise nicht sinkt. Empfangene Dateien sind Ihre heruntergeladenen Dateien und werden hier niemals gelöscht.</string>
|
||||
<string name="storage_footer">Übertragungsdaten umfassen Ihren Verlauf und zwischengespeicherte Inhalte aktiver Freigaben. Nicht mehr benötigter Cache wird nach dem Löschen der Datensätze automatisch bereinigt; dies kann etwas dauern. Empfangene Dateien werden für diese Übersicht erfasst, aber hier niemals gelöscht.</string>
|
||||
<string name="storage_received_files">Empfangene Dateien</string>
|
||||
<string name="storage_temporary">Temporäre Dateien</string>
|
||||
<string name="storage_title">Speicher</string>
|
||||
|
||||
@@ -200,9 +200,10 @@
|
||||
<string name="status_stopped">Detenido</string>
|
||||
<string name="storage_calculating">Calculando…</string>
|
||||
<string name="storage_delete_transfers">Eliminar todas las transferencias</string>
|
||||
<string name="storage_delete_transfers_description">Esto borra todos los registros de transferencias enviadas y recibidas de su historial. Sus archivos recibidos no se eliminan. Nota: el motor de transferencia conserva su contenido almacenado, por lo que los datos de transferencia pueden no disminuir. Esto no se puede deshacer.</string>
|
||||
<string name="storage_delete_transfers_description">Esto borra de su historial todos los registros de transferencias enviadas y recibidas. Sus archivos recibidos no se eliminan. El contenido compartido en caché que ya no se necesita se recupera automáticamente, lo que puede tardar un poco. Esto no se puede deshacer.</string>
|
||||
<string name="storage_app_data">Datos de la aplicación</string>
|
||||
<string name="storage_deleting">Eliminando…</string>
|
||||
<string name="storage_footer">Los datos de transferencia los gestiona el motor de transferencia: su historial más el contenido de los archivos que ha compartido. El botón de abajo borra sus registros de transferencias; el motor conserva su contenido almacenado, por lo que este valor puede no bajar. Los archivos recibidos son los archivos que ha descargado y nunca se eliminan aquí.</string>
|
||||
<string name="storage_footer">Los datos de transferencia incluyen su historial y el contenido en caché de los recursos compartidos activos. La caché innecesaria se recupera automáticamente tras eliminar los registros, lo que puede tardar un poco. Los archivos recibidos se registran para este resumen, pero nunca se eliminan aquí.</string>
|
||||
<string name="storage_received_files">Archivos recibidos</string>
|
||||
<string name="storage_temporary">Archivos temporales</string>
|
||||
<string name="storage_title">Almacenamiento</string>
|
||||
|
||||
@@ -200,9 +200,10 @@
|
||||
<string name="status_stopped">Arrêté</string>
|
||||
<string name="storage_calculating">Calcul…</string>
|
||||
<string name="storage_delete_transfers">Supprimer tous les transferts</string>
|
||||
<string name="storage_delete_transfers_description">Cela efface tous les enregistrements de transferts envoyés et reçus de votre historique. Vos fichiers reçus ne sont pas supprimés. Remarque : le moteur de transfert conserve son contenu stocké, donc les données de transfert peuvent ne pas diminuer. Cette action est irréversible.</string>
|
||||
<string name="storage_delete_transfers_description">Cela efface de votre historique tous les enregistrements de transferts envoyés et reçus. Vos fichiers reçus ne sont pas supprimés. Le contenu partagé mis en cache qui n’est plus nécessaire est récupéré automatiquement, ce qui peut prendre un peu de temps. Cette action est irréversible.</string>
|
||||
<string name="storage_app_data">Données de l’app</string>
|
||||
<string name="storage_deleting">Suppression…</string>
|
||||
<string name="storage_footer">Les données de transfert sont gérées par le moteur de transfert — votre historique ainsi que le contenu des fichiers que vous avez partagés. Le bouton ci-dessous efface vos enregistrements de transferts ; le moteur conserve son contenu stocké, donc cette valeur peut ne pas baisser. Les fichiers reçus sont vos fichiers téléchargés et ne sont jamais supprimés ici.</string>
|
||||
<string name="storage_footer">Les données de transfert comprennent votre historique et le contenu mis en cache pour les partages actifs. Le cache inutile est récupéré automatiquement après la suppression des enregistrements, ce qui peut prendre un peu de temps. Les fichiers reçus sont suivis pour ce récapitulatif, mais ne sont jamais supprimés ici.</string>
|
||||
<string name="storage_received_files">Fichiers reçus</string>
|
||||
<string name="storage_temporary">Fichiers temporaires</string>
|
||||
<string name="storage_title">Stockage</string>
|
||||
|
||||
@@ -200,9 +200,10 @@
|
||||
<string name="status_stopped">Interrotto</string>
|
||||
<string name="storage_calculating">Calcolo…</string>
|
||||
<string name="storage_delete_transfers">Elimina tutti i trasferimenti</string>
|
||||
<string name="storage_delete_transfers_description">Questo cancella dalla cronologia tutti i record dei trasferimenti inviati e ricevuti. I suoi file ricevuti non vengono eliminati. Nota: il motore di trasferimento conserva il contenuto memorizzato, quindi i dati di trasferimento potrebbero non diminuire. Questa operazione non può essere annullata.</string>
|
||||
<string name="storage_delete_transfers_description">Questo cancella dalla cronologia tutti i record dei trasferimenti inviati e ricevuti. I file ricevuti non vengono eliminati. Il contenuto condiviso nella cache che non serve più viene recuperato automaticamente, operazione che può richiedere un po’ di tempo. Questa azione non può essere annullata.</string>
|
||||
<string name="storage_app_data">Dati dell’app</string>
|
||||
<string name="storage_deleting">Eliminazione…</string>
|
||||
<string name="storage_footer">I dati di trasferimento sono gestiti dal motore di trasferimento: la sua cronologia più il contenuto dei file che ha condiviso. Il pulsante qui sotto cancella i record dei trasferimenti; il motore conserva il contenuto memorizzato, quindi questo valore potrebbe non diminuire. I file ricevuti sono i file che ha scaricato e non vengono mai eliminati qui.</string>
|
||||
<string name="storage_footer">I dati di trasferimento includono la cronologia e il contenuto nella cache per le condivisioni attive. La cache non necessaria viene recuperata automaticamente dopo la rimozione dei record, operazione che può richiedere un po’ di tempo. I file ricevuti vengono monitorati per questo riepilogo, ma non sono mai eliminati qui.</string>
|
||||
<string name="storage_received_files">File ricevuti</string>
|
||||
<string name="storage_temporary">File temporanei</string>
|
||||
<string name="storage_title">Archiviazione</string>
|
||||
|
||||
@@ -200,9 +200,10 @@
|
||||
<string name="status_stopped">Gestopt</string>
|
||||
<string name="storage_calculating">Berekenen…</string>
|
||||
<string name="storage_delete_transfers">Alle overdrachten verwijderen</string>
|
||||
<string name="storage_delete_transfers_description">Hiermee worden alle records van verzonden en ontvangen overdrachten uit uw geschiedenis gewist. Uw ontvangen bestanden worden niet verwijderd. Let op: de overdrachtsengine bewaart de opgeslagen inhoud, dus de overdrachtsgegevens nemen mogelijk niet af. Dit kan niet ongedaan worden gemaakt.</string>
|
||||
<string name="storage_delete_transfers_description">Hiermee worden alle records van verzonden en ontvangen overdrachten uit uw geschiedenis gewist. Uw ontvangen bestanden worden niet verwijderd. Gedeelde inhoud in de cache die niet meer nodig is, wordt automatisch opgeruimd; dit kan enige tijd duren. Dit kan niet ongedaan worden gemaakt.</string>
|
||||
<string name="storage_app_data">Appgegevens</string>
|
||||
<string name="storage_deleting">Verwijderen…</string>
|
||||
<string name="storage_footer">Overdrachtsgegevens worden beheerd door de overdrachtsengine — uw geschiedenis plus de inhoud van de bestanden die u hebt gedeeld. De knop hieronder wist uw overdrachtsrecords; de engine bewaart de opgeslagen inhoud, dus deze waarde daalt mogelijk niet. Ontvangen bestanden zijn uw gedownloade bestanden en worden hier nooit verwijderd.</string>
|
||||
<string name="storage_footer">Overdrachtsgegevens omvatten uw geschiedenis en inhoud in de cache voor actieve shares. Onnodige cache wordt automatisch opgeruimd nadat overdrachtsrecords zijn verwijderd; dit kan enige tijd duren. Ontvangen bestanden worden voor dit overzicht bijgehouden, maar hier nooit verwijderd.</string>
|
||||
<string name="storage_received_files">Ontvangen bestanden</string>
|
||||
<string name="storage_temporary">Tijdelijke bestanden</string>
|
||||
<string name="storage_title">Opslag</string>
|
||||
|
||||
@@ -200,9 +200,10 @@
|
||||
<string name="status_stopped">Zatrzymany</string>
|
||||
<string name="storage_calculating">Obliczanie…</string>
|
||||
<string name="storage_delete_transfers">Usuń wszystkie transfery</string>
|
||||
<string name="storage_delete_transfers_description">To usuwa z historii wszystkie rekordy wysłanych i odebranych transferów. Twoje odebrane pliki nie są usuwane. Uwaga: silnik transferu zachowuje przechowywaną zawartość, więc dane transferu mogą się nie zmniejszyć. Tej operacji nie można cofnąć.</string>
|
||||
<string name="storage_delete_transfers_description">Spowoduje to usunięcie z historii wszystkich rekordów wysłanych i odebranych transferów. Odebrane pliki nie zostaną usunięte. Niepotrzebna już zawartość udostępniona w pamięci podręcznej jest odzyskiwana automatycznie, co może chwilę potrwać. Tej operacji nie można cofnąć.</string>
|
||||
<string name="storage_app_data">Dane aplikacji</string>
|
||||
<string name="storage_deleting">Usuwanie…</string>
|
||||
<string name="storage_footer">Danymi transferu zarządza silnik transferu — Twoja historia oraz zawartość udostępnionych plików. Przycisk poniżej usuwa rekordy transferów; silnik zachowuje przechowywaną zawartość, więc ta wartość może się nie zmniejszyć. Odebrane pliki to Twoje pobrane pliki i nigdy nie są tu usuwane.</string>
|
||||
<string name="storage_footer">Dane transferu obejmują historię oraz zawartość w pamięci podręcznej dla aktywnych udostępnień. Niepotrzebna pamięć podręczna jest odzyskiwana automatycznie po usunięciu rekordów, co może chwilę potrwać. Odebrane pliki są śledzone na potrzeby tego podsumowania, ale nigdy nie są tu usuwane.</string>
|
||||
<string name="storage_received_files">Odebrane pliki</string>
|
||||
<string name="storage_temporary">Pliki tymczasowe</string>
|
||||
<string name="storage_title">Pamięć</string>
|
||||
|
||||
@@ -200,9 +200,10 @@
|
||||
<string name="status_stopped">Parada</string>
|
||||
<string name="storage_calculating">A calcular…</string>
|
||||
<string name="storage_delete_transfers">Eliminar todas as transferências</string>
|
||||
<string name="storage_delete_transfers_description">Isto elimina do seu histórico todos os registos de transferências enviadas e recebidas. Os seus ficheiros recebidos não são eliminados. Nota: o motor de transferência mantém o conteúdo armazenado, pelo que os dados de transferência podem não diminuir. Isto não pode ser anulado.</string>
|
||||
<string name="storage_delete_transfers_description">Isto elimina do histórico todos os registos de transferências enviadas e recebidas. Os ficheiros recebidos não são eliminados. O conteúdo partilhado em cache que já não é necessário é recuperado automaticamente, o que pode demorar algum tempo. Esta ação não pode ser anulada.</string>
|
||||
<string name="storage_app_data">Dados da aplicação</string>
|
||||
<string name="storage_deleting">A eliminar…</string>
|
||||
<string name="storage_footer">Os dados de transferência são geridos pelo motor de transferência — o seu histórico mais o conteúdo dos ficheiros que partilhou. O botão abaixo elimina os seus registos de transferências; o motor mantém o conteúdo armazenado, pelo que este valor pode não descer. Os ficheiros recebidos são os ficheiros que descarregou e nunca são eliminados aqui.</string>
|
||||
<string name="storage_footer">Os dados de transferência incluem o histórico e o conteúdo em cache das partilhas ativas. A cache desnecessária é recuperada automaticamente após a remoção dos registos, o que pode demorar algum tempo. Os ficheiros recebidos são acompanhados para este resumo, mas nunca são eliminados aqui.</string>
|
||||
<string name="storage_received_files">Ficheiros recebidos</string>
|
||||
<string name="storage_temporary">Ficheiros temporários</string>
|
||||
<string name="storage_title">Armazenamento</string>
|
||||
|
||||
@@ -200,9 +200,10 @@
|
||||
<string name="status_stopped">Остановлено</string>
|
||||
<string name="storage_calculating">Вычисление…</string>
|
||||
<string name="storage_delete_transfers">Удалить все передачи</string>
|
||||
<string name="storage_delete_transfers_description">Это удалит из истории все записи об отправленных и полученных передачах. Ваши полученные файлы не удаляются. Примечание: механизм передачи сохраняет своё содержимое, поэтому объём данных передачи может не уменьшиться. Это действие нельзя отменить.</string>
|
||||
<string name="storage_delete_transfers_description">Это удалит из истории все записи об отправленных и полученных передачах. Полученные файлы не удаляются. Кэшированное общее содержимое, которое больше не требуется, освобождается автоматически; это может занять некоторое время. Это действие нельзя отменить.</string>
|
||||
<string name="storage_app_data">Данные приложения</string>
|
||||
<string name="storage_deleting">Удаление…</string>
|
||||
<string name="storage_footer">Данными передачи управляет механизм передачи — ваша история плюс содержимое файлов, которыми вы поделились. Кнопка ниже удаляет записи о передачах; механизм сохраняет своё содержимое, поэтому это значение может не уменьшиться. Полученные файлы — это загруженные вами файлы, и они никогда не удаляются здесь.</string>
|
||||
<string name="storage_footer">Данные передачи включают историю и кэшированное содержимое активных раздач. Ненужный кэш освобождается автоматически после удаления записей; это может занять некоторое время. Полученные файлы учитываются в этой сводке, но никогда не удаляются здесь.</string>
|
||||
<string name="storage_received_files">Полученные файлы</string>
|
||||
<string name="storage_temporary">Временные файлы</string>
|
||||
<string name="storage_title">Хранилище</string>
|
||||
|
||||
@@ -200,9 +200,10 @@
|
||||
<string name="status_stopped">Stopped</string>
|
||||
<string name="storage_calculating">Calculating…</string>
|
||||
<string name="storage_delete_transfers">Delete all transfers</string>
|
||||
<string name="storage_delete_transfers_description">This clears all sent and received transfer records from your history. Your received files are not deleted. Note: the transfer engine keeps its stored content, so Transfer data may not decrease. This can’t be undone.</string>
|
||||
<string name="storage_delete_transfers_description">This clears all sent and received transfer records from your history. Your received files are not deleted. Cached shared content that is no longer needed is reclaimed automatically, which may take a little time. This can’t be undone.</string>
|
||||
<string name="storage_app_data">App data</string>
|
||||
<string name="storage_deleting">Deleting…</string>
|
||||
<string name="storage_footer">Transfer data is managed by the transfer engine — your history plus the content of files you’ve shared. The button below clears your transfer records; the engine keeps its stored content, so this value may not drop. Received files are your downloaded files and are never deleted here.</string>
|
||||
<string name="storage_footer">Transfer data includes your history and cached content for active shares. Unneeded cache is reclaimed automatically after transfer records are removed, which may take a little time. Received files are tracked for this summary but are never deleted here.</string>
|
||||
<string name="storage_received_files">Received files</string>
|
||||
<string name="storage_temporary">Temporary files</string>
|
||||
<string name="storage_title">Storage</string>
|
||||
|
||||
@@ -88,6 +88,7 @@ fun App(
|
||||
dependencies.environment,
|
||||
dependencies.deviceInfoProvider,
|
||||
dependencies.fileSystemService,
|
||||
graph.coreRepository,
|
||||
graph.preferencesRepository,
|
||||
dependencies.localNotificationService,
|
||||
graph.messages,
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.vnidrop.app.core
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import uniffi.vnidrop.ReceiveOutputSink
|
||||
import uniffi.vnidrop.ReceiveOutputSinkV2
|
||||
|
||||
data class CoreStatus(
|
||||
val endpointId: String,
|
||||
@@ -113,6 +114,23 @@ data class CoreState(
|
||||
val lastInspection: TicketInspectionModel? = null,
|
||||
)
|
||||
|
||||
data class CoreStorageUsageModel(
|
||||
val blobStoreBytes: ULong,
|
||||
val databaseBytes: ULong,
|
||||
val logsBytes: ULong,
|
||||
val previewsBytes: ULong,
|
||||
val otherCoreBytes: ULong,
|
||||
) {
|
||||
val appDataBytes: ULong get() = databaseBytes + logsBytes + previewsBytes + otherCoreBytes
|
||||
}
|
||||
|
||||
data class ReceivedArtifactModel(
|
||||
val id: String,
|
||||
val locator: String,
|
||||
val locatorKind: uniffi.vnidrop.ReceivedLocatorKind,
|
||||
val logicalSize: ULong,
|
||||
)
|
||||
|
||||
sealed interface CoreSignal {
|
||||
data class ApprovalChanged(val transferId: ULong) : CoreSignal
|
||||
data class ReceiverHistoryChanged(val transferId: ULong) : CoreSignal
|
||||
@@ -144,6 +162,9 @@ interface CoreGateway {
|
||||
suspend fun inspectTicket(ticket: String): Result<TicketInspectionModel>
|
||||
suspend fun receive(ticket: String, outputDir: String, receiverName: String): Result<Unit>
|
||||
suspend fun receiveWithOutputSink(ticket: String, outputSink: ReceiveOutputSink, receiverName: String): Result<Unit>
|
||||
suspend fun receiveWithOutputSinkV2(ticket: String, outputSink: ReceiveOutputSinkV2, receiverName: String): Result<Unit>
|
||||
suspend fun storageUsage(): Result<CoreStorageUsageModel>
|
||||
suspend fun receivedArtifacts(): Result<List<ReceivedArtifactModel>>
|
||||
suspend fun cancel(transferId: ULong): Result<Unit>
|
||||
suspend fun delete(transferId: ULong): Result<Unit>
|
||||
suspend fun clearReceiveHistory(): Result<ULong>
|
||||
|
||||
@@ -17,6 +17,7 @@ import kotlin.random.Random
|
||||
import uniffi.vnidrop.CoreEvent
|
||||
import uniffi.vnidrop.CoreEventSink
|
||||
import uniffi.vnidrop.ReceiveOutputSink
|
||||
import uniffi.vnidrop.ReceiveOutputSinkV2
|
||||
import uniffi.vnidrop.ReceiverRequest
|
||||
import uniffi.vnidrop.ShareMetadataInput
|
||||
import uniffi.vnidrop.ShareResult
|
||||
@@ -133,6 +134,37 @@ class CoreRepository(
|
||||
refreshSnapshot()
|
||||
}
|
||||
|
||||
override suspend fun receiveWithOutputSinkV2(
|
||||
ticket: String,
|
||||
outputSink: ReceiveOutputSinkV2,
|
||||
receiverName: String,
|
||||
): Result<Unit> = runCore {
|
||||
requireCore().receiveWithOutputSinkV2(ticket, outputSink, receiverName.ifBlank { null })
|
||||
refreshSnapshot()
|
||||
}
|
||||
|
||||
override suspend fun storageUsage(): Result<CoreStorageUsageModel> = runCore {
|
||||
val usage = requireCore().storageUsage()
|
||||
CoreStorageUsageModel(
|
||||
blobStoreBytes = usage.blobStoreBytes,
|
||||
databaseBytes = usage.databaseBytes,
|
||||
logsBytes = usage.logsBytes,
|
||||
previewsBytes = usage.previewsBytes,
|
||||
otherCoreBytes = usage.otherCoreBytes,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun receivedArtifacts(): Result<List<ReceivedArtifactModel>> = runCore {
|
||||
requireCore().listReceivedArtifacts().map { artifact ->
|
||||
ReceivedArtifactModel(
|
||||
id = artifact.id,
|
||||
locator = artifact.locator,
|
||||
locatorKind = artifact.locatorKind,
|
||||
logicalSize = artifact.logicalSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun cancel(transferId: ULong): Result<Unit> = runCore {
|
||||
requireCore().cancelTransfer(transferId)
|
||||
refreshSnapshot()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.vnidrop.app.core
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import uniffi.vnidrop.ReceiveOutputSink
|
||||
import uniffi.vnidrop.ReceiveOutputSinkV2
|
||||
|
||||
enum class ReceiveFolderKind {
|
||||
FileSystemPath,
|
||||
@@ -19,6 +19,13 @@ data class ReceiveFolder(
|
||||
val displayName: String,
|
||||
)
|
||||
|
||||
data class ReceivedStorageInspection(
|
||||
val existingBytes: ULong,
|
||||
val existingCount: Int,
|
||||
val missingCount: Int,
|
||||
val inaccessibleCount: Int,
|
||||
)
|
||||
|
||||
enum class FolderAccessStatus {
|
||||
Writable,
|
||||
PermissionRequired,
|
||||
@@ -32,7 +39,9 @@ interface FileSystemService {
|
||||
fun effectiveReceiveFolder(configuredFolder: ReceiveFolder): ReceiveFolder =
|
||||
if (supportsCustomReceiveFolders) configuredFolder else defaultReceiveFolder()
|
||||
suspend fun validateReceiveFolder(folder: ReceiveFolder): FolderAccessStatus
|
||||
fun createReceiveOutputSink(folder: ReceiveFolder): ReceiveOutputSink?
|
||||
suspend fun inspectReceivedArtifacts(artifacts: List<ReceivedArtifactModel>): ReceivedStorageInspection
|
||||
suspend fun temporaryUsage(): ULong
|
||||
fun createReceiveOutputSink(folder: ReceiveFolder): ReceiveOutputSinkV2?
|
||||
fun canRevealReceiveFolder(folder: ReceiveFolder): Boolean = false
|
||||
suspend fun revealReceiveFolder(folder: ReceiveFolder): Result<Unit> =
|
||||
Result.failure(UnsupportedOperationException("Revealing the receive folder is not supported"))
|
||||
|
||||
@@ -162,7 +162,7 @@ class ReceiveViewModel(
|
||||
}
|
||||
val outputSink = fileSystemService.createReceiveOutputSink(folder)
|
||||
val result = if (outputSink != null) {
|
||||
repository.receiveWithOutputSink(current.ticket, outputSink, current.receiverName)
|
||||
repository.receiveWithOutputSinkV2(current.ticket, outputSink, current.receiverName)
|
||||
} else {
|
||||
repository.receive(current.ticket, folder.value, current.receiverName)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import vnidrop.shared.generated.resources.appearance_title
|
||||
import vnidrop.shared.generated.resources.notifications_title
|
||||
import vnidrop.shared.generated.resources.preferences_title
|
||||
import vnidrop.shared.generated.resources.settings_title
|
||||
import vnidrop.shared.generated.resources.storage_title
|
||||
|
||||
@Composable
|
||||
internal fun SettingsOverview(
|
||||
@@ -32,6 +33,13 @@ internal fun SettingsOverview(
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
SettingsGroup {
|
||||
SettingsRow(
|
||||
icon = SettingsIcons.Drive,
|
||||
title = stringResource(Res.string.storage_title),
|
||||
selected = state.selectedSection == SettingsSection.Storage,
|
||||
onClick = { onSectionSelected(SettingsSection.Storage) },
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
icon = SettingsIcons.Device,
|
||||
title = stringResource(Res.string.preferences_title),
|
||||
|
||||
@@ -35,5 +35,6 @@ fun SettingsRoute(viewModel: SettingsViewModel, windowClass: WindowClass) {
|
||||
onBugContactChanged = viewModel::setBugContact,
|
||||
onBugIncludeLogsChanged = viewModel::setBugIncludeLogs,
|
||||
onSubmitBugReport = viewModel::submitBugReport,
|
||||
onDeleteAllTransfers = viewModel::deleteAllTransfers,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ fun SettingsScreen(
|
||||
onBugContactChanged: (String) -> Unit,
|
||||
onBugIncludeLogsChanged: (Boolean) -> Unit,
|
||||
onSubmitBugReport: () -> Unit,
|
||||
onDeleteAllTransfers: () -> Unit = {},
|
||||
) {
|
||||
if (windowClass == WindowClass.Desktop) {
|
||||
Row(
|
||||
@@ -58,6 +59,7 @@ fun SettingsScreen(
|
||||
onBugContactChanged = onBugContactChanged,
|
||||
onBugIncludeLogsChanged = onBugIncludeLogsChanged,
|
||||
onSubmitBugReport = onSubmitBugReport,
|
||||
onDeleteAllTransfers = onDeleteAllTransfers,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -91,6 +93,7 @@ fun SettingsScreen(
|
||||
onBugContactChanged = onBugContactChanged,
|
||||
onBugIncludeLogsChanged = onBugIncludeLogsChanged,
|
||||
onSubmitBugReport = onSubmitBugReport,
|
||||
onDeleteAllTransfers = onDeleteAllTransfers,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -116,12 +119,14 @@ private fun SettingsSectionContent(
|
||||
onBugContactChanged: (String) -> Unit,
|
||||
onBugIncludeLogsChanged: (Boolean) -> Unit,
|
||||
onSubmitBugReport: () -> Unit,
|
||||
onDeleteAllTransfers: () -> Unit,
|
||||
) {
|
||||
when (section) {
|
||||
SettingsSection.Overview -> Unit
|
||||
SettingsSection.Preferences -> PreferencesSettings(state, onUsernameChanged, onChooseFolder, onResetFolder, onBack, showBack)
|
||||
SettingsSection.Appearance -> AppearanceSettings(state.themeMode, onThemeModeChanged, onBack, showBack)
|
||||
SettingsSection.Notifications -> NotificationSettings(state, onNotificationsChanged, onOpenNotificationSettings, onBack, showBack)
|
||||
SettingsSection.Storage -> StorageSettings(state, onDeleteAllTransfers, onBack, showBack)
|
||||
SettingsSection.About -> AboutSettings(
|
||||
state = state,
|
||||
onDiagnosticsChanged = onDiagnosticsChanged,
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.vnidrop.app.DeviceInfo
|
||||
import com.vnidrop.app.DeviceInfoProvider
|
||||
import com.vnidrop.app.PlatformEnvironment
|
||||
import com.vnidrop.app.core.FileSystemService
|
||||
import com.vnidrop.app.core.CoreGateway
|
||||
import com.vnidrop.app.core.FolderAccessStatus
|
||||
import com.vnidrop.app.core.ReceiveFolder
|
||||
import com.vnidrop.app.diagnostics.BugReportDraft
|
||||
@@ -48,10 +49,23 @@ enum class SettingsSection {
|
||||
Preferences,
|
||||
Appearance,
|
||||
Notifications,
|
||||
Storage,
|
||||
About,
|
||||
BugReport,
|
||||
}
|
||||
|
||||
data class StorageBreakdown(
|
||||
val transferCacheBytes: ULong,
|
||||
val appDataBytes: ULong,
|
||||
val temporaryBytes: ULong,
|
||||
val receivedBytes: ULong,
|
||||
val receivedFileCount: Int,
|
||||
val missingReceivedFileCount: Int,
|
||||
val inaccessibleReceivedFileCount: Int,
|
||||
) {
|
||||
val deviceImpactBytes: ULong get() = transferCacheBytes + appDataBytes + temporaryBytes + receivedBytes
|
||||
}
|
||||
|
||||
data class SettingsState(
|
||||
val selectedSection: SettingsSection = SettingsSection.Overview,
|
||||
val username: String = "",
|
||||
@@ -73,6 +87,9 @@ data class SettingsState(
|
||||
val bugIncludeLogs: Boolean = true,
|
||||
val isSubmittingBugReport: Boolean = false,
|
||||
val bugLogPreviewBytes: Int = 0,
|
||||
val storage: StorageBreakdown? = null,
|
||||
val isCalculatingStorage: Boolean = false,
|
||||
val isDeletingTransfers: Boolean = false,
|
||||
)
|
||||
|
||||
sealed interface SettingsEffect {
|
||||
@@ -83,6 +100,7 @@ class SettingsViewModel(
|
||||
private val environment: PlatformEnvironment,
|
||||
private val deviceInfoProvider: DeviceInfoProvider,
|
||||
private val fileSystemService: FileSystemService,
|
||||
private val repository: CoreGateway,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val notifications: LocalNotificationService,
|
||||
private val messages: UiMessageController,
|
||||
@@ -130,6 +148,7 @@ class SettingsViewModel(
|
||||
fun selectSection(section: SettingsSection) {
|
||||
_state.update { it.copy(selectedSection = section) }
|
||||
when (section) {
|
||||
SettingsSection.Storage -> loadStorageUsage()
|
||||
SettingsSection.About, SettingsSection.BugReport -> {
|
||||
loadDeviceInfo()
|
||||
if (section == SettingsSection.BugReport) refreshBugLogPreview()
|
||||
@@ -138,6 +157,53 @@ class SettingsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun loadStorageUsage() {
|
||||
if (_state.value.isCalculatingStorage) return
|
||||
viewModelScope.launch {
|
||||
_state.update { it.copy(isCalculatingStorage = true) }
|
||||
try {
|
||||
val coreUsage = repository.storageUsage().getOrThrow()
|
||||
val artifacts = repository.receivedArtifacts().getOrThrow()
|
||||
val received = fileSystemService.inspectReceivedArtifacts(artifacts)
|
||||
_state.update {
|
||||
it.copy(
|
||||
storage = StorageBreakdown(
|
||||
transferCacheBytes = coreUsage.blobStoreBytes,
|
||||
appDataBytes = coreUsage.appDataBytes,
|
||||
temporaryBytes = fileSystemService.temporaryUsage(),
|
||||
receivedBytes = received.existingBytes,
|
||||
receivedFileCount = received.existingCount,
|
||||
missingReceivedFileCount = received.missingCount,
|
||||
inaccessibleReceivedFileCount = received.inaccessibleCount,
|
||||
),
|
||||
isCalculatingStorage = false,
|
||||
)
|
||||
}
|
||||
} catch (error: CancellationException) {
|
||||
throw error
|
||||
} catch (error: Throwable) {
|
||||
_state.update { it.copy(isCalculatingStorage = false) }
|
||||
messages.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAllTransfers() {
|
||||
if (_state.value.isDeletingTransfers) return
|
||||
viewModelScope.launch {
|
||||
_state.update { it.copy(isDeletingTransfers = true) }
|
||||
val failures = repository.state.value.transfers
|
||||
.map { repository.delete(it.transferId) }
|
||||
.count { it.isFailure }
|
||||
_state.update { it.copy(isDeletingTransfers = false) }
|
||||
if (failures == 0) {
|
||||
loadStorageUsage()
|
||||
} else {
|
||||
messages.error(IllegalStateException("Could not delete $failures transfer records"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setUsername(value: String) {
|
||||
hasLocalUsernameDraft = true
|
||||
_state.update { it.copy(username = value) }
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.vnidrop.app.feature.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vnidrop.app.ui.state.formatBytes
|
||||
import com.vnidrop.app.ui.theme.LocalVniDropColors
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import vnidrop.shared.generated.resources.Res
|
||||
import vnidrop.shared.generated.resources.storage_app_data
|
||||
import vnidrop.shared.generated.resources.storage_calculating
|
||||
import vnidrop.shared.generated.resources.storage_delete_transfers
|
||||
import vnidrop.shared.generated.resources.storage_deleting
|
||||
import vnidrop.shared.generated.resources.storage_total
|
||||
import vnidrop.shared.generated.resources.storage_received_files
|
||||
import vnidrop.shared.generated.resources.storage_temporary
|
||||
import vnidrop.shared.generated.resources.storage_title
|
||||
import vnidrop.shared.generated.resources.storage_transfer_data
|
||||
import vnidrop.shared.generated.resources.storage_footer
|
||||
|
||||
@Composable
|
||||
internal fun StorageSettings(
|
||||
state: SettingsState,
|
||||
onDeleteAllTransfers: () -> Unit,
|
||||
onBack: () -> Unit,
|
||||
showBack: Boolean,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
SettingsTopBar(stringResource(Res.string.storage_title), onBack, showBack)
|
||||
val storage = state.storage
|
||||
if (storage == null || state.isCalculatingStorage) {
|
||||
SettingsGroup {
|
||||
SettingsRow(
|
||||
icon = SettingsIcons.Drive,
|
||||
title = stringResource(Res.string.storage_calculating),
|
||||
trailing = { CircularProgressIndicator() },
|
||||
)
|
||||
}
|
||||
} else {
|
||||
SettingsGroup {
|
||||
StorageRow(stringResource(Res.string.storage_transfer_data), storage.transferCacheBytes)
|
||||
SettingsDivider()
|
||||
StorageRow(stringResource(Res.string.storage_app_data), storage.appDataBytes)
|
||||
SettingsDivider()
|
||||
StorageRow(stringResource(Res.string.storage_temporary), storage.temporaryBytes)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
icon = SettingsIcons.Folder,
|
||||
title = stringResource(Res.string.storage_received_files),
|
||||
value = formatBytes(storage.receivedBytes),
|
||||
)
|
||||
SettingsDivider()
|
||||
StorageRow(stringResource(Res.string.storage_total), storage.deviceImpactBytes)
|
||||
}
|
||||
}
|
||||
Button(
|
||||
onClick = onDeleteAllTransfers,
|
||||
enabled = !state.isDeletingTransfers,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error),
|
||||
) {
|
||||
Text(stringResource(if (state.isDeletingTransfers) Res.string.storage_deleting else Res.string.storage_delete_transfers))
|
||||
}
|
||||
Text(
|
||||
stringResource(Res.string.storage_footer),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = LocalVniDropColors.current.foregroundLighter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StorageRow(title: String, bytes: ULong) {
|
||||
SettingsRow(icon = SettingsIcons.Drive, title = title, value = formatBytes(bytes))
|
||||
}
|
||||
Reference in New Issue
Block a user