// Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use std::{fs, path::PathBuf}; use super::{Chunk, Graves, SanityCheckCounts, UnchunkedChanges}; use crate::backend_proto::sync_server_method_in::Method; use crate::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub enum SyncRequest { HostKey(HostKeyIn), Meta(MetaIn), Start(StartIn), ApplyGraves(ApplyGravesIn), ApplyChanges(ApplyChangesIn), Chunk, ApplyChunk(ApplyChunkIn), #[serde(rename = "sanityCheck2")] SanityCheck(SanityCheckIn), Finish, Abort, #[serde(rename = "upload")] FullUpload(PathBuf), #[serde(rename = "download")] FullDownload, } impl SyncRequest { /// Return method name and payload bytes. pub(crate) fn into_method_and_data(self) -> Result<(&'static str, Vec)> { use serde_json::to_vec; Ok(match self { SyncRequest::HostKey(v) => ("hostKey", to_vec(&v)?), SyncRequest::Meta(v) => ("meta", to_vec(&v)?), SyncRequest::Start(v) => ("start", to_vec(&v)?), SyncRequest::ApplyGraves(v) => ("applyGraves", to_vec(&v)?), SyncRequest::ApplyChanges(v) => ("applyChanges", to_vec(&v)?), SyncRequest::Chunk => ("chunk", b"{}".to_vec()), SyncRequest::ApplyChunk(v) => ("applyChunk", to_vec(&v)?), SyncRequest::SanityCheck(v) => ("sanityCheck2", to_vec(&v)?), SyncRequest::Finish => ("finish", b"{}".to_vec()), SyncRequest::Abort => ("abort", b"{}".to_vec()), SyncRequest::FullUpload(v) => { // fixme: stream in the data instead, in a different call ("upload", fs::read(&v)?) } SyncRequest::FullDownload => ("download", b"{}".to_vec()), }) } pub(crate) fn from_method_and_data(method: Method, data: Vec) -> Result { use serde_json::from_slice; Ok(match method { Method::HostKey => SyncRequest::HostKey(from_slice(&data)?), Method::Meta => SyncRequest::Meta(from_slice(&data)?), Method::Start => SyncRequest::Start(from_slice(&data)?), Method::ApplyGraves => SyncRequest::ApplyGraves(from_slice(&data)?), Method::ApplyChanges => SyncRequest::ApplyChanges(from_slice(&data)?), Method::Chunk => SyncRequest::Chunk, Method::ApplyChunk => SyncRequest::ApplyChunk(from_slice(&data)?), Method::SanityCheck => SyncRequest::SanityCheck(from_slice(&data)?), Method::Finish => SyncRequest::Finish, Method::Abort => SyncRequest::Abort, Method::FullUpload => { let path = PathBuf::from(String::from_utf8(data).expect("path was not in utf8")); SyncRequest::FullUpload(path) } Method::FullDownload => SyncRequest::FullDownload, }) } } #[derive(Serialize, Deserialize, Debug)] pub struct HostKeyIn { #[serde(rename = "u")] pub username: String, #[serde(rename = "p")] pub password: String, } #[derive(Serialize, Deserialize, Debug)] pub struct HostKeyOut { pub key: String, } #[derive(Serialize, Deserialize, Debug)] pub struct MetaIn { #[serde(rename = "v")] pub sync_version: u8, #[serde(rename = "cv")] pub client_version: String, } #[derive(Serialize, Deserialize, Debug)] pub struct StartIn { #[serde(rename = "minUsn")] pub client_usn: Usn, #[serde(rename = "lnewer")] pub local_is_newer: bool, /// Unfortunately AnkiDroid is still using this #[serde(rename = "graves", default)] pub deprecated_client_graves: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct ApplyGravesIn { pub chunk: Graves, } #[derive(Serialize, Deserialize, Debug)] pub struct ApplyChangesIn { pub changes: UnchunkedChanges, } #[derive(Serialize, Deserialize, Debug)] pub struct ApplyChunkIn { pub chunk: Chunk, } #[derive(Serialize, Deserialize, Debug)] pub struct SanityCheckIn { pub client: SanityCheckCounts, }