Working config for WebDAV

This commit is contained in:
Daniele Verducci 2024-11-17 09:12:12 +01:00
parent 83be0fa5fb
commit 308092415b
7 changed files with 229 additions and 103 deletions

View File

@ -14,10 +14,12 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
import com.thegrizzlylabs.sardineandroid.impl.SardineException import com.thegrizzlylabs.sardineandroid.impl.SardineException
import it.danieleverducci.lunatracker.SettingsActivity
import it.danieleverducci.lunatracker.adapters.LunaEventRecyclerAdapter import it.danieleverducci.lunatracker.adapters.LunaEventRecyclerAdapter
import it.danieleverducci.lunatracker.entities.Logbook import it.danieleverducci.lunatracker.entities.Logbook
import it.danieleverducci.lunatracker.entities.LunaEvent import it.danieleverducci.lunatracker.entities.LunaEvent
import it.danieleverducci.lunatracker.entities.LunaEventType import it.danieleverducci.lunatracker.entities.LunaEventType
import it.danieleverducci.lunatracker.repository.FileLogbookRepository
import it.danieleverducci.lunatracker.repository.LocalSettingsRepository import it.danieleverducci.lunatracker.repository.LocalSettingsRepository
import it.danieleverducci.lunatracker.repository.LogbookLoadedListener import it.danieleverducci.lunatracker.repository.LogbookLoadedListener
import it.danieleverducci.lunatracker.repository.LogbookRepository import it.danieleverducci.lunatracker.repository.LogbookRepository
@ -42,7 +44,7 @@ class MainActivity : AppCompatActivity() {
loadLogbook() loadLogbook()
handler.postDelayed(updateListRunnable, 1000*60) handler.postDelayed(updateListRunnable, 1000*60)
} }
lateinit var logbookRepo: LogbookRepository var logbookRepo: LogbookRepository? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -52,11 +54,6 @@ class MainActivity : AppCompatActivity() {
if (webDavCredentials == null) { if (webDavCredentials == null) {
TODO("Not supported ATM (TODO: apply settings)") TODO("Not supported ATM (TODO: apply settings)")
} }
logbookRepo = WebDAVLogbookRepository( // TODO: support also FileLogbookRepository
webDavCredentials[0],
webDavCredentials[1],
webDavCredentials[2]
)
handler = Handler(mainLooper) handler = Handler(mainLooper)
adapter = LunaEventRecyclerAdapter(this) adapter = LunaEventRecyclerAdapter(this)
@ -114,7 +111,6 @@ class MainActivity : AppCompatActivity() {
fun showSettings() { fun showSettings() {
val i = Intent(this, SettingsActivity::class.java) val i = Intent(this, SettingsActivity::class.java)
startActivity(i) startActivity(i)
} }
fun showLogbook() { fun showLogbook() {
@ -127,6 +123,21 @@ class MainActivity : AppCompatActivity() {
override fun onStart() { override fun onStart() {
super.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 // Update list dates
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
@ -190,7 +201,7 @@ class MainActivity : AppCompatActivity() {
// Load data // Load data
progressIndicator.visibility = View.VISIBLE progressIndicator.visibility = View.VISIBLE
logbookRepo.loadLogbook(this, object: LogbookLoadedListener{ logbookRepo?.loadLogbook(this, object: LogbookLoadedListener{
override fun onLogbookLoaded(lb: Logbook) { override fun onLogbookLoaded(lb: Logbook) {
runOnUiThread({ runOnUiThread({
progressIndicator.visibility = View.INVISIBLE progressIndicator.visibility = View.INVISIBLE
@ -201,16 +212,37 @@ class MainActivity : AppCompatActivity() {
} }
override fun onIOError(error: IOException) { 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) { 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) { 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 progressIndicator.visibility = View.VISIBLE
logbook.logs.add(0, event) logbook.logs.add(0, event)
logbookRepo.saveLogbook(this, logbook, object: LogbookSavedListener{ logbookRepo?.saveLogbook(this, logbook, object: LogbookSavedListener{
override fun onLogbookSaved() { override fun onLogbookSaved() {
Log.d(TAG, "Logbook saved") Log.d(TAG, "Logbook saved")
runOnUiThread({ runOnUiThread({
@ -244,18 +276,54 @@ class MainActivity : AppCompatActivity() {
}) })
} }
override fun onError(error: String) { override fun onIOError(error: IOException) {
Log.e(TAG, "ERROR: Logbook was NOT saved!")
runOnUiThread({ runOnUiThread({
progressIndicator.visibility = View.INVISIBLE progressIndicator.visibility = View.INVISIBLE
Toast.makeText(this@MainActivity, getString(R.string.settings_network_error) + error.toString(), Toast.LENGTH_SHORT).show()
Toast.makeText(this@MainActivity, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show() onAddError(event, error.toString())
adapter.items.remove(event)
adapter.notifyDataSetChanged()
savingEvent = false
}) })
} }
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
}) })
} }

View File

@ -1,8 +1,6 @@
package it.danieleverducci.lunatracker package it.danieleverducci.lunatracker
import android.os.Bundle import android.os.Bundle
import android.os.PersistableBundle
import android.util.Log
import android.view.View import android.view.View
import android.widget.RadioButton import android.widget.RadioButton
import android.widget.TextView import android.widget.TextView
@ -10,15 +8,12 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
import com.thegrizzlylabs.sardineandroid.impl.SardineException 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.LocalSettingsRepository
import it.danieleverducci.lunatracker.repository.LogbookLoadedListener
import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository
import okio.IOException import okio.IOException
import org.json.JSONException import org.json.JSONException
class SettingsActivity : AppCompatActivity() { open class SettingsActivity : AppCompatActivity() {
protected lateinit var settingsRepository: LocalSettingsRepository protected lateinit var settingsRepository: LocalSettingsRepository
protected lateinit var radioDataLocal: RadioButton protected lateinit var radioDataLocal: RadioButton
protected lateinit var radioDataWebDAV: 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 // Try to connect to WebDAV and check if the save file already exists
val logbookRepo = WebDAVLogbookRepository( val webDAVLogbookRepo = WebDAVLogbookRepository(
textViewWebDAVUrl.text.toString(), textViewWebDAVUrl.text.toString(),
textViewWebDAVUser.text.toString(), textViewWebDAVUser.text.toString(),
textViewWebDAVPass.text.toString() textViewWebDAVPass.text.toString()
) )
progressIndicator.visibility = View.VISIBLE progressIndicator.visibility = View.VISIBLE
logbookRepo.loadLogbook(this, object: LogbookLoadedListener { webDAVLogbookRepo.createLogbook(this, object: WebDAVLogbookRepository.LogbookCreatedListener{
override fun onLogbookLoaded(logbook: Logbook) { override fun onLogbookCreated() {
progressIndicator.visibility = View.INVISIBLE runOnUiThread({
// Save file does exist. Settings valid. Save. progressIndicator.visibility = View.INVISIBLE
saveSettings() saveSettings()
Toast.makeText(this@SettingsActivity, R.string.settings_webdav_creation_ok, Toast.LENGTH_SHORT).show()
})
} }
override fun onIOError(error: IOException) { override fun onIOError(error: IOException) {
// Unable to reach network
runOnUiThread({ runOnUiThread({
progressIndicator.visibility = View.INVISIBLE
Toast.makeText(this@SettingsActivity, getString(R.string.settings_network_error) + error.toString(), Toast.LENGTH_SHORT).show() Toast.makeText(this@SettingsActivity, getString(R.string.settings_network_error) + error.toString(), Toast.LENGTH_SHORT).show()
}) })
} }
override fun onWebDAVError(error: SardineException) { override fun onWebDAVError(error: SardineException) {
// Save file does not exist, upload the local one
runOnUiThread({ runOnUiThread({
progressIndicator.visibility = View.INVISIBLE 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() 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 { } else {
Toast.makeText(this@SettingsActivity, getString(R.string.settings_webdav_error_generic) + error.toString(), Toast.LENGTH_SHORT).show() Toast.makeText(this@SettingsActivity, getString(R.string.settings_webdav_error_generic) + error.toString(), Toast.LENGTH_SHORT).show()
} }
}) })
} }
override fun onJSONError(error: JSONException) { override fun onJSONError(error: JSONException) {
progressIndicator.visibility = View.INVISIBLE runOnUiThread({
// Save file exists, but is corrupted progressIndicator.visibility = View.INVISIBLE
Toast.makeText(this@SettingsActivity, getString(R.string.settings_json_error) + error.toString(), Toast.LENGTH_SHORT).show() Toast.makeText(this@SettingsActivity, getString(R.string.settings_json_error) + error.toString(), Toast.LENGTH_SHORT).show()
})
} }
override fun onError(error: Exception) { override fun onError(error: Exception) {
progressIndicator.visibility = View.INVISIBLE runOnUiThread({
Toast.makeText(this@SettingsActivity, getString(R.string.settings_generic_error) + error.toString(), Toast.LENGTH_SHORT).show() progressIndicator.visibility = View.INVISIBLE
Toast.makeText(this@SettingsActivity, getString(R.string.settings_generic_error) + error.toString(), Toast.LENGTH_SHORT).show()
})
} }
}) })
} }

View File

@ -15,21 +15,25 @@ class FileLogbookRepository: LogbookRepository {
} }
override fun loadLogbook(context: Context, listener: LogbookLoadedListener) { override fun loadLogbook(context: Context, listener: LogbookLoadedListener) {
val logbook = Logbook()
val file = File(context.getFilesDir(), "data.json")
try { try {
val json = FileInputStream(file).bufferedReader().use { it.readText() } listener.onLogbookLoaded(loadLogbook(context))
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) { } catch (e: FileNotFoundException) {
Log.d(TAG, "No logbook file found") Log.d(TAG, "No logbook file found")
listener.onIOError(e) 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( override fun saveLogbook(

View File

@ -8,7 +8,7 @@ class LocalSettingsRepository(val context: Context) {
companion object { companion object {
val SHARED_PREFS_FILE_NAME = "lunasettings" val SHARED_PREFS_FILE_NAME = "lunasettings"
val SHARED_PREFS_BB_CONTENT = "bbcontent" 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_URL = "webdav_url"
val SHARED_PREFS_DAV_USER = "webdav_user" val SHARED_PREFS_DAV_USER = "webdav_user"
val SHARED_PREFS_DAV_PASS = "webdav_password" val SHARED_PREFS_DAV_PASS = "webdav_password"

View File

@ -21,5 +21,8 @@ interface LogbookLoadedListener {
interface LogbookSavedListener { interface LogbookSavedListener {
fun onLogbookSaved() fun onLogbookSaved()
fun onError(error: String) fun onIOError(error: IOException)
fun onWebDAVError(error: SardineException)
fun onJSONError(error: JSONException)
fun onError(error: Exception)
} }

View File

@ -10,6 +10,7 @@ import kotlinx.coroutines.Runnable
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONException import org.json.JSONException
import java.io.BufferedReader import java.io.BufferedReader
import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import kotlin.io.bufferedReader 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) { override fun loadLogbook(context: Context, listener: LogbookLoadedListener) {
Thread(Runnable { Thread(Runnable {
try { try {
val inputStream = sardine.get("$webDavURL/$FILE_NAME") val logbook = loadLogbook(context)
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)
}
listener.onLogbookLoaded(logbook) listener.onLogbookLoaded(logbook)
} catch (e: SardineException) { } catch (e: SardineException) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
@ -60,31 +52,115 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p
}).start() }).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) { override fun saveLogbook(context: Context, logbook: Logbook, listener: LogbookSavedListener) {
Thread(Runnable { 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 { try {
sardine.put(getUrl(), ja.toString().toByteArray()) saveLogbook(context, logbook)
listener.onLogbookSaved() listener.onLogbookSaved()
} catch (e: SardineException) { } 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() }).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 { private fun getUrl(): String {
return "$webDavURL/$FILE_NAME" 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)
}
} }

View File

@ -54,8 +54,10 @@
<string name="settings_storage_dav_user">Username</string> <string name="settings_storage_dav_user">Username</string>
<string name="settings_storage_dav_pass">Password</string> <string name="settings_storage_dav_pass">Password</string>
<string name="settings_network_error">Impossibile raggiungere il server: </string> <string name="settings_network_error">Impossibile raggiungere il server: </string>
<string name="settings_webdav_error_denied">Nome utente o password sbagliati</string> <string name="settings_webdav_error_denied">Nome utente o password WebDAV sbagliati</string>
<string name="settings_webdav_error_generic">Si è verificato un errore tentando di accedere al server WebDAV:</string> <string name="settings_webdav_error_generic">Si è verificato un errore tentando di accedere al server WebDAV:</string>
<string name="settings_webdav_creation_error_generic">Impossibile creare un file di salvataggio sul server WebDAV:</string>
<string name="settings_webdav_creation_ok">Connessione al server WebDAV avvenuta con successo</string>
<string name="settings_json_error">Sul server esiste un salvataggio, ma è corrotto o illeggibile. Cancellare il file </string> <string name="settings_json_error">Sul server esiste un salvataggio, ma è corrotto o illeggibile. Cancellare il file </string>
<string name="settings_generic_error">Si è verificato un errore: </string> <string name="settings_generic_error">Si è verificato un errore: </string>
</resources> </resources>