initial implementation of sync-collection
This commit is contained in:
parent
18f2154151
commit
a2f5b451bd
4 changed files with 154 additions and 15 deletions
|
@ -42,7 +42,7 @@ pub struct DavDag {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum SyncChange {
|
pub enum SyncChange {
|
||||||
Ok(FileName),
|
Ok((FileName, BlobId)),
|
||||||
NotFound(FileName),
|
NotFound(FileName),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,8 @@ impl DavDag {
|
||||||
|
|
||||||
// Record the change in the ephemeral synchronization map
|
// Record the change in the ephemeral synchronization map
|
||||||
if let Some(sync_token) = sync_token {
|
if let Some(sync_token) = sync_token {
|
||||||
self.change.insert(sync_token, SyncChange::Ok(filename));
|
self.change
|
||||||
|
.insert(sync_token, SyncChange::Ok((filename, blob_id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,10 @@ use hyper::body::Frame;
|
||||||
use hyper::body::Incoming;
|
use hyper::body::Incoming;
|
||||||
use hyper::{body::Bytes, Request, Response};
|
use hyper::{body::Bytes, Request, Response};
|
||||||
|
|
||||||
use aero_collections::user::User;
|
use aero_collections::{davdag::Token, user::User};
|
||||||
use aero_dav::caltypes as cal;
|
use aero_dav::caltypes as cal;
|
||||||
use aero_dav::realization::{self, All};
|
use aero_dav::realization::{self, All};
|
||||||
|
use aero_dav::synctypes as sync;
|
||||||
use aero_dav::types as dav;
|
use aero_dav::types as dav;
|
||||||
use aero_dav::versioningtypes as vers;
|
use aero_dav::versioningtypes as vers;
|
||||||
use aero_ical::query::is_component_match;
|
use aero_ical::query::is_component_match;
|
||||||
|
@ -17,7 +18,7 @@ use aero_ical::query::is_component_match;
|
||||||
use crate::dav::codec;
|
use crate::dav::codec;
|
||||||
use crate::dav::codec::{depth, deserialize, serialize, text_body};
|
use crate::dav::codec::{depth, deserialize, serialize, text_body};
|
||||||
use crate::dav::node::DavNode;
|
use crate::dav::node::DavNode;
|
||||||
use crate::dav::resource::RootNode;
|
use crate::dav::resource::{RootNode, BASE_TOKEN_URI};
|
||||||
|
|
||||||
pub(super) type ArcUser = std::sync::Arc<User>;
|
pub(super) type ArcUser = std::sync::Arc<User>;
|
||||||
pub(super) type HttpResponse = Response<UnsyncBoxBody<Bytes, std::io::Error>>;
|
pub(super) type HttpResponse = Response<UnsyncBoxBody<Bytes, std::io::Error>>;
|
||||||
|
@ -109,6 +110,7 @@ impl Controller {
|
||||||
// Internal representation that will handle processed request
|
// Internal representation that will handle processed request
|
||||||
let (mut ok_node, mut not_found) = (Vec::new(), Vec::new());
|
let (mut ok_node, mut not_found) = (Vec::new(), Vec::new());
|
||||||
let calprop: Option<cal::CalendarSelector<All>>;
|
let calprop: Option<cal::CalendarSelector<All>>;
|
||||||
|
let extension: Option<realization::Multistatus>;
|
||||||
|
|
||||||
// Extracting request information
|
// Extracting request information
|
||||||
match cal_report {
|
match cal_report {
|
||||||
|
@ -136,15 +138,54 @@ impl Controller {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
calprop = m.selector;
|
calprop = m.selector;
|
||||||
|
extension = None;
|
||||||
}
|
}
|
||||||
vers::Report::Extension(realization::ReportType::Cal(cal::ReportType::Query(q))) => {
|
vers::Report::Extension(realization::ReportType::Cal(cal::ReportType::Query(q))) => {
|
||||||
calprop = q.selector;
|
calprop = q.selector;
|
||||||
|
extension = None;
|
||||||
ok_node = apply_filter(self.node.children(&self.user).await, &q.filter)
|
ok_node = apply_filter(self.node.children(&self.user).await, &q.filter)
|
||||||
.try_collect()
|
.try_collect()
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
vers::Report::Extension(realization::ReportType::Sync(_sync_col)) => {
|
vers::Report::Extension(realization::ReportType::Sync(sync_col)) => {
|
||||||
todo!()
|
calprop = Some(cal::CalendarSelector::Prop(sync_col.prop));
|
||||||
|
|
||||||
|
if sync_col.limit.is_some() {
|
||||||
|
tracing::warn!("limit is not supported, ignoring");
|
||||||
|
}
|
||||||
|
if matches!(sync_col.sync_level, sync::SyncLevel::Infinite) {
|
||||||
|
tracing::debug!("aerogramme calendar collections are not nested");
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = match sync_col.sync_token {
|
||||||
|
sync::SyncTokenRequest::InitialSync => None,
|
||||||
|
sync::SyncTokenRequest::IncrementalSync(token_raw) => {
|
||||||
|
// parse token
|
||||||
|
if token_raw.len() != BASE_TOKEN_URI.len() + 48 {
|
||||||
|
anyhow::bail!("invalid token length")
|
||||||
|
}
|
||||||
|
let token = token_raw[BASE_TOKEN_URI.len()..]
|
||||||
|
.parse()
|
||||||
|
.or(Err(anyhow::anyhow!("can't parse token")))?;
|
||||||
|
Some(token)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// do the diff
|
||||||
|
let new_token: Token;
|
||||||
|
(new_token, ok_node, not_found) = match self.node.diff(token).await {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
std::io::ErrorKind::NotFound => return Ok(Response::builder()
|
||||||
|
.status(410)
|
||||||
|
.body(text_body("Diff failed, token might be expired"))?),
|
||||||
|
_ => return Ok(Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.body(text_body("Server error, maybe this operation is not supported on this collection"))?),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
extension = Some(realization::Multistatus::Sync(sync::Multistatus {
|
||||||
|
sync_token: sync::SyncToken(new_token.to_string()),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Ok(Response::builder()
|
return Ok(Response::builder()
|
||||||
|
@ -162,7 +203,7 @@ impl Controller {
|
||||||
|
|
||||||
serialize(
|
serialize(
|
||||||
status,
|
status,
|
||||||
Self::multistatus(&self.user, ok_node, not_found, props).await,
|
Self::multistatus(&self.user, ok_node, not_found, props, extension).await,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +249,7 @@ impl Controller {
|
||||||
let not_found = vec![];
|
let not_found = vec![];
|
||||||
serialize(
|
serialize(
|
||||||
status,
|
status,
|
||||||
Self::multistatus(&self.user, nodes, not_found, propname).await,
|
Self::multistatus(&self.user, nodes, not_found, propname, None).await,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +318,7 @@ impl Controller {
|
||||||
nodes: Vec<Box<dyn DavNode>>,
|
nodes: Vec<Box<dyn DavNode>>,
|
||||||
not_found: Vec<dav::Href>,
|
not_found: Vec<dav::Href>,
|
||||||
props: Option<dav::PropName<All>>,
|
props: Option<dav::PropName<All>>,
|
||||||
|
extension: Option<realization::Multistatus>,
|
||||||
) -> dav::Multistatus<All> {
|
) -> dav::Multistatus<All> {
|
||||||
// Collect properties on existing objects
|
// Collect properties on existing objects
|
||||||
let mut responses: Vec<dav::Response<All>> = match props {
|
let mut responses: Vec<dav::Response<All>> = match props {
|
||||||
|
@ -309,7 +351,7 @@ impl Controller {
|
||||||
dav::Multistatus::<All> {
|
dav::Multistatus::<All> {
|
||||||
responses,
|
responses,
|
||||||
responsedescription: None,
|
responsedescription: None,
|
||||||
extension: None,
|
extension,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use futures::future::{BoxFuture, FutureExt};
|
||||||
use futures::stream::{BoxStream, StreamExt};
|
use futures::stream::{BoxStream, StreamExt};
|
||||||
use hyper::body::Bytes;
|
use hyper::body::Bytes;
|
||||||
|
|
||||||
use aero_collections::davdag::Etag;
|
use aero_collections::davdag::{Etag, Token};
|
||||||
use aero_dav::realization::All;
|
use aero_dav::realization::All;
|
||||||
use aero_dav::types as dav;
|
use aero_dav::types as dav;
|
||||||
|
|
||||||
|
@ -55,8 +55,14 @@ pub(crate) trait DavNode: Send {
|
||||||
fn content<'a>(&self) -> Content<'a>;
|
fn content<'a>(&self) -> Content<'a>;
|
||||||
/// Delete
|
/// Delete
|
||||||
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>>;
|
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>>;
|
||||||
|
/// Sync
|
||||||
//@FIXME maybe add etag, maybe add a way to set content
|
fn diff<'a>(
|
||||||
|
&self,
|
||||||
|
sync_token: Option<Token>,
|
||||||
|
) -> BoxFuture<
|
||||||
|
'a,
|
||||||
|
std::result::Result<(Token, Vec<Box<dyn DavNode>>, Vec<dav::Href>), std::io::Error>,
|
||||||
|
>;
|
||||||
|
|
||||||
/// Utility function to get a propname response from a node
|
/// Utility function to get a propname response from a node
|
||||||
fn response_propname(&self, user: &ArcUser) -> dav::Response<All> {
|
fn response_propname(&self, user: &ArcUser) -> dav::Response<All> {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use futures::{future::BoxFuture, future::FutureExt};
|
||||||
|
|
||||||
use aero_collections::{
|
use aero_collections::{
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
davdag::{BlobId, Etag},
|
davdag::{BlobId, Etag, SyncChange, Token},
|
||||||
user::User,
|
user::User,
|
||||||
};
|
};
|
||||||
use aero_dav::acltypes as acl;
|
use aero_dav::acltypes as acl;
|
||||||
|
@ -21,6 +21,8 @@ use aero_dav::versioningtypes as vers;
|
||||||
use super::node::PropertyStream;
|
use super::node::PropertyStream;
|
||||||
use crate::dav::node::{Content, DavNode, PutPolicy};
|
use crate::dav::node::{Content, DavNode, PutPolicy};
|
||||||
|
|
||||||
|
pub const BASE_TOKEN_URI: &str = "https://aerogramme.0/sync/";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct RootNode {}
|
pub(crate) struct RootNode {}
|
||||||
impl DavNode for RootNode {
|
impl DavNode for RootNode {
|
||||||
|
@ -117,6 +119,16 @@ impl DavNode for RootNode {
|
||||||
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
|
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
|
||||||
async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed()
|
async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn diff<'a>(
|
||||||
|
&self,
|
||||||
|
_sync_token: Option<Token>,
|
||||||
|
) -> BoxFuture<
|
||||||
|
'a,
|
||||||
|
std::result::Result<(Token, Vec<Box<dyn DavNode>>, Vec<dav::Href>), std::io::Error>,
|
||||||
|
> {
|
||||||
|
async { Err(std::io::Error::from(std::io::ErrorKind::Unsupported)) }.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -229,6 +241,15 @@ impl DavNode for HomeNode {
|
||||||
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
|
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
|
||||||
async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed()
|
async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed()
|
||||||
}
|
}
|
||||||
|
fn diff<'a>(
|
||||||
|
&self,
|
||||||
|
_sync_token: Option<Token>,
|
||||||
|
) -> BoxFuture<
|
||||||
|
'a,
|
||||||
|
std::result::Result<(Token, Vec<Box<dyn DavNode>>, Vec<dav::Href>), std::io::Error>,
|
||||||
|
> {
|
||||||
|
async { Err(std::io::Error::from(std::io::ErrorKind::Unsupported)) }.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -353,6 +374,15 @@ impl DavNode for CalendarListNode {
|
||||||
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
|
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
|
||||||
async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed()
|
async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed()
|
||||||
}
|
}
|
||||||
|
fn diff<'a>(
|
||||||
|
&self,
|
||||||
|
_sync_token: Option<Token>,
|
||||||
|
) -> BoxFuture<
|
||||||
|
'a,
|
||||||
|
std::result::Result<(Token, Vec<Box<dyn DavNode>>, Vec<dav::Href>), std::io::Error>,
|
||||||
|
> {
|
||||||
|
async { Err(std::io::Error::from(std::io::ErrorKind::Unsupported)) }.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -480,8 +510,8 @@ impl DavNode for CalendarNode {
|
||||||
)) => match col.token().await {
|
)) => match col.token().await {
|
||||||
Ok(token) => dav::Property::Extension(all::Property::Sync(
|
Ok(token) => dav::Property::Extension(all::Property::Sync(
|
||||||
sync::Property::SyncToken(sync::SyncToken(format!(
|
sync::Property::SyncToken(sync::SyncToken(format!(
|
||||||
"https://aerogramme.0/sync/{}",
|
"{}{}",
|
||||||
token
|
BASE_TOKEN_URI, token
|
||||||
))),
|
))),
|
||||||
)),
|
)),
|
||||||
_ => return Err(n.clone()),
|
_ => return Err(n.clone()),
|
||||||
|
@ -535,6 +565,48 @@ impl DavNode for CalendarNode {
|
||||||
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
|
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
|
||||||
async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed()
|
async { Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied)) }.boxed()
|
||||||
}
|
}
|
||||||
|
fn diff<'a>(
|
||||||
|
&self,
|
||||||
|
sync_token: Option<Token>,
|
||||||
|
) -> BoxFuture<
|
||||||
|
'a,
|
||||||
|
std::result::Result<(Token, Vec<Box<dyn DavNode>>, Vec<dav::Href>), std::io::Error>,
|
||||||
|
> {
|
||||||
|
let col = self.col.clone();
|
||||||
|
let calname = self.calname.clone();
|
||||||
|
async move {
|
||||||
|
let sync_token = sync_token.unwrap();
|
||||||
|
let (new_token, listed_changes) = match col.diff(sync_token).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::info!(err=?e, "token resolution failed, maybe a forgotten token");
|
||||||
|
return Err(std::io::Error::from(std::io::ErrorKind::NotFound));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ok_nodes: Vec<Box<dyn DavNode>> = vec![];
|
||||||
|
let mut rm_nodes: Vec<dav::Href> = vec![];
|
||||||
|
for change in listed_changes.into_iter() {
|
||||||
|
match change {
|
||||||
|
SyncChange::Ok((filename, blob_id)) => {
|
||||||
|
let child = Box::new(EventNode {
|
||||||
|
col: col.clone(),
|
||||||
|
calname: calname.clone(),
|
||||||
|
filename,
|
||||||
|
blob_id,
|
||||||
|
});
|
||||||
|
ok_nodes.push(child);
|
||||||
|
}
|
||||||
|
SyncChange::NotFound(filename) => {
|
||||||
|
rm_nodes.push(dav::Href(filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((new_token, ok_nodes, rm_nodes))
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -757,6 +829,15 @@ impl DavNode for EventNode {
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
fn diff<'a>(
|
||||||
|
&self,
|
||||||
|
_sync_token: Option<Token>,
|
||||||
|
) -> BoxFuture<
|
||||||
|
'a,
|
||||||
|
std::result::Result<(Token, Vec<Box<dyn DavNode>>, Vec<dav::Href>), std::io::Error>,
|
||||||
|
> {
|
||||||
|
async { Err(std::io::Error::from(std::io::ErrorKind::Unsupported)) }.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -849,4 +930,13 @@ impl DavNode for CreateEventNode {
|
||||||
// Nothing to delete
|
// Nothing to delete
|
||||||
async { Ok(()) }.boxed()
|
async { Ok(()) }.boxed()
|
||||||
}
|
}
|
||||||
|
fn diff<'a>(
|
||||||
|
&self,
|
||||||
|
_sync_token: Option<Token>,
|
||||||
|
) -> BoxFuture<
|
||||||
|
'a,
|
||||||
|
std::result::Result<(Token, Vec<Box<dyn DavNode>>, Vec<dav::Href>), std::io::Error>,
|
||||||
|
> {
|
||||||
|
async { Err(std::io::Error::from(std::io::ErrorKind::Unsupported)) }.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue