add server-side config --snapshot-{days,versions}

This commit is contained in:
Dustin J. Mitchell 2021-10-06 04:01:22 +00:00
parent 74aee49107
commit 4d19ca7bdb
14 changed files with 305 additions and 91 deletions

View file

@ -6,3 +6,6 @@
Run `taskchampion-sync-server` to start the sync server. Run `taskchampion-sync-server` to start the sync server.
Use `--port` to specify the port it should listen on, and `--data-dir` to specify the directory which it should store its data. Use `--port` to specify the port it should listen on, and `--data-dir` to specify the directory which it should store its data.
It only serves HTTP; the expectation is that a frontend proxy will be used for HTTPS support. It only serves HTTP; the expectation is that a frontend proxy will be used for HTTPS support.
The server has optional parameters `--snapshot-days` and `--snapshot-version`, giving the target number of days and versions, respectively, between snapshots of the client state.
The default values for these parameters are generally adequate.

View file

@ -5,7 +5,7 @@ use taskchampion_sync_server::{storage::InMemoryStorage, Server};
#[actix_rt::test] #[actix_rt::test]
async fn cross_sync() -> anyhow::Result<()> { async fn cross_sync() -> anyhow::Result<()> {
let server = Server::new(Box::new(InMemoryStorage::new())); let server = Server::new(Default::default(), Box::new(InMemoryStorage::new()));
let httpserver = let httpserver =
HttpServer::new(move || App::new().configure(|sc| server.config(sc))).bind("0.0.0.0:0")?; HttpServer::new(move || App::new().configure(|sc| server.config(sc))).bind("0.0.0.0:0")?;

View file

@ -2,6 +2,7 @@ use crate::api::{client_key_header, failure_to_ise, ServerState, SNAPSHOT_CONTEN
use crate::server::{add_snapshot, VersionId, NIL_VERSION_ID}; use crate::server::{add_snapshot, VersionId, NIL_VERSION_ID};
use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result};
use futures::StreamExt; use futures::StreamExt;
use std::sync::Arc;
/// Max snapshot size: 100MB /// Max snapshot size: 100MB
const MAX_SIZE: usize = 100 * 1024 * 1024; const MAX_SIZE: usize = 100 * 1024 * 1024;
@ -17,7 +18,7 @@ const MAX_SIZE: usize = 100 * 1024 * 1024;
#[post("/v1/client/add-snapshot/{version_id}")] #[post("/v1/client/add-snapshot/{version_id}")]
pub(crate) async fn service( pub(crate) async fn service(
req: HttpRequest, req: HttpRequest,
server_state: web::Data<ServerState>, server_state: web::Data<Arc<ServerState>>,
web::Path((version_id,)): web::Path<(VersionId,)>, web::Path((version_id,)): web::Path<(VersionId,)>,
mut payload: web::Payload, mut payload: web::Payload,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
@ -46,7 +47,7 @@ pub(crate) async fn service(
// note that we do not open the transaction until the body has been read // note that we do not open the transaction until the body has been read
// completely, to avoid blocking other storage access while that data is // completely, to avoid blocking other storage access while that data is
// in transit. // in transit.
let mut txn = server_state.txn().map_err(failure_to_ise)?; let mut txn = server_state.storage.txn().map_err(failure_to_ise)?;
// get, or create, the client // get, or create, the client
let client = match txn.get_client(client_key).map_err(failure_to_ise)? { let client = match txn.get_client(client_key).map_err(failure_to_ise)? {
@ -58,7 +59,15 @@ pub(crate) async fn service(
} }
}; };
add_snapshot(txn, client_key, client, version_id, body.to_vec()).map_err(failure_to_ise)?; add_snapshot(
txn,
&server_state.config,
client_key,
client,
version_id,
body.to_vec(),
)
.map_err(failure_to_ise)?;
Ok(HttpResponse::Ok().body("")) Ok(HttpResponse::Ok().body(""))
} }
@ -84,7 +93,7 @@ mod test {
txn.add_version(client_key, version_id, NIL_VERSION_ID, vec![])?; txn.add_version(client_key, version_id, NIL_VERSION_ID, vec![])?;
} }
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;
@ -126,7 +135,7 @@ mod test {
txn.new_client(client_key, NIL_VERSION_ID).unwrap(); txn.new_client(client_key, NIL_VERSION_ID).unwrap();
} }
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;
@ -156,7 +165,7 @@ mod test {
let client_key = Uuid::new_v4(); let client_key = Uuid::new_v4();
let version_id = Uuid::new_v4(); let version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;
@ -176,7 +185,7 @@ mod test {
let client_key = Uuid::new_v4(); let client_key = Uuid::new_v4();
let version_id = Uuid::new_v4(); let version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;

View file

@ -5,6 +5,7 @@ use crate::api::{
use crate::server::{add_version, AddVersionResult, SnapshotUrgency, VersionId, NIL_VERSION_ID}; use crate::server::{add_version, AddVersionResult, SnapshotUrgency, VersionId, NIL_VERSION_ID};
use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result};
use futures::StreamExt; use futures::StreamExt;
use std::sync::Arc;
/// Max history segment size: 100MB /// Max history segment size: 100MB
const MAX_SIZE: usize = 100 * 1024 * 1024; const MAX_SIZE: usize = 100 * 1024 * 1024;
@ -25,7 +26,7 @@ const MAX_SIZE: usize = 100 * 1024 * 1024;
#[post("/v1/client/add-version/{parent_version_id}")] #[post("/v1/client/add-version/{parent_version_id}")]
pub(crate) async fn service( pub(crate) async fn service(
req: HttpRequest, req: HttpRequest,
server_state: web::Data<ServerState>, server_state: web::Data<Arc<ServerState>>,
web::Path((parent_version_id,)): web::Path<(VersionId,)>, web::Path((parent_version_id,)): web::Path<(VersionId,)>,
mut payload: web::Payload, mut payload: web::Payload,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
@ -54,7 +55,7 @@ pub(crate) async fn service(
// note that we do not open the transaction until the body has been read // note that we do not open the transaction until the body has been read
// completely, to avoid blocking other storage access while that data is // completely, to avoid blocking other storage access while that data is
// in transit. // in transit.
let mut txn = server_state.txn().map_err(failure_to_ise)?; let mut txn = server_state.storage.txn().map_err(failure_to_ise)?;
// get, or create, the client // get, or create, the client
let client = match txn.get_client(client_key).map_err(failure_to_ise)? { let client = match txn.get_client(client_key).map_err(failure_to_ise)? {
@ -66,9 +67,15 @@ pub(crate) async fn service(
} }
}; };
let (result, snap_urgency) = let (result, snap_urgency) = add_version(
add_version(txn, client_key, client, parent_version_id, body.to_vec()) txn,
.map_err(failure_to_ise)?; &server_state.config,
client_key,
client,
parent_version_id,
body.to_vec(),
)
.map_err(failure_to_ise)?;
Ok(match result { Ok(match result {
AddVersionResult::Ok(version_id) => { AddVersionResult::Ok(version_id) => {
@ -114,7 +121,7 @@ mod test {
txn.new_client(client_key, Uuid::nil()).unwrap(); txn.new_client(client_key, Uuid::nil()).unwrap();
} }
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;
@ -156,7 +163,7 @@ mod test {
txn.new_client(client_key, version_id).unwrap(); txn.new_client(client_key, version_id).unwrap();
} }
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;
@ -184,7 +191,7 @@ mod test {
let client_key = Uuid::new_v4(); let client_key = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;
@ -204,7 +211,7 @@ mod test {
let client_key = Uuid::new_v4(); let client_key = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;

View file

@ -4,6 +4,7 @@ use crate::api::{
}; };
use crate::server::{get_child_version, GetVersionResult, VersionId}; use crate::server::{get_child_version, GetVersionResult, VersionId};
use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
use std::sync::Arc;
/// Get a child version. /// Get a child version.
/// ///
@ -16,10 +17,10 @@ use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
#[get("/v1/client/get-child-version/{parent_version_id}")] #[get("/v1/client/get-child-version/{parent_version_id}")]
pub(crate) async fn service( pub(crate) async fn service(
req: HttpRequest, req: HttpRequest,
server_state: web::Data<ServerState>, server_state: web::Data<Arc<ServerState>>,
web::Path((parent_version_id,)): web::Path<(VersionId,)>, web::Path((parent_version_id,)): web::Path<(VersionId,)>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let mut txn = server_state.txn().map_err(failure_to_ise)?; let mut txn = server_state.storage.txn().map_err(failure_to_ise)?;
let client_key = client_key_header(&req)?; let client_key = client_key_header(&req)?;
@ -28,8 +29,14 @@ pub(crate) async fn service(
.map_err(failure_to_ise)? .map_err(failure_to_ise)?
.ok_or_else(|| error::ErrorNotFound("no such client"))?; .ok_or_else(|| error::ErrorNotFound("no such client"))?;
return match get_child_version(txn, client_key, client, parent_version_id) return match get_child_version(
.map_err(failure_to_ise)? txn,
&server_state.config,
client_key,
client,
parent_version_id,
)
.map_err(failure_to_ise)?
{ {
GetVersionResult::Success { GetVersionResult::Success {
version_id, version_id,
@ -69,7 +76,7 @@ mod test {
.unwrap(); .unwrap();
} }
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;
@ -103,7 +110,7 @@ mod test {
let client_key = Uuid::new_v4(); let client_key = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;
@ -129,7 +136,7 @@ mod test {
let mut txn = storage.txn().unwrap(); let mut txn = storage.txn().unwrap();
txn.new_client(client_key, Uuid::new_v4()).unwrap(); txn.new_client(client_key, Uuid::new_v4()).unwrap();
} }
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;

View file

@ -3,6 +3,7 @@ use crate::api::{
}; };
use crate::server::get_snapshot; use crate::server::get_snapshot;
use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
use std::sync::Arc;
/// Get a snapshot. /// Get a snapshot.
/// ///
@ -15,9 +16,9 @@ use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
#[get("/v1/client/snapshot")] #[get("/v1/client/snapshot")]
pub(crate) async fn service( pub(crate) async fn service(
req: HttpRequest, req: HttpRequest,
server_state: web::Data<ServerState>, server_state: web::Data<Arc<ServerState>>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let mut txn = server_state.txn().map_err(failure_to_ise)?; let mut txn = server_state.storage.txn().map_err(failure_to_ise)?;
let client_key = client_key_header(&req)?; let client_key = client_key_header(&req)?;
@ -27,7 +28,7 @@ pub(crate) async fn service(
.ok_or_else(|| error::ErrorNotFound("no such client"))?; .ok_or_else(|| error::ErrorNotFound("no such client"))?;
if let Some((version_id, data)) = if let Some((version_id, data)) =
get_snapshot(txn, client_key, client).map_err(failure_to_ise)? get_snapshot(txn, &server_state.config, client_key, client).map_err(failure_to_ise)?
{ {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type(SNAPSHOT_CONTENT_TYPE) .content_type(SNAPSHOT_CONTENT_TYPE)
@ -58,7 +59,7 @@ mod test {
txn.new_client(client_key, Uuid::new_v4()).unwrap(); txn.new_client(client_key, Uuid::new_v4()).unwrap();
} }
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;
@ -94,7 +95,7 @@ mod test {
.unwrap(); .unwrap();
} }
let server = Server::new(storage); let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;

View file

@ -1,7 +1,7 @@
use crate::server::ClientKey; use crate::server::ClientKey;
use crate::storage::Storage; use crate::storage::Storage;
use crate::ServerConfig;
use actix_web::{error, http::StatusCode, web, HttpRequest, Result, Scope}; use actix_web::{error, http::StatusCode, web, HttpRequest, Result, Scope};
use std::sync::Arc;
mod add_snapshot; mod add_snapshot;
mod add_version; mod add_version;
@ -27,8 +27,11 @@ pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id";
/// The header name for parent version ID /// The header name for parent version ID
pub(crate) const SNAPSHOT_REQUEST_HEADER: &str = "X-Snapshot-Request"; pub(crate) const SNAPSHOT_REQUEST_HEADER: &str = "X-Snapshot-Request";
/// The type containing a reference to the Storage object in the Actix state. /// The type containing a reference to the persistent state for the server
pub(crate) type ServerState = Arc<dyn Storage>; pub(crate) struct ServerState {
pub(crate) storage: Box<dyn Storage>,
pub(crate) config: ServerConfig,
}
pub(crate) fn api_scope() -> Scope { pub(crate) fn api_scope() -> Scope {
web::scope("") web::scope("")

View file

@ -3,7 +3,7 @@
use actix_web::{middleware::Logger, App, HttpServer}; use actix_web::{middleware::Logger, App, HttpServer};
use clap::Arg; use clap::Arg;
use taskchampion_sync_server::storage::SqliteStorage; use taskchampion_sync_server::storage::SqliteStorage;
use taskchampion_sync_server::Server; use taskchampion_sync_server::{Server, ServerConfig};
#[actix_web::main] #[actix_web::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
@ -31,12 +31,33 @@ async fn main() -> anyhow::Result<()> {
.takes_value(true) .takes_value(true)
.required(true), .required(true),
) )
.arg(
Arg::with_name("snapshot-versions")
.long("snapshot-versions")
.value_name("NUM")
.help("Target number of versions between snapshots")
.default_value("100")
.takes_value(true)
.required(false),
)
.arg(
Arg::with_name("snapshot-days")
.long("snapshot-days")
.value_name("NUM")
.help("Target number of days between snapshots")
.default_value("14")
.takes_value(true)
.required(false),
)
.get_matches(); .get_matches();
let data_dir = matches.value_of("data-dir").unwrap(); let data_dir = matches.value_of("data-dir").unwrap();
let port = matches.value_of("port").unwrap(); let port = matches.value_of("port").unwrap();
let snapshot_versions = matches.value_of("snapshot-versions").unwrap();
let snapshot_days = matches.value_of("snapshot-versions").unwrap();
let server = Server::new(Box::new(SqliteStorage::new(data_dir)?)); let config = ServerConfig::from_args(snapshot_days, snapshot_versions)?;
let server = Server::new(config, Box::new(SqliteStorage::new(data_dir)?));
log::warn!("Serving on port {}", port); log::warn!("Serving on port {}", port);
HttpServer::new(move || { HttpServer::new(move || {
@ -58,7 +79,7 @@ mod test {
#[actix_rt::test] #[actix_rt::test]
async fn test_index_get() { async fn test_index_get() {
let server = Server::new(Box::new(InMemoryStorage::new())); let server = Server::new(Default::default(), Box::new(InMemoryStorage::new()));
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;

View file

@ -6,7 +6,9 @@ pub mod storage;
use crate::storage::Storage; use crate::storage::Storage;
use actix_web::{get, middleware, web, Responder}; use actix_web::{get, middleware, web, Responder};
use anyhow::Context;
use api::{api_scope, ServerState}; use api::{api_scope, ServerState};
use std::sync::Arc;
#[get("/")] #[get("/")]
async fn index() -> impl Responder { async fn index() -> impl Responder {
@ -16,14 +18,44 @@ async fn index() -> impl Responder {
/// A Server represents a sync server. /// A Server represents a sync server.
#[derive(Clone)] #[derive(Clone)]
pub struct Server { pub struct Server {
storage: ServerState, server_state: Arc<ServerState>,
}
/// ServerConfig contains configuration parameters for the server.
pub struct ServerConfig {
/// Target number of days between snapshots.
pub snapshot_days: i64,
/// Target number of versions between snapshots.
pub snapshot_versions: u32,
}
impl Default for ServerConfig {
fn default() -> Self {
ServerConfig {
snapshot_days: 14,
snapshot_versions: 100,
}
}
}
impl ServerConfig {
pub fn from_args(snapshot_days: &str, snapshot_versions: &str) -> anyhow::Result<ServerConfig> {
Ok(ServerConfig {
snapshot_days: snapshot_days
.parse()
.context("--snapshot-days must be a number")?,
snapshot_versions: snapshot_versions
.parse()
.context("--snapshot-days must be a number")?,
})
}
} }
impl Server { impl Server {
/// Create a new sync server with the given storage implementation. /// Create a new sync server with the given storage implementation.
pub fn new(storage: Box<dyn Storage>) -> Self { pub fn new(config: ServerConfig, storage: Box<dyn Storage>) -> Self {
Self { Self {
storage: storage.into(), server_state: Arc::new(ServerState { config, storage }),
} }
} }
@ -31,7 +63,7 @@ impl Server {
pub fn config(&self, cfg: &mut web::ServiceConfig) { pub fn config(&self, cfg: &mut web::ServiceConfig) {
cfg.service( cfg.service(
web::scope("") web::scope("")
.data(self.storage.clone()) .data(self.server_state.clone())
.wrap( .wrap(
middleware::DefaultHeaders::new() middleware::DefaultHeaders::new()
.header("Cache-Control", "no-store, max-age=0"), .header("Cache-Control", "no-store, max-age=0"),
@ -55,7 +87,7 @@ mod test {
#[actix_rt::test] #[actix_rt::test]
async fn test_cache_control() { async fn test_cache_control() {
let server = Server::new(Box::new(InMemoryStorage::new())); let server = Server::new(Default::default(), Box::new(InMemoryStorage::new()));
let app = App::new().configure(|sc| server.config(sc)); let app = App::new().configure(|sc| server.config(sc));
let mut app = test::init_service(app).await; let mut app = test::init_service(app).await;

View file

@ -2,6 +2,7 @@
//! invariants, and so on. This does not implement the HTTP-specific portions; those //! invariants, and so on. This does not implement the HTTP-specific portions; those
//! are in [`crate::api`]. See the protocol documentation for details. //! are in [`crate::api`]. See the protocol documentation for details.
use crate::storage::{Client, Snapshot, StorageTxn}; use crate::storage::{Client, Snapshot, StorageTxn};
use crate::ServerConfig; // TODO: move here
use chrono::Utc; use chrono::Utc;
use uuid::Uuid; use uuid::Uuid;
@ -13,12 +14,6 @@ pub const NIL_VERSION_ID: VersionId = Uuid::nil();
/// than this will be rejected. /// than this will be rejected.
const SNAPSHOT_SEARCH_LEN: i32 = 5; const SNAPSHOT_SEARCH_LEN: i32 = 5;
/// Maximum number of days between snapshots
const SNAPSHOT_DAYS: i64 = 14;
/// Maximum number of versions between snapshots
const SNAPSHOT_VERSIONS: u32 = 30;
pub(crate) type HistorySegment = Vec<u8>; pub(crate) type HistorySegment = Vec<u8>;
pub(crate) type ClientKey = Uuid; pub(crate) type ClientKey = Uuid;
pub(crate) type VersionId = Uuid; pub(crate) type VersionId = Uuid;
@ -38,6 +33,7 @@ pub(crate) enum GetVersionResult {
/// Implementation of the GetChildVersion protocol transaction /// Implementation of the GetChildVersion protocol transaction
pub(crate) fn get_child_version<'a>( pub(crate) fn get_child_version<'a>(
mut txn: Box<dyn StorageTxn + 'a>, mut txn: Box<dyn StorageTxn + 'a>,
_config: &ServerConfig,
client_key: ClientKey, client_key: ClientKey,
client: Client, client: Client,
parent_version_id: VersionId, parent_version_id: VersionId,
@ -96,10 +92,10 @@ pub(crate) enum SnapshotUrgency {
impl SnapshotUrgency { impl SnapshotUrgency {
/// Calculate the urgency for a snapshot based on its age in days /// Calculate the urgency for a snapshot based on its age in days
fn for_days(days: i64) -> Self { fn for_days(config: &ServerConfig, days: i64) -> Self {
if days >= SNAPSHOT_DAYS * 3 / 2 { if days >= config.snapshot_days * 3 / 2 {
SnapshotUrgency::High SnapshotUrgency::High
} else if days >= SNAPSHOT_DAYS { } else if days >= config.snapshot_days {
SnapshotUrgency::Low SnapshotUrgency::Low
} else { } else {
SnapshotUrgency::None SnapshotUrgency::None
@ -107,10 +103,10 @@ impl SnapshotUrgency {
} }
/// Calculate the urgency for a snapshot based on its age in versions /// Calculate the urgency for a snapshot based on its age in versions
fn for_versions_since(versions_since: u32) -> Self { fn for_versions_since(config: &ServerConfig, versions_since: u32) -> Self {
if versions_since >= SNAPSHOT_VERSIONS * 3 / 2 { if versions_since >= config.snapshot_versions * 3 / 2 {
SnapshotUrgency::High SnapshotUrgency::High
} else if versions_since >= SNAPSHOT_VERSIONS { } else if versions_since >= config.snapshot_versions {
SnapshotUrgency::Low SnapshotUrgency::Low
} else { } else {
SnapshotUrgency::None SnapshotUrgency::None
@ -121,6 +117,7 @@ impl SnapshotUrgency {
/// Implementation of the AddVersion protocol transaction /// Implementation of the AddVersion protocol transaction
pub(crate) fn add_version<'a>( pub(crate) fn add_version<'a>(
mut txn: Box<dyn StorageTxn + 'a>, mut txn: Box<dyn StorageTxn + 'a>,
config: &ServerConfig,
client_key: ClientKey, client_key: ClientKey,
client: Client, client: Client,
parent_version_id: VersionId, parent_version_id: VersionId,
@ -156,7 +153,7 @@ pub(crate) fn add_version<'a>(
let time_urgency = match client.snapshot { let time_urgency = match client.snapshot {
None => SnapshotUrgency::High, None => SnapshotUrgency::High,
Some(Snapshot { timestamp, .. }) => { Some(Snapshot { timestamp, .. }) => {
SnapshotUrgency::for_days((Utc::now() - timestamp).num_days()) SnapshotUrgency::for_days(config, (Utc::now() - timestamp).num_days())
} }
}; };
@ -164,7 +161,7 @@ pub(crate) fn add_version<'a>(
let version_urgency = match client.snapshot { let version_urgency = match client.snapshot {
None => SnapshotUrgency::High, None => SnapshotUrgency::High,
Some(Snapshot { versions_since, .. }) => { Some(Snapshot { versions_since, .. }) => {
SnapshotUrgency::for_versions_since(versions_since) SnapshotUrgency::for_versions_since(config, versions_since)
} }
}; };
@ -177,6 +174,7 @@ pub(crate) fn add_version<'a>(
/// Implementation of the AddSnapshot protocol transaction /// Implementation of the AddSnapshot protocol transaction
pub(crate) fn add_snapshot<'a>( pub(crate) fn add_snapshot<'a>(
mut txn: Box<dyn StorageTxn + 'a>, mut txn: Box<dyn StorageTxn + 'a>,
_config: &ServerConfig,
client_key: ClientKey, client_key: ClientKey,
client: Client, client: Client,
version_id: VersionId, version_id: VersionId,
@ -261,6 +259,7 @@ pub(crate) fn add_snapshot<'a>(
/// Implementation of the GetSnapshot protocol transaction /// Implementation of the GetSnapshot protocol transaction
pub(crate) fn get_snapshot<'a>( pub(crate) fn get_snapshot<'a>(
mut txn: Box<dyn StorageTxn + 'a>, mut txn: Box<dyn StorageTxn + 'a>,
_config: &ServerConfig,
client_key: ClientKey, client_key: ClientKey,
client: Client, client: Client,
) -> anyhow::Result<Option<(Uuid, Vec<u8>)>> { ) -> anyhow::Result<Option<(Uuid, Vec<u8>)>> {
@ -297,18 +296,29 @@ mod test {
#[test] #[test]
fn snapshot_urgency_for_days() { fn snapshot_urgency_for_days() {
use SnapshotUrgency::*; use SnapshotUrgency::*;
assert_eq!(SnapshotUrgency::for_days(0), None); let config = ServerConfig::default();
assert_eq!(SnapshotUrgency::for_days(SNAPSHOT_DAYS), Low); assert_eq!(SnapshotUrgency::for_days(&config, 0), None);
assert_eq!(SnapshotUrgency::for_days(SNAPSHOT_DAYS * 2), High); assert_eq!(
SnapshotUrgency::for_days(&config, config.snapshot_days),
Low
);
assert_eq!(
SnapshotUrgency::for_days(&config, config.snapshot_days * 2),
High
);
} }
#[test] #[test]
fn snapshot_urgency_for_versions_since() { fn snapshot_urgency_for_versions_since() {
use SnapshotUrgency::*; use SnapshotUrgency::*;
assert_eq!(SnapshotUrgency::for_versions_since(0), None); let config = ServerConfig::default();
assert_eq!(SnapshotUrgency::for_versions_since(SNAPSHOT_VERSIONS), Low); assert_eq!(SnapshotUrgency::for_versions_since(&config, 0), None);
assert_eq!( assert_eq!(
SnapshotUrgency::for_versions_since(SNAPSHOT_VERSIONS * 2), SnapshotUrgency::for_versions_since(&config, config.snapshot_versions),
Low
);
assert_eq!(
SnapshotUrgency::for_versions_since(&config, config.snapshot_versions * 2),
High High
); );
} }
@ -325,7 +335,13 @@ mod test {
// when no snapshot exists, the first version is NotFound // when no snapshot exists, the first version is NotFound
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
assert_eq!( assert_eq!(
get_child_version(txn, client_key, client, NIL_VERSION_ID)?, get_child_version(
txn,
&ServerConfig::default(),
client_key,
client,
NIL_VERSION_ID
)?,
GetVersionResult::NotFound GetVersionResult::NotFound
); );
Ok(()) Ok(())
@ -353,7 +369,13 @@ mod test {
// when a snapshot exists, the first version is GONE // when a snapshot exists, the first version is GONE
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
assert_eq!( assert_eq!(
get_child_version(txn, client_key, client, NIL_VERSION_ID)?, get_child_version(
txn,
&ServerConfig::default(),
client_key,
client,
NIL_VERSION_ID
)?,
GetVersionResult::Gone GetVersionResult::Gone
); );
Ok(()) Ok(())
@ -374,7 +396,13 @@ mod test {
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
assert_eq!( assert_eq!(
get_child_version(txn, client_key, client, parent_version_id)?, get_child_version(
txn,
&ServerConfig::default(),
client_key,
client,
parent_version_id
)?,
GetVersionResult::NotFound GetVersionResult::NotFound
); );
Ok(()) Ok(())
@ -395,7 +423,13 @@ mod test {
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
assert_eq!( assert_eq!(
get_child_version(txn, client_key, client, parent_version_id)?, get_child_version(
txn,
&ServerConfig::default(),
client_key,
client,
parent_version_id
)?,
GetVersionResult::Gone GetVersionResult::Gone
); );
Ok(()) Ok(())
@ -422,7 +456,13 @@ mod test {
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
assert_eq!( assert_eq!(
get_child_version(txn, client_key, client, parent_version_id)?, get_child_version(
txn,
&ServerConfig::default(),
client_key,
client,
parent_version_id
)?,
GetVersionResult::Success { GetVersionResult::Success {
version_id, version_id,
parent_version_id, parent_version_id,
@ -516,7 +556,15 @@ mod test {
// try to add a child of a version other than the latest // try to add a child of a version other than the latest
assert_eq!( assert_eq!(
add_version(txn, client_key, client, versions[1], vec![3, 6, 9])?.0, add_version(
txn,
&ServerConfig::default(),
client_key,
client,
versions[1],
vec![3, 6, 9]
)?
.0,
AddVersionResult::ExpectedParentVersion(versions[2]) AddVersionResult::ExpectedParentVersion(versions[2])
); );
@ -539,7 +587,14 @@ mod test {
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
let result = add_version(txn, client_key, client, versions[0], vec![3, 6, 9])?; let result = add_version(
txn,
&ServerConfig::default(),
client_key,
client,
versions[0],
vec![3, 6, 9],
)?;
av_success_check( av_success_check(
&storage, &storage,
@ -563,7 +618,14 @@ mod test {
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
let parent_version_id = Uuid::nil(); let parent_version_id = Uuid::nil();
let result = add_version(txn, client_key, client, parent_version_id, vec![3, 6, 9])?; let result = add_version(
txn,
&ServerConfig::default(),
client_key,
client,
parent_version_id,
vec![3, 6, 9],
)?;
av_success_check( av_success_check(
&storage, &storage,
@ -586,7 +648,14 @@ mod test {
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
let result = add_version(txn, client_key, client, versions[0], vec![1, 2, 3])?; let result = add_version(
txn,
&ServerConfig::default(),
client_key,
client,
versions[0],
vec![1, 2, 3],
)?;
av_success_check( av_success_check(
&storage, &storage,
@ -610,7 +679,14 @@ mod test {
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
let result = add_version(txn, client_key, client, versions[0], vec![1, 2, 3])?; let result = add_version(
txn,
&ServerConfig::default(),
client_key,
client,
versions[0],
vec![1, 2, 3],
)?;
av_success_check( av_success_check(
&storage, &storage,
@ -634,7 +710,17 @@ mod test {
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
let result = add_version(txn, client_key, client, versions[49], vec![1, 2, 3])?; let result = add_version(
txn,
&ServerConfig {
snapshot_versions: 30,
..ServerConfig::default()
},
client_key,
client,
versions[49],
vec![1, 2, 3],
)?;
av_success_check( av_success_check(
&storage, &storage,
@ -664,7 +750,14 @@ mod test {
// add a snapshot for that version // add a snapshot for that version
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
add_snapshot(txn, client_key, client, version_id, vec![1, 2, 3])?; add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client,
version_id,
vec![1, 2, 3],
)?;
// verify the snapshot // verify the snapshot
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
@ -697,7 +790,14 @@ mod test {
// add a snapshot for version 1 // add a snapshot for version 1
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
add_snapshot(txn, client_key, client, version_id_1, vec![1, 2, 3])?; add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client,
version_id_1,
vec![1, 2, 3],
)?;
// verify the snapshot // verify the snapshot
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
@ -731,7 +831,14 @@ mod test {
// add a snapshot for unknown version // add a snapshot for unknown version
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
let version_id_unk = Uuid::new_v4(); let version_id_unk = Uuid::new_v4();
add_snapshot(txn, client_key, client, version_id_unk, vec![1, 2, 3])?; add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client,
version_id_unk,
vec![1, 2, 3],
)?;
// verify the snapshot does not exist // verify the snapshot does not exist
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
@ -763,7 +870,14 @@ mod test {
// add a snapshot for the earliest of those // add a snapshot for the earliest of those
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
add_snapshot(txn, client_key, client, version_ids[0], vec![1, 2, 3])?; add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client,
version_ids[0],
vec![1, 2, 3],
)?;
// verify the snapshot does not exist // verify the snapshot does not exist
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
@ -805,7 +919,14 @@ mod test {
// add a snapshot for the earliest of those // add a snapshot for the earliest of those
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
add_snapshot(txn, client_key, client, version_ids[0], vec![9, 9, 9])?; add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client,
version_ids[0],
vec![9, 9, 9],
)?;
// verify the snapshot was not replaced // verify the snapshot was not replaced
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
@ -834,7 +955,14 @@ mod test {
// add a snapshot for the nil version // add a snapshot for the nil version
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
add_snapshot(txn, client_key, client, NIL_VERSION_ID, vec![9, 9, 9])?; add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client,
NIL_VERSION_ID,
vec![9, 9, 9],
)?;
// verify the snapshot does not exist // verify the snapshot does not exist
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
@ -867,7 +995,7 @@ mod test {
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
assert_eq!( assert_eq!(
get_snapshot(txn, client_key, client)?, get_snapshot(txn, &ServerConfig::default(), client_key, client)?,
Some((snapshot_version_id, data.clone())) Some((snapshot_version_id, data.clone()))
); );
@ -885,7 +1013,10 @@ mod test {
txn.new_client(client_key, NIL_VERSION_ID)?; txn.new_client(client_key, NIL_VERSION_ID)?;
let client = txn.get_client(client_key)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
assert_eq!(get_snapshot(txn, client_key, client)?, None); assert_eq!(
get_snapshot(txn, &ServerConfig::default(), client_key, client)?,
None
);
Ok(()) Ok(())
} }

View file

@ -1,5 +1,5 @@
use crate::server::{ use crate::server::{
AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NIL_VERSION_ID,
}; };
use crate::storage::sqlite::StoredUuid; use crate::storage::sqlite::StoredUuid;
use anyhow::Context; use anyhow::Context;
@ -53,7 +53,7 @@ impl LocalServer {
|r| r.get(0), |r| r.get(0),
) )
.optional()?; .optional()?;
Ok(result.map(|x| x.0).unwrap_or(NO_VERSION_ID)) Ok(result.map(|x| x.0).unwrap_or(NIL_VERSION_ID))
} }
fn set_latest_version_id(&mut self, version_id: VersionId) -> anyhow::Result<()> { fn set_latest_version_id(&mut self, version_id: VersionId) -> anyhow::Result<()> {
@ -122,7 +122,7 @@ impl Server for LocalServer {
// check the parent_version_id for linearity // check the parent_version_id for linearity
let latest_version_id = self.get_latest_version_id()?; let latest_version_id = self.get_latest_version_id()?;
if latest_version_id != NO_VERSION_ID && parent_version_id != latest_version_id { if latest_version_id != NIL_VERSION_ID && parent_version_id != latest_version_id {
return Ok(AddVersionResult::ExpectedParentVersion(latest_version_id)); return Ok(AddVersionResult::ExpectedParentVersion(latest_version_id));
} }
@ -166,7 +166,7 @@ mod test {
fn test_empty() -> anyhow::Result<()> { fn test_empty() -> anyhow::Result<()> {
let tmp_dir = TempDir::new()?; let tmp_dir = TempDir::new()?;
let mut server = LocalServer::new(&tmp_dir.path())?; let mut server = LocalServer::new(&tmp_dir.path())?;
let child_version = server.get_child_version(NO_VERSION_ID)?; let child_version = server.get_child_version(NIL_VERSION_ID)?;
assert_eq!(child_version, GetVersionResult::NoSuchVersion); assert_eq!(child_version, GetVersionResult::NoSuchVersion);
Ok(()) Ok(())
} }
@ -176,17 +176,17 @@ mod test {
let tmp_dir = TempDir::new()?; let tmp_dir = TempDir::new()?;
let mut server = LocalServer::new(&tmp_dir.path())?; let mut server = LocalServer::new(&tmp_dir.path())?;
let history = b"1234".to_vec(); let history = b"1234".to_vec();
match server.add_version(NO_VERSION_ID, history.clone())? { match server.add_version(NIL_VERSION_ID, history.clone())? {
AddVersionResult::ExpectedParentVersion(_) => { AddVersionResult::ExpectedParentVersion(_) => {
panic!("should have accepted the version") panic!("should have accepted the version")
} }
AddVersionResult::Ok(version_id) => { AddVersionResult::Ok(version_id) => {
let new_version = server.get_child_version(NO_VERSION_ID)?; let new_version = server.get_child_version(NIL_VERSION_ID)?;
assert_eq!( assert_eq!(
new_version, new_version,
GetVersionResult::Version { GetVersionResult::Version {
version_id, version_id,
parent_version_id: NO_VERSION_ID, parent_version_id: NIL_VERSION_ID,
history_segment: history, history_segment: history,
} }
); );

View file

@ -1,5 +1,5 @@
use crate::server::{ use crate::server::{
AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID, AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NIL_VERSION_ID,
}; };
use std::collections::HashMap; use std::collections::HashMap;
use uuid::Uuid; use uuid::Uuid;
@ -20,7 +20,7 @@ impl TestServer {
/// A test server has no notion of clients, signatures, encryption, etc. /// A test server has no notion of clients, signatures, encryption, etc.
pub fn new() -> TestServer { pub fn new() -> TestServer {
TestServer { TestServer {
latest_version_id: NO_VERSION_ID, latest_version_id: NIL_VERSION_ID,
versions: HashMap::new(), versions: HashMap::new(),
} }
} }
@ -38,7 +38,7 @@ impl Server for TestServer {
// no signature validation // no signature validation
// check the parent_version_id for linearity // check the parent_version_id for linearity
if self.latest_version_id != NO_VERSION_ID { if self.latest_version_id != NIL_VERSION_ID {
if parent_version_id != self.latest_version_id { if parent_version_id != self.latest_version_id {
return Ok(AddVersionResult::ExpectedParentVersion( return Ok(AddVersionResult::ExpectedParentVersion(
self.latest_version_id, self.latest_version_id,

View file

@ -4,7 +4,7 @@ use uuid::Uuid;
pub type VersionId = Uuid; pub type VersionId = Uuid;
/// The distinguished value for "no version" /// The distinguished value for "no version"
pub const NO_VERSION_ID: VersionId = Uuid::nil(); pub const NIL_VERSION_ID: VersionId = Uuid::nil();
/// A segment in the history of this task database, in the form of a sequence of operations. This /// A segment in the history of this task database, in the form of a sequence of operations. This
/// data is pre-encoded, and from the protocol level appears as a sequence of bytes. /// data is pre-encoded, and from the protocol level appears as a sequence of bytes.

View file

@ -36,7 +36,7 @@ fn taskmap_with(mut properties: Vec<(String, String)>) -> TaskMap {
pub use crate::server::VersionId; pub use crate::server::VersionId;
/// The default for base_version. /// The default for base_version.
pub(crate) const DEFAULT_BASE_VERSION: Uuid = crate::server::NO_VERSION_ID; pub(crate) const DEFAULT_BASE_VERSION: Uuid = crate::server::NIL_VERSION_ID;
/// A Storage transaction, in which storage operations are performed. /// A Storage transaction, in which storage operations are performed.
/// ///