gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-android] 03/03: [pos] create merchant-lib and move first v1


From: gnunet
Subject: [taler-taler-android] 03/03: [pos] create merchant-lib and move first v1 API endpoint there
Date: Mon, 20 Jul 2020 21:38:32 +0200

This is an automated email from the git hooks/post-receive script.

torsten-grote pushed a commit to branch master
in repository taler-android.

commit c9fb036798fc533a07b4b75386b51151b31f8be0
Author: Torsten Grote <t@grobox.de>
AuthorDate: Mon Jul 20 16:37:46 2020 -0300

    [pos] create merchant-lib and move first v1 API endpoint there
---
 .gitlab-ci.yml                                     |  1 +
 .idea/gradle.xml                                   |  1 +
 merchant-lib/.gitignore                            |  1 +
 merchant-lib/.gitlab-ci.yml                        | 11 ++++
 merchant-lib/build.gradle                          | 58 ++++++++++++++++++++++
 merchant-lib/consumer-rules.pro                    |  0
 merchant-lib/proguard-rules.pro                    | 21 ++++++++
 merchant-lib/src/main/AndroidManifest.xml          | 23 +++++++++
 .../java/net/taler/merchantlib/ConfigResponse.kt   | 34 +++++++++++++
 .../main/java/net/taler/merchantlib/MerchantApi.kt | 43 ++++++++++++++++
 .../java/net/taler/merchantlib/MerchantApiTest.kt  | 43 ++++++++++++++++
 .../java/net/taler/merchantlib/MockHttpClient.kt   | 58 ++++++++++++++++++++++
 merchant-terminal/build.gradle                     |  8 ++-
 .../java/net/taler/merchantpos/MainViewModel.kt    |  4 +-
 .../net/taler/merchantpos/config/ConfigManager.kt  | 31 +++++++-----
 merchant-terminal/src/main/res/values/strings.xml  |  1 +
 settings.gradle                                    |  1 +
 17 files changed, 325 insertions(+), 14 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index da3611e..48f1aec 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -11,6 +11,7 @@ stages:
 
 include:
   - local: 'cashier/.gitlab-ci.yml'
+  - local: 'merchant-lib/.gitlab-ci.yml'
   - local: 'merchant-terminal/.gitlab-ci.yml'
   - local: 'taler-kotlin-common/.gitlab-ci.yml'
   - local: 'wallet/.gitlab-ci.yml'
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 697ff36..581abbf 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -12,6 +12,7 @@
             <option value="$PROJECT_DIR$" />
             <option value="$PROJECT_DIR$/anastasis-ui" />
             <option value="$PROJECT_DIR$/cashier" />
+            <option value="$PROJECT_DIR$/merchant-lib" />
             <option value="$PROJECT_DIR$/merchant-terminal" />
             <option value="$PROJECT_DIR$/taler-kotlin-common" />
             <option value="$PROJECT_DIR$/wallet" />
diff --git a/merchant-lib/.gitignore b/merchant-lib/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/merchant-lib/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/merchant-lib/.gitlab-ci.yml b/merchant-lib/.gitlab-ci.yml
new file mode 100644
index 0000000..62a7516
--- /dev/null
+++ b/merchant-lib/.gitlab-ci.yml
@@ -0,0 +1,11 @@
+merchant_lib_test:
+  stage: test
+  only:
+    changes:
+      - merchant-lib/**/*
+      - build.gradle
+  script: ./gradlew :merchant-lib:check
+  artifacts:
+    paths:
+      - merchant-lib/build/reports/lint-results.html
+    expire_in: 1 week
diff --git a/merchant-lib/build.gradle b/merchant-lib/build.gradle
new file mode 100644
index 0000000..08da35d
--- /dev/null
+++ b/merchant-lib/build.gradle
@@ -0,0 +1,58 @@
+/*
+ * 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/>
+ */
+
+plugins {
+    id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+    compileSdkVersion 29
+    //noinspection GradleDependency
+    buildToolsVersion "$build_tools_version"
+
+    defaultConfig {
+        minSdkVersion 21
+        targetSdkVersion 29
+        versionCode 1
+        versionName "0.1"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles 
getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+
+    def ktor_version = "1.3.2"
+    implementation "io.ktor:ktor-client:$ktor_version"
+    implementation "io.ktor:ktor-client-okhttp:$ktor_version"
+    implementation "io.ktor:ktor-client-serialization-jvm:$ktor_version"
+
+    testImplementation 'junit:junit:4.13'
+    testApi "io.ktor:ktor-client-mock-jvm:$ktor_version"
+}
diff --git a/merchant-lib/consumer-rules.pro b/merchant-lib/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/merchant-lib/proguard-rules.pro b/merchant-lib/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/merchant-lib/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/merchant-lib/src/main/AndroidManifest.xml 
b/merchant-lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7318c07
--- /dev/null
+++ b/merchant-lib/src/main/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<!--
+  ~ 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/>
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android";
+    package="net.taler.merchantlib">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+
+</manifest>
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/ConfigResponse.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/ConfigResponse.kt
new file mode 100644
index 0000000..49164e6
--- /dev/null
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/ConfigResponse.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.merchantlib
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ConfigResponse(
+    /**
+     * libtool-style representation of the Merchant protocol version, see
+     * 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+     * The format is "current:revision:age".
+     */
+    val version: String,
+
+    /**
+    Currency supported by this backend.
+     */
+    val currency: String
+)
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
new file mode 100644
index 0000000..656b093
--- /dev/null
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.merchantlib
+
+import io.ktor.client.HttpClient
+import io.ktor.client.engine.okhttp.OkHttp
+import io.ktor.client.features.json.JsonFeature
+import io.ktor.client.features.json.serializer.KotlinxSerializer
+import io.ktor.client.request.get
+import io.ktor.client.request.header
+import io.ktor.http.HttpHeaders.Authorization
+
+class MerchantApi(private val httpClient: HttpClient) {
+
+    constructor() : this(getDefaultHttpClient())
+
+    suspend fun getConfig(baseUrl: String, apiKey: String = "sandbox"): 
ConfigResponse {
+        return httpClient.get("$baseUrl/config") {
+            header(Authorization, "ApiKey $apiKey")
+        }
+    }
+
+}
+
+private fun getDefaultHttpClient(): HttpClient = HttpClient(OkHttp) {
+    install(JsonFeature) {
+        serializer = KotlinxSerializer()
+    }
+}
diff --git 
a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt 
b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
new file mode 100644
index 0000000..6b2199b
--- /dev/null
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.merchantlib
+
+import kotlinx.coroutines.runBlocking
+import net.taler.merchantlib.MockHttpClient.giveJsonResponse
+import net.taler.merchantlib.MockHttpClient.httpClient
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class MerchantApiTest {
+
+    private val api = MerchantApi(httpClient)
+
+    @Test
+    fun testGetConfig() = runBlocking {
+        httpClient.giveJsonResponse("https://backend.int.taler.net/config";) {
+            """
+            {
+              "currency": "INTKUDOS",
+              "version": "0:0:0"
+            }
+            """.trimIndent()
+        }
+        val response = api.getConfig("https://backend.int.taler.net";)
+        assertEquals(ConfigResponse("0:0:0", "INTKUDOS"), response)
+    }
+
+}
diff --git a/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt 
b/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt
new file mode 100644
index 0000000..076b77e
--- /dev/null
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.merchantlib
+
+import io.ktor.client.HttpClient
+import io.ktor.client.engine.mock.MockEngine
+import io.ktor.client.engine.mock.MockEngineConfig
+import io.ktor.client.engine.mock.respond
+import io.ktor.client.features.json.JsonFeature
+import io.ktor.client.features.json.serializer.KotlinxSerializer
+import io.ktor.http.ContentType.Application.Json
+import io.ktor.http.Url
+import io.ktor.http.fullPath
+import io.ktor.http.headersOf
+import io.ktor.http.hostWithPort
+
+object MockHttpClient {
+
+    val httpClient = HttpClient(MockEngine) {
+        install(JsonFeature) {
+            serializer = KotlinxSerializer()
+        }
+        engine {
+            addHandler { error("No response handler set") }
+        }
+    }
+
+    fun HttpClient.giveJsonResponse(url: String, jsonProducer: () -> String) {
+        val httpConfig = engineConfig as MockEngineConfig
+        httpConfig.requestHandlers.removeAt(0)
+        httpConfig.requestHandlers.add { request ->
+            if (request.url.fullUrl == url) {
+                val headers = headersOf("Content-Type" to 
listOf(Json.toString()))
+                respond(jsonProducer(), headers = headers)
+            } else {
+                error("Unexpected URL: ${request.url.fullUrl}")
+            }
+        }
+    }
+
+    private val Url.hostWithPortIfRequired: String get() = if (port == 
protocol.defaultPort) host else hostWithPort
+    private val Url.fullUrl: String get() = 
"${protocol.name}://$hostWithPortIfRequired$fullPath"
+
+}
diff --git a/merchant-terminal/build.gradle b/merchant-terminal/build.gradle
index 8a7eac7..2ba1a66 100644
--- a/merchant-terminal/build.gradle
+++ b/merchant-terminal/build.gradle
@@ -46,10 +46,16 @@ android {
         //      
https://github.com/material-components/material-components-android/issues/504
         ignore "WrongConstant"
     }
+
+    packagingOptions {
+        exclude 'META-INF/common.kotlin_module'
+        exclude 'META-INF/*.kotlin_module'
+    }
 }
 
 dependencies {
     implementation project(":taler-kotlin-common")
+    implementation project(":merchant-lib")
 
     implementation 'com.google.android.material:material:1.1.0'
     implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
@@ -63,7 +69,7 @@ dependencies {
     // HTTP Requests
     implementation 'com.android.volley:volley:1.1.1'
 
-    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
+    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
 
     // JSON parsing and serialization
     implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2"
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt
index 3fe472d..2dd2c24 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainViewModel.kt
@@ -23,6 +23,7 @@ import com.android.volley.toolbox.Volley
 import 
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.KotlinModule
+import net.taler.merchantlib.MerchantApi
 import net.taler.merchantpos.config.ConfigManager
 import net.taler.merchantpos.history.HistoryManager
 import net.taler.merchantpos.history.RefundManager
@@ -31,13 +32,14 @@ import net.taler.merchantpos.payment.PaymentManager
 
 class MainViewModel(app: Application) : AndroidViewModel(app) {
 
+    private val api = MerchantApi()
     private val mapper = ObjectMapper()
         .registerModule(KotlinModule())
         .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
     private val queue = Volley.newRequestQueue(app)
 
     val orderManager = OrderManager(app, mapper)
-    val configManager = ConfigManager(app, viewModelScope, mapper, 
queue).apply {
+    val configManager = ConfigManager(app, viewModelScope, api, mapper, 
queue).apply {
         addConfigurationReceiver(orderManager)
     }
     val paymentManager = PaymentManager(configManager, queue, mapper)
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 171cf28..eee7905 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,10 +34,14 @@ import com.fasterxml.jackson.module.kotlin.readValue
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+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"
@@ -60,6 +64,7 @@ interface ConfigurationReceiver {
 class ConfigManager(
     private val context: Context,
     private val scope: CoroutineScope,
+    private val api: MerchantApi,
     private val mapper: ObjectMapper,
     private val queue: RequestQueue
 ) {
@@ -114,25 +119,27 @@ class ConfigManager(
             return
         }
 
-        val params = mapOf("instance" to merchantConfig.instance)
-        val req = MerchantRequest(GET, merchantConfig, "config", params, null,
-            Listener { onMerchantConfigReceived(config, json, merchantConfig, 
it) },
-            LogErrorListener { onNetworkError(it) }
-        )
-        queue.add(req)
+        scope.launch(Dispatchers.IO) {
+            val configResponse = api.getConfig(merchantConfig.baseUrl, 
merchantConfig.apiKey)
+            onMerchantConfigReceived(config, json, merchantConfig, 
configResponse)
+        }
     }
 
     private fun onMerchantConfigReceived(
         newConfig: Config?,
         configJson: JSONObject,
         merchantConfig: MerchantConfig,
-        json: JSONObject
+        configResponse: ConfigResponse
     ) = scope.launch(Dispatchers.Default) {
-        val currency = json.getString("currency")
-
+        // TODO do real matching
+        if (VERSION != configResponse.version) {
+            val str = context.getString(R.string.config_error_version)
+            mConfigUpdateResult.postValue(ConfigUpdateResult.Error(str))
+            return@launch
+        }
         for (receiver in configurationReceivers) {
             val result = try {
-                receiver.onConfigurationReceived(configJson, currency)
+                receiver.onConfigurationReceived(configJson, 
configResponse.currency)
             } catch (e: Exception) {
                 Log.e(TAG, "Error handling configuration by 
${receiver::class.java.simpleName}", e)
                 context.getString(R.string.config_error_unknown)
@@ -146,8 +153,8 @@ class ConfigManager(
             config = it
             saveConfig(it)
         }
-        this@ConfigManager.merchantConfig = merchantConfig.copy(currency = 
currency)
-        mConfigUpdateResult.postValue(ConfigUpdateResult.Success(currency))
+        this@ConfigManager.merchantConfig = merchantConfig.copy(currency = 
configResponse.currency)
+        
mConfigUpdateResult.postValue(ConfigUpdateResult.Success(configResponse.currency))
     }
 
     fun forgetPassword() {
diff --git a/merchant-terminal/src/main/res/values/strings.xml 
b/merchant-terminal/src/main/res/values/strings.xml
index b3dcd8d..931f31c 100644
--- a/merchant-terminal/src/main/res/values/strings.xml
+++ b/merchant-terminal/src/main/res/values/strings.xml
@@ -22,6 +22,7 @@
     <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/settings.gradle b/settings.gradle
index b45a276..14d898d 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,4 @@
 include ':cashier', ':merchant-terminal', ':wallet'
 include ':taler-kotlin-common'
+include ':merchant-lib'
 include ':anastasis-ui'

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]