On a quest for agency in Bellairs

Add (partial) OCaml bindings

+12
.gitignore
···
+
_build
+
_coverage
+
_metrics
+
*~
+
*.install
+
*.merlin
+
_opam
+
.envrc
+
\#*
+
.#*
+
.*.swp
+
**/.DS_Store
+2 -1
README.md
···
- Add your SSH key to the tangled.sh login under 'Settings'.
-
- use port 2222 for the SSH !!! Put the following into `~/.ssh/config`:
+
- use port 2222 for the SSH !!!
+
Put the following into `~/.ssh/config`:
```
Host git.recoil.org
-18
mvp/bellairs.mli
···
-
-
type dir
-
type file
-
-
type entry = {
-
name: string;
-
file: file;
-
}
-
-
val root : dir
-
val list : dir -> entry list
-
-
val create : dir -> string -> file
-
val open_ : dir -> string -> file
-
val delete : dir -> string -> unit
-
-
val size : file -> int64
-
val read : file -> ?off:int64 -> ?len:int64 -> string
mvp/bellairs.schema mvp/schema/storage.capnp
-4
mvp/dune
···
-
(library
-
(name bellairs)
-
(modules_without_implementation bellairs)
-
(modules bellairs))
-1
mvp/dune-project
···
-
(lang dune 3.17)
+1
mvp/ocaml/bellairs.ml
···
+
include Bellairs_intf
+1
mvp/ocaml/bellairs.mli
···
+
include Bellairs_intf.Sigs
+16
mvp/ocaml/bellairs_intf.ml
···
+
module type Storage = sig
+
type dir
+
type file
+
type entry = { name : string; file : file }
+
+
val list : dir -> entry list
+
val create : dir -> string -> file
+
val open_ : dir -> string -> file
+
val delete : dir -> string -> unit
+
val size : file -> int64
+
val read : ?off:int64 -> ?len:int64 -> file -> string
+
end
+
+
module type Sigs = sig
+
module type Storage = Storage
+
end
+59
mvp/ocaml/client/client.ml
···
+
open Capnp_rpc.Std
+
open Eio.Std
+
+
module Storage :
+
Bellairs.Storage
+
with type file = [ `File_e62ce624f782d37e ] Capability.t
+
and type dir = [ `Directory_8e98f5ea254aace5 ] Capability.t = struct
+
module API = Schema.Storage.MakeRPC (Capnp_rpc)
+
+
type file = API.Client.File.t Capability.t
+
type dir = API.Client.Directory.t Capability.t
+
type entry = { name : string; file : file }
+
+
let entry r =
+
let name = API.Reader.Directory.Entry.name_get r in
+
let file = API.Reader.Directory.Entry.file_get r in
+
match file with
+
| Some file -> { name; file }
+
| None -> failwith "missing entry.file"
+
+
let todo msg = failwith ("Storage." ^ msg)
+
+
let list t =
+
let open API.Client.Directory.List in
+
let request = Capability.Request.create_no_args () in
+
let results = Capability.call_for_value_exn t method_id request in
+
let entries = Results.entries_get_list results in
+
List.map entry entries
+
+
let create _ = todo "create"
+
let open_ _ = todo "open"
+
let delete _ = todo "delete"
+
let size _ = todo "size"
+
let read ?off:_ ?len:_ _ = todo "read"
+
end
+
+
let ls net uri =
+
Switch.run @@ fun sw ->
+
let client_vat = Capnp_rpc_unix.client_only_vat ~sw net in
+
let sr = Capnp_rpc_unix.Vat.import_exn client_vat uri in
+
let entries = Capnp_rpc_unix.with_cap_exn sr Storage.list in
+
List.iter (fun { Storage.name; _ } -> Fmt.pr "- %s\n" name) entries
+
+
open Cmdliner
+
+
let () =
+
Logs.set_level (Some Logs.Warning);
+
Logs.set_reporter (Logs_fmt.reporter ())
+
+
let connect_addr =
+
let i = Arg.info [] ~docv:"ADDR" ~doc:"Address of server (capnp://...)" in
+
Arg.(required @@ pos 0 (some Capnp_rpc_unix.sturdy_uri) None i)
+
+
let ls_cmd env =
+
let doc = "run the client" in
+
let info = Cmd.info "ls" ~doc in
+
Cmd.v info Term.(const (ls env#net) $ connect_addr)
+
+
let () = exit @@ Eio_main.run @@ fun env -> Cmd.eval (ls_cmd env)
+1
mvp/ocaml/client/client.mli
···
+
(* empty *)
+3
mvp/ocaml/client/dune
···
+
(executables
+
(names client)
+
(libraries bellairs eio_main capnp-rpc logs.fmt capnp-rpc-unix))
+3
mvp/ocaml/server/dune
···
+
(executable
+
(name server)
+
(libraries bellairs eio_main capnp-rpc logs.fmt capnp-rpc-unix))
+139
mvp/ocaml/server/server.ml
···
+
open Eio.Std
+
open Capnp_rpc_net
+
+
let cap_file = "storage.cap"
+
+
module API = Schema.Storage.MakeRPC (Capnp_rpc)
+
open Capnp_rpc.Std
+
+
let todo msg = failwith ("TODO: " ^ msg)
+
+
module Impl : sig
+
include Bellairs.Storage
+
+
val root : unit -> dir
+
end = struct
+
type file = { content : string; size : int64 }
+
type dir = (string, file) Hashtbl.t
+
type entry = { name : string; file : file }
+
+
let create files name =
+
let file = { content = ""; size = 0L } in
+
Hashtbl.add files name file;
+
file
+
+
let root () =
+
let tbl = Hashtbl.create 10 in
+
let _ = create tbl "foo" in
+
let _ = create tbl "bar" in
+
tbl
+
+
let open_ (files : dir) name =
+
try Hashtbl.find files name with Not_found -> failwith "file not found"
+
+
let delete files name = Hashtbl.remove files name
+
+
let read ?(off = 0L) ?(len = Int64.max_int) file =
+
let content_len = String.length file.content in
+
let off = Int64.to_int off in
+
let len = Int64.to_int len in
+
let off = max 0 (min off content_len) in
+
let max_len = content_len - off in
+
let len = if len >= max_len then max_len else len in
+
String.sub file.content off len
+
+
let size file = file.size
+
+
let list files =
+
Hashtbl.fold (fun name file acc -> { name; file } :: acc) files []
+
end
+
+
module File = struct
+
let local file =
+
let module File = API.Service.File in
+
File.local
+
@@ object
+
inherit File.service
+
+
method read_impl params release_param_caps =
+
let open File.Read in
+
let off = Stdint.Int64.of_uint64 (Params.off_get params) in
+
let len = Stdint.Int64.of_uint64 (Params.len_get params) in
+
release_param_caps ();
+
let response, results =
+
Service.Response.create Results.init_pointer
+
in
+
let data = Impl.read file ~off ~len in
+
Results.data_set results data;
+
Service.return response
+
+
method size_impl _ release_param_caps =
+
let open File.Size in
+
release_param_caps ();
+
let response, results =
+
Service.Response.create Results.init_pointer
+
in
+
let size = Stdint.Int64.to_uint64 (Impl.size file) in
+
Results.size_set results size;
+
Service.return response
+
end
+
end
+
+
module Directory = struct
+
let local dir =
+
let module Directory = API.Service.Directory in
+
Directory.local
+
@@ object
+
inherit Directory.service
+
method create_impl _ = todo "create_impl"
+
+
method list_impl _ release_param_caps =
+
let open Directory.List in
+
release_param_caps ();
+
let response, results =
+
Service.Response.create Results.init_pointer
+
in
+
let entries = Impl.list dir in
+
+
let entries_array =
+
Results.entries_init results (List.length entries)
+
in
+
List.iteri
+
(fun i e ->
+
let entry = Capnp.Array.get entries_array i in
+
API.Builder.Directory.Entry.name_set entry e.Impl.name;
+
API.Builder.Directory.Entry.file_set entry
+
(Some (File.local e.Impl.file)))
+
entries;
+
+
Service.return response
+
+
method size_impl _ = todo "size_impl"
+
method open_impl _ = todo "open_impl"
+
method delete_impl _ = todo "delete_impl"
+
end
+
end
+
+
let serve config =
+
Switch.run @@ fun sw ->
+
let service_id = Capnp_rpc_unix.Vat_config.derived_id config "main" in
+
let restore = Restorer.single service_id (Directory.local (Impl.root ())) in
+
let vat = Capnp_rpc_unix.serve ~sw ~restore config in
+
match Capnp_rpc_unix.Cap_file.save_service vat service_id cap_file with
+
| Error (`Msg m) -> failwith m
+
| Ok () ->
+
traceln "Server running. Connect using %S." cap_file;
+
Fiber.await_cancel ()
+
+
open Cmdliner
+
+
let () =
+
Logs.set_level (Some Logs.Warning);
+
Logs.set_reporter (Logs_fmt.reporter ())
+
+
let serve_cmd env =
+
let doc = "run the server" in
+
let info = Cmd.info "serve" ~doc in
+
Cmd.v info Term.(const serve $ Capnp_rpc_unix.Vat_config.cmd env)
+
+
let () = exit @@ Eio_main.run @@ fun env -> Cmd.eval (serve_cmd env)
+1
mvp/ocaml/server/server.mli
···
+
(* empty *)