build an in-memory sync server implementation

This commit is contained in:
Dustin J. Mitchell 2020-11-26 12:13:00 -05:00
parent 2457d8bc43
commit 2dae271851
3 changed files with 117 additions and 37 deletions

View file

@ -1,6 +1,6 @@
use actix_web::{App, HttpServer};
use api::ServerState;
use server::{NullSyncServer, SyncServer};
use server::{InMemorySyncServer, SyncServer};
mod api;
mod server;
@ -9,7 +9,7 @@ mod server;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let server_box: Box<dyn SyncServer> = Box::new(NullSyncServer::new());
let server_box: Box<dyn SyncServer> = Box::new(InMemorySyncServer::new());
let server_state = ServerState::new(server_box);
HttpServer::new(move || {

View file

@ -0,0 +1,108 @@
use super::{
AddVersionResult, ClientId, GetVersionResult, HistorySegment, SyncServer, VersionId,
NO_VERSION_ID,
};
use failure::Fallible;
use std::collections::HashMap;
use std::sync::{Mutex, RwLock};
use taskchampion::Uuid;
/// An in-memory server backend that can be useful for testing.
pub(crate) struct InMemorySyncServer {
clients: RwLock<HashMap<ClientId, Mutex<Client>>>,
}
struct Version {
version_id: VersionId,
history_segment: HistorySegment,
}
struct Client {
latest_version_id: VersionId,
// NOTE: indexed by parent_version_id!
versions: HashMap<VersionId, Version>,
}
impl InMemorySyncServer {
pub(crate) fn new() -> Self {
Self {
clients: RwLock::new(HashMap::new()),
}
}
}
impl SyncServer for InMemorySyncServer {
fn get_child_version(
&self,
client_id: ClientId,
parent_version_id: VersionId,
) -> Fallible<Option<GetVersionResult>> {
let clients = self.clients.read().expect("poisoned lock");
if let Some(client) = clients.get(&client_id) {
let client = client.lock().expect("poisoned lock");
if let Some(version) = client.versions.get(&parent_version_id) {
return Ok(Some(GetVersionResult {
version_id: version.version_id,
parent_version_id,
history_segment: version.history_segment.clone(),
}));
}
}
Ok(None)
}
fn add_version(
&self,
client_id: ClientId,
parent_version_id: VersionId,
history_segment: HistorySegment,
) -> Fallible<AddVersionResult> {
let mut clients = self.clients.write().expect("poisoned lock");
if let Some(client) = clients.get_mut(&client_id) {
let mut client = client.lock().expect("poisoned lock");
if client.latest_version_id != NO_VERSION_ID {
if parent_version_id != client.latest_version_id {
return Ok(AddVersionResult::ExpectedParentVersion(
client.latest_version_id,
));
}
}
// invent a new ID for this version
let version_id = Uuid::new_v4();
client.versions.insert(
parent_version_id,
Version {
version_id,
history_segment,
},
);
client.latest_version_id = version_id;
Ok(AddVersionResult::Ok(version_id))
} else {
// new client, so insert a client with just this new version
let latest_version_id = Uuid::new_v4();
let mut versions = HashMap::new();
versions.insert(
parent_version_id,
Version {
version_id: latest_version_id,
history_segment,
},
);
clients.insert(
client_id,
Mutex::new(Client {
latest_version_id,
versions,
}),
);
Ok(AddVersionResult::Ok(latest_version_id))
}
}
}

View file

@ -1,6 +1,13 @@
use failure::Fallible;
use taskchampion::Uuid;
mod inmemory;
pub(crate) use inmemory::InMemorySyncServer;
/// The distinguished value for "no version"
pub const NO_VERSION_ID: VersionId = Uuid::nil();
pub(crate) type HistorySegment = Vec<u8>;
pub(crate) type ClientId = Uuid;
pub(crate) type VersionId = Uuid;
@ -33,38 +40,3 @@ pub(crate) trait SyncServer: Sync + Send {
history_segment: HistorySegment,
) -> Fallible<AddVersionResult>;
}
// TODO: temporary
/// A "null" sync server's implementation; HTTP API methods call through to methods on a single
/// instance of this type.
pub(crate) struct NullSyncServer {}
impl NullSyncServer {
pub(crate) fn new() -> Self {
Self {}
}
}
impl SyncServer for NullSyncServer {
fn get_child_version(
&self,
_client_id: ClientId,
parent_version_id: VersionId,
) -> Fallible<Option<GetVersionResult>> {
Ok(Some(GetVersionResult {
version_id: Uuid::new_v4(),
parent_version_id,
history_segment: b"abcd".to_vec(),
}))
}
fn add_version(
&self,
_client_id: ClientId,
_parent_version_id: VersionId,
_history_segment: HistorySegment,
) -> Fallible<AddVersionResult> {
//Ok(AddVersionResult::Ok(Uuid::new_v4()))
Ok(AddVersionResult::ExpectedParentVersion(Uuid::new_v4()))
}
}