mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-07 09:38:52 -06:00
Reorganize top level directory
This commit is contained in:
35
uhabits-server/src/org/isoron/uhabits/sync/SyncData.kt
Normal file
35
uhabits-server/src/org/isoron/uhabits/sync/SyncData.kt
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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
|
||||
|
||||
import com.fasterxml.jackson.databind.*
|
||||
|
||||
data class SyncData(
|
||||
val version: Long,
|
||||
val content: String
|
||||
)
|
||||
|
||||
data class RegisterReponse(val key: String)
|
||||
|
||||
data class GetDataVersionResponse(val version: Long)
|
||||
|
||||
val defaultMapper = ObjectMapper()
|
||||
fun SyncData.toJson(): String = defaultMapper.writeValueAsString(this)
|
||||
fun GetDataVersionResponse.toJson(): String = defaultMapper.writeValueAsString(this)
|
||||
28
uhabits-server/src/org/isoron/uhabits/sync/SyncException.kt
Normal file
28
uhabits-server/src/org/isoron/uhabits/sync/SyncException.kt
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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
|
||||
|
||||
open class SyncException: RuntimeException()
|
||||
|
||||
class KeyNotFoundException: SyncException()
|
||||
|
||||
class ServiceUnavailable: SyncException()
|
||||
|
||||
class EditConflictException: SyncException()
|
||||
55
uhabits-server/src/org/isoron/uhabits/sync/app/LinkModule.kt
Normal file
55
uhabits-server/src/org/isoron/uhabits/sync/app/LinkModule.kt
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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.app
|
||||
|
||||
import io.ktor.application.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import org.isoron.uhabits.sync.*
|
||||
|
||||
data class LinkRegisterRequestData(
|
||||
val syncKey: String,
|
||||
)
|
||||
fun LinkRegisterRequestData.toJson(): String = defaultMapper.writeValueAsString(this)
|
||||
|
||||
fun Routing.links(app: SyncApplication) {
|
||||
post("/links") {
|
||||
try {
|
||||
val data = call.receive<LinkRegisterRequestData>()
|
||||
val link = app.server.registerLink(data.syncKey)
|
||||
call.respond(HttpStatusCode.OK, link)
|
||||
} catch (e: ServiceUnavailable) {
|
||||
call.respond(HttpStatusCode.ServiceUnavailable)
|
||||
}
|
||||
}
|
||||
get("/links/{id}") {
|
||||
try {
|
||||
val id = call.parameters["id"]!!
|
||||
val link = app.server.getLink(id)
|
||||
call.respond(HttpStatusCode.OK, link)
|
||||
} catch (e: ServiceUnavailable) {
|
||||
call.respond(HttpStatusCode.ServiceUnavailable)
|
||||
} catch (e: KeyNotFoundException) {
|
||||
call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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.app
|
||||
|
||||
import io.ktor.application.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import io.prometheus.client.*
|
||||
import io.prometheus.client.exporter.common.*
|
||||
import io.prometheus.client.hotspot.*
|
||||
import java.io.*
|
||||
|
||||
|
||||
fun Routing.metrics(app: SyncApplication) {
|
||||
// Register JVM metrics
|
||||
DefaultExports.initialize()
|
||||
|
||||
get("/metrics") {
|
||||
val writer = StringWriter()
|
||||
TextFormat.write004(
|
||||
writer,
|
||||
CollectorRegistry.defaultRegistry.filteredMetricFamilySamples(setOf<String>()),
|
||||
)
|
||||
call.respond(HttpStatusCode.OK, writer.toString())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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.app
|
||||
|
||||
import io.ktor.application.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import org.isoron.uhabits.sync.*
|
||||
|
||||
fun Routing.registration(app: SyncApplication) {
|
||||
post("/register") {
|
||||
try {
|
||||
val key = app.server.register()
|
||||
call.respond(HttpStatusCode.OK, RegisterReponse(key))
|
||||
} catch (e: ServiceUnavailable) {
|
||||
call.respond(HttpStatusCode.ServiceUnavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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.app
|
||||
|
||||
import io.ktor.application.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import org.isoron.uhabits.sync.*
|
||||
|
||||
fun Routing.storage(app: SyncApplication) {
|
||||
route("/db/{key}") {
|
||||
get {
|
||||
val key = call.parameters["key"]!!
|
||||
try {
|
||||
val data = app.server.getData(key)
|
||||
call.respond(HttpStatusCode.OK, data)
|
||||
} catch(e: KeyNotFoundException) {
|
||||
call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
put {
|
||||
val key = call.parameters["key"]!!
|
||||
val data = call.receive<SyncData>()
|
||||
try {
|
||||
app.server.put(key, data)
|
||||
call.respond(HttpStatusCode.OK)
|
||||
} catch (e: KeyNotFoundException) {
|
||||
call.respond(HttpStatusCode.NotFound)
|
||||
} catch (e: EditConflictException) {
|
||||
call.respond(HttpStatusCode.Conflict)
|
||||
}
|
||||
}
|
||||
get("version") {
|
||||
val key = call.parameters["key"]!!
|
||||
try {
|
||||
val version = app.server.getDataVersion(key)
|
||||
call.respond(HttpStatusCode.OK, GetDataVersionResponse(version))
|
||||
} catch(e: KeyNotFoundException) {
|
||||
call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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.app
|
||||
|
||||
import io.ktor.application.*
|
||||
import io.ktor.features.*
|
||||
import io.ktor.jackson.*
|
||||
import io.ktor.routing.*
|
||||
import org.isoron.uhabits.sync.*
|
||||
import org.isoron.uhabits.sync.repository.*
|
||||
import org.isoron.uhabits.sync.server.*
|
||||
import java.nio.file.*
|
||||
|
||||
fun Application.main() = SyncApplication().apply { main() }
|
||||
|
||||
val REPOSITORY_PATH: Path = Paths.get(System.getenv("LOOP_REPO_PATH")!!)
|
||||
|
||||
class SyncApplication(
|
||||
val server: AbstractSyncServer = RepositorySyncServer(
|
||||
FileRepository(REPOSITORY_PATH),
|
||||
),
|
||||
) {
|
||||
fun Application.main() {
|
||||
install(DefaultHeaders)
|
||||
install(CallLogging)
|
||||
install(ContentNegotiation) {
|
||||
jackson { }
|
||||
}
|
||||
routing {
|
||||
registration(this@SyncApplication)
|
||||
storage(this@SyncApplication)
|
||||
links(this@SyncApplication)
|
||||
metrics(this@SyncApplication)
|
||||
}
|
||||
}
|
||||
}
|
||||
37
uhabits-server/src/org/isoron/uhabits/sync/links/Link.kt
Normal file
37
uhabits-server/src/org/isoron/uhabits/sync/links/Link.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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.links
|
||||
|
||||
import org.isoron.uhabits.sync.*
|
||||
import java.time.*
|
||||
|
||||
/**
|
||||
* A Link maps a public URL (such as https://sync.loophabits.org/links/B752A6)
|
||||
* to a synchronization key. They are used to transfer sync keys between devices
|
||||
* without ever exposing the original sync key. Unlike sync keys, links expire
|
||||
* after a few minutes.
|
||||
*/
|
||||
data class Link(
|
||||
val id: String,
|
||||
val syncKey: String,
|
||||
val createdAt: Long,
|
||||
)
|
||||
|
||||
fun Link.toJson(): String = defaultMapper.writeValueAsString(this)
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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.links
|
||||
|
||||
import org.isoron.uhabits.sync.*
|
||||
import org.isoron.uhabits.sync.utils.*
|
||||
|
||||
class LinkManager(
|
||||
private val timeoutInMillis: Long = 900_000,
|
||||
) {
|
||||
private val links = HashMap<String, Link>()
|
||||
|
||||
fun register(syncKey: String): Link {
|
||||
val link = Link(
|
||||
id = randomString(64),
|
||||
syncKey = syncKey,
|
||||
createdAt = System.currentTimeMillis(),
|
||||
)
|
||||
links[link.id] = link
|
||||
return link
|
||||
}
|
||||
|
||||
fun get(id: String): Link {
|
||||
val link = links[id] ?: throw KeyNotFoundException()
|
||||
val ageInMillis = System.currentTimeMillis() - link.createdAt
|
||||
if (ageInMillis > timeoutInMillis) {
|
||||
links.remove(id)
|
||||
throw KeyNotFoundException()
|
||||
}
|
||||
return link
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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.*
|
||||
import java.io.*
|
||||
import java.nio.file.*
|
||||
|
||||
class FileRepository(
|
||||
private val basepath: Path,
|
||||
) : Repository {
|
||||
|
||||
override suspend fun put(key: String, data: SyncData) {
|
||||
// Create directory
|
||||
val dataPath = key.toDataPath()
|
||||
val dataDir = dataPath.toFile()
|
||||
dataDir.mkdirs()
|
||||
|
||||
// Create metadata
|
||||
val metadataFile = dataPath.resolve("version").toFile()
|
||||
metadataFile.outputStream().use { outputStream ->
|
||||
PrintWriter(outputStream).use { printWriter ->
|
||||
printWriter.print(data.version)
|
||||
}
|
||||
}
|
||||
|
||||
// Create data file
|
||||
val dataFile = dataPath.resolve("content").toFile()
|
||||
dataFile.outputStream().use { outputStream ->
|
||||
PrintWriter(outputStream).use { printWriter ->
|
||||
printWriter.print(data.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun get(key: String): SyncData {
|
||||
val dataPath = key.toDataPath()
|
||||
val contentFile = dataPath.resolve("content").toFile()
|
||||
val versionFile = dataPath.resolve("version").toFile()
|
||||
if (!contentFile.exists() || !versionFile.exists()) {
|
||||
throw KeyNotFoundException()
|
||||
}
|
||||
val version = versionFile.readText().trim().toLong()
|
||||
return SyncData(version, contentFile.readText())
|
||||
}
|
||||
|
||||
override suspend fun contains(key: String): Boolean {
|
||||
val dataPath = key.toDataPath()
|
||||
val versionFile = dataPath.resolve("version").toFile()
|
||||
return versionFile.exists()
|
||||
}
|
||||
|
||||
private fun String.toDataPath(): Path {
|
||||
return basepath.resolve("${this[0]}/${this[1]}/${this[2]}/${this[3]}/$this")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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 com.sun.org.apache.xpath.internal.operations.*
|
||||
import org.isoron.uhabits.sync.*
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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.server
|
||||
|
||||
import org.isoron.uhabits.sync.*
|
||||
import org.isoron.uhabits.sync.links.*
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* Registers a new temporary link (mapping to the given sync key) and returns it.
|
||||
*
|
||||
* @throws ServiceUnavailable If the link cannot be generated at this time due to
|
||||
* insufficient server resources.
|
||||
*/
|
||||
suspend fun registerLink(syncKey: String): Link
|
||||
|
||||
/**
|
||||
* Retrieves the syncKey associated with the given link id.
|
||||
*
|
||||
* @throws ServiceUnavailable If the link cannot be resolved at this time due to
|
||||
* insufficient server resources.
|
||||
* @throws KeyNotFoundException If the link id cannot be found, or if it has
|
||||
* expired.
|
||||
*/
|
||||
suspend fun getLink(id: String): Link
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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.server
|
||||
|
||||
import io.prometheus.client.*
|
||||
import org.isoron.uhabits.sync.*
|
||||
import org.isoron.uhabits.sync.links.*
|
||||
import org.isoron.uhabits.sync.repository.*
|
||||
import org.isoron.uhabits.sync.utils.*
|
||||
|
||||
/**
|
||||
* An AbstractSyncServer that stores all data in a [Repository].
|
||||
*/
|
||||
class RepositorySyncServer(
|
||||
private val repo: Repository,
|
||||
private val linkManager: LinkManager = LinkManager(),
|
||||
) : AbstractSyncServer {
|
||||
|
||||
private val requestsCounter: Counter = Counter.build()
|
||||
.name("requests_total")
|
||||
.help("Total number of requests")
|
||||
.labelNames("method")
|
||||
.register()
|
||||
|
||||
override suspend fun register(): String {
|
||||
requestsCounter.labels("register").inc()
|
||||
val key = generateKey()
|
||||
repo.put(key, SyncData(0, ""))
|
||||
return key
|
||||
}
|
||||
|
||||
override suspend fun put(key: String, newData: SyncData) {
|
||||
requestsCounter.labels("put").inc()
|
||||
if (!repo.contains(key)) {
|
||||
throw KeyNotFoundException()
|
||||
}
|
||||
val prevData = repo.get(key)
|
||||
if (newData.version != prevData.version + 1) {
|
||||
throw EditConflictException()
|
||||
}
|
||||
repo.put(key, newData)
|
||||
}
|
||||
|
||||
override suspend fun getData(key: String): SyncData {
|
||||
requestsCounter.labels("getData").inc()
|
||||
if (!repo.contains(key)) {
|
||||
throw KeyNotFoundException()
|
||||
}
|
||||
return repo.get(key)
|
||||
}
|
||||
|
||||
override suspend fun getDataVersion(key: String): Long {
|
||||
requestsCounter.labels("getDataVersion").inc()
|
||||
if (!repo.contains(key)) {
|
||||
throw KeyNotFoundException()
|
||||
}
|
||||
return repo.get(key).version
|
||||
}
|
||||
|
||||
override suspend fun registerLink(syncKey: String): Link {
|
||||
requestsCounter.labels("registerLink").inc()
|
||||
return linkManager.register(syncKey)
|
||||
}
|
||||
|
||||
override suspend fun getLink(id: String): Link {
|
||||
requestsCounter.labels("getLink").inc()
|
||||
return linkManager.get(id)
|
||||
}
|
||||
|
||||
private suspend fun generateKey(): String {
|
||||
while (true) {
|
||||
val key = randomString(64)
|
||||
if (!repo.contains(key))
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
31
uhabits-server/src/org/isoron/uhabits/sync/utils/String.kt
Normal file
31
uhabits-server/src/org/isoron/uhabits/sync/utils/String.kt
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Alinson 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.utils
|
||||
|
||||
import java.util.*
|
||||
import kotlin.streams.*
|
||||
|
||||
fun randomString(length: Long): String {
|
||||
val chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
return Random().ints(length, 0, chars.length)
|
||||
.asSequence()
|
||||
.map(chars::get)
|
||||
.joinToString("")
|
||||
}
|
||||
Reference in New Issue
Block a user