From 5d3d85fa7190a70eea8fa67866c343005b9922b0 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 21 Jul 2020 10:44:32 -0300 Subject: [pos] compare version from backend and show error if incompatible --- .../net/taler/merchantpos/config/ConfigManager.kt | 13 ++-- merchant-terminal/src/main/res/values/strings.xml | 1 - .../src/main/java/net/taler/common/AndroidUtils.kt | 9 +++ .../src/main/java/net/taler/common/Version.kt | 70 ++++++++++++++++++++++ .../src/main/res/values/strings.xml | 21 +++++++ .../src/test/java/net/taler/common/VersionTest.kt | 65 ++++++++++++++++++++ 6 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 taler-kotlin-common/src/main/java/net/taler/common/Version.kt create mode 100644 taler-kotlin-common/src/main/res/values/strings.xml create mode 100644 taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt 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 eee7905..a7aff18 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 931f31c..b3dcd8d 100644 --- a/merchant-terminal/src/main/res/values/strings.xml +++ b/merchant-terminal/src/main/res/values/strings.xml @@ -22,7 +22,6 @@ Password Fetch configuration Error: Invalid username or password - Error: Incompatible backend version Error: Could not connect to configuration server Error: No valid product category found Error: The configuration JSON is malformed 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 ba6ee1c..b46f306 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 0000000..8774115 --- /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 + */ + +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 0000000..a5b1df1 --- /dev/null +++ b/taler-kotlin-common/src/main/res/values/strings.xml @@ -0,0 +1,21 @@ + + + + Invalid version. Please try again later! + App too old. Please upgrade! + Service outdated. Please wait until it gets upgraded. + 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 0000000..70f30eb --- /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 + */ + +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")) + ) + } + +} -- cgit v1.2.3