mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-02 10:15:50 +00:00
parent
3f41f9cf41
commit
06d59f59a5
18 changed files with 561 additions and 502 deletions
8
Makefile
8
Makefile
|
|
@ -131,7 +131,8 @@ install-dev:
|
|||
pip install -U --force-reinstall \
|
||||
git+https://github.com/mitsuhiko/click \
|
||||
git+https://github.com/click-contrib/click-log \
|
||||
git+https://github.com/kennethreitz/requests; \
|
||||
git+https://github.com/kennethreitz/requests \
|
||||
"git+https://github.com/untitaker/shippai#subdirectory=python"; \
|
||||
elif [ "$(REQUIREMENTS)" = "minimal" ]; then \
|
||||
pip install -U --force-reinstall $$(python setup.py --quiet minimal_requirements); \
|
||||
fi
|
||||
|
|
@ -152,4 +153,7 @@ rust-ext:
|
|||
[ "$$READTHEDOCS" != "True" ] || $(MAKE) install-rust
|
||||
cd ./rust && cargo build --release
|
||||
|
||||
.PHONY: docs
|
||||
rust/vdirsyncer_rustext.h:
|
||||
cbindgen -c rust/cbindgen.toml rust/ > $@
|
||||
|
||||
.PHONY: docs rust/vdirsyncer_rustext.h
|
||||
|
|
|
|||
1
rust/.gitignore
vendored
1
rust/.gitignore
vendored
|
|
@ -1,2 +1 @@
|
|||
target/
|
||||
src/storage/exports.rs
|
||||
|
|
|
|||
45
rust/Cargo.lock
generated
45
rust/Cargo.lock
generated
|
|
@ -120,6 +120,25 @@ dependencies = [
|
|||
"backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.3"
|
||||
|
|
@ -310,6 +329,14 @@ dependencies = [
|
|||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shippai"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.6.0"
|
||||
|
|
@ -333,6 +360,15 @@ dependencies = [
|
|||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempdir"
|
||||
version = "0.3.5"
|
||||
|
|
@ -383,13 +419,14 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vdirsyncer_rustext"
|
||||
name = "vdirsyncer-rustext"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"atomicwrites 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cbindgen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"shippai 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vobject 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
|
@ -456,6 +493,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
|
||||
"checksum either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "740178ddf48b1a9e878e6d6509a1442a2d42fd2928aae8e7a6f8a36fb01981b3"
|
||||
"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
|
||||
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
|
||||
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
|
||||
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
|
||||
|
|
@ -481,9 +520,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0"
|
||||
"checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5"
|
||||
"checksum serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9db7266c7d63a4c4b7fe8719656ccdd51acf1bed6124b174f933b009fb10bcb"
|
||||
"checksum shippai 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c342d76b09cc95857d435bb96f03c1eac8218c819e799adcc1225bdc7fe48ede"
|
||||
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
|
||||
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
|
||||
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
|
||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
||||
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
[package]
|
||||
name = "vdirsyncer_rustext"
|
||||
name = "vdirsyncer-rustext"
|
||||
version = "0.1.0"
|
||||
authors = ["Markus Unterwaditzer <markus@unterwaditzer.net>"]
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
name = "vdirsyncer_rustext"
|
||||
|
|
@ -11,7 +10,8 @@ crate-type = ["cdylib"]
|
|||
[dependencies]
|
||||
vobject = "0.4.2"
|
||||
ring = "0.12.1"
|
||||
error-chain = "0.11.0"
|
||||
failure = "0.1"
|
||||
shippai = "0.1.1"
|
||||
atomicwrites = "0.1.4"
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
|||
161
rust/build.rs
161
rust/build.rs
|
|
@ -1,161 +0,0 @@
|
|||
extern crate cbindgen;
|
||||
|
||||
use std::env;
|
||||
use std::fs::{remove_file, File};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
const TEMPLATE_EACH: &'static str = r#"
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_{name}_list(
|
||||
storage: *mut {path},
|
||||
err: *mut VdirsyncerError
|
||||
) -> *mut VdirsyncerStorageListing {
|
||||
match (*storage).list() {
|
||||
Ok(x) => Box::into_raw(Box::new(VdirsyncerStorageListing {
|
||||
iterator: x,
|
||||
href: None,
|
||||
etag: None
|
||||
})),
|
||||
Err(e) => {
|
||||
e.fill_c_err(err);
|
||||
mem::zeroed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_{name}_get(
|
||||
storage: *mut {path},
|
||||
c_href: *const c_char,
|
||||
err: *mut VdirsyncerError
|
||||
) -> *mut VdirsyncerStorageGetResult {
|
||||
let href = CStr::from_ptr(c_href);
|
||||
match (*storage).get(href.to_str().unwrap()) {
|
||||
Ok((item, href)) => {
|
||||
Box::into_raw(Box::new(VdirsyncerStorageGetResult {
|
||||
item: Box::into_raw(Box::new(item)),
|
||||
etag: CString::new(href).unwrap().into_raw()
|
||||
}))
|
||||
},
|
||||
Err(e) => {
|
||||
e.fill_c_err(err);
|
||||
mem::zeroed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_{name}_upload(
|
||||
storage: *mut {path},
|
||||
item: *mut Item,
|
||||
err: *mut VdirsyncerError
|
||||
) -> *mut VdirsyncerStorageUploadResult {
|
||||
match (*storage).upload((*item).clone()) {
|
||||
Ok((href, etag)) => {
|
||||
Box::into_raw(Box::new(VdirsyncerStorageUploadResult {
|
||||
href: CString::new(href).unwrap().into_raw(),
|
||||
etag: CString::new(etag).unwrap().into_raw()
|
||||
}))
|
||||
},
|
||||
Err(e) => {
|
||||
e.fill_c_err(err);
|
||||
mem::zeroed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_{name}_update(
|
||||
storage: *mut {path},
|
||||
c_href: *const c_char,
|
||||
item: *mut Item,
|
||||
c_etag: *const c_char,
|
||||
err: *mut VdirsyncerError
|
||||
) -> *const c_char {
|
||||
let href = CStr::from_ptr(c_href);
|
||||
let etag = CStr::from_ptr(c_etag);
|
||||
match (*storage).update(href.to_str().unwrap(), (*item).clone(), etag.to_str().unwrap()) {
|
||||
Ok(etag) => CString::new(etag).unwrap().into_raw(),
|
||||
Err(e) => {
|
||||
e.fill_c_err(err);
|
||||
mem::zeroed()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_{name}_delete(
|
||||
storage: *mut {path},
|
||||
c_href: *const c_char,
|
||||
c_etag: *const c_char,
|
||||
err: *mut VdirsyncerError
|
||||
) {
|
||||
let href = CStr::from_ptr(c_href);
|
||||
let etag = CStr::from_ptr(c_etag);
|
||||
match (*storage).delete(href.to_str().unwrap(), etag.to_str().unwrap()) {
|
||||
Ok(()) => (),
|
||||
Err(e) => e.fill_c_err(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_{name}_buffered(storage: *mut {path}) {
|
||||
(*storage).buffered();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_{name}_flush(
|
||||
storage: *mut {path},
|
||||
err: *mut VdirsyncerError
|
||||
) {
|
||||
match (*storage).flush() {
|
||||
Ok(_) => (),
|
||||
Err(e) => e.fill_c_err(err)
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
fn export_storage(f: &mut File, name: &str, path: &str) {
|
||||
// String formatting in rust is at compile time. That doesn't work well for our case.
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
TEMPLATE_EACH
|
||||
.replace("{name}", name)
|
||||
.replace("{path}", path)
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
|
||||
let mut f = File::create(Path::new(&crate_dir).join("src/storage/exports.rs")).unwrap();
|
||||
write!(f, "// Auto-generated, do not check in.\n").unwrap();
|
||||
write!(f, "use std::os::raw::c_char;\n").unwrap();
|
||||
write!(f, "use std::mem;\n").unwrap();
|
||||
write!(f, "use std::ffi::{{CStr, CString}};\n").unwrap();
|
||||
write!(f, "use errors::*;\n").unwrap();
|
||||
write!(f, "use item::Item;\n").unwrap();
|
||||
write!(f, "use super::VdirsyncerStorageListing;\n").unwrap();
|
||||
write!(f, "use super::VdirsyncerStorageGetResult;\n").unwrap();
|
||||
write!(f, "use super::VdirsyncerStorageUploadResult;\n").unwrap();
|
||||
write!(f, "use super::Storage;\n").unwrap();
|
||||
|
||||
write!(f, "use super::singlefile;\n").unwrap();
|
||||
export_storage(&mut f, "singlefile", "singlefile::SinglefileStorage");
|
||||
drop(f);
|
||||
|
||||
let _ = remove_file(Path::new(&crate_dir).join("target/vdirsyncer_rustext.h"));
|
||||
|
||||
let res = cbindgen::Builder::new()
|
||||
.with_crate(crate_dir)
|
||||
.with_language(cbindgen::Language::C)
|
||||
.generate();
|
||||
|
||||
match res {
|
||||
Ok(x) => x.write_to_file("target/vdirsyncer_rustext.h"),
|
||||
Err(e) => println!("FAILED TO GENERATE BINDINGS: {:?}", e),
|
||||
}
|
||||
}
|
||||
4
rust/cbindgen.toml
Normal file
4
rust/cbindgen.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
language = "C"
|
||||
|
||||
[parse]
|
||||
expand = ["vdirsyncer-rustext"]
|
||||
74
rust/src/errors.rs
Normal file
74
rust/src/errors.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
use failure;
|
||||
|
||||
pub type Fallible<T> = Result<T, failure::Error>;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "The item cannot be parsed")]
|
||||
pub struct ItemUnparseable;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "Unexpected version {}, expected {}", found, expected)]
|
||||
pub struct UnexpectedVobjectVersion {
|
||||
pub found: String,
|
||||
pub expected: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "Unexpected component {}, expected {}", found, expected)]
|
||||
pub struct UnexpectedVobject {
|
||||
pub found: String,
|
||||
pub expected: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "Item '{}' not found", href)]
|
||||
pub struct ItemNotFound {
|
||||
pub href: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "The href '{}' is already taken", href)]
|
||||
pub struct ItemAlreadyExisting {
|
||||
pub href: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "A wrong etag for '{}' was provided. Another client's requests might \
|
||||
conflict with vdirsyncer.",
|
||||
href)]
|
||||
pub struct WrongEtag {
|
||||
pub href: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "The mtime for '{}' has unexpectedly changed. Please close other programs\
|
||||
accessing this file.",
|
||||
filepath)]
|
||||
pub struct MtimeMismatch {
|
||||
pub filepath: String,
|
||||
}
|
||||
|
||||
pub unsafe fn export_result<V>(
|
||||
res: Result<V, failure::Error>,
|
||||
c_err: *mut *mut exports::ShippaiError,
|
||||
) -> Option<V> {
|
||||
match res {
|
||||
Ok(v) => Some(v),
|
||||
Err(e) => {
|
||||
*c_err = Box::into_raw(Box::new(e.into()));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod exports {
|
||||
shippai_export! {
|
||||
super::ItemUnparseable as ITEM_UNPARSEABLE,
|
||||
super::UnexpectedVobjectVersion as UNEXPECTED_VOBJECT_VERSION,
|
||||
super::UnexpectedVobject as UNEXPECTED_VOBJECT,
|
||||
super::ItemNotFound as ITEM_NOT_FOUND,
|
||||
super::ItemAlreadyExisting as ITEM_ALREADY_EXISTING,
|
||||
super::WrongEtag as WRONG_ETAG,
|
||||
super::MtimeMismatch as MTIME_MISMATCH
|
||||
}
|
||||
}
|
||||
|
|
@ -41,13 +41,13 @@ impl Item {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn with_uid(&self, uid: &str) -> Result<Self> {
|
||||
pub fn with_uid(&self, uid: &str) -> Fallible<Self> {
|
||||
if let Item::Parsed(ref component) = *self {
|
||||
let mut new_component = component.clone();
|
||||
change_uid(&mut new_component, uid);
|
||||
Ok(Item::from_raw(vobject::write_component(&new_component)))
|
||||
} else {
|
||||
Err(ErrorKind::ItemUnparseable.into())
|
||||
Err(ItemUnparseable.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,34 +60,34 @@ impl Item {
|
|||
}
|
||||
|
||||
/// Component of item if parseable
|
||||
pub fn get_component(&self) -> Result<&vobject::Component> {
|
||||
pub fn get_component(&self) -> Fallible<&vobject::Component> {
|
||||
match *self {
|
||||
Item::Parsed(ref component) => Ok(component),
|
||||
_ => Err(ErrorKind::ItemUnparseable.into()),
|
||||
_ => Err(ItemUnparseable.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Component of item if parseable
|
||||
pub fn into_component(self) -> Result<vobject::Component> {
|
||||
pub fn into_component(self) -> Fallible<vobject::Component> {
|
||||
match self {
|
||||
Item::Parsed(component) => Ok(component),
|
||||
_ => Err(ErrorKind::ItemUnparseable.into()),
|
||||
_ => Err(ItemUnparseable.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for etags
|
||||
pub fn get_hash(&self) -> Result<String> {
|
||||
pub fn get_hash(&self) -> Fallible<String> {
|
||||
// FIXME: cache
|
||||
if let Item::Parsed(ref component) = *self {
|
||||
Ok(hash_component(component))
|
||||
} else {
|
||||
Err(ErrorKind::ItemUnparseable.into())
|
||||
Err(ItemUnparseable.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for generating hrefs and matching up items during synchronization. This is either the
|
||||
/// UID or the hash of the item's content.
|
||||
pub fn get_ident(&self) -> Result<String> {
|
||||
pub fn get_ident(&self) -> Fallible<String> {
|
||||
if let Some(x) = self.get_uid() {
|
||||
return Ok(x);
|
||||
}
|
||||
|
|
@ -188,7 +188,7 @@ fn hash_component(c: &vobject::Component) -> String {
|
|||
|
||||
pub mod exports {
|
||||
use super::Item;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
use errors::*;
|
||||
|
|
@ -225,29 +225,25 @@ pub mod exports {
|
|||
pub unsafe extern "C" fn vdirsyncer_with_uid(
|
||||
c: *mut Item,
|
||||
uid: *const c_char,
|
||||
err: *mut VdirsyncerError,
|
||||
err: *mut *mut exports::ShippaiError,
|
||||
) -> *mut Item {
|
||||
let uid_cstring = CStr::from_ptr(uid);
|
||||
match (*c).with_uid(uid_cstring.to_str().unwrap()) {
|
||||
Ok(x) => Box::into_raw(Box::new(x)),
|
||||
Err(e) => {
|
||||
e.fill_c_err(err);
|
||||
mem::zeroed()
|
||||
}
|
||||
if let Some(x) = export_result((*c).with_uid(uid_cstring.to_str().unwrap()), err) {
|
||||
Box::into_raw(Box::new(x))
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_get_hash(
|
||||
c: *mut Item,
|
||||
err: *mut VdirsyncerError,
|
||||
err: *mut *mut exports::ShippaiError,
|
||||
) -> *const c_char {
|
||||
match (*c).get_hash() {
|
||||
Ok(x) => CString::new(x).unwrap().into_raw(),
|
||||
Err(e) => {
|
||||
e.fill_c_err(err);
|
||||
mem::zeroed()
|
||||
}
|
||||
if let Some(x) = export_result((*c).get_hash(), err) {
|
||||
CString::new(x).unwrap().into_raw()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
101
rust/src/lib.rs
101
rust/src/lib.rs
|
|
@ -1,104 +1,25 @@
|
|||
extern crate atomicwrites;
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate shippai;
|
||||
extern crate ring;
|
||||
extern crate vobject;
|
||||
|
||||
pub mod item;
|
||||
pub mod storage;
|
||||
|
||||
mod errors {
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
use vobject;
|
||||
use atomicwrites;
|
||||
|
||||
error_chain!{
|
||||
links {
|
||||
Vobject(vobject::error::VObjectError, vobject::error::VObjectErrorKind);
|
||||
}
|
||||
|
||||
foreign_links {
|
||||
Io(::std::io::Error);
|
||||
}
|
||||
|
||||
errors {
|
||||
ItemUnparseable {
|
||||
description("ItemUnparseable: The item cannot be parsed."),
|
||||
display("The item cannot be parsed."),
|
||||
}
|
||||
|
||||
VobjectVersionMismatch(first: String, second: String) {
|
||||
description("Incompatible vobject versions."),
|
||||
display("Conflict between {} and {}", first, second),
|
||||
}
|
||||
|
||||
UnexpectedVobject(found: String, expected: String) {
|
||||
description("Unexpected component type"),
|
||||
display("Found type {}, expected {}", found, expected),
|
||||
}
|
||||
|
||||
ItemNotFound(href: String) {
|
||||
description("ItemNotFound: The item could not be found"),
|
||||
display("The item '{}' could not be found", href),
|
||||
}
|
||||
|
||||
AlreadyExisting(href: String) {
|
||||
description("AlreadyExisting: An item at this href already exists"),
|
||||
display("The href '{}' is already taken", href),
|
||||
}
|
||||
|
||||
WrongEtag(href: String) {
|
||||
description("WrongEtag: A wrong etag was provided."),
|
||||
display("A wrong etag for '{}' was provided. This indicates that two clients are writing data at the same time.", href),
|
||||
}
|
||||
|
||||
MtimeMismatch(filepath: String) {
|
||||
description("MtimeMismatch: Two programs access the same file."),
|
||||
display("The mtime of {} has unexpectedly changed. Please close other programs accessing this file.", filepath),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<atomicwrites::Error<Error>> for Error {
|
||||
fn from(e: atomicwrites::Error<Error>) -> Error {
|
||||
match e {
|
||||
atomicwrites::Error::Internal(x) => x.into(),
|
||||
atomicwrites::Error::User(x) => x,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ErrorExt: ::std::error::Error {
|
||||
unsafe fn fill_c_err(&self, err: *mut VdirsyncerError) {
|
||||
(*err).failed = true;
|
||||
(*err).msg = CString::new(self.description()).unwrap().into_raw();
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorExt for Error {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct VdirsyncerError {
|
||||
pub failed: bool,
|
||||
pub msg: *mut c_char,
|
||||
}
|
||||
}
|
||||
mod item;
|
||||
mod storage;
|
||||
mod errors;
|
||||
|
||||
pub mod exports {
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ptr;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
use errors::*;
|
||||
|
||||
pub use super::item::exports::*;
|
||||
pub use super::storage::exports::*;
|
||||
pub use super::errors::exports::*;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_free_str(s: *const c_char) {
|
||||
CStr::from_ptr(s);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_clear_err(e: *mut VdirsyncerError) {
|
||||
CString::from_raw((*e).msg);
|
||||
(*e).msg = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
193
rust/src/storage/exports.rs
Normal file
193
rust/src/storage/exports.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
use std::ffi::{CStr, CString};
|
||||
use errors::*;
|
||||
use item::Item;
|
||||
use super::Storage;
|
||||
pub use super::singlefile::exports::*;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_free(storage: *mut Box<Storage>) {
|
||||
let _: Box<Box<Storage>> = Box::from_raw(storage);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_list(
|
||||
storage: *mut Box<Storage>,
|
||||
err: *mut *mut exports::ShippaiError,
|
||||
) -> *mut VdirsyncerStorageListing {
|
||||
if let Some(x) = export_result((**storage).list(), err) {
|
||||
Box::into_raw(Box::new(VdirsyncerStorageListing {
|
||||
iterator: x,
|
||||
href: None,
|
||||
etag: None,
|
||||
}))
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_get(
|
||||
storage: *mut Box<Storage>,
|
||||
c_href: *const c_char,
|
||||
err: *mut *mut exports::ShippaiError,
|
||||
) -> *mut VdirsyncerStorageGetResult {
|
||||
let href = CStr::from_ptr(c_href);
|
||||
if let Some((item, href)) = export_result((**storage).get(href.to_str().unwrap()), err) {
|
||||
Box::into_raw(Box::new(VdirsyncerStorageGetResult {
|
||||
item: Box::into_raw(Box::new(item)),
|
||||
etag: CString::new(href).unwrap().into_raw(),
|
||||
}))
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_upload(
|
||||
storage: *mut Box<Storage>,
|
||||
item: *mut Item,
|
||||
err: *mut *mut exports::ShippaiError,
|
||||
) -> *mut VdirsyncerStorageUploadResult {
|
||||
if let Some((href, etag)) = export_result((**storage).upload((*item).clone()), err) {
|
||||
Box::into_raw(Box::new(VdirsyncerStorageUploadResult {
|
||||
href: CString::new(href).unwrap().into_raw(),
|
||||
etag: CString::new(etag).unwrap().into_raw(),
|
||||
}))
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_update(
|
||||
storage: *mut Box<Storage>,
|
||||
c_href: *const c_char,
|
||||
item: *mut Item,
|
||||
c_etag: *const c_char,
|
||||
err: *mut *mut exports::ShippaiError,
|
||||
) -> *const c_char {
|
||||
let href = CStr::from_ptr(c_href);
|
||||
let etag = CStr::from_ptr(c_etag);
|
||||
let res = (**storage).update(
|
||||
href.to_str().unwrap(),
|
||||
(*item).clone(),
|
||||
etag.to_str().unwrap(),
|
||||
);
|
||||
if let Some(etag) = export_result(res, err) {
|
||||
CString::new(etag).unwrap().into_raw()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_delete(
|
||||
storage: *mut Box<Storage>,
|
||||
c_href: *const c_char,
|
||||
c_etag: *const c_char,
|
||||
err: *mut *mut exports::ShippaiError,
|
||||
) {
|
||||
let href = CStr::from_ptr(c_href);
|
||||
let etag = CStr::from_ptr(c_etag);
|
||||
let res = (**storage).delete(href.to_str().unwrap(), etag.to_str().unwrap());
|
||||
let _ = export_result(res, err);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_buffered(storage: *mut Box<Storage>) {
|
||||
(**storage).buffered();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_flush(
|
||||
storage: *mut Box<Storage>,
|
||||
err: *mut *mut exports::ShippaiError,
|
||||
) {
|
||||
let _ = export_result((**storage).flush(), err);
|
||||
}
|
||||
|
||||
pub struct VdirsyncerStorageListing {
|
||||
iterator: Box<Iterator<Item = (String, String)>>,
|
||||
href: Option<String>,
|
||||
etag: Option<String>,
|
||||
}
|
||||
|
||||
impl VdirsyncerStorageListing {
|
||||
pub fn advance(&mut self) -> bool {
|
||||
match self.iterator.next() {
|
||||
Some((href, etag)) => {
|
||||
self.href = Some(href);
|
||||
self.etag = Some(etag);
|
||||
true
|
||||
}
|
||||
None => {
|
||||
self.href = None;
|
||||
self.etag = None;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_href(&mut self) -> Option<String> {
|
||||
self.href.take()
|
||||
}
|
||||
pub fn get_etag(&mut self) -> Option<String> {
|
||||
self.etag.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_free_storage_listing(listing: *mut VdirsyncerStorageListing) {
|
||||
let _: Box<VdirsyncerStorageListing> = Box::from_raw(listing);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_advance_storage_listing(
|
||||
listing: *mut VdirsyncerStorageListing,
|
||||
) -> bool {
|
||||
(*listing).advance()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_listing_get_href(
|
||||
listing: *mut VdirsyncerStorageListing,
|
||||
) -> *const c_char {
|
||||
CString::new((*listing).get_href().unwrap())
|
||||
.unwrap()
|
||||
.into_raw()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_listing_get_etag(
|
||||
listing: *mut VdirsyncerStorageListing,
|
||||
) -> *const c_char {
|
||||
CString::new((*listing).get_etag().unwrap())
|
||||
.unwrap()
|
||||
.into_raw()
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct VdirsyncerStorageGetResult {
|
||||
pub item: *mut Item,
|
||||
pub etag: *const c_char,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_free_storage_get_result(res: *mut VdirsyncerStorageGetResult) {
|
||||
let _: Box<VdirsyncerStorageGetResult> = Box::from_raw(res);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct VdirsyncerStorageUploadResult {
|
||||
pub href: *const c_char,
|
||||
pub etag: *const c_char,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_free_storage_upload_result(
|
||||
res: *mut VdirsyncerStorageUploadResult,
|
||||
) {
|
||||
let _: Box<VdirsyncerStorageUploadResult> = Box::from_raw(res);
|
||||
}
|
||||
|
|
@ -1,40 +1,20 @@
|
|||
pub mod singlefile;
|
||||
pub mod exports;
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
use errors::*;
|
||||
use errors::Fallible;
|
||||
use item::Item;
|
||||
|
||||
type ItemAndEtag = (Item, String);
|
||||
|
||||
pub trait Storage: Sized {
|
||||
pub trait Storage {
|
||||
/// returns an iterator of `(href, etag)`
|
||||
fn list<'a>(&'a mut self) -> Result<Box<Iterator<Item = (String, String)> + 'a>>;
|
||||
fn list<'a>(&'a mut self) -> Fallible<Box<Iterator<Item = (String, String)> + 'a>>;
|
||||
|
||||
///Fetch a single item.
|
||||
///
|
||||
///:param href: href to fetch
|
||||
///:returns: (item, etag)
|
||||
///:raises: :exc:`vdirsyncer.exceptions.PreconditionFailed` if item can't be found.
|
||||
fn get(&mut self, href: &str) -> Result<ItemAndEtag>;
|
||||
|
||||
/// Fetch multiple items. Duplicate hrefs must be ignored.
|
||||
///
|
||||
/// Functionally similar to `get`, but might bring performance benefits on some storages when
|
||||
/// used cleverly.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `hrefs`: list of hrefs to fetch
|
||||
/// - returns an iterator of `(href, item, etag)`
|
||||
fn get_multi<'a, I: Iterator<Item = String> + 'a>(
|
||||
&'a mut self,
|
||||
hrefs: I,
|
||||
) -> Box<Iterator<Item = (String, Result<ItemAndEtag>)> + 'a> {
|
||||
Box::new(DefaultGetMultiIterator {
|
||||
storage: self,
|
||||
href_iter: hrefs,
|
||||
})
|
||||
}
|
||||
fn get(&mut self, href: &str) -> Fallible<ItemAndEtag>;
|
||||
|
||||
/// Upload a new item.
|
||||
///
|
||||
|
|
@ -43,17 +23,17 @@ pub trait Storage: Sized {
|
|||
/// special case only exists because of DAV. Avoid this situation whenever possible.
|
||||
///
|
||||
/// Returns `(href, etag)`
|
||||
fn upload(&mut self, item: Item) -> Result<(String, String)>;
|
||||
fn upload(&mut self, item: Item) -> Fallible<(String, String)>;
|
||||
|
||||
/// Update an item.
|
||||
///
|
||||
/// The etag may be none in some cases, see `upload`.
|
||||
///
|
||||
/// Returns `etag`
|
||||
fn update(&mut self, href: &str, item: Item, etag: &str) -> Result<String>;
|
||||
fn update(&mut self, href: &str, item: Item, etag: &str) -> Fallible<String>;
|
||||
|
||||
/// Delete an item by href.
|
||||
fn delete(&mut self, href: &str, etag: &str) -> Result<()>;
|
||||
fn delete(&mut self, href: &str, etag: &str) -> Fallible<()>;
|
||||
|
||||
/// Enter buffered mode for storages that support it.
|
||||
///
|
||||
|
|
@ -64,111 +44,7 @@ pub trait Storage: Sized {
|
|||
fn buffered(&mut self) {}
|
||||
|
||||
/// Write back all changes to the collection.
|
||||
fn flush(&mut self) -> Result<()> {
|
||||
fn flush(&mut self) -> Fallible<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct DefaultGetMultiIterator<'a, S: Storage + 'a, I: Iterator<Item = String>> {
|
||||
storage: &'a mut S,
|
||||
href_iter: I,
|
||||
}
|
||||
|
||||
impl<'a, S, I> Iterator for DefaultGetMultiIterator<'a, S, I>
|
||||
where
|
||||
S: Storage,
|
||||
I: Iterator<Item = String>,
|
||||
{
|
||||
type Item = (String, Result<ItemAndEtag>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.href_iter.next() {
|
||||
Some(x) => Some((x.to_owned(), self.storage.get(&x))),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VdirsyncerStorageListing {
|
||||
iterator: Box<Iterator<Item = (String, String)>>,
|
||||
href: Option<String>,
|
||||
etag: Option<String>,
|
||||
}
|
||||
|
||||
impl VdirsyncerStorageListing {
|
||||
pub fn advance(&mut self) -> bool {
|
||||
match self.iterator.next() {
|
||||
Some((href, etag)) => {
|
||||
self.href = Some(href);
|
||||
self.etag = Some(etag);
|
||||
true
|
||||
}
|
||||
None => {
|
||||
self.href = None;
|
||||
self.etag = None;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_href(&mut self) -> Option<String> {
|
||||
self.href.take()
|
||||
}
|
||||
pub fn get_etag(&mut self) -> Option<String> {
|
||||
self.etag.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_free_storage_listing(listing: *mut VdirsyncerStorageListing) {
|
||||
let _: Box<VdirsyncerStorageListing> = Box::from_raw(listing);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_advance_storage_listing(
|
||||
listing: *mut VdirsyncerStorageListing,
|
||||
) -> bool {
|
||||
(*listing).advance()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_listing_get_href(
|
||||
listing: *mut VdirsyncerStorageListing,
|
||||
) -> *const c_char {
|
||||
CString::new((*listing).get_href().unwrap())
|
||||
.unwrap()
|
||||
.into_raw()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_storage_listing_get_etag(
|
||||
listing: *mut VdirsyncerStorageListing,
|
||||
) -> *const c_char {
|
||||
CString::new((*listing).get_etag().unwrap())
|
||||
.unwrap()
|
||||
.into_raw()
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct VdirsyncerStorageGetResult {
|
||||
pub item: *mut Item,
|
||||
pub etag: *const c_char,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_free_storage_get_result(res: *mut VdirsyncerStorageGetResult) {
|
||||
let _: Box<VdirsyncerStorageGetResult> = Box::from_raw(res);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct VdirsyncerStorageUploadResult {
|
||||
pub href: *const c_char,
|
||||
pub etag: *const c_char,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_free_storage_upload_result(
|
||||
res: *mut VdirsyncerStorageUploadResult,
|
||||
) {
|
||||
let _: Box<VdirsyncerStorageUploadResult> = Box::from_raw(res);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::btree_map::Entry::*;
|
||||
|
|
@ -34,14 +32,14 @@ impl SinglefileStorage {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_items(&mut self) -> Result<&mut ItemCache> {
|
||||
fn get_items(&mut self) -> Fallible<&mut ItemCache> {
|
||||
if self.items_cache.is_none() {
|
||||
self.list()?;
|
||||
}
|
||||
Ok(&mut self.items_cache.as_mut().unwrap().0)
|
||||
}
|
||||
|
||||
fn write_back(&mut self) -> Result<()> {
|
||||
fn write_back(&mut self) -> Fallible<()> {
|
||||
self.dirty_cache = true;
|
||||
if self.buffered_mode {
|
||||
return Ok(());
|
||||
|
|
@ -52,19 +50,22 @@ impl SinglefileStorage {
|
|||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_init_singlefile(path: *const c_char) -> *mut SinglefileStorage {
|
||||
let cstring = CStr::from_ptr(path);
|
||||
Box::into_raw(Box::new(SinglefileStorage::new(cstring.to_str().unwrap())))
|
||||
}
|
||||
pub mod exports {
|
||||
use super::*;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_free_singlefile(s: *mut SinglefileStorage) {
|
||||
let _: Box<SinglefileStorage> = Box::from_raw(s);
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vdirsyncer_init_singlefile(path: *const c_char) -> *mut Box<Storage> {
|
||||
let cstring = CStr::from_ptr(path);
|
||||
Box::into_raw(Box::new(Box::new(SinglefileStorage::new(
|
||||
cstring.to_str().unwrap(),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
impl Storage for SinglefileStorage {
|
||||
fn list<'a>(&'a mut self) -> Result<Box<Iterator<Item = (String, String)> + 'a>> {
|
||||
fn list<'a>(&'a mut self) -> Fallible<Box<Iterator<Item = (String, String)> + 'a>> {
|
||||
let mut new_cache = BTreeMap::new();
|
||||
let mtime = metadata(&self.path)?.modified()?;
|
||||
let mut f = File::open(&self.path)?;
|
||||
|
|
@ -84,51 +85,61 @@ impl Storage for SinglefileStorage {
|
|||
)))
|
||||
}
|
||||
|
||||
fn get(&mut self, href: &str) -> Result<(Item, String)> {
|
||||
fn get(&mut self, href: &str) -> Fallible<(Item, String)> {
|
||||
match self.get_items()?.get(href) {
|
||||
Some(&(ref href, ref etag)) => Ok((href.clone(), etag.clone())),
|
||||
None => Err(ErrorKind::ItemNotFound(href.to_owned()))?,
|
||||
None => Err(ItemNotFound {
|
||||
href: href.to_owned(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
|
||||
fn upload(&mut self, item: Item) -> Result<(String, String)> {
|
||||
fn upload(&mut self, item: Item) -> Fallible<(String, String)> {
|
||||
let hash = item.get_hash()?;
|
||||
let href = item.get_ident()?;
|
||||
match self.get_items()?.entry(href.clone()) {
|
||||
Occupied(_) => Err(ErrorKind::AlreadyExisting(href.clone()))?,
|
||||
Occupied(_) => Err(ItemAlreadyExisting { href: href.clone() })?,
|
||||
Vacant(vc) => vc.insert((item, hash.clone())),
|
||||
};
|
||||
self.write_back()?;
|
||||
Ok((href, hash))
|
||||
}
|
||||
|
||||
fn update(&mut self, href: &str, item: Item, etag: &str) -> Result<String> {
|
||||
fn update(&mut self, href: &str, item: Item, etag: &str) -> Fallible<String> {
|
||||
let hash = match self.get_items()?.entry(href.to_owned()) {
|
||||
Occupied(mut oc) => {
|
||||
if oc.get().1 == etag {
|
||||
let hash = item.get_hash()?;
|
||||
oc.insert((item, hash.clone()));
|
||||
Ok(hash)
|
||||
hash
|
||||
} else {
|
||||
Err(ErrorKind::WrongEtag(href.to_owned()))
|
||||
Err(WrongEtag {
|
||||
href: href.to_owned(),
|
||||
})?
|
||||
}
|
||||
}
|
||||
Vacant(_) => Err(ErrorKind::ItemNotFound(href.to_owned())),
|
||||
}?;
|
||||
Vacant(_) => Err(ItemNotFound {
|
||||
href: href.to_owned(),
|
||||
})?,
|
||||
};
|
||||
self.write_back()?;
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
fn delete(&mut self, href: &str, etag: &str) -> Result<()> {
|
||||
fn delete(&mut self, href: &str, etag: &str) -> Fallible<()> {
|
||||
match self.get_items()?.entry(href.to_owned()) {
|
||||
Occupied(oc) => {
|
||||
if oc.get().1 == etag {
|
||||
oc.remove();
|
||||
} else {
|
||||
Err(ErrorKind::WrongEtag(href.to_owned()))?
|
||||
Err(WrongEtag {
|
||||
href: href.to_owned(),
|
||||
})?
|
||||
}
|
||||
}
|
||||
Vacant(_) => Err(ErrorKind::ItemNotFound(href.to_owned()))?,
|
||||
Vacant(_) => Err(ItemNotFound {
|
||||
href: href.to_owned(),
|
||||
})?,
|
||||
}
|
||||
self.write_back()?;
|
||||
Ok(())
|
||||
|
|
@ -138,7 +149,7 @@ impl Storage for SinglefileStorage {
|
|||
self.buffered_mode = true;
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<()> {
|
||||
fn flush(&mut self) -> Fallible<()> {
|
||||
if !self.dirty_cache {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -146,16 +157,21 @@ impl Storage for SinglefileStorage {
|
|||
|
||||
let af = AtomicFile::new(&self.path, AllowOverwrite);
|
||||
let content = join_collection(items.into_iter().map(|(_, (item, _))| item))?;
|
||||
af.write::<(), Error, _>(|f| {
|
||||
f.write_all(content.as_bytes())?;
|
||||
|
||||
let real_mtime = metadata(&self.path)?.modified()?;
|
||||
let path = &self.path;
|
||||
let write_inner = |f: &mut File| -> Fallible<()> {
|
||||
f.write_all(content.as_bytes())?;
|
||||
let real_mtime = metadata(path)?.modified()?;
|
||||
if mtime != real_mtime {
|
||||
Err(ErrorKind::MtimeMismatch(
|
||||
self.path.to_string_lossy().into_owned(),
|
||||
))?;
|
||||
Err(MtimeMismatch {
|
||||
filepath: path.to_string_lossy().into_owned(),
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
af.write::<(), ::failure::Compat<::failure::Error>, _>(|f| {
|
||||
write_inner(f).map_err(|e| e.compat())
|
||||
})?;
|
||||
|
||||
self.dirty_cache = false;
|
||||
|
|
@ -164,10 +180,11 @@ impl Storage for SinglefileStorage {
|
|||
}
|
||||
}
|
||||
|
||||
fn split_collection(mut input: &str) -> Result<Vec<vobject::Component>> {
|
||||
fn split_collection(mut input: &str) -> Fallible<Vec<vobject::Component>> {
|
||||
let mut rv = vec![];
|
||||
while !input.is_empty() {
|
||||
let (component, remainder) = vobject::read_component(input)?;
|
||||
let (component, remainder) =
|
||||
vobject::read_component(input).map_err(::failure::SyncFailure::new)?;
|
||||
input = remainder;
|
||||
|
||||
match component.name.as_ref() {
|
||||
|
|
@ -175,17 +192,17 @@ fn split_collection(mut input: &str) -> Result<Vec<vobject::Component>> {
|
|||
"VCARD" => rv.push(component),
|
||||
"VADDRESSBOOK" => for vcard in component.subcomponents {
|
||||
if vcard.name != "VCARD" {
|
||||
Err(ErrorKind::UnexpectedVobject(
|
||||
vcard.name.clone(),
|
||||
"VCARD".to_owned(),
|
||||
))?;
|
||||
Err(UnexpectedVobject {
|
||||
found: vcard.name.clone(),
|
||||
expected: "VCARD".to_owned(),
|
||||
})?;
|
||||
}
|
||||
rv.push(vcard);
|
||||
},
|
||||
_ => Err(ErrorKind::UnexpectedVobject(
|
||||
component.name.clone(),
|
||||
"VCALENDAR | VCARD | VADDRESSBOOK".to_owned(),
|
||||
))?,
|
||||
_ => Err(UnexpectedVobject {
|
||||
found: component.name.clone(),
|
||||
expected: "VCALENDAR | VCARD | VADDRESSBOOK".to_owned(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +211,7 @@ fn split_collection(mut input: &str) -> Result<Vec<vobject::Component>> {
|
|||
|
||||
/// Split one VCALENDAR component into multiple VCALENDAR components
|
||||
#[inline]
|
||||
fn split_vcalendar(mut vcalendar: vobject::Component) -> Result<Vec<vobject::Component>> {
|
||||
fn split_vcalendar(mut vcalendar: vobject::Component) -> Fallible<Vec<vobject::Component>> {
|
||||
vcalendar.props.remove("METHOD");
|
||||
|
||||
let mut timezones = BTreeMap::new(); // tzid => component
|
||||
|
|
@ -210,10 +227,10 @@ fn split_vcalendar(mut vcalendar: vobject::Component) -> Result<Vec<vobject::Com
|
|||
timezones.insert(tzid, component);
|
||||
}
|
||||
"VTODO" | "VEVENT" | "VJOURNAL" => subcomponents.push(component),
|
||||
_ => Err(ErrorKind::UnexpectedVobject(
|
||||
component.name.clone(),
|
||||
"VTIMEZONE | VTODO | VEVENT | VJOURNAL".to_owned(),
|
||||
))?,
|
||||
_ => Err(UnexpectedVobject {
|
||||
found: component.name.clone(),
|
||||
expected: "VTIMEZONE | VTODO | VEVENT | VJOURNAL".to_owned(),
|
||||
})?,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -262,7 +279,7 @@ fn split_vcalendar(mut vcalendar: vobject::Component) -> Result<Vec<vobject::Com
|
|||
.collect())
|
||||
}
|
||||
|
||||
fn join_collection<I: Iterator<Item = Item>>(item_iter: I) -> Result<String> {
|
||||
fn join_collection<I: Iterator<Item = Item>>(item_iter: I) -> Fallible<String> {
|
||||
let mut items = item_iter.peekable();
|
||||
|
||||
let item_name = match items.peek() {
|
||||
|
|
@ -273,10 +290,10 @@ fn join_collection<I: Iterator<Item = Item>>(item_iter: I) -> Result<String> {
|
|||
let wrapper_name = match item_name.as_ref() {
|
||||
"VCARD" => "VADDRESSBOOK",
|
||||
"VCALENDAR" => "VCALENDAR",
|
||||
_ => Err(ErrorKind::UnexpectedVobject(
|
||||
item_name.clone(),
|
||||
"VCARD | VCALENDAR".to_owned(),
|
||||
))?,
|
||||
_ => Err(UnexpectedVobject {
|
||||
found: item_name.clone(),
|
||||
expected: "VCARD | VCALENDAR".to_owned(),
|
||||
})?,
|
||||
};
|
||||
|
||||
let mut wrapper = vobject::Component::new(wrapper_name);
|
||||
|
|
@ -285,17 +302,20 @@ fn join_collection<I: Iterator<Item = Item>>(item_iter: I) -> Result<String> {
|
|||
for item in items {
|
||||
let mut c = item.into_component()?;
|
||||
if c.name != item_name {
|
||||
return Err(ErrorKind::UnexpectedVobject(c.name, item_name.clone()).into());
|
||||
return Err(UnexpectedVobject {
|
||||
found: c.name,
|
||||
expected: item_name.clone(),
|
||||
}.into());
|
||||
}
|
||||
|
||||
if item_name == wrapper_name {
|
||||
wrapper.subcomponents.extend(c.subcomponents.drain(..));
|
||||
match (version.as_ref(), c.get_only("VERSION")) {
|
||||
(Some(x), Some(y)) if x.raw_value != y.raw_value => {
|
||||
return Err(ErrorKind::VobjectVersionMismatch(
|
||||
x.raw_value.clone(),
|
||||
y.raw_value.clone(),
|
||||
).into());
|
||||
return Err(UnexpectedVobjectVersion {
|
||||
expected: x.raw_value.clone(),
|
||||
found: y.raw_value.clone(),
|
||||
}.into());
|
||||
}
|
||||
(None, Some(_)) => (),
|
||||
_ => continue,
|
||||
|
|
|
|||
90
rust/vdirsyncer_rustext.h
Normal file
90
rust/vdirsyncer_rustext.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct Box_Storage Box_Storage;
|
||||
|
||||
typedef struct Item Item;
|
||||
|
||||
typedef struct ShippaiError ShippaiError;
|
||||
|
||||
typedef struct VdirsyncerStorageListing VdirsyncerStorageListing;
|
||||
|
||||
typedef struct {
|
||||
Item *item;
|
||||
const char *etag;
|
||||
} VdirsyncerStorageGetResult;
|
||||
|
||||
typedef struct {
|
||||
const char *href;
|
||||
const char *etag;
|
||||
} VdirsyncerStorageUploadResult;
|
||||
|
||||
void shippai_free_failure(ShippaiError *t);
|
||||
|
||||
void shippai_free_str(char *t);
|
||||
|
||||
const char *shippai_get_cause_display(ShippaiError *t);
|
||||
|
||||
const char *shippai_get_cause_name(ShippaiError *t);
|
||||
|
||||
const char *shippai_get_cause_names(void);
|
||||
|
||||
const char *shippai_get_debug(ShippaiError *t);
|
||||
|
||||
bool vdirsyncer_advance_storage_listing(VdirsyncerStorageListing *listing);
|
||||
|
||||
void vdirsyncer_free_item(Item *c);
|
||||
|
||||
void vdirsyncer_free_storage_get_result(VdirsyncerStorageGetResult *res);
|
||||
|
||||
void vdirsyncer_free_storage_listing(VdirsyncerStorageListing *listing);
|
||||
|
||||
void vdirsyncer_free_storage_upload_result(VdirsyncerStorageUploadResult *res);
|
||||
|
||||
void vdirsyncer_free_str(const char *s);
|
||||
|
||||
const char *vdirsyncer_get_hash(Item *c, ShippaiError **err);
|
||||
|
||||
const char *vdirsyncer_get_raw(Item *c);
|
||||
|
||||
const char *vdirsyncer_get_uid(Item *c);
|
||||
|
||||
Box_Storage *vdirsyncer_init_singlefile(const char *path);
|
||||
|
||||
Item *vdirsyncer_item_from_raw(const char *s);
|
||||
|
||||
bool vdirsyncer_item_is_parseable(Item *c);
|
||||
|
||||
void vdirsyncer_storage_buffered(Box_Storage *storage);
|
||||
|
||||
void vdirsyncer_storage_delete(Box_Storage *storage,
|
||||
const char *c_href,
|
||||
const char *c_etag,
|
||||
ShippaiError **err);
|
||||
|
||||
void vdirsyncer_storage_flush(Box_Storage *storage, ShippaiError **err);
|
||||
|
||||
void vdirsyncer_storage_free(Box_Storage *storage);
|
||||
|
||||
VdirsyncerStorageGetResult *vdirsyncer_storage_get(Box_Storage *storage,
|
||||
const char *c_href,
|
||||
ShippaiError **err);
|
||||
|
||||
VdirsyncerStorageListing *vdirsyncer_storage_list(Box_Storage *storage, ShippaiError **err);
|
||||
|
||||
const char *vdirsyncer_storage_listing_get_etag(VdirsyncerStorageListing *listing);
|
||||
|
||||
const char *vdirsyncer_storage_listing_get_href(VdirsyncerStorageListing *listing);
|
||||
|
||||
const char *vdirsyncer_storage_update(Box_Storage *storage,
|
||||
const char *c_href,
|
||||
Item *item,
|
||||
const char *c_etag,
|
||||
ShippaiError **err);
|
||||
|
||||
VdirsyncerStorageUploadResult *vdirsyncer_storage_upload(Box_Storage *storage,
|
||||
Item *item,
|
||||
ShippaiError **err);
|
||||
|
||||
Item *vdirsyncer_with_uid(Item *c, const char *uid, ShippaiError **err);
|
||||
8
setup.py
8
setup.py
|
|
@ -34,7 +34,10 @@ requirements = [
|
|||
|
||||
# https://github.com/untitaker/python-atomicwrites/commit/4d12f23227b6a944ab1d99c507a69fdbc7c9ed6d # noqa
|
||||
'atomicwrites>=0.1.7',
|
||||
milksnake
|
||||
|
||||
milksnake,
|
||||
|
||||
'shippai >= 0.1.1',
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -48,8 +51,7 @@ def build_native(spec):
|
|||
module_path='vdirsyncer._native',
|
||||
dylib=lambda: build.find_dylib(
|
||||
'vdirsyncer_rustext', in_path='rust/target/release'),
|
||||
header_filename=lambda: build.find_header(
|
||||
'vdirsyncer_rustext.h', in_path='rust/target'),
|
||||
header_filename='rust/vdirsyncer_rustext.h',
|
||||
# Rust bug: If thread-local storage is used, this flag is necessary
|
||||
# (mitsuhiko)
|
||||
rtld_flags=['NOW', 'NODELETE']
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
from ._native import ffi, lib
|
||||
import shippai
|
||||
|
||||
from . import exceptions
|
||||
from ._native import ffi, lib
|
||||
|
||||
|
||||
errors = shippai.Shippai(ffi, lib)
|
||||
|
||||
|
||||
def string_rv(c_str):
|
||||
|
|
@ -13,20 +18,16 @@ def item_rv(c):
|
|||
return ffi.gc(c, lib.vdirsyncer_free_item)
|
||||
|
||||
|
||||
def get_error_pointer():
|
||||
return ffi.new("ShippaiError **")
|
||||
|
||||
|
||||
def check_error(e):
|
||||
try:
|
||||
if e.failed:
|
||||
msg = ffi.string(e.msg).decode('utf-8')
|
||||
if msg.startswith('ItemNotFound'):
|
||||
raise exceptions.NotFoundError(msg)
|
||||
elif msg.startswith('AlreadyExisting'):
|
||||
raise exceptions.AlreadyExistingError(msg)
|
||||
elif msg.startswith('WrongEtag'):
|
||||
raise exceptions.WrongEtagError(msg)
|
||||
elif msg.startswith('ItemUnparseable'):
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
raise Exception(msg)
|
||||
finally:
|
||||
if e.failed:
|
||||
lib.vdirsyncer_clear_err(e)
|
||||
errors.check_exception(e[0])
|
||||
except errors.ItemNotFound as e:
|
||||
raise exceptions.NotFoundError(e)
|
||||
except errors.ItemAlreadyExisting as e:
|
||||
raise exceptions.AlreadyExistingError(e)
|
||||
except errors.WrongEtag as e:
|
||||
raise exceptions.WrongEtagError(e)
|
||||
|
|
|
|||
|
|
@ -8,13 +8,12 @@ class RustStorageMixin:
|
|||
|
||||
def _native(self, name):
|
||||
return partial(
|
||||
getattr(native.lib,
|
||||
'vdirsyncer_{}_{}'.format(self.storage_name, name)),
|
||||
getattr(native.lib, 'vdirsyncer_storage_{}'.format(name)),
|
||||
self._native_storage
|
||||
)
|
||||
|
||||
def list(self):
|
||||
e = native.ffi.new('VdirsyncerError *')
|
||||
e = native.get_error_pointer()
|
||||
listing = self._native('list')(e)
|
||||
native.check_error(e)
|
||||
listing = native.ffi.gc(listing,
|
||||
|
|
@ -28,7 +27,7 @@ class RustStorageMixin:
|
|||
|
||||
def get(self, href):
|
||||
href = href.encode('utf-8')
|
||||
e = native.ffi.new('VdirsyncerError *')
|
||||
e = native.get_error_pointer()
|
||||
result = self._native('get')(href, e)
|
||||
native.check_error(e)
|
||||
result = native.ffi.gc(result,
|
||||
|
|
@ -40,7 +39,7 @@ class RustStorageMixin:
|
|||
# FIXME: implement get_multi
|
||||
|
||||
def upload(self, item):
|
||||
e = native.ffi.new('VdirsyncerError *')
|
||||
e = native.get_error_pointer()
|
||||
result = self._native('upload')(item._native, e)
|
||||
native.check_error(e)
|
||||
result = native.ffi.gc(
|
||||
|
|
@ -52,7 +51,7 @@ class RustStorageMixin:
|
|||
def update(self, href, item, etag):
|
||||
href = href.encode('utf-8')
|
||||
etag = etag.encode('utf-8')
|
||||
e = native.ffi.new('VdirsyncerError *')
|
||||
e = native.get_error_pointer()
|
||||
etag = self._native('update')(href, item._native, etag, e)
|
||||
native.check_error(e)
|
||||
return native.string_rv(etag)
|
||||
|
|
@ -60,7 +59,7 @@ class RustStorageMixin:
|
|||
def delete(self, href, etag):
|
||||
href = href.encode('utf-8')
|
||||
etag = etag.encode('utf-8')
|
||||
e = native.ffi.new('VdirsyncerError *')
|
||||
e = native.get_error_pointer()
|
||||
self._native('delete')(href, etag, e)
|
||||
native.check_error(e)
|
||||
|
||||
|
|
@ -68,6 +67,6 @@ class RustStorageMixin:
|
|||
self._native('buffered')()
|
||||
|
||||
def flush(self):
|
||||
e = native.ffi.new('VdirsyncerError *')
|
||||
e = native.get_error_pointer()
|
||||
self._native('flush')(e)
|
||||
native.check_error(e)
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class SingleFileStorage(RustStorageMixin, Storage):
|
|||
|
||||
self._native_storage = native.ffi.gc(
|
||||
native.lib.vdirsyncer_init_singlefile(path.encode('utf-8')),
|
||||
native.lib.vdirsyncer_free_singlefile
|
||||
native.lib.vdirsyncer_storage_free
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class Item(object):
|
|||
new_uid = new_uid or ''
|
||||
assert isinstance(new_uid, str), type(new_uid)
|
||||
|
||||
e = native.ffi.new('VdirsyncerError *')
|
||||
e = native.get_error_pointer()
|
||||
rv = native.lib.vdirsyncer_with_uid(self._native,
|
||||
new_uid.encode('utf-8'),
|
||||
e)
|
||||
|
|
@ -49,7 +49,7 @@ class Item(object):
|
|||
|
||||
@cached_property
|
||||
def hash(self):
|
||||
e = native.ffi.new('VdirsyncerError *')
|
||||
e = native.get_error_pointer()
|
||||
rv = native.lib.vdirsyncer_get_hash(self._native, e)
|
||||
native.check_error(e)
|
||||
return native.string_rv(rv)
|
||||
|
|
|
|||
Loading…
Reference in a new issue