From da018fc64dfe4050475b0a62d915253f9b62e692 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 17 Apr 2021 19:53:24 -0500 Subject: [PATCH] Temporarily remove device sync --- uhabits-android/build.gradle.kts | 1 - .../uhabits/sync/RemoteSyncServerTest.kt | 154 --------------- uhabits-android/src/main/AndroidManifest.xml | 20 -- .../common/dialogs/ConfirmSyncKeyDialog.kt | 45 ----- .../habits/list/ListHabitsActivity.kt | 10 - .../habits/list/ListHabitsScreen.kt | 18 -- .../activities/settings/SettingsFragment.kt | 18 -- .../uhabits/activities/sync/SyncActivity.kt | 130 ------------- .../inject/HabitsApplicationComponent.kt | 2 - .../org/isoron/uhabits/inject/HabitsModule.kt | 17 -- .../isoron/uhabits/intents/IntentFactory.kt | 5 - .../uhabits/sync/AndroidNetworkManager.kt | 59 ------ .../isoron/uhabits/sync/RemoteSyncServer.kt | 105 ---------- .../src/main/res/layout/activity_sync.xml | 147 -------------- .../src/main/res/values/strings.xml | 12 -- .../src/main/res/xml/preferences.xml | 24 --- .../uhabits/core/preferences/Preferences.kt | 22 --- .../uhabits/core/sync/AbstractSyncServer.kt | 60 ------ .../isoron/uhabits/core/sync/EncryptionExt.kt | 142 -------------- .../uhabits/core/sync/NetworkManager.kt | 29 --- .../org/isoron/uhabits/core/sync/SyncData.kt | 29 --- .../isoron/uhabits/core/sync/SyncException.kt | 28 --- .../isoron/uhabits/core/sync/SyncManager.kt | 183 ------------------ .../screens/habits/list/ListHabitsBehavior.kt | 15 -- .../core/ui/screens/sync/SyncBehavior.kt | 66 ------- .../uhabits/core/sync/EncryptionExtTest.kt | 70 ------- 26 files changed, 1411 deletions(-) delete mode 100644 uhabits-android/src/androidTest/java/org/isoron/uhabits/sync/RemoteSyncServerTest.kt delete mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmSyncKeyDialog.kt delete mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/activities/sync/SyncActivity.kt delete mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/sync/AndroidNetworkManager.kt delete mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/sync/RemoteSyncServer.kt delete mode 100644 uhabits-android/src/main/res/layout/activity_sync.xml delete mode 100644 uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/AbstractSyncServer.kt delete mode 100644 uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/EncryptionExt.kt delete mode 100644 uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/NetworkManager.kt delete mode 100644 uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncData.kt delete mode 100644 uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncException.kt delete mode 100644 uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncManager.kt delete mode 100644 uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/sync/SyncBehavior.kt delete mode 100644 uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/sync/EncryptionExtTest.kt diff --git a/uhabits-android/build.gradle.kts b/uhabits-android/build.gradle.kts index 826f82dab..72a28a8f9 100644 --- a/uhabits-android/build.gradle.kts +++ b/uhabits-android/build.gradle.kts @@ -109,7 +109,6 @@ dependencies { implementation("androidx.legacy:legacy-preference-v14:1.0.0") implementation("androidx.legacy:legacy-support-v4:1.0.0") implementation("com.google.android.material:material:1.3.0") - implementation("com.google.zxing:core:3.4.1") implementation("com.opencsv:opencsv:5.4") implementation(project(":uhabits-core")) kapt("com.google.dagger:dagger-compiler:$daggerVersion") diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/sync/RemoteSyncServerTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/sync/RemoteSyncServerTest.kt deleted file mode 100644 index 619af32b6..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/sync/RemoteSyncServerTest.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.sync - -import androidx.test.filters.MediumTest -import com.fasterxml.jackson.databind.ObjectMapper -import io.ktor.client.HttpClient -import io.ktor.client.engine.mock.MockEngine -import io.ktor.client.engine.mock.MockRequestHandleScope -import io.ktor.client.engine.mock.respond -import io.ktor.client.engine.mock.respondError -import io.ktor.client.engine.mock.respondOk -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.request.HttpRequestData -import io.ktor.client.request.HttpResponseData -import io.ktor.http.HttpStatusCode -import io.ktor.http.fullPath -import io.ktor.http.headersOf -import kotlinx.coroutines.runBlocking -import org.isoron.uhabits.BaseAndroidTest -import org.isoron.uhabits.core.sync.AbstractSyncServer -import org.isoron.uhabits.core.sync.GetDataVersionResponse -import org.isoron.uhabits.core.sync.KeyNotFoundException -import org.isoron.uhabits.core.sync.RegisterReponse -import org.isoron.uhabits.core.sync.ServiceUnavailable -import org.isoron.uhabits.core.sync.SyncData -import org.junit.Test - -@MediumTest -class RemoteSyncServerTest : BaseAndroidTest() { - - private val mapper = ObjectMapper() - val data = SyncData(1, "Hello world") - - @Test - fun when_register_succeeds_should_return_key() = runBlocking { - val server = server("/register") { - respondWithJson(RegisterReponse("ABCDEF")) - } - assertEquals("ABCDEF", server.register()) - } - - @Test(expected = ServiceUnavailable::class) - fun when_register_fails_should_raise_correct_exception() = runBlocking { - val server = server("/register") { - respondError(HttpStatusCode.ServiceUnavailable) - } - server.register() - return@runBlocking - } - - @Test - fun when_get_data_version_succeeds_should_return_version() = runBlocking { - server("/db/ABC/version") { - respondWithJson(GetDataVersionResponse(5)) - }.apply { - assertEquals(5, getDataVersion("ABC")) - } - return@runBlocking - } - - @Test(expected = ServiceUnavailable::class) - fun when_get_data_version_with_server_error_should_raise_exception() = runBlocking { - server("/db/ABC/version") { - respondError(HttpStatusCode.InternalServerError) - }.apply { - getDataVersion("ABC") - } - return@runBlocking - } - - @Test(expected = KeyNotFoundException::class) - fun when_get_data_version_with_invalid_key_should_raise_exception() = runBlocking { - server("/db/ABC/version") { - respondError(HttpStatusCode.NotFound) - }.apply { - getDataVersion("ABC") - } - return@runBlocking - } - - @Test - fun when_get_data_succeeds_should_return_data() = runBlocking { - server("/db/ABC") { - respondWithJson(data) - }.apply { - assertEquals(data, getData("ABC")) - } - return@runBlocking - } - - @Test(expected = KeyNotFoundException::class) - fun when_get_data_with_invalid_key_should_raise_exception() = runBlocking { - server("/db/ABC") { - respondError(HttpStatusCode.NotFound) - }.apply { - getData("ABC") - } - return@runBlocking - } - - @Test - fun when_put_succeeds_should_not_raise_exceptions() = runBlocking { - server("/db/ABC") { - respondOk() - }.apply { - put("ABC", data) - } - return@runBlocking - } - - private fun server( - expectedPath: String, - action: MockRequestHandleScope.(HttpRequestData) -> HttpResponseData - ): AbstractSyncServer { - return RemoteSyncServer( - httpClient = HttpClient(MockEngine) { - install(JsonFeature) - engine { - addHandler { request -> - when (request.url.fullPath) { - expectedPath -> action(request) - else -> error("unexpected call: ${request.url.fullPath}") - } - } - } - }, - preferences = prefs - ) - } - - private fun MockRequestHandleScope.respondWithJson(content: Any) = - respond( - mapper.writeValueAsBytes(content), - headers = headersOf("Content-Type" to listOf("application/json")) - ) -} diff --git a/uhabits-android/src/main/AndroidManifest.xml b/uhabits-android/src/main/AndroidManifest.xml index 141cfc414..b5d06f181 100644 --- a/uhabits-android/src/main/AndroidManifest.xml +++ b/uhabits-android/src/main/AndroidManifest.xml @@ -22,8 +22,6 @@ - - - - - - @@ -59,16 +49,6 @@ android:exported="true" android:label="@string/main_activity_title" android:launchMode="singleTop"> - - - - - - - - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ -package org.isoron.uhabits.activities.common.dialogs - -import android.content.Context -import android.content.DialogInterface -import androidx.appcompat.app.AlertDialog -import org.isoron.uhabits.R -import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback -import org.isoron.uhabits.inject.ActivityContext - -class ConfirmSyncKeyDialog( - @ActivityContext context: Context, - callback: OnConfirmedCallback -) : AlertDialog(context) { - init { - setTitle(R.string.device_sync) - val res = context.resources - setMessage(res.getString(R.string.sync_confirm)) - setButton( - BUTTON_POSITIVE, - res.getString(R.string.yes) - ) { dialog: DialogInterface?, which: Int -> callback.onConfirmed() } - setButton( - BUTTON_NEGATIVE, - res.getString(R.string.no) - ) { dialog: DialogInterface?, which: Int -> } - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt index a31b78458..da5ae7d00 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt @@ -26,12 +26,10 @@ import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import org.isoron.uhabits.BaseExceptionHandler import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter import org.isoron.uhabits.core.preferences.Preferences -import org.isoron.uhabits.core.sync.SyncManager import org.isoron.uhabits.core.tasks.TaskRunner import org.isoron.uhabits.core.ui.ThemeSwitcher.Companion.THEME_DARK import org.isoron.uhabits.core.utils.MidnightTimer @@ -49,7 +47,6 @@ class ListHabitsActivity : AppCompatActivity() { lateinit var screen: ListHabitsScreen lateinit var prefs: Preferences lateinit var midnightTimer: MidnightTimer - lateinit var syncManager: SyncManager private val scope = CoroutineScope(Dispatchers.Main) private lateinit var menu: ListHabitsMenu @@ -66,7 +63,6 @@ class ListHabitsActivity : AppCompatActivity() { component.themeSwitcher.apply() prefs = appComponent.preferences - syncManager = appComponent.syncManager pureBlack = prefs.isPureBlackEnabled midnightTimer = appComponent.midnightTimer rootView = component.listHabitsRootView @@ -83,9 +79,6 @@ class ListHabitsActivity : AppCompatActivity() { midnightTimer.onPause() screen.onDettached() adapter.cancelRefresh() - scope.launch { - syncManager.onPause() - } super.onPause() } @@ -94,9 +87,6 @@ class ListHabitsActivity : AppCompatActivity() { screen.onAttached() rootView.postInvalidate() midnightTimer.onResume() - scope.launch { - syncManager.onResume() - } taskRunner.run { AutoBackup(this@ListHabitsActivity).run() } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt index 6168ed781..5ede0ab44 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt @@ -22,13 +22,11 @@ package org.isoron.uhabits.activities.habits.list import android.app.Activity import android.content.Context import android.content.Intent -import android.util.Log import androidx.appcompat.app.AppCompatActivity import dagger.Lazy import org.isoron.uhabits.R import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog -import org.isoron.uhabits.activities.common.dialogs.ConfirmSyncKeyDialog import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter @@ -53,8 +51,6 @@ import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.FILE_NOT_RECOGNIZED import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.IMPORT_FAILED import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.IMPORT_SUCCESSFUL -import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.SYNC_ENABLED -import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.SYNC_KEY_ALREADY_INSTALLED import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsMenuBehavior import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsSelectionMenuBehavior import org.isoron.uhabits.inject.ActivityContext @@ -103,14 +99,6 @@ class ListHabitsScreen fun onAttached() { commandRunner.addListener(this) - if (activity.intent.action == "android.intent.action.VIEW") { - val uri = activity.intent.data!!.toString() - val parts = uri.replace(Regex("^.*sync/"), "").split("#") - val syncKey = parts[0] - val encKey = parts[1] - Log.i("ListHabitsScreen", "sync: $syncKey enc: $encKey") - behavior.get().onSyncKeyOffer(syncKey, encKey) - } } fun onDettached() { @@ -208,8 +196,6 @@ class ListHabitsScreen DATABASE_REPAIRED -> R.string.database_repaired COULD_NOT_GENERATE_BUG_REPORT -> R.string.bug_report_failed FILE_NOT_RECOGNIZED -> R.string.file_not_recognized - SYNC_ENABLED -> R.string.sync_enabled - SYNC_KEY_ALREADY_INSTALLED -> R.string.sync_key_already_installed } ) ) @@ -244,10 +230,6 @@ class ListHabitsScreen numberPickerFactory.create(value, unit, callback).show() } - override fun showConfirmInstallSyncKey(callback: OnConfirmedCallback) { - ConfirmSyncKeyDialog(activity, callback).show() - } - private fun getExecuteString(command: Command): String? { when (command) { is ArchiveHabitsCommand -> { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.kt index 448d84c9d..cc8bcecfe 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.kt @@ -19,7 +19,6 @@ package org.isoron.uhabits.activities.settings import android.app.backup.BackupManager -import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener @@ -28,7 +27,6 @@ import android.os.Build.VERSION import android.os.Bundle import android.provider.Settings import android.util.Log -import androidx.preference.CheckBoxPreference import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceCategory @@ -43,7 +41,6 @@ import org.isoron.uhabits.activities.habits.list.RESULT_REPAIR_DB import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.ui.NotificationTray import org.isoron.uhabits.core.utils.DateUtils.Companion.getLongWeekdayNames -import org.isoron.uhabits.intents.IntentFactory import org.isoron.uhabits.notifications.AndroidNotificationTray.Companion.createAndroidNotificationChannel import org.isoron.uhabits.notifications.RingtoneManager import org.isoron.uhabits.widgets.WidgetUpdater @@ -100,13 +97,6 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID) startActivity(intent) return true - } else if (key == "pref_sync_enabled_dummy") { - if (prefs.isSyncEnabled) { - prefs.disableSync() - } else { - val context: Context? = activity - context!!.startActivity(IntentFactory().startSyncActivity(context)) - } } return super.onPreferenceTreeClick(preference) } @@ -121,7 +111,6 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis devCategory.isVisible = false } updateWeekdayPreference() - updateSyncPreferences() if (VERSION.SDK_INT < Build.VERSION_CODES.O) findPreference("reminderCustomize").isVisible = false @@ -130,12 +119,6 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis } } - private fun updateSyncPreferences() { - findPreference("pref_sync_display").isVisible = prefs.isSyncEnabled - (findPreference("pref_sync_enabled_dummy") as CheckBoxPreference).isChecked = - prefs.isSyncEnabled - } - private fun updateWeekdayPreference() { val weekdayPref = findPreference("pref_first_weekday") as ListPreference val currentFirstWeekday = prefs.firstWeekday.daysSinceSunday + 1 @@ -157,7 +140,6 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis } BackupManager.dataChanged("org.isoron.uhabits") updateWeekdayPreference() - updateSyncPreferences() } private fun setResultOnPreferenceClick(key: String, result: Int) { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/sync/SyncActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/sync/SyncActivity.kt deleted file mode 100644 index 5b84ef8c2..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/sync/SyncActivity.kt +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.activities.sync - -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Color -import android.os.Bundle -import android.text.Html -import android.view.View -import androidx.appcompat.app.AppCompatActivity -import com.google.zxing.BarcodeFormat -import com.google.zxing.qrcode.QRCodeWriter -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.invoke -import kotlinx.coroutines.launch -import org.isoron.uhabits.HabitsApplication -import org.isoron.uhabits.R -import org.isoron.uhabits.activities.AndroidThemeSwitcher -import org.isoron.uhabits.core.models.PaletteColor -import org.isoron.uhabits.core.ui.screens.sync.SyncBehavior -import org.isoron.uhabits.databinding.ActivitySyncBinding -import org.isoron.uhabits.sync.RemoteSyncServer -import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome -import org.isoron.uhabits.utils.setupToolbar -import org.isoron.uhabits.utils.showMessage - -class SyncActivity : AppCompatActivity(), SyncBehavior.Screen { - - private lateinit var binding: ActivitySyncBinding - private lateinit var behavior: SyncBehavior - - private val scope = CoroutineScope(Dispatchers.Main) - - override fun onCreate(savedInstance: Bundle?) { - super.onCreate(savedInstance) - val component = (application as HabitsApplication).component - val preferences = component.preferences - val server = RemoteSyncServer(preferences = preferences) - AndroidThemeSwitcher(this, component.preferences).apply() - - behavior = SyncBehavior(this, preferences, server, component.logging) - binding = ActivitySyncBinding.inflate(layoutInflater) - binding.errorIcon.typeface = getFontAwesome(this) - binding.root.setupToolbar( - toolbar = binding.toolbar, - color = PaletteColor(11), - title = resources.getString(R.string.device_sync), - ) - binding.syncLink.setOnClickListener { copyToClipboard() } - binding.instructions.text = Html.fromHtml(resources.getString(R.string.sync_instructions)) - setContentView(binding.root) - } - - override fun onResume() { - super.onResume() - scope.launch { - behavior.onResume() - } - } - - private fun copyToClipboard() { - val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - clipboard.setPrimaryClip(ClipData.newPlainText("Loop Sync Link", binding.syncLink.text)) - showMessage(resources.getString(R.string.copied_to_the_clipboard)) - } - - suspend fun generateQR(msg: String): Bitmap = Dispatchers.IO { - val writer = QRCodeWriter() - val matrix = writer.encode(msg, BarcodeFormat.QR_CODE, 1024, 1024) - val height = matrix.height - val width = matrix.width - val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565) - val bgColor = Color.WHITE - val fgColor = Color.BLACK - for (x in 0 until width) { - for (y in 0 until height) { - val color = if (matrix.get(x, y)) fgColor else bgColor - bitmap.setPixel(x, y, color) - } - } - return@IO bitmap - } - - suspend fun showQR(msg: String) { - binding.progress.visibility = View.GONE - binding.qrCode.visibility = View.VISIBLE - binding.qrCode.setImageBitmap(generateQR(msg)) - } - - override suspend fun showLoadingScreen() { - binding.qrCode.visibility = View.GONE - binding.progress.visibility = View.VISIBLE - binding.errorPanel.visibility = View.GONE - } - - override suspend fun showErrorScreen() { - binding.qrCode.visibility = View.GONE - binding.progress.visibility = View.GONE - binding.errorPanel.visibility = View.VISIBLE - } - - override suspend fun showLink(link: String) { - binding.qrCode.visibility = View.GONE - binding.progress.visibility = View.VISIBLE - binding.errorPanel.visibility = View.GONE - binding.syncLink.text = link - showQR(link) - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsApplicationComponent.kt b/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsApplicationComponent.kt index 49bb8d67f..da7f957f3 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsApplicationComponent.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsApplicationComponent.kt @@ -29,7 +29,6 @@ import org.isoron.uhabits.core.models.ModelFactory import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.WidgetPreferences import org.isoron.uhabits.core.reminders.ReminderScheduler -import org.isoron.uhabits.core.sync.SyncManager import org.isoron.uhabits.core.tasks.TaskRunner import org.isoron.uhabits.core.ui.NotificationTray import org.isoron.uhabits.core.ui.screens.habits.list.HabitCardListCache @@ -64,5 +63,4 @@ interface HabitsApplicationComponent { val taskRunner: TaskRunner val widgetPreferences: WidgetPreferences val widgetUpdater: WidgetUpdater - val syncManager: SyncManager } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsModule.kt b/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsModule.kt index 8a0e1c242..c7b6843d0 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsModule.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsModule.kt @@ -19,7 +19,6 @@ package org.isoron.uhabits.inject -import android.content.Context import dagger.Module import dagger.Provides import org.isoron.uhabits.core.AppScope @@ -34,8 +33,6 @@ import org.isoron.uhabits.core.models.sqlite.SQLiteHabitList import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.WidgetPreferences import org.isoron.uhabits.core.reminders.ReminderScheduler -import org.isoron.uhabits.core.sync.AbstractSyncServer -import org.isoron.uhabits.core.sync.NetworkManager import org.isoron.uhabits.core.tasks.TaskRunner import org.isoron.uhabits.core.ui.NotificationTray import org.isoron.uhabits.database.AndroidDatabase @@ -44,8 +41,6 @@ import org.isoron.uhabits.intents.IntentScheduler import org.isoron.uhabits.io.AndroidLogging import org.isoron.uhabits.notifications.AndroidNotificationTray import org.isoron.uhabits.preferences.SharedPreferencesStorage -import org.isoron.uhabits.sync.AndroidNetworkManager -import org.isoron.uhabits.sync.RemoteSyncServer import org.isoron.uhabits.utils.DatabaseUtils import java.io.File @@ -114,18 +109,6 @@ class HabitsModule(dbFile: File) { return AndroidLogging() } - @Provides - @AppScope - fun getNetworkManager(@AppContext context: Context): NetworkManager { - return AndroidNetworkManager(context) - } - - @Provides - @AppScope - fun getSyncServer(preferences: Preferences): AbstractSyncServer { - return RemoteSyncServer(preferences) - } - @Provides @AppScope fun getDatabase(): Database { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentFactory.kt b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentFactory.kt index 3ee368a02..71b2938e7 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentFactory.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentFactory.kt @@ -28,7 +28,6 @@ import org.isoron.uhabits.activities.habits.edit.EditHabitActivity import org.isoron.uhabits.activities.habits.show.ShowHabitActivity import org.isoron.uhabits.activities.intro.IntroActivity import org.isoron.uhabits.activities.settings.SettingsActivity -import org.isoron.uhabits.activities.sync.SyncActivity import org.isoron.uhabits.core.models.Habit import javax.inject.Inject @@ -101,8 +100,4 @@ class IntentFactory intent.putExtra("habitType", habitType) return intent } - - fun startSyncActivity(context: Context): Intent { - return Intent(context, SyncActivity::class.java) - } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/sync/AndroidNetworkManager.kt b/uhabits-android/src/main/java/org/isoron/uhabits/sync/AndroidNetworkManager.kt deleted file mode 100644 index ed40b9161..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/sync/AndroidNetworkManager.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.sync - -import android.content.Context -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkRequest -import org.isoron.uhabits.core.sync.NetworkManager - -class AndroidNetworkManager( - val context: Context, -) : NetworkManager, ConnectivityManager.NetworkCallback() { - - val listeners = mutableListOf() - var connected = false - - init { - val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - cm.registerNetworkCallback(NetworkRequest.Builder().build(), this) - } - - override fun addListener(listener: NetworkManager.Listener) { - if (connected) listener.onNetworkAvailable() - else listener.onNetworkLost() - listeners.add(listener) - } - - override fun remoteListener(listener: NetworkManager.Listener) { - listeners.remove(listener) - } - - override fun onAvailable(network: Network) { - connected = true - for (l in listeners) l.onNetworkAvailable() - } - - override fun onLost(network: Network) { - connected = false - for (l in listeners) l.onNetworkLost() - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/sync/RemoteSyncServer.kt b/uhabits-android/src/main/java/org/isoron/uhabits/sync/RemoteSyncServer.kt deleted file mode 100644 index 4e8f4a705..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/sync/RemoteSyncServer.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.sync - -import android.util.Log -import io.ktor.client.HttpClient -import io.ktor.client.engine.android.Android -import io.ktor.client.features.ClientRequestException -import io.ktor.client.features.ServerResponseException -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.request.get -import io.ktor.client.request.header -import io.ktor.client.request.post -import io.ktor.client.request.put -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.invoke -import org.isoron.uhabits.core.preferences.Preferences -import org.isoron.uhabits.core.sync.AbstractSyncServer -import org.isoron.uhabits.core.sync.EditConflictException -import org.isoron.uhabits.core.sync.GetDataVersionResponse -import org.isoron.uhabits.core.sync.KeyNotFoundException -import org.isoron.uhabits.core.sync.RegisterReponse -import org.isoron.uhabits.core.sync.ServiceUnavailable -import org.isoron.uhabits.core.sync.SyncData - -class RemoteSyncServer( - private val preferences: Preferences, - private val httpClient: HttpClient = HttpClient(Android) { - install(JsonFeature) - } -) : AbstractSyncServer { - - override suspend fun register(): String = Dispatchers.IO { - try { - val url = "${preferences.syncBaseURL}/register" - Log.i("RemoteSyncServer", "POST $url") - val response: RegisterReponse = httpClient.post(url) - return@IO response.key - } catch (e: ServerResponseException) { - throw ServiceUnavailable() - } - } - - override suspend fun put(key: String, newData: SyncData) = Dispatchers.IO { - try { - val url = "${preferences.syncBaseURL}/db/$key" - Log.i("RemoteSyncServer", "PUT $url") - val response: String = httpClient.put(url) { - header("Content-Type", "application/json") - body = newData - } - } catch (e: ServerResponseException) { - throw ServiceUnavailable() - } catch (e: ClientRequestException) { - Log.w("RemoteSyncServer", "ClientRequestException", e) - if (e.message!!.contains("409")) throw EditConflictException() - if (e.message!!.contains("404")) throw KeyNotFoundException() - throw e - } - } - - override suspend fun getData(key: String): SyncData = Dispatchers.IO { - try { - val url = "${preferences.syncBaseURL}/db/$key" - Log.i("RemoteSyncServer", "GET $url") - return@IO httpClient.get(url) - } catch (e: ServerResponseException) { - throw ServiceUnavailable() - } catch (e: ClientRequestException) { - Log.w("RemoteSyncServer", "ClientRequestException", e) - throw KeyNotFoundException() - } - } - - override suspend fun getDataVersion(key: String): Long = Dispatchers.IO { - try { - val url = "${preferences.syncBaseURL}/db/$key/version" - Log.i("RemoteSyncServer", "GET $url") - val response: GetDataVersionResponse = httpClient.get(url) - return@IO response.version - } catch (e: ServerResponseException) { - throw ServiceUnavailable() - } catch (e: ClientRequestException) { - Log.w("RemoteSyncServer", "ClientRequestException", e) - throw KeyNotFoundException() - } - } -} diff --git a/uhabits-android/src/main/res/layout/activity_sync.xml b/uhabits-android/src/main/res/layout/activity_sync.xml deleted file mode 100644 index 5d7040d5e..000000000 --- a/uhabits-android/src/main/res/layout/activity_sync.xml +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/uhabits-android/src/main/res/values/strings.xml b/uhabits-android/src/main/res/values/strings.xml index 61487bb04..61354e942 100644 --- a/uhabits-android/src/main/res/values/strings.xml +++ b/uhabits-android/src/main/res/values/strings.xml @@ -223,18 +223,6 @@ Decrement Enable skip days Toggle twice to add a skip instead of a checkmark. Skips keep your score unchanged and don\'t break your streak. - Device sync - When enabled, an encrypted copy of your data will be uploaded to our servers. See privacy policy. - Sync data across devices - Show device sync instructions - Instructions:
1. Install Loop in your second device.
2. Open the link below in your second device.
Important: Do not not make this information public. It gives anyone access to your data.]]>
- Sync link - Sync link (QR code) - Password - Copied to the clipboard - - Device sync enabled - Sync key already installed Show question marks for missing data Differentiate days without data from actual lapses. To enter a lapse, toggle twice. You are now a developer diff --git a/uhabits-android/src/main/res/xml/preferences.xml b/uhabits-android/src/main/res/xml/preferences.xml index 26afdeac3..8d44da556 100644 --- a/uhabits-android/src/main/res/xml/preferences.xml +++ b/uhabits-android/src/main/res/xml/preferences.xml @@ -114,30 +114,6 @@ - - - - - - - - - - - diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt index e739840a6..8fdd26af9 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt @@ -204,27 +204,6 @@ open class Preferences(private val storage: Storage) { set(value) { storage.putBoolean("pref_skip_enabled", value) } - val syncBaseURL: String - get() = storage.getString("pref_sync_base_url", "") - val syncKey: String - get() = storage.getString("pref_sync_key", "") - val encryptionKey: String - get() = storage.getString("pref_encryption_key", "") - val isSyncEnabled: Boolean - get() = storage.getBoolean("pref_sync_enabled", false) - - fun enableSync(syncKey: String, encKey: String) { - storage.putBoolean("pref_sync_enabled", true) - storage.putString("pref_sync_key", syncKey) - storage.putString("pref_encryption_key", encKey) - for (l in listeners) l.onSyncEnabled() - } - - fun disableSync() { - storage.putBoolean("pref_sync_enabled", false) - storage.putString("pref_sync_key", "") - storage.putString("pref_encryption_key", "") - } fun areQuestionMarksEnabled(): Boolean { return storage.getBoolean("pref_unknown_enabled", false) @@ -261,7 +240,6 @@ open class Preferences(private val storage: Storage) { interface Listener { fun onCheckmarkSequenceChanged() {} fun onNotificationsChanged() {} - fun onSyncEnabled() {} } interface Storage { diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/AbstractSyncServer.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/AbstractSyncServer.kt deleted file mode 100644 index 713c81a5e..000000000 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/AbstractSyncServer.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.core.sync - -interface AbstractSyncServer { - /** - * Generates and returns a new sync key, which can be used to store and retrive - * data. - * - * @throws ServiceUnavailable If key cannot be generated at this time, for example, - * due to insufficient server resources, temporary server maintenance or network problems. - */ - suspend fun register(): String - - /** - * Replaces data for a given sync key. - * - * @throws KeyNotFoundException If key is not found - * @throws EditConflictException If the version of the data provided is not - * exactly the current data version plus one. - * @throws ServiceUnavailable If data cannot be put at this time, for example, due - * to insufficient server resources or network problems. - */ - suspend fun put(key: String, newData: SyncData) - - /** - * Returns data for a given sync key. - * - * @throws KeyNotFoundException If key is not found - * @throws ServiceUnavailable If data cannot be retrieved at this time, for example, due - * to insufficient server resources or network problems. - */ - suspend fun getData(key: String): SyncData - - /** - * Returns the current data version for the given key - * - * @throws KeyNotFoundException If key is not found - * @throws ServiceUnavailable If data cannot be retrieved at this time, for example, due - * to insufficient server resources or network problems. - */ - suspend fun getDataVersion(key: String): Long -} diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/EncryptionExt.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/EncryptionExt.kt deleted file mode 100644 index 2134c9908..000000000 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/EncryptionExt.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -@file:Suppress("UnstableApiUsage") - -package org.isoron.uhabits.core.sync - -import com.google.common.io.ByteStreams -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.invoke -import org.apache.commons.codec.binary.Base64 -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.nio.ByteBuffer -import java.util.zip.GZIPInputStream -import java.util.zip.GZIPOutputStream -import javax.crypto.Cipher -import javax.crypto.KeyGenerator -import javax.crypto.SecretKey -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec - -/** - * Encryption key which can be used with [File.encryptToString], [String.decryptToFile], - * [ByteArray.encrypt] and [ByteArray.decrypt]. - * - * To randomly generate a new key, use [EncryptionKey.generate]. To load a key from a - * Base64-encoded string, use [EncryptionKey.fromBase64]. - */ -class EncryptionKey private constructor( - val base64: String, - val secretKey: SecretKey -) { - companion object { - - fun fromBase64(base64: String): EncryptionKey { - val keySpec = SecretKeySpec(base64.decodeBase64(), "AES") - return EncryptionKey(base64, keySpec) - } - - private fun fromSecretKey(spec: SecretKey): EncryptionKey { - val base64 = spec.encoded.encodeBase64().trim() - return EncryptionKey(base64, spec) - } - - suspend fun generate(): EncryptionKey = Dispatchers.IO { - try { - val generator = KeyGenerator.getInstance("AES").apply { init(256) } - return@IO fromSecretKey(generator.generateKey()) - } catch (e: Exception) { - throw RuntimeException(e) - } - } - } -} - -/** - * Encrypts the byte stream using the provided symmetric encryption key. - * - * The initialization vector (16 bytes) is prepended to the cipher text. To decrypt the result, use - * [ByteArray.decrypt], providing the same key. - */ -fun ByteArray.encrypt(key: EncryptionKey): ByteArray { - val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING") - cipher.init(Cipher.ENCRYPT_MODE, key.secretKey) - val encrypted = cipher.doFinal(this) - return ByteBuffer - .allocate(16 + encrypted.size) - .put(cipher.iv) - .put(encrypted) - .array() -} - -/** - * Decrypts a byte stream generated by [ByteArray.encrypt]. - */ -fun ByteArray.decrypt(key: EncryptionKey): ByteArray { - val buffer = ByteBuffer.wrap(this) - val iv = ByteArray(16) - buffer.get(iv) - val encrypted = ByteArray(buffer.remaining()) - buffer.get(encrypted) - val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING") - cipher.init(Cipher.DECRYPT_MODE, key.secretKey, IvParameterSpec(iv)) - return cipher.doFinal(encrypted) -} - -/** - * Takes a string produced by [File.encryptToString], decodes it with Base64, decompresses it with - * gzip, decrypts it with the provided key, then writes the output to the specified file. - */ -fun String.decryptToFile(key: EncryptionKey, output: File) { - val bytes = this.decodeBase64().decrypt(key) - ByteArrayInputStream(bytes).use { bytesInputStream -> - GZIPInputStream(bytesInputStream).use { gzipInputStream -> - FileOutputStream(output).use { fileOutputStream -> - ByteStreams.copy(gzipInputStream, fileOutputStream) - } - } - } -} - -/** - * Compresses the file with gzip, encrypts it using the the provided key, then returns a string - * containing the Base64-encoded cipher bytes. - * - * To decrypt and decompress the cipher text back into a file, use [String.decryptToFile]. - */ -fun File.encryptToString(key: EncryptionKey): String { - ByteArrayOutputStream().use { bytesOutputStream -> - FileInputStream(this).use { inputStream -> - GZIPOutputStream(bytesOutputStream).use { gzipOutputStream -> - ByteStreams.copy(inputStream, gzipOutputStream) - gzipOutputStream.close() - val bytes = bytesOutputStream.toByteArray() - return bytes.encrypt(key).encodeBase64() - } - } - } -} - -fun ByteArray.encodeBase64(): String = Base64.encodeBase64(this).decodeToString() -fun String.decodeBase64(): ByteArray = Base64.decodeBase64(this.toByteArray()) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/NetworkManager.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/NetworkManager.kt deleted file mode 100644 index 9ba68ff8d..000000000 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/NetworkManager.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.core.sync - -interface NetworkManager { - fun addListener(listener: Listener) - fun remoteListener(listener: Listener) - interface Listener { - fun onNetworkAvailable() - fun onNetworkLost() - } -} diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncData.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncData.kt deleted file mode 100644 index 3910ce775..000000000 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncData.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.core.sync - -data class SyncData( - val version: Long, - val content: String -) - -data class RegisterReponse(val key: String) - -data class GetDataVersionResponse(val version: Long) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncException.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncException.kt deleted file mode 100644 index 407880566..000000000 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncException.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.core.sync - -open class SyncException : RuntimeException() - -class KeyNotFoundException : SyncException() - -class ServiceUnavailable : SyncException() - -class EditConflictException : SyncException() diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncManager.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncManager.kt deleted file mode 100644 index 5c26969aa..000000000 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/sync/SyncManager.kt +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.core.sync - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.invoke -import kotlinx.coroutines.launch -import org.isoron.uhabits.core.AppScope -import org.isoron.uhabits.core.commands.Command -import org.isoron.uhabits.core.commands.CommandRunner -import org.isoron.uhabits.core.database.Database -import org.isoron.uhabits.core.io.Logging -import org.isoron.uhabits.core.io.LoopDBImporter -import org.isoron.uhabits.core.preferences.Preferences -import java.io.File -import javax.inject.Inject - -@AppScope -class SyncManager @Inject constructor( - val preferences: Preferences, - val commandRunner: CommandRunner, - val logging: Logging, - val networkManager: NetworkManager, - val server: AbstractSyncServer, - val db: Database, - val dbImporter: LoopDBImporter, -) : Preferences.Listener, CommandRunner.Listener, NetworkManager.Listener { - - private var logger = logging.getLogger("SyncManager") - private var connected = false - private val tmpFile = File.createTempFile("import", "") - private var currVersion = 1L - private var dirty = true - private lateinit var encryptionKey: EncryptionKey - private lateinit var syncKey: String - - init { - preferences.addListener(this) - commandRunner.addListener(this) - networkManager.addListener(this) - } - - fun sync() = CoroutineScope(Dispatchers.Main).launch { - if (!preferences.isSyncEnabled) { - logger.info("Device sync is disabled. Skipping sync.") - return@launch - } - - encryptionKey = EncryptionKey.fromBase64(preferences.encryptionKey) - syncKey = preferences.syncKey - logger.info("Starting sync (key: $syncKey)") - - try { - pull() - push() - logger.info("Sync finished successfully.") - } catch (e: ConnectionLostException) { - logger.info("Network unavailable. Aborting sync.") - } catch (e: ServiceUnavailable) { - logger.info("Sync service unavailable. Aborting sync.") - } catch (e: Exception) { - logger.error("Unexpected sync exception. Disabling sync.") - logger.error(e) - preferences.disableSync() - } - } - - private suspend fun push(depth: Int = 0) { - if (depth >= 5) { - throw RuntimeException() - } - - if (!dirty) { - logger.info("Local database not modified. Skipping push.") - return - } - - logger.info("Encrypting local database...") - val encryptedDB = db.file!!.encryptToString(encryptionKey) - val size = encryptedDB.length / 1024 - - try { - logger.info("Pushing local database (version $currVersion, $size KB)") - assertConnected() - server.put(preferences.syncKey, SyncData(currVersion, encryptedDB)) - dirty = false - } catch (e: EditConflictException) { - logger.info("Sync conflict detected while pushing.") - setCurrentVersion(0) - pull() - push(depth = depth + 1) - } - } - - private suspend fun pull() = Dispatchers.IO { - logger.info("Querying remote database version...") - assertConnected() - val remoteVersion = server.getDataVersion(syncKey) - logger.info("Remote database version: $remoteVersion") - - if (remoteVersion <= currVersion) { - logger.info("Local database is up-to-date. Skipping merge.") - } else { - logger.info("Pulling remote database...") - assertConnected() - val data = server.getData(syncKey) - val size = data.content.length / 1024 - logger.info("Pulled remote database (version ${data.version}, $size KB)") - logger.info("Decrypting remote database and merging with local changes...") - data.content.decryptToFile(encryptionKey, tmpFile) - - try { - db.beginTransaction() - dbImporter.importHabitsFromFile(tmpFile) - db.setTransactionSuccessful() - } catch (e: Exception) { - logger.error("Failed to import database") - logger.error(e) - } finally { - db.endTransaction() - } - - dirty = true - setCurrentVersion(data.version + 1) - } - } - - fun onResume() = sync() - - fun onPause() = sync() - - override fun onSyncEnabled() { - logger.info("Sync enabled.") - setCurrentVersion(1) - dirty = true - sync() - } - - override fun onNetworkAvailable() { - logger.info("Network available.") - connected = true - sync() - } - - override fun onNetworkLost() { - logger.info("Network unavailable.") - connected = false - } - - override fun onCommandFinished(command: Command) { - if (!dirty) setCurrentVersion(currVersion + 1) - dirty = true - } - - private fun assertConnected() { - if (!connected) throw ConnectionLostException() - } - - private fun setCurrentVersion(v: Long) { - currVersion = v - logger.info("Setting local database version: $currVersion") - } -} - -class ConnectionLostException : RuntimeException() diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt index 9877e8ad4..89fb09af4 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt @@ -26,7 +26,6 @@ import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.tasks.ExportCSVTask import org.isoron.uhabits.core.tasks.TaskRunner -import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday import java.io.File import java.io.IOException @@ -111,17 +110,6 @@ open class ListHabitsBehavior @Inject constructor( ) } - fun onSyncKeyOffer(syncKey: String, encryptionKey: String) { - if (prefs.syncKey == syncKey) { - screen.showMessage(Message.SYNC_KEY_ALREADY_INSTALLED) - return - } - screen.showConfirmInstallSyncKey { - prefs.enableSync(syncKey, encryptionKey) - screen.showMessage(Message.SYNC_ENABLED) - } - } - enum class Message { COULD_NOT_EXPORT, IMPORT_SUCCESSFUL, @@ -129,8 +117,6 @@ open class ListHabitsBehavior @Inject constructor( DATABASE_REPAIRED, COULD_NOT_GENERATE_BUG_REPORT, FILE_NOT_RECOGNIZED, - SYNC_ENABLED, - SYNC_KEY_ALREADY_INSTALLED } interface BugReporter { @@ -161,6 +147,5 @@ open class ListHabitsBehavior @Inject constructor( fun showSendBugReportToDeveloperScreen(log: String) fun showSendFileScreen(filename: String) - fun showConfirmInstallSyncKey(callback: OnConfirmedCallback) } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/sync/SyncBehavior.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/sync/SyncBehavior.kt deleted file mode 100644 index 3bf80b6da..000000000 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/sync/SyncBehavior.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.core.ui.screens.sync - -import org.isoron.uhabits.core.io.Logging -import org.isoron.uhabits.core.preferences.Preferences -import org.isoron.uhabits.core.sync.AbstractSyncServer -import org.isoron.uhabits.core.sync.EncryptionKey - -class SyncBehavior( - val screen: Screen, - val preferences: Preferences, - val server: AbstractSyncServer, - val logging: Logging, -) { - val logger = logging.getLogger("SyncBehavior") - - suspend fun onResume() { - if (preferences.syncKey.isBlank()) { - register() - } else { - displayCurrentKey() - } - } - - suspend fun displayCurrentKey() { - screen.showLink("https://loophabits.org/sync/${preferences.syncKey}#${preferences.encryptionKey}") - } - - suspend fun register() { - screen.showLoadingScreen() - try { - val syncKey = server.register() - val encKey = EncryptionKey.generate() - preferences.enableSync(syncKey, encKey.base64) - displayCurrentKey() - } catch (e: Exception) { - logger.error("Unexpected exception") - logger.error(e) - screen.showErrorScreen() - } - } - - interface Screen { - suspend fun showLoadingScreen() - suspend fun showErrorScreen() - suspend fun showLink(link: String) - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/sync/EncryptionExtTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/sync/EncryptionExtTest.kt deleted file mode 100644 index 3a7584f21..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/sync/EncryptionExtTest.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ -package org.isoron.uhabits.core.sync - -import kotlinx.coroutines.runBlocking -import org.hamcrest.CoreMatchers.equalTo -import org.hamcrest.Matchers.greaterThan -import org.junit.Assert.assertEquals -import org.junit.Assert.assertThat -import org.junit.Test -import java.io.File -import java.io.PrintWriter -import java.util.Random - -class EncryptionExtTest { - - @Test - fun test_encode_decode() { - val original = ByteArray(5000) - Random().nextBytes(original) - val encoded = original.encodeBase64() - val decoded = encoded.decodeBase64() - assertThat(decoded, equalTo(original)) - } - - @Test - fun test_encrypt_decrypt_bytes() = runBlocking { - val original = ByteArray(5000) - Random().nextBytes(original) - val key = EncryptionKey.generate() - val encrypted = original.encrypt(key) - val decrypted = encrypted.decrypt(key) - assertThat(decrypted, equalTo(original)) - } - - @Test - fun test_encrypt_decrypt_file() = runBlocking { - val original = File.createTempFile("file", ".txt") - val writer = PrintWriter(original.outputStream()) - writer.println("hello world") - writer.println("encryption test") - writer.close() - assertThat(original.length(), equalTo(28L)) - - val key = EncryptionKey.generate() - val encrypted = original.encryptToString(key) - assertThat(encrypted.length, greaterThan(10)) - - val decrypted = File.createTempFile("file", ".txt") - encrypted.decryptToFile(key, decrypted) - assertThat(decrypted.length(), equalTo(28L)) - assertEquals("hello world\nencryption test\n", decrypted.readText()) - } -}