summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-07-21 10:44:32 -0300
committerTorsten Grote <t@grobox.de>2020-07-21 15:06:53 -0300
commit5d3d85fa7190a70eea8fa67866c343005b9922b0 (patch)
tree784e53d20b17f63e5def8f0704e7539f338ed637
parentc9fb036798fc533a07b4b75386b51151b31f8be0 (diff)
downloadtaler-android-5d3d85fa7190a70eea8fa67866c343005b9922b0.tar.gz
taler-android-5d3d85fa7190a70eea8fa67866c343005b9922b0.tar.bz2
taler-android-5d3d85fa7190a70eea8fa67866c343005b9922b0.zip
[pos] compare version from backend and show error if incompatible
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt13
-rw-r--r--merchant-terminal/src/main/res/values/strings.xml1
-rw-r--r--taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt9
-rw-r--r--taler-kotlin-common/src/main/java/net/taler/common/Version.kt70
-rw-r--r--taler-kotlin-common/src/main/res/values/strings.xml21
-rw-r--r--taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt65
6 files changed, 172 insertions, 7 deletions
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
index eee79051..a7aff188 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
@@ -34,14 +34,14 @@ import com.fasterxml.jackson.module.kotlin.readValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import net.taler.common.Version
+import net.taler.common.getIncompatibleStringOrNull
import net.taler.merchantlib.ConfigResponse
import net.taler.merchantlib.MerchantApi
import net.taler.merchantpos.LogErrorListener
import net.taler.merchantpos.R
import org.json.JSONObject
-private const val VERSION = "0:0:0"
-
private const val SETTINGS_NAME = "taler-merchant-terminal"
private const val SETTINGS_CONFIG_URL = "configUrl"
@@ -52,6 +52,8 @@ internal const val CONFIG_URL_DEMO = "https://docs.taler.net/_static/sample-pos-
internal const val CONFIG_USERNAME_DEMO = ""
internal const val CONFIG_PASSWORD_DEMO = ""
+private val VERSION = Version(1, 0, 0)
+
private val TAG = ConfigManager::class.java.simpleName
interface ConfigurationReceiver {
@@ -131,10 +133,9 @@ class ConfigManager(
merchantConfig: MerchantConfig,
configResponse: ConfigResponse
) = scope.launch(Dispatchers.Default) {
- // TODO do real matching
- if (VERSION != configResponse.version) {
- val str = context.getString(R.string.config_error_version)
- mConfigUpdateResult.postValue(ConfigUpdateResult.Error(str))
+ val versionIncompatible = VERSION.getIncompatibleStringOrNull(context, configResponse.version)
+ if (versionIncompatible != null) {
+ mConfigUpdateResult.postValue(ConfigUpdateResult.Error(versionIncompatible))
return@launch
}
for (receiver in configurationReceivers) {
diff --git a/merchant-terminal/src/main/res/values/strings.xml b/merchant-terminal/src/main/res/values/strings.xml
index 931f31c7..b3dcd8da 100644
--- a/merchant-terminal/src/main/res/values/strings.xml
+++ b/merchant-terminal/src/main/res/values/strings.xml
@@ -22,7 +22,6 @@
<string name="config_password">Password</string>
<string name="config_ok">Fetch configuration</string>
<string name="config_auth_error">Error: Invalid username or password</string>
- <string name="config_error_version">Error: Incompatible backend version</string>
<string name="config_error_network">Error: Could not connect to configuration server</string>
<string name="config_error_category">Error: No valid product category found</string>
<string name="config_error_malformed">Error: The configuration JSON is malformed</string>
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
index ba6ee1c7..b46f3062 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
+++ b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
@@ -112,3 +112,12 @@ fun Long.toShortDate(context: Context): CharSequence {
val flags = FORMAT_SHOW_DATE or FORMAT_SHOW_YEAR or FORMAT_ABBREV_ALL
return formatDateTime(context, this, flags)
}
+
+fun Version.getIncompatibleStringOrNull(context: Context, otherVersion: String): String? {
+ val other = Version.parse(otherVersion) ?: return context.getString(R.string.version_invalid)
+ val match = compare(other) ?: return context.getString(R.string.version_invalid)
+ if (match.compatible) return null
+ if (match.currentCmp < 0) return context.getString(R.string.version_too_old)
+ if (match.currentCmp > 0) return context.getString(R.string.version_too_new)
+ throw AssertionError("$this == $other")
+}
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Version.kt b/taler-kotlin-common/src/main/java/net/taler/common/Version.kt
new file mode 100644
index 00000000..87741153
--- /dev/null
+++ b/taler-kotlin-common/src/main/java/net/taler/common/Version.kt
@@ -0,0 +1,70 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler 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, or (at your option) any later version.
+ *
+ * GNU Taler 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
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlin.math.sign
+
+/**
+ * Semantic versioning, but libtool-style.
+ * See https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
+ */
+data class Version(
+ val current: Int,
+ val revision: Int,
+ val age: Int
+) {
+ companion object {
+ fun parse(v: String): Version? {
+ val elements = v.split(":")
+ if (elements.size != 3) return null
+ val (currentStr, revisionStr, ageStr) = elements
+ val current = currentStr.toIntOrNull()
+ val revision = revisionStr.toIntOrNull()
+ val age = ageStr.toIntOrNull()
+ if (current == null || revision == null || age == null) return null
+ return Version(current, revision, age)
+ }
+ }
+
+ /**
+ * Compare two libtool-style versions.
+ *
+ * Returns a [VersionMatchResult] or null if the given version was null.
+ */
+ fun compare(other: Version?): VersionMatchResult? {
+ if (other == null) return null
+ val compatible = current - age <= other.current &&
+ current >= other.current - other.age
+ val currentCmp = sign((current - other.current).toDouble()).toInt()
+ return VersionMatchResult(compatible, currentCmp)
+ }
+
+ /**
+ * Result of comparing two libtool versions.
+ */
+ data class VersionMatchResult(
+ /**
+ * Is the first version compatible with the second?
+ */
+ val compatible: Boolean,
+ /**
+ * Is the first version older (-1), newer (+1) or identical (0)?
+ */
+ val currentCmp: Int
+ )
+
+}
diff --git a/taler-kotlin-common/src/main/res/values/strings.xml b/taler-kotlin-common/src/main/res/values/strings.xml
new file mode 100644
index 00000000..a5b1df15
--- /dev/null
+++ b/taler-kotlin-common/src/main/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ This file is part of GNU Taler
+ ~ (C) 2020 Taler Systems S.A.
+ ~
+ ~ GNU Taler 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, or (at your option) any later version.
+ ~
+ ~ GNU Taler 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
+ ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ -->
+
+<resources>
+ <string name="version_invalid">Invalid version. Please try again later!</string>
+ <string name="version_too_old">App too old. Please upgrade!</string>
+ <string name="version_too_new">Service outdated. Please wait until it gets upgraded.</string>
+</resources>
diff --git a/taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt b/taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt
new file mode 100644
index 00000000..70f30eb2
--- /dev/null
+++ b/taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt
@@ -0,0 +1,65 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler 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, or (at your option) any later version.
+ *
+ * GNU Taler 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
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+class VersionTest {
+
+ @Test
+ fun testParse() {
+ assertNull(Version.parse(""))
+ assertNull(Version.parse("foo"))
+ assertNull(Version.parse("foo:bar:foo"))
+ assertNull(Version.parse("0:0:0:"))
+ assertNull(Version.parse("0:0:"))
+ assertEquals(Version(0, 0, 0), Version.parse("0:0:0"))
+ assertEquals(Version(1, 2, 3), Version.parse("1:2:3"))
+ assertEquals(Version(1337, 42, 23), Version.parse("1337:42:23"))
+ }
+
+ @Test
+ fun testComparision() {
+ assertEquals(
+ Version.VersionMatchResult(true, 0),
+ Version.parse("0:0:0")!!.compare(Version.parse("0:0:0"))
+ )
+ assertEquals(
+ Version.VersionMatchResult(true, -1),
+ Version.parse("0:0:0")!!.compare(Version.parse("1:0:1"))
+ )
+ assertEquals(
+ Version.VersionMatchResult(true, -1),
+ Version.parse("0:0:0")!!.compare(Version.parse("1:5:1"))
+ )
+ assertEquals(
+ Version.VersionMatchResult(false, -1),
+ Version.parse("0:0:0")!!.compare(Version.parse("1:5:0"))
+ )
+ assertEquals(
+ Version.VersionMatchResult(false, 1),
+ Version.parse("1:0:0")!!.compare(Version.parse("0:5:0"))
+ )
+ assertEquals(
+ Version.VersionMatchResult(true, 0),
+ Version.parse("1:0:1")!!.compare(Version.parse("1:5:1"))
+ )
+ }
+
+}