From 6432045419f19f31e08882fd3bac1ba15d0a2fcd Mon Sep 17 00:00:00 2001 From: Daniele Verducci Date: Thu, 7 Nov 2024 19:43:56 +0100 Subject: [PATCH] First working sync (with hardcoded credentials and no concurrent modification checks) --- .gitignore | 3 + .idea/.gitignore | 3 - .idea/compiler.xml | 6 -- .idea/inspectionProfiles/Project_Default.xml | 57 ------------- app/build.gradle.kts | 2 + app/src/main/AndroidManifest.xml | 2 + .../lunatracker/MainActivity.kt | 79 +++++++++++++++---- .../lunatracker/entities/Logbook.kt | 37 --------- .../repository/FileLogbookRepository.kt | 48 +++++++++++ .../repository/LogbookRepository.kt | 19 +++++ .../repository/WebDAVLogbookRepository.kt | 74 +++++++++++++++++ app/src/main/res/layout/activity_main.xml | 16 +++- gradle/libs.versions.toml | 2 + settings.gradle.kts | 1 + 14 files changed, 229 insertions(+), 120 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 app/src/main/java/it/danieleverducci/lunatracker/repository/FileLogbookRepository.kt create mode 100644 app/src/main/java/it/danieleverducci/lunatracker/repository/LogbookRepository.kt create mode 100644 app/src/main/java/it/danieleverducci/lunatracker/repository/WebDAVLogbookRepository.kt diff --git a/.gitignore b/.gitignore index db634fc..50a88d8 100644 --- a/.gitignore +++ b/.gitignore @@ -103,3 +103,6 @@ app/release/output-metadata.json .idea/misc.xml .idea/deploymentTargetSelector.xml .idea/other.xml + +# Other +app/src/main/java/it/danieleverducci/lunatracker/TemporaryHardcodedCredentials.kt diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b86273d..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index cde3e19..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7e39054..73323a1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,6 +51,8 @@ dependencies { implementation(libs.androidx.material3) implementation(libs.androidx.appcompat) implementation(libs.androidx.recyclerview) + implementation("com.github.thegrizzlylabs:sardine-android:v0.9") + implementation(libs.material) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4744267..16000e1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + (R.id.progress_indicator) recyclerView = findViewById(R.id.list_events) - recyclerView.setLayoutManager(LinearLayoutManager(this)) - adapter = LunaEventRecyclerAdapter(this) - adapter.items.addAll(logbook.logs) + recyclerView.setLayoutManager(LinearLayoutManager(applicationContext)) recyclerView.adapter = adapter // Set listeners @@ -82,11 +87,18 @@ class MainActivity : AppCompatActivity() { ) } } + fun showLogbook() { + // Show logbook + adapter.items.clear() + adapter.items.addAll(logbook.logs) + adapter.notifyDataSetChanged() + } + override fun onStart() { super.onStart() // Update list dates - adapter.notifyDataSetChanged() + loadLogbook() handler.postDelayed(updateListRunnable, 1000*30) } @@ -138,13 +150,52 @@ class MainActivity : AppCompatActivity() { alertDialog.show() } + fun loadLogbook() { + // Load data + progressIndicator.visibility = View.VISIBLE + logbookRepo.loadLogbook(this, object: LogbookLoadedListener{ + override fun onLogbookLoaded(lb: Logbook) { + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + logbook = lb + showLogbook() + }) + } + + override fun onError(error: String) { + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + Log.e(TAG, "Unable to load logbook. Create a new one.") + logbook = Logbook() + showLogbook() + }) + } + }) + } + fun logEvent(event: LunaEvent) { adapter.items.add(0, event) adapter.notifyItemInserted(0) recyclerView.smoothScrollToPosition(0) + progressIndicator.visibility = View.VISIBLE logbook.logs.add(0, event) - logbook.save(this) + logbookRepo.saveLogbook(this, logbook, object: LogbookSavedListener{ + override fun onLogbookSaved() { + Log.d(TAG, "Logbook saved") + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + }) + } + + override fun onError(error: String) { + Log.e(TAG, "ERROR: Logbook was NOT saved!") + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + }) + } + + }) Toast.makeText(this, R.string.toast_event_added, Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/it/danieleverducci/lunatracker/entities/Logbook.kt b/app/src/main/java/it/danieleverducci/lunatracker/entities/Logbook.kt index 8985a91..216b1b6 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/entities/Logbook.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/entities/Logbook.kt @@ -1,42 +1,5 @@ package it.danieleverducci.lunatracker.entities -import android.content.Context -import android.util.Log -import org.json.JSONArray -import java.io.File -import java.io.FileInputStream -import java.io.FileNotFoundException - class Logbook { - companion object { - val TAG = "Logbook" - - fun load(context: Context): Logbook { - val logbook = Logbook() - val file = File(context.getFilesDir(), "data.json") - try { - val json = FileInputStream(file).bufferedReader().use { it.readText() } - val ja = JSONArray(json) - for (i in 0 until ja.length()) { - val jo = ja.getJSONObject(i) - val evt = LunaEvent.fromJson(jo) - logbook.logs.add(evt) - } - } catch (e: FileNotFoundException) { - Log.d(TAG, "No logbook file found") - } - return logbook - } - } - val logs = ArrayList() - - fun save(context: Context) { - val file = File(context.getFilesDir(), "data.json") - val ja = JSONArray() - for (l in logs) { - ja.put(l.toJson()) - } - file.writeText(ja.toString()) - } } \ No newline at end of file diff --git a/app/src/main/java/it/danieleverducci/lunatracker/repository/FileLogbookRepository.kt b/app/src/main/java/it/danieleverducci/lunatracker/repository/FileLogbookRepository.kt new file mode 100644 index 0000000..f09e650 --- /dev/null +++ b/app/src/main/java/it/danieleverducci/lunatracker/repository/FileLogbookRepository.kt @@ -0,0 +1,48 @@ +package it.danieleverducci.lunatracker.repository + +import android.content.Context +import it.danieleverducci.lunatracker.entities.Logbook +import android.util.Log +import it.danieleverducci.lunatracker.entities.LunaEvent +import org.json.JSONArray +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException + +class FileLogbookRepository: LogbookRepository { + companion object { + val TAG = "FileLogbookRepository" + } + + override fun loadLogbook(context: Context, listener: LogbookLoadedListener) { + val logbook = Logbook() + val file = File(context.getFilesDir(), "data.json") + try { + val json = FileInputStream(file).bufferedReader().use { it.readText() } + val ja = JSONArray(json) + for (i in 0 until ja.length()) { + val jo = ja.getJSONObject(i) + val evt = LunaEvent.fromJson(jo) + logbook.logs.add(evt) + } + } catch (e: FileNotFoundException) { + Log.d(TAG, "No logbook file found") + listener.onError(e.toString()) + } + listener.onLogbookLoaded(logbook) + } + + override fun saveLogbook( + context: Context, + logbook: Logbook, + listener: LogbookSavedListener + ) { + val file = File(context.getFilesDir(), "data.json") + val ja = JSONArray() + for (l in logbook.logs) { + ja.put(l.toJson()) + } + file.writeText(ja.toString()) + listener.onLogbookSaved() + } +} \ No newline at end of file diff --git a/app/src/main/java/it/danieleverducci/lunatracker/repository/LogbookRepository.kt b/app/src/main/java/it/danieleverducci/lunatracker/repository/LogbookRepository.kt new file mode 100644 index 0000000..f729903 --- /dev/null +++ b/app/src/main/java/it/danieleverducci/lunatracker/repository/LogbookRepository.kt @@ -0,0 +1,19 @@ +package it.danieleverducci.lunatracker.repository + +import android.content.Context +import it.danieleverducci.lunatracker.entities.Logbook + +interface LogbookRepository { + fun loadLogbook(context: Context, listener: LogbookLoadedListener) + fun saveLogbook(context: Context, logbook: Logbook, listener: LogbookSavedListener) +} + +interface LogbookLoadedListener { + fun onLogbookLoaded(logbook: Logbook) + fun onError(error: String) +} + +interface LogbookSavedListener { + fun onLogbookSaved() + fun onError(error: String) +} \ No newline at end of file diff --git a/app/src/main/java/it/danieleverducci/lunatracker/repository/WebDAVLogbookRepository.kt b/app/src/main/java/it/danieleverducci/lunatracker/repository/WebDAVLogbookRepository.kt new file mode 100644 index 0000000..60d464e --- /dev/null +++ b/app/src/main/java/it/danieleverducci/lunatracker/repository/WebDAVLogbookRepository.kt @@ -0,0 +1,74 @@ +package it.danieleverducci.lunatracker.repository + +import android.content.Context +import com.thegrizzlylabs.sardineandroid.impl.OkHttpSardine +import com.thegrizzlylabs.sardineandroid.impl.SardineException +import it.danieleverducci.lunatracker.TemporaryHardcodedCredentials +import it.danieleverducci.lunatracker.entities.Logbook +import it.danieleverducci.lunatracker.entities.LunaEvent +import kotlinx.coroutines.Runnable +import org.json.JSONArray +import java.io.BufferedReader +import kotlin.io.bufferedReader + +class WebDAVLogbookRepository(val webDavURL: String, val username: String, val password: String): LogbookRepository { + companion object { + val TAG = "LogbookRepository" + val FILE_NAME = "lunatracker_logbook.json" + } + val sardine: OkHttpSardine = OkHttpSardine() + + init { + sardine.setCredentials( + username, + password + ) + } + + override fun loadLogbook(context: Context, listener: LogbookLoadedListener) { + Thread(Runnable { + try { + val inputStream = sardine.get("$webDavURL/$FILE_NAME") + val json = inputStream.bufferedReader().use(BufferedReader::readText) + val ja = JSONArray(json) + val logbook = Logbook() + for (i in 0 until ja.length()) { + val jo = ja.getJSONObject(i) + val evt = LunaEvent.fromJson(jo) + logbook.logs.add(evt) + } + listener.onLogbookLoaded(logbook) + } catch (e: SardineException) { + listener.onError(e.toString()) + } + }).start() + } + + override fun saveLogbook(context: Context, logbook: Logbook, listener: LogbookSavedListener) { + Thread(Runnable { + // Lock logbook on WebDAV to avoid concurrent changes + //sardine.lock(getUrl()) + // Reload logbook from WebDAV + // Merge logbooks (based on time) + // Write logbook + // Unlock logbook on WebDAV + //sardine.unlock(getUrl()) + + val ja = JSONArray() + for (l in logbook.logs) { + ja.put(l.toJson()) + } + try { + sardine.put(getUrl(), ja.toString().toByteArray()) + listener.onLogbookSaved() + } catch (e: SardineException) { + listener.onError(e.toString()) + } + + }).start() + } + + private fun getUrl(): String { + return "${TemporaryHardcodedCredentials.URL}/$FILE_NAME" + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b8578d6..58f03ce 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,7 +1,7 @@ - + + + android:textColor="@color/accent" + android:textStyle="bold"/>