Skip to content

Commit

Permalink
Adding backup controls
Browse files Browse the repository at this point in the history
  • Loading branch information
jsixface committed Mar 13, 2024
1 parent 02c79d3 commit 2b23aee
Show file tree
Hide file tree
Showing 18 changed files with 291 additions and 114 deletions.
3 changes: 2 additions & 1 deletion composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ kotlin {
implementation(compose.components.resources)
implementation(projects.shared)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
implementation(libs.napier)
implementation(libs.bundles.koin.client)
implementation(libs.bundles.ktor.client)
Expand All @@ -62,7 +63,7 @@ compose {

compose.desktop {
application {
mainClass = "MainKt"
mainClass = "DesktopMain"

nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
Expand Down
17 changes: 8 additions & 9 deletions composeApp/src/commonMain/kotlin/services/Koin.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package services

import Backend
import io.ktor.client.HttpClient
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.logging.DEFAULT
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.resources.Resources
import io.ktor.serialization.kotlinx.cbor.cbor
import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.plugins.resources.*
import io.ktor.serialization.kotlinx.cbor.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.cbor.Cbor
import org.koin.dsl.module
import viewmodels.BackupScreenViewModel
import viewmodels.JobsScreenModel
import viewmodels.SettingsScreenModel
import viewmodels.VideoListViewModel
Expand All @@ -23,6 +21,7 @@ object Koin {

factory { SettingsScreenModel(client = get()) }
factory { JobsScreenModel(client = get()) }
factory { BackupScreenViewModel(client = get()) }
factory { VideoListViewModel(client = get()) }
}

Expand Down
132 changes: 132 additions & 0 deletions composeApp/src/commonMain/kotlin/ui/BackupsScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package ui

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.sharp.Delete
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch
import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import org.koin.compose.koinInject
import ui.model.ModelState
import ui.model.Screen
import viewmodels.BackupScreenViewModel

object BackupsScreen : Screen {

private val padding = Modifier.padding(16.dp)
private val paddingSmall = Modifier.padding(8.dp)
private val filePattern = Regex(".*\\.([0-9]+)\\.bkp$")

override val name: String
get() = "Backups"

@Composable
override fun icon() {
Icon(Icons.Filled.Archive, contentDescription = name)
}

@Composable
override fun content() {
Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
val backupScreenViewModel = koinInject<BackupScreenViewModel>()
val scope = rememberCoroutineScope()
var backups by remember { mutableStateOf(listOf<String>()) }
suspend fun loadBackups() {
backupScreenViewModel.backupFiles.collect {
if (it is ModelState.Success) {
backups = it.result
}
}
}
LaunchedEffect(Unit) {
scope.launch {
loadBackups()
}
}
BackupContent(
backups,
onClear = {
scope.launch {
backupScreenViewModel.clearBackups()
loadBackups()
}
},
onDelete = {
scope.launch {
backupScreenViewModel.delete(it)
loadBackups()
}
})

}

}

@Composable
fun BackupContent(backups: List<String>, onDelete: (String) -> Unit, onClear: () -> Unit) {
Card(
modifier = Modifier.width(width = 900.dp).fillMaxHeight().padding(20.dp)
) {
Column(modifier = padding) {
Row {
Text(
text = "Backups",
fontSize = 30.sp,
modifier = padding,
textAlign = TextAlign.Center,
)
}
Column {
backups.forEach { backup -> BackupItem(backup) { onDelete(backup) } }
}
Row(modifier = Modifier.fillMaxSize()) {
Spacer(modifier = Modifier.weight(1f))
ElevatedButton(onClick = onClear, modifier = padding) {
Text("Clear All Backups")
}
Spacer(modifier = Modifier.weight(1f))
}
}
}
}

@Composable
fun BackupItem(backup: String, onDelete: () -> Unit) {
Column {
OutlinedCard(
modifier = Modifier.padding(16.dp, 4.dp).fillMaxWidth()
) {
val ts = filePattern.find(backup)?.groupValues?.get(1)?.toLongOrNull() ?: 0
val modified = Instant.fromEpochSeconds(ts).toLocalDateTime(TimeZone.currentSystemDefault())
Row(verticalAlignment = Alignment.CenterVertically) {
Column(modifier = Modifier.weight(1f).padding(8.dp)) {
Text(backup.substringAfterLast('/'), modifier = paddingSmall)
Text(
"Completed at ${modified.date} ${modified.time}",
style = MaterialTheme.typography.labelSmall,
modifier = paddingSmall
)
}
IconButton(onClick = onDelete) {
Icon(Icons.Sharp.Delete, "Delete Backup")
}
}
}
}
}
}
22 changes: 4 additions & 18 deletions composeApp/src/commonMain/kotlin/ui/MainScreen.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
package ui

import Backend
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cloud
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
Expand All @@ -40,7 +26,7 @@ fun MainScreen() {
}

NavigationRail(modifier = Modifier.fillMaxHeight()) {
listOf(HomeScreen, JobsScreen, SettingsScreen).forEach { screen ->
listOf(HomeScreen, JobsScreen,BackupsScreen, SettingsScreen).forEach { screen ->
NavigationRailItem(icon = { screen.icon() },
label = { Text(screen.name) },
selected = currentScreen == screen,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package viewmodels

import io.github.jsixface.common.Api
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.resources.*
import io.ktor.http.*
import kotlinx.coroutines.flow.flow
import ui.model.ModelState
import ui.model.ModelState.*
import util.log

class BackupScreenViewModel(private val client: HttpClient) {

init {
log("New backupScreenViewModel")
}

val backupFiles = flow<ModelState<List<String>>> {
emit(Init())
val result = kotlin.runCatching { client.get(Api.Backups) }.getOrNull()
log("Got result: $result")
if (result?.status?.isSuccess() == true) emit(Success(result.body()))
else emit(Error("Error. Status: ${result?.status}"))
}

suspend fun delete(backup: String) {
client.delete(Api.Backups.Backup(path = backup))
}

suspend fun clearBackups() {
client.delete(Api.Backups)
}
}
21 changes: 21 additions & 0 deletions composeApp/src/desktopMain/kotlin/DesktopMain.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.application
import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier

object DesktopMain {
@JvmStatic
fun main(args: Array<String>) = application {
Napier.base(DebugAntilog())
Window(
onCloseRequest = ::exitApplication,
title = "CodeXvert",
state = WindowState(size = DpSize(1200.dp, 800.dp))
) {
App()
}
}
}
23 changes: 13 additions & 10 deletions composeApp/src/desktopMain/kotlin/Previews.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.runtime.Composable
import io.github.jsixface.common.ConversionJob
import io.github.jsixface.common.JobStatus
import io.github.jsixface.common.MediaTrack
import io.github.jsixface.common.TrackType
import io.github.jsixface.common.VideoFile
import kotlin.random.Random
import ui.BackendDialogContent
import ui.JobsScreen
import ui.MainScreen
import ui.SettingsScreen
import io.github.jsixface.common.*
import ui.*
import ui.home.FileDetails
import ui.home.HomeScreen
import ui.theme.AppTheme
import kotlin.random.Random


private val videos = listOf(
Expand Down Expand Up @@ -72,6 +66,15 @@ fun seeJobs() {
}
}

@Composable
@Preview
fun seeBackups() {
AppTheme {
val backups = listOf("The-Boys-S03E01-Payback-WEBDL-1080p.mkv.1710158838.bkp" )
BackupsScreen.BackupContent(backups, {}, {})
}
}

@Composable
@Preview
fun previewBackendDialog() {
Expand Down
26 changes: 0 additions & 26 deletions composeApp/src/desktopMain/kotlin/main.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.github.jsixface.codexvert.api

import io.github.jsixface.codexvert.logger
import java.io.File

class BackupApi {

private val logger = logger<BackupApi>()
private val backupPattern = ".*[0-9]+\\.bkp$".toRegex()

fun getBackups(): List<String> {
val settings = SavedData.load().settings
return settings.libraryLocations.flatMap { location ->
File(location).walk().filter { it.name.matches(backupPattern) }.map { it.absolutePath }
}
}

fun deleteBackup(path: String) {
logger.info("Deleting backup: $path")
File(path).delete()
}

fun deleteAllBackups() {
val settings = SavedData.load().settings
settings.libraryLocations.forEach { location ->
logger.info("Deleting all backups in $location")
File(location).walk().filter { it.name.matches(backupPattern) }.forEach { it.delete() }
}
}
}
Loading

0 comments on commit 2b23aee

Please sign in to comment.