206 lines
6.9 KiB
Rust
206 lines
6.9 KiB
Rust
use std::sync::Arc;
|
|
|
|
use thiserror::Error;
|
|
use tokio_rusqlite::{OptionalExtension as _, named_params, rusqlite};
|
|
|
|
use crate::{reader::Reader, writer::Writer};
|
|
|
|
#[derive(Clone)]
|
|
pub struct ProgressSync {
|
|
writer: Arc<Writer>,
|
|
reader: Arc<Reader>,
|
|
}
|
|
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum SyncError {
|
|
#[error("Database call failed {0}")]
|
|
DatabaseFailure(#[from] tokio_rusqlite::Error)
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub struct Document {
|
|
// document identifier, name of the file, md5 of the file or something
|
|
pub document_id: String,
|
|
// opaque progress
|
|
pub progress: String,
|
|
pub percentage: f32,
|
|
pub device: String,
|
|
pub device_id: Option<String>,
|
|
pub timestamp: i64,
|
|
}
|
|
|
|
impl ProgressSync {
|
|
pub fn new(writer: Arc<Writer>, reader: Arc<Reader>) -> Self {
|
|
Self{writer,reader}
|
|
}
|
|
|
|
pub async fn set_progress(&self, username: &str, document: &Document) -> Result<(), SyncError> {
|
|
let username = username.to_owned();
|
|
let doc = document.clone();
|
|
self.writer.connection.call(move |conn| -> rusqlite::Result<()> {
|
|
conn.execute("insert into progress (user_id, document_id, progress, percentage, device, device_id, timestamp)
|
|
select id, :document_id, :progress, :percentage, :device, :device_id, :timestamp from users
|
|
where username = :username
|
|
on conflict (user_id, document_id) do update
|
|
set progress = excluded.progress,
|
|
percentage = excluded.percentage,
|
|
device = excluded.device,
|
|
device_id = excluded.device_id,
|
|
timestamp = excluded.timestamp", named_params!
|
|
{ ":username": username,
|
|
":document_id": doc.document_id,
|
|
":progress": doc.progress,
|
|
":percentage": doc.percentage,
|
|
":device": doc.device,
|
|
":device_id": doc.device_id,
|
|
":timestamp": doc.timestamp,
|
|
})?;
|
|
Ok(())
|
|
}).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_progress(&self, username: &str, document_id: &str) -> Result<Option<Document>, SyncError> {
|
|
let username = username.to_owned();
|
|
let document_id = document_id.to_owned();
|
|
let doc = self.reader.connection.call(move |conn| -> rusqlite::Result<Option<Document>> {
|
|
let x : Option<Document> = conn.query_row("select
|
|
document_id, progress, percentage, device, device_id, timestamp
|
|
from progress
|
|
left join users on progress.user_id = users.id
|
|
where users.username = :username and progress.document_id = :document_id", named_params!
|
|
{ ":username": username,
|
|
":document_id": document_id,
|
|
}, |row| {
|
|
let document_id : String = row.get(0)?;
|
|
let progress : String = row.get(1)?;
|
|
let percentage : f32 = row.get(2)?;
|
|
let device : String = row.get(3)?;
|
|
let device_id : Option<String> = row.get(4)?;
|
|
let timestamp : i64 = row.get(5)?;
|
|
Ok(Document{ document_id, progress, percentage, device, device_id, timestamp })
|
|
}).optional()?;
|
|
Ok(x)
|
|
}).await?;
|
|
Ok(doc)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
use secrecy::SecretString;
|
|
use tempfile::{TempDir, tempdir};
|
|
|
|
use crate::users::Users;
|
|
|
|
use super::*;
|
|
|
|
async fn create_data() -> anyhow::Result<(TempDir, Users, ProgressSync)> {
|
|
let temp_dir = tempdir()?;
|
|
let path = temp_dir.path().join("kosync.db");
|
|
let writer = Arc::new(Writer::new(&path).await?);
|
|
let reader = Arc::new(Reader::new(&path).await?);
|
|
let users = Users::new(writer.clone(), reader.clone(), SecretString::from("secret"));
|
|
let sync = ProgressSync::new(writer.clone(), reader.clone());
|
|
Ok((temp_dir, users, sync))
|
|
}
|
|
|
|
fn now() -> anyhow::Result<i64> {
|
|
let n = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;
|
|
Ok(n)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn put_get_values() -> anyhow::Result<()> {
|
|
let (_t, users, sync) = create_data().await?;
|
|
|
|
let username = "foo@example.com";
|
|
let username = users.create_user(username, SecretString::from("password")).await?;
|
|
|
|
let doc = Document {
|
|
document_id: "foo".to_string(),
|
|
progress: "prog".to_string(),
|
|
percentage: 0.5,
|
|
device: "kobo".to_string(),
|
|
device_id: None,
|
|
timestamp: now()?,
|
|
};
|
|
|
|
sync.set_progress(&username, &doc).await?;
|
|
let got = sync.get_progress(&username, "foo").await?;
|
|
|
|
assert_eq!(got, Some(doc));
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn put_gets_latest() -> anyhow::Result<()> {
|
|
let (_t, users, sync) = create_data().await?;
|
|
|
|
let username = "foo@example.com";
|
|
let username = users.create_user(username, SecretString::from("password")).await?;
|
|
let document_id = "foo".to_string();
|
|
|
|
let doc1 = Document {
|
|
document_id: document_id.clone(),
|
|
progress: "prog".to_string(),
|
|
percentage: 0.5,
|
|
device: "kobo".to_string(),
|
|
device_id: None,
|
|
timestamp: now()?,
|
|
};
|
|
let doc2 = Document {
|
|
document_id: document_id.clone(),
|
|
progress: "prog2".to_string(),
|
|
percentage: 0.5,
|
|
device: "kobo".to_string(),
|
|
device_id: None,
|
|
timestamp: now()?,
|
|
};
|
|
|
|
sync.set_progress(&username, &doc1).await?;
|
|
sync.set_progress(&username, &doc2).await?;
|
|
let got = sync.get_progress(&username, &document_id).await?;
|
|
|
|
assert_eq!(got, Some(doc2));
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn put_gets_new_device() -> anyhow::Result<()> {
|
|
let (_t, users, sync) = create_data().await?;
|
|
|
|
let username = "foo@example.com";
|
|
let username = users.create_user(username, SecretString::from("password")).await?;
|
|
|
|
let document_id = "foo".to_string();
|
|
|
|
let doc1 = Document {
|
|
document_id: document_id.clone(),
|
|
progress: "prog".to_string(),
|
|
percentage: 0.5,
|
|
device: "kobo".to_string(),
|
|
device_id: None,
|
|
timestamp: now()?,
|
|
};
|
|
let doc2 = Document {
|
|
document_id: document_id.clone(),
|
|
progress: "prog2".to_string(),
|
|
percentage: 0.5,
|
|
device: "x4".to_string(),
|
|
device_id: None,
|
|
timestamp: now()? + 3,
|
|
};
|
|
|
|
sync.set_progress(&username, &doc1).await?;
|
|
sync.set_progress(&username, &doc2).await?;
|
|
let got = sync.get_progress(&username, &document_id).await?;
|
|
|
|
assert_eq!(got, Some(doc2));
|
|
Ok(())
|
|
}
|
|
}
|