From a6105c9cedf752eaed35ce59b89a4c82454d28e8 Mon Sep 17 00:00:00 2001 From: Wynd Date: Sun, 22 Feb 2026 00:47:44 +0200 Subject: [PATCH] Added network syncing --- app/src/main/AndroidManifest.xml | 3 + .../xyz/pixelatedw/recipe/MainActivity.kt | 6 +- .../java/xyz/pixelatedw/recipe/data/Recipe.kt | 1 + .../recipe/ui/components/MainScreen.kt | 6 +- .../pixelatedw/recipe/utils/RecipeParser.kt | 83 +++++++++------- .../pixelatedw/recipe/utils/SyncRecipes.kt | 96 +++++++++++++++++++ 6 files changed, 156 insertions(+), 39 deletions(-) create mode 100644 app/src/main/java/xyz/pixelatedw/recipe/utils/SyncRecipes.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 069ae9a..19512da 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,9 @@ + + + if (result.resultCode == RESULT_OK) { result.data?.data?.let { uri -> - parseRecipes(this, db, uri) + parseRecipes(this, uri) val recipes = db.recipeWithTagsDao().getAll() recipeView.setRecipes(recipes) diff --git a/app/src/main/java/xyz/pixelatedw/recipe/data/Recipe.kt b/app/src/main/java/xyz/pixelatedw/recipe/data/Recipe.kt index d31bc06..7dd0518 100644 --- a/app/src/main/java/xyz/pixelatedw/recipe/data/Recipe.kt +++ b/app/src/main/java/xyz/pixelatedw/recipe/data/Recipe.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import androidx.core.net.toUri +import androidx.documentfile.provider.DocumentFile import androidx.room.Dao import androidx.room.Delete import androidx.room.Entity diff --git a/app/src/main/java/xyz/pixelatedw/recipe/ui/components/MainScreen.kt b/app/src/main/java/xyz/pixelatedw/recipe/ui/components/MainScreen.kt index 91bcd27..f891783 100644 --- a/app/src/main/java/xyz/pixelatedw/recipe/ui/components/MainScreen.kt +++ b/app/src/main/java/xyz/pixelatedw/recipe/ui/components/MainScreen.kt @@ -1,6 +1,7 @@ package xyz.pixelatedw.recipe.ui.components import android.content.Intent +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -25,7 +26,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.navigation.compose.NavHost @@ -35,6 +35,7 @@ import xyz.pixelatedw.recipe.MainActivity import xyz.pixelatedw.recipe.R import xyz.pixelatedw.recipe.data.RecipeWithTags import xyz.pixelatedw.recipe.data.RecipesView +import xyz.pixelatedw.recipe.utils.sync import java.io.File @Composable @@ -131,7 +132,8 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) { } else { IconButton( modifier = Modifier.weight(0.15f), - onClick = { ctx.sourceChooser.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) }, + onClick = { sync(ctx) }, +// onClick = { ctx.sourceChooser.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) }, ) { Icon( imageVector = Icons.Default.Add, diff --git a/app/src/main/java/xyz/pixelatedw/recipe/utils/RecipeParser.kt b/app/src/main/java/xyz/pixelatedw/recipe/utils/RecipeParser.kt index ddce751..1228567 100644 --- a/app/src/main/java/xyz/pixelatedw/recipe/utils/RecipeParser.kt +++ b/app/src/main/java/xyz/pixelatedw/recipe/utils/RecipeParser.kt @@ -5,7 +5,7 @@ import android.net.Uri import androidx.core.text.trimmedLength import androidx.documentfile.provider.DocumentFile import io.github.wasabithumb.jtoml.JToml -import xyz.pixelatedw.recipe.data.AppDatabase +import xyz.pixelatedw.recipe.MainActivity import xyz.pixelatedw.recipe.data.Recipe import xyz.pixelatedw.recipe.data.RecipeTag import xyz.pixelatedw.recipe.data.Tag @@ -16,7 +16,7 @@ import java.io.InputStreamReader private val recipeFiles = mutableListOf() -fun parseRecipes(ctx: Context, db: AppDatabase, uri: Uri?) { +fun parseRecipes(ctx: MainActivity, uri: Uri?) { if (uri == null) { return } @@ -26,10 +26,21 @@ fun parseRecipes(ctx: Context, db: AppDatabase, uri: Uri?) { val dir = DocumentFile.fromTreeUri(ctx, uri) if (dir != null) { parseDir(ctx, dir, path) + parseRecipeFiles(ctx) + } +} - for (file in recipeFiles) { - parseRecipe(ctx, db, file) - } +fun parseRecipeFiles(ctx: MainActivity) { + for (file in recipeFiles) { + val lastModified = file.lastModified() + + val inputStream = ctx.contentResolver.openInputStream(file.uri) + val reader = BufferedReader(InputStreamReader(inputStream)) + val text = reader.readText() + + parseRecipe(ctx, lastModified, text) + + reader.close() } } @@ -41,33 +52,41 @@ fun parseDir(ctx: Context, dir: DocumentFile, path: String) { continue } - if (file.isFile && file.name?.endsWith(".jpg") == true) { - val picsDir = File(ctx.filesDir, path) - picsDir.mkdirs() - - val newFile = File(picsDir, file.name!!) - - ctx.contentResolver.openInputStream(file.uri).use { inStream -> - FileOutputStream(newFile, false).use { outStream -> - inStream?.copyTo(outStream) - } - } - } - if (file.isFile && file.name?.endsWith(".md") == true) { - recipeFiles.add(file) - } + handleFile(ctx, file, path) } } -private fun parseRecipe(ctx: Context, db: AppDatabase, file: DocumentFile) { - val lastModified = file.lastModified() +fun handleFile(ctx: Context, file: DocumentFile, path: String) { + if (!file.isFile) { + return + } - val inputStream = ctx.contentResolver.openInputStream(file.uri) - val reader = BufferedReader(InputStreamReader(inputStream)) - val text = reader.readText() + val fileName = file.name ?: return +// println("file: ${file.name} | ${file.uri.path} | ${file.length()}") + if (fileName.endsWith(".jpg")) { + val picsDir = File(ctx.filesDir, path) + picsDir.mkdirs() + + val newFile = File(picsDir, fileName) + if (newFile.exists()) { + return + } + + ctx.contentResolver.openInputStream(file.uri).use { inStream -> + FileOutputStream(newFile, false).use { outStream -> + inStream?.copyTo(outStream) + } + } + } + else if (fileName.endsWith(".md")) { + recipeFiles.add(file) + } +} + +fun parseRecipe(ctx: MainActivity, lastModified: Long, text: String) { val hash = text.hashCode() // Probably not the best way but its stable and works - if (db.recipeDao().hasHash(hash)) { + if (ctx.db.recipeDao().hasHash(hash)) { return } @@ -92,7 +111,6 @@ private fun parseRecipe(ctx: Context, db: AppDatabase, file: DocumentFile) { } if (!hasToml) { - reader.close() return } @@ -100,7 +118,6 @@ private fun parseRecipe(ctx: Context, db: AppDatabase, file: DocumentFile) { val doc = toml.readFromString(sb.toString()) if (!doc.contains("title")) { - reader.close() return } @@ -116,15 +133,15 @@ private fun parseRecipe(ctx: Context, db: AppDatabase, file: DocumentFile) { val tags = arrayListOf() if (doc.contains("tags")) { // Delete already existing tags for this recipe - db.recipeWithTagsDao().delete(recipeTitle) + ctx.db.recipeWithTagsDao().delete(recipeTitle) for (tomlElem in doc["tags"]!!.asArray()) { val tag = Tag(tomlElem!!.asPrimitive().asString()) tags.add(tag) val recipeWithTags = RecipeTag(recipeTitle, tag.name) - db.tagDao().insert(tag) - db.recipeWithTagsDao().insert(recipeWithTags) + ctx.db.tagDao().insert(tag) + ctx.db.recipeWithTagsDao().insert(recipeWithTags) } } @@ -138,7 +155,5 @@ private fun parseRecipe(ctx: Context, db: AppDatabase, file: DocumentFile) { hash = hash ) - db.recipeDao().insert(recipe) - - reader.close() + ctx.db.recipeDao().insert(recipe) } diff --git a/app/src/main/java/xyz/pixelatedw/recipe/utils/SyncRecipes.kt b/app/src/main/java/xyz/pixelatedw/recipe/utils/SyncRecipes.kt new file mode 100644 index 0000000..c9358d1 --- /dev/null +++ b/app/src/main/java/xyz/pixelatedw/recipe/utils/SyncRecipes.kt @@ -0,0 +1,96 @@ +package xyz.pixelatedw.recipe.utils + +import android.os.Build +import android.os.FileUtils +import android.os.Handler +import android.os.Looper +import androidx.annotation.RequiresApi +import androidx.documentfile.provider.DocumentFile +import xyz.pixelatedw.recipe.MainActivity +import java.io.BufferedOutputStream +import java.io.DataInputStream +import java.io.File +import java.io.FileOutputStream +import java.net.Socket +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets +import java.util.concurrent.Executors + + +fun sync(ctx: MainActivity) { + val executor = Executors.newSingleThreadExecutor() + val handler = Handler(Looper.getMainLooper()) + + executor.execute(Runnable() { + try { + val conn = Socket("192.168.0.100", 9696) + val stream = conn.getInputStream() + + val inputStream = DataInputStream(stream) + + var buffer = ByteArray(8) + inputStream.read(buffer) + val filesSent = ByteBuffer.wrap(buffer).getLong().toInt() +// println("files sent: $filesSent") + + for(f in 0.. contentBufLen) { + blockSize = contentBufLen - usedBytes + } + else if (blockSize > contentBufLen) { + blockSize = contentBufLen + } + + val contentBuffer = ByteArray(blockSize) + val readBytes = inputStream.read(contentBuffer) + + fos.write(contentBuffer, 0, readBytes) + fos.flush() + usedBytes += readBytes + } + fos.close() + +// println("new file: ${newFile.absolutePath} | $contentBufLen | ${newFile.length()}") + } + + val docDir = DocumentFile.fromFile(ctx.filesDir) + parseDir(ctx, docDir, "") + parseRecipeFiles(ctx) + + } catch (e: Exception) { + e.printStackTrace() + } + + handler.post(Runnable { + val recipes = ctx.db.recipeWithTagsDao().getAll() + ctx.recipeView.setRecipes(recipes) + println("syncing complete") + }) + }) +}