server: Move some classes to uhabits-core; reorganize

feature/sync2
Alinson S. Xavier 4 years ago
parent 232b25bed4
commit 1b07efe291
No known key found for this signature in database
GPG Key ID: DCA0DAD4D2F58624

@ -17,12 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.server package org.isoron.uhabits.core.sync
import org.isoron.uhabits.sync.EditConflictException
import org.isoron.uhabits.sync.KeyNotFoundException
import org.isoron.uhabits.sync.ServiceUnavailable
import org.isoron.uhabits.sync.SyncData
interface AbstractSyncServer { interface AbstractSyncServer {
/** /**

@ -17,9 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync package org.isoron.uhabits.core.sync
import com.fasterxml.jackson.databind.ObjectMapper
data class SyncData( data class SyncData(
val version: Long, val version: Long,
@ -29,7 +27,3 @@ data class SyncData(
data class RegisterReponse(val key: String) data class RegisterReponse(val key: String)
data class GetDataVersionResponse(val version: Long) data class GetDataVersionResponse(val version: Long)
val defaultMapper = ObjectMapper()
fun SyncData.toJson(): String = defaultMapper.writeValueAsString(this)
fun GetDataVersionResponse.toJson(): String = defaultMapper.writeValueAsString(this)

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync package org.isoron.uhabits.core.sync
open class SyncException : RuntimeException() open class SyncException : RuntimeException()

@ -36,6 +36,7 @@ dependencies {
val ktorVersion = "1.6.6" val ktorVersion = "1.6.6"
val kotlinVersion = "1.6.0" val kotlinVersion = "1.6.0"
val logbackVersion = "1.2.7" val logbackVersion = "1.2.7"
implementation(project(":uhabits-core"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
implementation("io.ktor:ktor-server-netty:$ktorVersion") implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("ch.qos.logback:logback-classic:$logbackVersion") implementation("ch.qos.logback:logback-classic:$logbackVersion")

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.app package org.isoron.uhabits.server.app
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode

@ -17,15 +17,15 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.app package org.isoron.uhabits.server.app
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.response.respond import io.ktor.response.respond
import io.ktor.routing.Routing import io.ktor.routing.Routing
import io.ktor.routing.post import io.ktor.routing.post
import org.isoron.uhabits.sync.RegisterReponse import org.isoron.uhabits.core.sync.RegisterReponse
import org.isoron.uhabits.sync.ServiceUnavailable import org.isoron.uhabits.core.sync.ServiceUnavailable
fun Routing.registration(app: SyncApplication) { fun Routing.registration(app: SyncApplication) {
post("/register") { post("/register") {

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.app package org.isoron.uhabits.server.app
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@ -27,10 +27,10 @@ import io.ktor.routing.Routing
import io.ktor.routing.get import io.ktor.routing.get
import io.ktor.routing.put import io.ktor.routing.put
import io.ktor.routing.route import io.ktor.routing.route
import org.isoron.uhabits.sync.EditConflictException import org.isoron.uhabits.core.sync.EditConflictException
import org.isoron.uhabits.sync.GetDataVersionResponse import org.isoron.uhabits.core.sync.GetDataVersionResponse
import org.isoron.uhabits.sync.KeyNotFoundException import org.isoron.uhabits.core.sync.KeyNotFoundException
import org.isoron.uhabits.sync.SyncData import org.isoron.uhabits.core.sync.SyncData
fun Routing.storage(app: SyncApplication) { fun Routing.storage(app: SyncApplication) {
route("/db/{key}") { route("/db/{key}") {

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.app package org.isoron.uhabits.server.app
import io.ktor.application.Application import io.ktor.application.Application
import io.ktor.application.install import io.ktor.application.install
@ -26,9 +26,9 @@ import io.ktor.features.ContentNegotiation
import io.ktor.features.DefaultHeaders import io.ktor.features.DefaultHeaders
import io.ktor.jackson.jackson import io.ktor.jackson.jackson
import io.ktor.routing.routing import io.ktor.routing.routing
import org.isoron.uhabits.sync.repository.FileRepository import org.isoron.uhabits.server.sync.Repository
import org.isoron.uhabits.sync.server.AbstractSyncServer import org.isoron.uhabits.core.sync.AbstractSyncServer
import org.isoron.uhabits.sync.server.RepositorySyncServer import org.isoron.uhabits.server.sync.RepositorySyncServer
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
@ -38,7 +38,7 @@ val REPOSITORY_PATH: Path = Paths.get(System.getenv("LOOP_REPO_PATH")!!)
class SyncApplication( class SyncApplication(
val server: AbstractSyncServer = RepositorySyncServer( val server: AbstractSyncServer = RepositorySyncServer(
FileRepository(REPOSITORY_PATH), Repository(REPOSITORY_PATH),
), ),
) { ) {
fun Application.main() { fun Application.main() {

@ -17,18 +17,17 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.repository package org.isoron.uhabits.server.sync
import org.isoron.uhabits.sync.KeyNotFoundException import org.isoron.uhabits.core.sync.KeyNotFoundException
import org.isoron.uhabits.sync.SyncData import org.isoron.uhabits.core.sync.SyncData
import java.io.PrintWriter import java.io.PrintWriter
import java.nio.file.Path import java.nio.file.Path
class FileRepository( class Repository(
private val basepath: Path, private val basepath: Path,
) : Repository { ) {
fun put(key: String, data: SyncData) {
override suspend fun put(key: String, data: SyncData) {
// Create directory // Create directory
val dataPath = key.toDataPath() val dataPath = key.toDataPath()
val dataDir = dataPath.toFile() val dataDir = dataPath.toFile()
@ -51,7 +50,7 @@ class FileRepository(
} }
} }
override suspend fun get(key: String): SyncData { fun get(key: String): SyncData {
val dataPath = key.toDataPath() val dataPath = key.toDataPath()
val contentFile = dataPath.resolve("content").toFile() val contentFile = dataPath.resolve("content").toFile()
val versionFile = dataPath.resolve("version").toFile() val versionFile = dataPath.resolve("version").toFile()
@ -62,7 +61,7 @@ class FileRepository(
return SyncData(version, contentFile.readText()) return SyncData(version, contentFile.readText())
} }
override suspend fun contains(key: String): Boolean { fun contains(key: String): Boolean {
val dataPath = key.toDataPath() val dataPath = key.toDataPath()
val versionFile = dataPath.resolve("version").toFile() val versionFile = dataPath.resolve("version").toFile()
return versionFile.exists() return versionFile.exists()

@ -17,14 +17,15 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.server package org.isoron.uhabits.server.sync
import io.prometheus.client.Counter import io.prometheus.client.Counter
import org.isoron.uhabits.sync.EditConflictException import org.isoron.uhabits.core.sync.AbstractSyncServer
import org.isoron.uhabits.sync.KeyNotFoundException import org.isoron.uhabits.core.sync.EditConflictException
import org.isoron.uhabits.sync.SyncData import org.isoron.uhabits.core.sync.KeyNotFoundException
import org.isoron.uhabits.sync.repository.Repository import org.isoron.uhabits.core.sync.SyncData
import org.isoron.uhabits.sync.utils.randomString import java.util.Random
import kotlin.streams.asSequence
/** /**
* An AbstractSyncServer that stores all data in a [Repository]. * An AbstractSyncServer that stores all data in a [Repository].
@ -74,11 +75,19 @@ class RepositorySyncServer(
return repo.get(key).version return repo.get(key).version
} }
private suspend fun generateKey(): String { private fun generateKey(): String {
while (true) { while (true) {
val key = randomString(64) val key = randomString(64)
if (!repo.contains(key)) if (!repo.contains(key))
return key return key
} }
} }
private fun randomString(length: Long): String {
val chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
return Random().ints(length, 0, chars.length)
.asSequence()
.map(chars::get)
.joinToString("")
}
} }

@ -17,15 +17,12 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.utils package org.isoron.uhabits.server.sync
import java.util.Random import com.fasterxml.jackson.databind.ObjectMapper
import kotlin.streams.asSequence import org.isoron.uhabits.core.sync.GetDataVersionResponse
import org.isoron.uhabits.core.sync.SyncData
fun randomString(length: Long): String { val defaultMapper = ObjectMapper()
val chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" fun SyncData.toJson(): String = defaultMapper.writeValueAsString(this)
return Random().ints(length, 0, chars.length) fun GetDataVersionResponse.toJson(): String = defaultMapper.writeValueAsString(this)
.asSequence()
.map(chars::get)
.joinToString("")
}

@ -1,45 +0,0 @@
/*
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.sync.repository
import org.isoron.uhabits.sync.KeyNotFoundException
import org.isoron.uhabits.sync.SyncData
/**
* A class that knows how to store and retrieve a large number of [SyncData] items.
*/
interface Repository {
/**
* Stores a data item, under the provided key. The item can be later retrieved with [get].
* Replaces existing items silently.
*/
suspend fun put(key: String, data: SyncData)
/**
* Retrieves a data item that was previously stored using [put].
* @throws KeyNotFoundException If no such key exists.
*/
suspend fun get(key: String): SyncData
/**
* Returns true if the repository contains a given key.
*/
suspend fun contains(key: String): Boolean
}

@ -17,11 +17,11 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.app package org.isoron.uhabits.server.app
import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.mock
import io.ktor.application.Application import io.ktor.application.Application
import org.isoron.uhabits.sync.server.AbstractSyncServer import org.isoron.uhabits.core.sync.AbstractSyncServer
open class BaseApplicationTest { open class BaseApplicationTest {

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.app package org.isoron.uhabits.server.app
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
@ -25,7 +25,7 @@ import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.handleRequest import io.ktor.server.testing.handleRequest
import io.ktor.server.testing.withTestApplication import io.ktor.server.testing.withTestApplication
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.isoron.uhabits.sync.ServiceUnavailable import org.isoron.uhabits.core.sync.ServiceUnavailable
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.app package org.isoron.uhabits.server.app
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
@ -31,11 +31,11 @@ import io.ktor.server.testing.handleRequest
import io.ktor.server.testing.setBody import io.ktor.server.testing.setBody
import io.ktor.server.testing.withTestApplication import io.ktor.server.testing.withTestApplication
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.isoron.uhabits.sync.EditConflictException import org.isoron.uhabits.core.sync.EditConflictException
import org.isoron.uhabits.sync.GetDataVersionResponse import org.isoron.uhabits.core.sync.GetDataVersionResponse
import org.isoron.uhabits.sync.KeyNotFoundException import org.isoron.uhabits.core.sync.KeyNotFoundException
import org.isoron.uhabits.sync.SyncData import org.isoron.uhabits.core.sync.SyncData
import org.isoron.uhabits.sync.toJson import org.isoron.uhabits.server.sync.toJson
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

@ -17,13 +17,12 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.sync.server package org.isoron.uhabits.server.sync
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.isoron.uhabits.sync.EditConflictException import org.isoron.uhabits.core.sync.EditConflictException
import org.isoron.uhabits.sync.KeyNotFoundException import org.isoron.uhabits.core.sync.KeyNotFoundException
import org.isoron.uhabits.sync.SyncData import org.isoron.uhabits.core.sync.SyncData
import org.isoron.uhabits.sync.repository.FileRepository
import org.junit.Test import org.junit.Test
import java.nio.file.Files import java.nio.file.Files
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -32,7 +31,7 @@ import kotlin.test.assertFailsWith
class RepositorySyncServerTest { class RepositorySyncServerTest {
private val tempdir = Files.createTempDirectory("db") private val tempdir = Files.createTempDirectory("db")
private val server = RepositorySyncServer(FileRepository(tempdir)) private val server = RepositorySyncServer(Repository(tempdir))
private val key = runBlocking { server.register() } private val key = runBlocking { server.register() }
@Test @Test

@ -19,23 +19,23 @@
@file:Suppress("BlockingMethodInNonBlockingContext") @file:Suppress("BlockingMethodInNonBlockingContext")
package org.isoron.uhabits.sync.repository package org.isoron.uhabits.server.sync
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.equalTo
import org.isoron.uhabits.sync.SyncData import org.isoron.uhabits.core.sync.SyncData
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertThat import org.junit.Assert.assertThat
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import java.nio.file.Files import java.nio.file.Files
class FileRepositoryTest { class RepositoryTest {
@Test @Test
fun testUsage() = runBlocking { fun testUsage() = runBlocking {
val tempdir = Files.createTempDirectory("db")!! val tempdir = Files.createTempDirectory("db")!!
val repo = FileRepository(tempdir) val repo = Repository(tempdir)
val original = SyncData(10, "Hello world") val original = SyncData(10, "Hello world")
repo.put("abcdefg", original) repo.put("abcdefg", original)
Loading…
Cancel
Save