diff --git a/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt b/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt index 4c03fe9..4474daa 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt @@ -14,10 +14,12 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.progressindicator.LinearProgressIndicator import com.thegrizzlylabs.sardineandroid.impl.SardineException +import it.danieleverducci.lunatracker.SettingsActivity import it.danieleverducci.lunatracker.adapters.LunaEventRecyclerAdapter import it.danieleverducci.lunatracker.entities.Logbook import it.danieleverducci.lunatracker.entities.LunaEvent import it.danieleverducci.lunatracker.entities.LunaEventType +import it.danieleverducci.lunatracker.repository.FileLogbookRepository import it.danieleverducci.lunatracker.repository.LocalSettingsRepository import it.danieleverducci.lunatracker.repository.LogbookLoadedListener import it.danieleverducci.lunatracker.repository.LogbookRepository @@ -42,7 +44,7 @@ class MainActivity : AppCompatActivity() { loadLogbook() handler.postDelayed(updateListRunnable, 1000*60) } - lateinit var logbookRepo: LogbookRepository + var logbookRepo: LogbookRepository? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -52,11 +54,6 @@ class MainActivity : AppCompatActivity() { if (webDavCredentials == null) { TODO("Not supported ATM (TODO: apply settings)") } - logbookRepo = WebDAVLogbookRepository( // TODO: support also FileLogbookRepository - webDavCredentials[0], - webDavCredentials[1], - webDavCredentials[2] - ) handler = Handler(mainLooper) adapter = LunaEventRecyclerAdapter(this) @@ -114,7 +111,6 @@ class MainActivity : AppCompatActivity() { fun showSettings() { val i = Intent(this, SettingsActivity::class.java) startActivity(i) - } fun showLogbook() { @@ -127,6 +123,21 @@ class MainActivity : AppCompatActivity() { override fun onStart() { super.onStart() + val settingsRepository = LocalSettingsRepository(this) + if (settingsRepository.loadDataRepository() == LocalSettingsRepository.DATA_REPO.WEBDAV) { + val webDavCredentials = settingsRepository.loadWebdavCredentials() + if (webDavCredentials == null) { + throw IllegalStateException("Corrupted local settings: repo is webdav, but no webdav login data saved") + } + logbookRepo = WebDAVLogbookRepository( + webDavCredentials[0], + webDavCredentials[1], + webDavCredentials[2] + ) + } else { + logbookRepo = FileLogbookRepository() + } + // Update list dates adapter.notifyDataSetChanged() @@ -190,7 +201,7 @@ class MainActivity : AppCompatActivity() { // Load data progressIndicator.visibility = View.VISIBLE - logbookRepo.loadLogbook(this, object: LogbookLoadedListener{ + logbookRepo?.loadLogbook(this, object: LogbookLoadedListener{ override fun onLogbookLoaded(lb: Logbook) { runOnUiThread({ progressIndicator.visibility = View.INVISIBLE @@ -201,16 +212,37 @@ class MainActivity : AppCompatActivity() { } override fun onIOError(error: IOException) { - onRepoError(error) // TODO: Meaningful message + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + Toast.makeText(this@MainActivity, getString(R.string.settings_network_error) + error.toString(), Toast.LENGTH_SHORT).show() + }) } override fun onWebDAVError(error: SardineException) { - onRepoError(error) // TODO: Meaningful message + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + if(error.toString().contains("401")) { + Toast.makeText(this@MainActivity, getString(R.string.settings_webdav_error_denied), Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(this@MainActivity, getString(R.string.settings_webdav_error_generic) + error.toString(), Toast.LENGTH_SHORT).show() + } + }) } override fun onJSONError(error: JSONException) { - onRepoError(error) // TODO: Meaningful message + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + Toast.makeText(this@MainActivity, getString(R.string.settings_json_error) + error.toString(), Toast.LENGTH_SHORT).show() + }) } + + override fun onError(error: Exception) { + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + Toast.makeText(this@MainActivity, getString(R.string.settings_generic_error) + error.toString(), Toast.LENGTH_SHORT).show() + }) + } + }) } @@ -233,7 +265,7 @@ class MainActivity : AppCompatActivity() { progressIndicator.visibility = View.VISIBLE logbook.logs.add(0, event) - logbookRepo.saveLogbook(this, logbook, object: LogbookSavedListener{ + logbookRepo?.saveLogbook(this, logbook, object: LogbookSavedListener{ override fun onLogbookSaved() { Log.d(TAG, "Logbook saved") runOnUiThread({ @@ -244,18 +276,54 @@ class MainActivity : AppCompatActivity() { }) } - override fun onError(error: String) { - Log.e(TAG, "ERROR: Logbook was NOT saved!") + override fun onIOError(error: IOException) { runOnUiThread({ progressIndicator.visibility = View.INVISIBLE - - Toast.makeText(this@MainActivity, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show() - adapter.items.remove(event) - adapter.notifyDataSetChanged() - savingEvent = false + Toast.makeText(this@MainActivity, getString(R.string.settings_network_error) + error.toString(), Toast.LENGTH_SHORT).show() + onAddError(event, error.toString()) }) } + override fun onWebDAVError(error: SardineException) { + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + if(error.toString().contains("401")) { + Toast.makeText(this@MainActivity, getString(R.string.settings_webdav_error_denied), Toast.LENGTH_SHORT).show() + onAddError(event, error.toString()) + } else { + Toast.makeText(this@MainActivity, getString(R.string.settings_webdav_error_generic) + error.toString(), Toast.LENGTH_SHORT).show() + onAddError(event, error.toString()) + } + }) + } + + override fun onJSONError(error: JSONException) { + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + Toast.makeText(this@MainActivity, getString(R.string.settings_json_error) + error.toString(), Toast.LENGTH_SHORT).show() + onAddError(event, error.toString()) + }) + } + + override fun onError(error: Exception) { + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + Toast.makeText(this@MainActivity, getString(R.string.settings_generic_error) + error.toString(), Toast.LENGTH_SHORT).show() + onAddError(event, error.toString()) + }) + } + }) + } + + private fun onAddError(event: LunaEvent, error: String) { + Log.e(TAG, "Logbook was NOT saved!") + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + + Toast.makeText(this@MainActivity, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show() + adapter.items.remove(event) + adapter.notifyDataSetChanged() + savingEvent = false }) } diff --git a/app/src/main/java/it/danieleverducci/lunatracker/SettingsActivity.kt b/app/src/main/java/it/danieleverducci/lunatracker/SettingsActivity.kt index 5c8fb3e..239bc4e 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/SettingsActivity.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/SettingsActivity.kt @@ -1,8 +1,6 @@ package it.danieleverducci.lunatracker import android.os.Bundle -import android.os.PersistableBundle -import android.util.Log import android.view.View import android.widget.RadioButton import android.widget.TextView @@ -10,15 +8,12 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.google.android.material.progressindicator.LinearProgressIndicator import com.thegrizzlylabs.sardineandroid.impl.SardineException -import it.danieleverducci.lunatracker.entities.Logbook -import it.danieleverducci.lunatracker.repository.FileLogbookRepository import it.danieleverducci.lunatracker.repository.LocalSettingsRepository -import it.danieleverducci.lunatracker.repository.LogbookLoadedListener import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository import okio.IOException import org.json.JSONException -class SettingsActivity : AppCompatActivity() { +open class SettingsActivity : AppCompatActivity() { protected lateinit var settingsRepository: LocalSettingsRepository protected lateinit var radioDataLocal: RadioButton protected lateinit var radioDataWebDAV: RadioButton @@ -71,75 +66,53 @@ class SettingsActivity : AppCompatActivity() { } // Try to connect to WebDAV and check if the save file already exists - val logbookRepo = WebDAVLogbookRepository( + val webDAVLogbookRepo = WebDAVLogbookRepository( textViewWebDAVUrl.text.toString(), textViewWebDAVUser.text.toString(), textViewWebDAVPass.text.toString() ) progressIndicator.visibility = View.VISIBLE - logbookRepo.loadLogbook(this, object: LogbookLoadedListener { - override fun onLogbookLoaded(logbook: Logbook) { - progressIndicator.visibility = View.INVISIBLE - // Save file does exist. Settings valid. Save. - saveSettings() + webDAVLogbookRepo.createLogbook(this, object: WebDAVLogbookRepository.LogbookCreatedListener{ + override fun onLogbookCreated() { + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + saveSettings() + Toast.makeText(this@SettingsActivity, R.string.settings_webdav_creation_ok, Toast.LENGTH_SHORT).show() + }) } override fun onIOError(error: IOException) { - // Unable to reach network runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE Toast.makeText(this@SettingsActivity, getString(R.string.settings_network_error) + error.toString(), Toast.LENGTH_SHORT).show() }) } override fun onWebDAVError(error: SardineException) { - // Save file does not exist, upload the local one runOnUiThread({ progressIndicator.visibility = View.INVISIBLE - if (error.toString().contains("401")) { + if(error.toString().contains("401")) { Toast.makeText(this@SettingsActivity, getString(R.string.settings_webdav_error_denied), Toast.LENGTH_SHORT).show() - } else if (error.toString().contains("404")) { - // Connection successful, but no existing save. Upload the local one. - val fileLogbookRepo = FileLogbookRepository() - fileLogbookRepo.loadLogbook(this@SettingsActivity, object: LogbookLoadedListener { - override fun onLogbookLoaded(logbook: Logbook) { - TODO("Not yet implemented") - } - - override fun onIOError(error: IOException) { - TODO("Not yet implemented") - } - - override fun onWebDAVError(error: SardineException) { - TODO("Not yet implemented") - } - - override fun onJSONError(error: JSONException) { - TODO("Not yet implemented") - } - - override fun onError(error: Exception) { - TODO("Not yet implemented") - } - - }) } else { Toast.makeText(this@SettingsActivity, getString(R.string.settings_webdav_error_generic) + error.toString(), Toast.LENGTH_SHORT).show() } }) - - } override fun onJSONError(error: JSONException) { - progressIndicator.visibility = View.INVISIBLE - // Save file exists, but is corrupted - Toast.makeText(this@SettingsActivity, getString(R.string.settings_json_error) + error.toString(), Toast.LENGTH_SHORT).show() + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + Toast.makeText(this@SettingsActivity, getString(R.string.settings_json_error) + error.toString(), Toast.LENGTH_SHORT).show() + }) } override fun onError(error: Exception) { - progressIndicator.visibility = View.INVISIBLE - Toast.makeText(this@SettingsActivity, getString(R.string.settings_generic_error) + error.toString(), Toast.LENGTH_SHORT).show() + runOnUiThread({ + progressIndicator.visibility = View.INVISIBLE + Toast.makeText(this@SettingsActivity, getString(R.string.settings_generic_error) + error.toString(), Toast.LENGTH_SHORT).show() + }) } + }) } diff --git a/app/src/main/java/it/danieleverducci/lunatracker/repository/FileLogbookRepository.kt b/app/src/main/java/it/danieleverducci/lunatracker/repository/FileLogbookRepository.kt index 169fd53..87b3444 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/repository/FileLogbookRepository.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/repository/FileLogbookRepository.kt @@ -15,21 +15,25 @@ class FileLogbookRepository: LogbookRepository { } 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) - } + listener.onLogbookLoaded(loadLogbook(context)) } catch (e: FileNotFoundException) { Log.d(TAG, "No logbook file found") listener.onIOError(e) } - listener.onLogbookLoaded(logbook) + } + + fun loadLogbook(context: Context): Logbook { + val logbook = Logbook() + val file = File(context.getFilesDir(), "data.json") + 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) + } + return logbook } override fun saveLogbook( diff --git a/app/src/main/java/it/danieleverducci/lunatracker/repository/LocalSettingsRepository.kt b/app/src/main/java/it/danieleverducci/lunatracker/repository/LocalSettingsRepository.kt index 98125cf..b48608c 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/repository/LocalSettingsRepository.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/repository/LocalSettingsRepository.kt @@ -8,7 +8,7 @@ class LocalSettingsRepository(val context: Context) { companion object { val SHARED_PREFS_FILE_NAME = "lunasettings" val SHARED_PREFS_BB_CONTENT = "bbcontent" - val SHARED_PREFS_DATA_REPO = "webdav_url" + val SHARED_PREFS_DATA_REPO = "data_repo" val SHARED_PREFS_DAV_URL = "webdav_url" val SHARED_PREFS_DAV_USER = "webdav_user" val SHARED_PREFS_DAV_PASS = "webdav_password" diff --git a/app/src/main/java/it/danieleverducci/lunatracker/repository/LogbookRepository.kt b/app/src/main/java/it/danieleverducci/lunatracker/repository/LogbookRepository.kt index b5a65bc..316909e 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/repository/LogbookRepository.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/repository/LogbookRepository.kt @@ -21,5 +21,8 @@ interface LogbookLoadedListener { interface LogbookSavedListener { fun onLogbookSaved() - fun onError(error: String) + fun onIOError(error: IOException) + fun onWebDAVError(error: SardineException) + fun onJSONError(error: JSONException) + fun onError(error: Exception) } \ 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 index d70be06..5d2aa9a 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/repository/WebDAVLogbookRepository.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/repository/WebDAVLogbookRepository.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.Runnable import org.json.JSONArray import org.json.JSONException import java.io.BufferedReader +import java.io.FileNotFoundException import java.io.IOException import java.net.SocketTimeoutException import kotlin.io.bufferedReader @@ -31,16 +32,7 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p override fun loadLogbook(context: Context, listener: LogbookLoadedListener) { Thread(Runnable { try { - val inputStream = sardine.get("$webDavURL/$FILE_NAME") - val json = inputStream.bufferedReader().use(BufferedReader::readText) - inputStream.close() - 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) - } + val logbook = loadLogbook(context) listener.onLogbookLoaded(logbook) } catch (e: SardineException) { Log.e(TAG, e.toString()) @@ -60,31 +52,115 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p }).start() } + private fun loadLogbook(context: Context): Logbook { + val inputStream = sardine.get("$webDavURL/$FILE_NAME") + val json = inputStream.bufferedReader().use(BufferedReader::readText) + inputStream.close() + 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) + } + return logbook + } + 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()) + saveLogbook(context, logbook) listener.onLogbookSaved() } catch (e: SardineException) { - listener.onError(e.toString()) + Log.e(TAG, e.toString()) + listener.onWebDAVError(e) + } catch (e: IOException) { + Log.e(TAG, e.toString()) + listener.onIOError(e) + } catch (e: SocketTimeoutException) { + Log.e(TAG, e.toString()) + listener.onIOError(e) + } catch (e: JSONException) { + Log.e(TAG, e.toString()) + listener.onJSONError(e) + } catch (e: Exception) { + listener.onError(e) } }).start() } + private fun saveLogbook(context: Context, logbook: Logbook) { + // 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()) + } + sardine.put(getUrl(), ja.toString().toByteArray()) + } + + /** + * Connect to server and check if a logbook already exists. + * If it does not exist, try to upload the local one (or create a new one). + */ + fun createLogbook(context: Context, listener: LogbookCreatedListener) { + Thread(Runnable { + try { + loadLogbook(context) + listener.onLogbookCreated() + } catch (e: SardineException) { + if (e.toString().contains("404")) { + // Connection successful, but no existing save. Upload the local one. + try { + val flr = FileLogbookRepository() + val logbook = flr.loadLogbook(context) + saveLogbook(context, logbook) + Log.d(TAG, "Local logbook file found, uploaded") + listener.onLogbookCreated() + } catch (e: FileNotFoundException) { + Log.d(TAG, "No local logbook file found, uploading empty file") + saveLogbook(context, Logbook()) + listener.onLogbookCreated() + } catch (e: SardineException) { + Log.e(TAG, "Unable to upload logbook: $e") + listener.onWebDAVError(e) + } + } else { + Log.e(TAG, e.toString()) + listener.onWebDAVError(e) + } + } catch (e: IOException) { + Log.e(TAG, e.toString()) + listener.onIOError(e) + } catch (e: SocketTimeoutException) { + Log.e(TAG, e.toString()) + listener.onIOError(e) + } catch (e: JSONException) { + Log.e(TAG, e.toString()) + listener.onJSONError(e) + } catch (e: Exception) { + listener.onError(e) + } + }).start() + } + private fun getUrl(): String { return "$webDavURL/$FILE_NAME" } + + + interface LogbookCreatedListener { + fun onLogbookCreated() + fun onIOError(error: okio.IOException) + fun onWebDAVError(error: SardineException) + fun onJSONError(error: JSONException) + fun onError(error: Exception) + } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7069cf4..8122f76 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -54,8 +54,10 @@ Username Password Impossibile raggiungere il server: - Nome utente o password sbagliati + Nome utente o password WebDAV sbagliati Si è verificato un errore tentando di accedere al server WebDAV: + Impossibile creare un file di salvataggio sul server WebDAV: + Connessione al server WebDAV avvenuta con successo Sul server esiste un salvataggio, ma è corrotto o illeggibile. Cancellare il file Si è verificato un errore: \ No newline at end of file