first cut of the C api
This commit is contained in:
parent
c6b6d59aa1
commit
c45351b121
12 changed files with 445 additions and 24 deletions
|
@ -2,6 +2,7 @@
|
|||
|
||||
members = [
|
||||
"automerge",
|
||||
"automerge-c",
|
||||
"automerge-cli",
|
||||
"automerge-backend",
|
||||
"automerge-backend-wasm",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//#![feature(set_stdio)]
|
||||
|
||||
use automerge_backend::{ActorID, AutomergeError, Backend, ChangeRequest, Clock, ChangeHash};
|
||||
use automerge_backend::{ActorID, AutomergeError, Backend, ChangeRequest, ChangeHash};
|
||||
use js_sys::{Array, Uint8Array};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
@ -102,17 +102,10 @@ impl State {
|
|||
|
||||
#[wasm_bindgen(js_name = getMissingDeps)]
|
||||
pub fn get_missing_deps(&self) -> Result<JsValue, JsValue> {
|
||||
let clock = self.backend.get_missing_deps();
|
||||
rust_to_js(&clock)
|
||||
let hashes = self.backend.get_missing_deps();
|
||||
rust_to_js(&hashes)
|
||||
}
|
||||
|
||||
/*
|
||||
#[wasm_bindgen(js_name = getClock)]
|
||||
pub fn get_clock(&self) -> Result<JsValue, JsValue> {
|
||||
rust_to_js(&self.backend.clock)
|
||||
}
|
||||
*/
|
||||
|
||||
#[wasm_bindgen(js_name = getUndoStack)]
|
||||
pub fn get_undo_stack(&self) -> Result<JsValue, JsValue> {
|
||||
rust_to_js(&self.backend.undo_stack)
|
||||
|
|
|
@ -10,6 +10,7 @@ impl ActorMap {
|
|||
ActorMap(Vec::new())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn index_of(&mut self, actor: &ActorID) -> usize {
|
||||
if let Some(index) = self.0.iter().position(|a| a == actor) {
|
||||
return index
|
||||
|
@ -18,6 +19,7 @@ impl ActorMap {
|
|||
self.0.len() - 1
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn actor_for(&self, index: usize) -> Option<&ActorID> {
|
||||
self.0.get(index)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,12 @@ edition = "2018"
|
|||
name = "automerge"
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
automerge-backend = { path = "../automerge-backend" }
|
||||
libc = "^0.2"
|
||||
serde = "^1.0"
|
||||
serde_json = "^1.0"
|
||||
errno = "^0.2"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "^0.14"
|
||||
|
|
|
@ -1,17 +1,30 @@
|
|||
|
||||
CC=gcc
|
||||
CFLAGS=-I.
|
||||
DEPS = automerge.h
|
||||
LIBS=-lautomerge -lpthread -ldl
|
||||
LDIR=../target/debug
|
||||
DEPS=automerge.h
|
||||
LIBS=-lautomerge -lpthread -ldl -lm
|
||||
LDIR=../target/release
|
||||
LIB=../target/release/libautomerge.a
|
||||
DEBUG_LIB=../target/debug/libautomerge.a
|
||||
|
||||
all: automerge $(LIB)
|
||||
|
||||
debug: LDIR=../target/debug
|
||||
debug: automerge $(DEBUG_LIB)
|
||||
|
||||
automerge: automerge.o $(LDIR)/libautomerge.a
|
||||
$(CC) -o $@ automerge.o $(LIBS) -L$(LDIR)
|
||||
|
||||
$(DEBUG_LIB): src/lib.rs
|
||||
cargo build
|
||||
|
||||
$(LIB): src/lib.rs
|
||||
cargo build --release
|
||||
|
||||
%.o: %.c $(DEPS)
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
automerge: automerge.o
|
||||
$(CC) -o $@ $^ $(LIBS) -L$(LDIR)
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
clean:
|
||||
rm -f *.o automerge
|
||||
rm -f *.o automerge $(LIB) $(DEBUG_LIB)
|
||||
|
|
|
@ -4,6 +4,14 @@
|
|||
#include "automerge.h"
|
||||
|
||||
int main() {
|
||||
int sum = add(1,2);
|
||||
printf("1 + 2 = %d\n", sum);
|
||||
printf("begin\n");
|
||||
Backend * b = automerge_init();
|
||||
const char * request = R"({"requestType":"change","actor":"111111","seq":1,"time":0,"version":0,"ops":[{"action":"set","obj":"00000000-0000-0000-0000-000000000000","key":"bird","value":"magpie"}]})";
|
||||
printf("request: %s\n",request);
|
||||
const char * patch = automerge_apply_local_change(b, request);
|
||||
printf("patch: %s\n",patch);
|
||||
printf("free resources\n");
|
||||
automerge_free_string(patch);
|
||||
automerge_free(b);
|
||||
printf("end\n");
|
||||
}
|
||||
|
|
|
@ -1,3 +1,61 @@
|
|||
#ifndef automerge_h
|
||||
#define automerge_h
|
||||
|
||||
int add(int a, int b);
|
||||
/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct Backend Backend;
|
||||
|
||||
typedef struct {
|
||||
uintptr_t len;
|
||||
uintptr_t cap;
|
||||
uint8_t *ptr;
|
||||
} Change;
|
||||
|
||||
typedef struct {
|
||||
uintptr_t len;
|
||||
uintptr_t cap;
|
||||
Change *ptr;
|
||||
} Changes;
|
||||
|
||||
typedef struct {
|
||||
uintptr_t len;
|
||||
uintptr_t cap;
|
||||
uint8_t *ptr;
|
||||
} Document;
|
||||
|
||||
const char *automerge_apply_changes(Backend *backend, uintptr_t num_changes, const Change *changes);
|
||||
|
||||
const char *automerge_apply_local_change(Backend *backend, const char *request);
|
||||
|
||||
Backend *automerge_clone(Backend *backend);
|
||||
|
||||
void automerge_free(Backend *backend);
|
||||
|
||||
void automerge_free_change(Change *change);
|
||||
|
||||
void automerge_free_changes(Changes *changes);
|
||||
|
||||
void automerge_free_document(Document *doc);
|
||||
|
||||
void automerge_free_string(const char *string);
|
||||
|
||||
const Changes *automerge_get_changes(Backend *backend, uintptr_t len, const uint8_t *binary);
|
||||
|
||||
const Changes *automerge_get_changes_for_actor(Backend *backend, const char *actor);
|
||||
|
||||
const char *automerge_get_missing_deps(Backend *backend);
|
||||
|
||||
const char *automerge_get_patch(Backend *backend);
|
||||
|
||||
Backend *automerge_init(void);
|
||||
|
||||
Backend *automerge_load(uintptr_t len, const uint8_t *binary);
|
||||
|
||||
void automerge_load_changes(Backend *backend, uintptr_t num_changes, const Change *changes);
|
||||
|
||||
const Document *automerge_save(Backend *backend);
|
||||
|
||||
#endif /* automerge_h */
|
||||
|
|
15
automerge-c/build.rs
Normal file
15
automerge-c/build.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
extern crate cbindgen;
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let crate_dir = PathBuf::from(
|
||||
env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR env var is not defined"),
|
||||
);
|
||||
|
||||
let config = cbindgen::Config::from_file("cbindgen.toml")
|
||||
.expect("Unable to find cbindgen.toml configuration file");
|
||||
|
||||
cbindgen::generate_with_config(&crate_dir, config).expect("ohno").write_to_file(crate_dir.join("automerge.h"));
|
||||
}
|
8
automerge-c/cbindgen.toml
Normal file
8
automerge-c/cbindgen.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
include_guard = "automerge_h"
|
||||
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
|
||||
language = "C"
|
||||
includes = []
|
||||
sys_includes = ["stdint.h", "stdbool.h"]
|
||||
no_includes = true
|
||||
line_length = 140
|
||||
|
|
@ -1,7 +1,295 @@
|
|||
#![feature(vec_into_raw_parts)]
|
||||
|
||||
extern crate libc;
|
||||
extern crate errno;
|
||||
extern crate serde;
|
||||
extern crate automerge_backend;
|
||||
|
||||
use automerge_backend::{ChangeRequest, Patch, ActorID};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ptr;
|
||||
use std::os::raw::{ c_void, c_char };
|
||||
use serde::ser::Serialize;
|
||||
use errno::{set_errno,Errno};
|
||||
|
||||
fn to_json<T: Serialize>(data: T) -> *const c_char {
|
||||
let json = serde_json::to_string(&data).unwrap();
|
||||
let json = CString::new(json).unwrap();
|
||||
json.into_raw()
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Backend(automerge_backend::Backend);
|
||||
|
||||
impl Deref for Backend {
|
||||
type Target = automerge_backend::Backend;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn from_buf_raw<T>(ptr: *const T, elts: usize) -> Vec<T> {
|
||||
let mut dst = Vec::with_capacity(elts);
|
||||
dst.set_len(elts);
|
||||
ptr::copy(ptr, dst.as_mut_ptr(), elts);
|
||||
dst
|
||||
}
|
||||
|
||||
impl DerefMut for Backend {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Value {
|
||||
pub datatype: usize,
|
||||
pub data: *mut c_void,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Document {
|
||||
pub len: usize,
|
||||
pub cap: usize,
|
||||
pub ptr: *mut u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Change {
|
||||
pub len: usize,
|
||||
pub cap: usize,
|
||||
pub ptr: *mut u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Changes {
|
||||
pub len: usize,
|
||||
pub cap: usize,
|
||||
pub ptr: *mut Change,
|
||||
}
|
||||
|
||||
impl From<*mut Backend> for Backend {
|
||||
fn from(b: *mut Backend) -> Self {
|
||||
unsafe {
|
||||
*Box::from_raw(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Backend> for *mut Backend {
|
||||
fn from(b: Backend) -> Self {
|
||||
Box::into_raw(Box::new(b))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Change {
|
||||
fn from(v: Vec<u8>) -> Self {
|
||||
let (ptr,len,cap) = v.into_raw_parts();
|
||||
Change {
|
||||
ptr, len, cap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Change>> for Changes {
|
||||
fn from(v: Vec<Change>) -> Self {
|
||||
let (ptr,len,cap) = v.into_raw_parts();
|
||||
Changes {
|
||||
ptr, len, cap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Changes> for *const Changes {
|
||||
fn from(c: Changes) -> Self {
|
||||
Box::into_raw(Box::new(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Document {
|
||||
fn from(v: Vec<u8>) -> Self {
|
||||
let (ptr,len,cap) = v.into_raw_parts();
|
||||
Document {
|
||||
ptr, len, cap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Document> for *const Document {
|
||||
fn from(c: Document) -> Self {
|
||||
Box::into_raw(Box::new(c))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
init => automerge_init
|
||||
clone => automerge_clone
|
||||
free => automerge_free
|
||||
save => automerge_save
|
||||
load => automerge_load
|
||||
applyLocalChange => automerge_apply_local_change
|
||||
getPatch => automerge_get_patch
|
||||
applyChanges => automerge_apply_changes
|
||||
loadChanges => automerge_load_changes
|
||||
getChangesForActor => automerge_get_changes_for_actor
|
||||
getChanges => automerge_get_changes
|
||||
getMissingDeps => automerge_get_missing_deps
|
||||
getUndoStack => ..
|
||||
getRedoStack => ..
|
||||
*/
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add(a: i32, b:i32) -> i32 {
|
||||
a + b
|
||||
pub extern "C" fn automerge_init() -> *mut Backend {
|
||||
Backend(automerge_backend::Backend::init()).into()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_free(backend: *mut Backend) {
|
||||
let backend : Backend = backend.into();
|
||||
drop(backend)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_free_string(string: *const c_char) {
|
||||
let string : CString = CString::from_raw(std::mem::transmute(string));
|
||||
drop(string);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_free_changes(changes: *mut Changes) {
|
||||
let changes = Box::from_raw(changes);
|
||||
let changes : Vec<Change> = Vec::from_raw_parts(changes.ptr, changes.len, changes.cap);
|
||||
let data : Vec<Vec<u8>> = changes.iter().map(|ch| Vec::from_raw_parts(ch.ptr, ch.len, ch.cap)).collect();
|
||||
drop(data);
|
||||
drop(changes);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_free_document(doc: *mut Document) {
|
||||
let doc = Box::from_raw(doc);
|
||||
let doc : Vec<u8> = Vec::from_raw_parts(doc.ptr, doc.len, doc.cap);
|
||||
drop(doc)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_free_change(change: *mut Change) {
|
||||
let change = Box::from_raw(change);
|
||||
let change : Vec<u8> = Vec::from_raw_parts(change.ptr, change.len, change.cap);
|
||||
drop(change)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_apply_local_change(backend: *mut Backend, request: *const c_char) -> *const c_char {
|
||||
let request : &CStr = CStr::from_ptr(request);
|
||||
let request = request.to_string_lossy();
|
||||
let request : ChangeRequest = serde_json::from_str(&request).unwrap();
|
||||
let patch : Patch = (*backend).apply_local_change(request).unwrap();
|
||||
to_json(patch)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_apply_changes(backend: *mut Backend, num_changes: usize, changes: *const Change) -> *const c_char {
|
||||
let mut c : Vec<Vec<u8>> = Vec::new();
|
||||
for i in 0..num_changes {
|
||||
let change = changes.offset(i as isize);
|
||||
let bytes = from_buf_raw((*change).ptr, (*change).len);
|
||||
c.push(bytes)
|
||||
}
|
||||
if let Ok(patch) = (*backend).apply_changes_binary(c) {
|
||||
to_json(patch)
|
||||
} else {
|
||||
set_errno(Errno(1));
|
||||
ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_get_patch(backend: *mut Backend) -> *const c_char {
|
||||
if let Ok(patch) = (*backend).get_patch() {
|
||||
to_json(patch)
|
||||
} else {
|
||||
set_errno(Errno(1));
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_load_changes(backend: *mut Backend, num_changes: usize, changes: *const Change) {
|
||||
let mut c : Vec<Vec<u8>> = Vec::new();
|
||||
for i in 0..num_changes {
|
||||
let change = changes.offset(i as isize);
|
||||
let bytes = from_buf_raw((*change).ptr, (*change).len);
|
||||
c.push(bytes)
|
||||
}
|
||||
if let Err(_) = (*backend).load_changes_binary(c) {
|
||||
set_errno(Errno(1));
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_clone(backend: *mut Backend) -> *mut Backend {
|
||||
(*backend).clone().into()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_save(backend: *mut Backend) -> *const Document {
|
||||
let backend : Backend = backend.into();
|
||||
if let Ok(data) = backend.save() {
|
||||
let change : Document = data.into();
|
||||
change.into()
|
||||
} else {
|
||||
set_errno(Errno(1));
|
||||
ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_load(len: usize, binary: *const u8) -> *mut Backend {
|
||||
let bytes = from_buf_raw(binary, len);
|
||||
if let Ok(backend) = automerge_backend::Backend::load(bytes) {
|
||||
Backend(backend).into()
|
||||
} else {
|
||||
set_errno(Errno(1));
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_get_changes_for_actor(backend: *mut Backend, actor: *const c_char) -> *const Changes {
|
||||
let actor : &CStr = CStr::from_ptr(actor);
|
||||
let actor = actor.to_string_lossy();
|
||||
let actor : ActorID = actor.as_ref().into();
|
||||
if let Ok(mut changes) = (*backend).get_changes_for_actor_id(&actor) {
|
||||
let changes : Vec<Change> = changes.drain(..).map(|c| c.into()).collect();
|
||||
let changes : Changes = changes.into();
|
||||
changes.into()
|
||||
} else {
|
||||
set_errno(Errno(1));
|
||||
ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_get_changes(backend: *mut Backend, len: usize, binary: *const u8) -> *const Changes {
|
||||
let mut have_deps = Vec::new();
|
||||
for i in 0..len {
|
||||
have_deps.push(from_buf_raw(binary.offset(i as isize * 32),32).as_slice().into())
|
||||
}
|
||||
if let Ok(mut changes) = (*backend).get_changes(&have_deps) {
|
||||
let changes : Vec<Change> = changes.drain(..).map(|c| c.into()).collect();
|
||||
let changes : Changes = changes.into();
|
||||
changes.into()
|
||||
} else {
|
||||
set_errno(Errno(1));
|
||||
ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_get_missing_deps(backend: *mut Backend) -> *const c_char {
|
||||
let missing = (*backend).get_missing_deps();
|
||||
to_json(missing)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
11
automerge-cli/Cargo.toml
Normal file
11
automerge-cli/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "automerge-cli"
|
||||
version = "0.1.0"
|
||||
authors = ["Orion Henry <orion.henry@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
automerge-backend = { path = "../automerge-backend" }
|
||||
|
18
automerge-cli/src/main.rs
Normal file
18
automerge-cli/src/main.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
extern crate automerge_backend;
|
||||
|
||||
use automerge_backend::{Backend};
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let args : Vec<_> = std::env::args().collect();
|
||||
if args.len() != 2 {
|
||||
println!("usage: automerge PATH");
|
||||
return Ok(());
|
||||
}
|
||||
let data : Vec<u8> = std::fs::read(&args[1])?;
|
||||
println!("DATA: {:?}",data.len());
|
||||
let mut db = Backend::init();
|
||||
db.load_changes_binary(vec![data]).unwrap();
|
||||
return Ok(());
|
||||
}
|
Loading…
Add table
Reference in a new issue