On a quest for agency in Bellairs

ls/read/write now work

Changed files
+161 -55
mvp
+1
mvp/ocaml/bellairs_intf.ml
···
val delete : dir -> string -> unit
val size : file -> int64
val read : ?off:int64 -> ?len:int64 -> file -> string
+
val write : ?off:int64 -> ?len:int64 -> file -> string -> unit
end
module type Sigs = sig
+78 -28
mvp/ocaml/client/client.ml
···
let results = Capability.call_for_value_exn t method_id request in
match Results.file_get results with
| Some file -> file
-
| None -> failwith "create: no file returned"
+
| None -> failwith "Storage.create: no file returned"
let open_ t name =
let open API.Client.Directory.Open in
let request, params = Capability.Request.create Params.init_pointer in
Params.name_set params name;
-
let results = Capability.call_for_value_exn t method_id request in
-
match Results.file_get results with
-
| Some file -> file
-
| None -> failwith "open: no file returned"
+
Capability.call_for_caps t method_id request Results.file_get_pipelined
let delete t name =
let open API.Client.Directory.Delete in
···
in
let results = Capability.call_for_value_exn t method_id request in
Results.data_get results
+
+
let write ?off ?len t d =
+
let open API.Client.File.Write in
+
let request, params = Capability.Request.create Params.init_pointer in
+
let () =
+
match off with
+
| None -> ()
+
| Some off -> Params.off_set params (Stdint.Int64.to_uint64 off)
+
in
+
let () =
+
match len with
+
| None -> ()
+
| Some len -> Params.len_set params (Stdint.Int64.to_uint64 len)
+
in
+
Params.data_set params d;
+
Capability.call_for_unit_exn t method_id request
end
let connect net uri f =
···
let pp_name ppf name = Fmt.pf ppf "%a" Fmt.(styled `Bold string) name
let pp_entry ppf entry = pp_name ppf entry.Storage.name
-
let ls net uri =
+
let ls net () uri =
connect net uri @@ fun dir ->
let entries = Storage.list dir in
-
Printf.printf "total %d:\n" (List.length entries);
+
Fmt.pr "total %d:\n" (List.length entries);
List.iter (Fmt.pr "- %a\n" pp_entry) entries;
Fmt.pr "%!"
-
let create net uri name =
+
let create net () uri name data =
connect net uri @@ fun dir ->
-
let _file = Storage.create dir name in
-
Fmt.pr "Created file '%a'\n" pp_name name
+
let file = Storage.create dir name in
+
Storage.write file data;
+
Fmt.pr "%a is created.\n%!" pp_name name
-
let open_file net uri name =
+
let open_file net () uri name =
connect net uri @@ fun dir ->
let _file = Storage.open_ dir name in
-
Fmt.pr "Opened file <raw>\n"
+
Fmt.pr "%a: <raw>.\n%!" pp_name name
-
let delete net uri name =
+
let delete net () uri name =
connect net uri @@ fun dir ->
Storage.delete dir name;
-
Fmt.pr "Deleted file '%a'\n" pp_name name
+
Fmt.pr "%a is deleted.\n%!" pp_name name
-
let size net uri name =
+
let size net () uri name =
connect net uri @@ fun dir ->
let file = Storage.open_ dir name in
let size = Storage.size file in
-
Fmt.pr "Size of '%s': %Ld bytes\n" name size
+
Fmt.pr "%a: %Ld bytes.\n%!" pp_name name size
-
let read net addr name offset length =
-
connect net addr @@ fun dir ->
-
let file = Storage.open_ dir name in
+
let read net () uri name offset length =
+
connect net uri @@ fun dir ->
+
Capability.with_ref (Storage.open_ dir name) @@ fun file ->
let data = Storage.read ?off:offset ?len:length file in
-
Printf.printf "Contents of '%s':\n%s\n" name data
+
Fmt.pr "%a: %S\n%!" pp_name name data
+
+
let write net () uri name offset length data =
+
connect net uri @@ fun dir ->
+
Capability.with_ref (Storage.open_ dir name) @@ fun file ->
+
Storage.write ?off:offset ?len:length file data;
+
Fmt.pr "%d bytes successfully written into %a.\n%!" (String.length data)
+
pp_name name
open Cmdliner
···
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 env = Cmd.Env.info "BELLAIRS_CAP" in
+
let i =
+
Arg.info ~env [ "cap" ] ~docv:"CAP" ~doc:"capabilities file(capnp://...)"
+
in
+
Arg.(required @@ opt (some Capnp_rpc_unix.sturdy_uri) None i)
let name_arg =
let i = Arg.info [] ~docv:"NAME" ~doc:"Name of the file" in
+
Arg.(required @@ pos 0 (some string) None i)
+
+
let data_arg =
+
let i = Arg.info [] ~docv:"DATA" ~doc:"Data of the file" in
Arg.(required @@ pos 1 (some string) None i)
let offset_arg =
···
in
Arg.(value @@ opt (some int64) None i)
+
let setup_log style_renderer level =
+
Fmt_tty.setup_std_outputs ?style_renderer ();
+
Logs.set_level level;
+
Logs.set_reporter (Logs_fmt.reporter ());
+
()
+
+
let setup_log =
+
Term.(const setup_log $ Fmt_cli.style_renderer () $ Logs_cli.level ())
+
let ls_cmd env =
let doc = "List files in the directory" in
let info = Cmd.info "ls" ~doc in
-
Cmd.v info Term.(const (ls env#net) $ connect_addr)
+
Cmd.v info Term.(const (ls env#net) $ setup_log $ connect_addr)
let create_cmd env =
let doc = "Create a new file" in
let info = Cmd.info "create" ~doc in
-
Cmd.v info Term.(const (create env#net) $ connect_addr $ name_arg)
+
Cmd.v info
+
Term.(
+
const (create env#net) $ setup_log $ connect_addr $ name_arg $ data_arg)
let open_cmd env =
let doc = "Open an existing file and show its ID" in
let info = Cmd.info "open" ~doc in
-
Cmd.v info Term.(const (open_file env#net) $ connect_addr $ name_arg)
+
Cmd.v info
+
Term.(const (open_file env#net) $ setup_log $ connect_addr $ name_arg)
let delete_cmd env =
let doc = "Delete a file" in
let info = Cmd.info "delete" ~doc in
-
Cmd.v info Term.(const (delete env#net) $ connect_addr $ name_arg)
+
Cmd.v info Term.(const (delete env#net) $ setup_log $ connect_addr $ name_arg)
let size_cmd env =
let doc = "Get the size of a file" in
let info = Cmd.info "size" ~doc in
-
Cmd.v info Term.(const (size env#net) $ connect_addr $ name_arg)
+
Cmd.v info Term.(const (size env#net) $ setup_log $ connect_addr $ name_arg)
let read_cmd env =
let doc = "Read the contents of a file" in
let info = Cmd.info "read" ~doc in
Cmd.v info
Term.(
-
const (read env#net) $ connect_addr $ name_arg $ offset_arg $ length_arg)
+
const (read env#net)
+
$ setup_log $ connect_addr $ name_arg $ offset_arg $ length_arg)
+
+
let write_cmd env =
+
let doc = "Write some contents to a file" in
+
let info = Cmd.info "write" ~doc in
+
Cmd.v info
+
Term.(
+
const (write env#net)
+
$ setup_log $ connect_addr $ name_arg $ offset_arg $ length_arg $ data_arg)
let main_cmd env =
let doc = "Bellairs Storage Client" in
···
delete_cmd env;
size_cmd env;
read_cmd env;
+
write_cmd env;
]
let () = exit @@ Eio_main.run @@ fun env -> Cmd.eval (main_cmd env)
+9 -1
mvp/ocaml/client/dune
···
(executables
(names client)
-
(libraries bellairs eio_main capnp-rpc logs.fmt capnp-rpc-unix))
+
(libraries
+
bellairs
+
eio_main
+
capnp-rpc
+
logs.fmt
+
capnp-rpc-unix
+
fmt.cli
+
logs.cli
+
fmt.tty))
+7 -7
mvp/ocaml/server/directory.ml
···
let open Directory.Create in
let name = Params.name_get params in
release_param_caps ();
-
let response, results = Service.Response.create Results.init_pointer in
let file = Impl.create dir name in
let file_cap = File.local file in
-
(* TODO: add persistence *)
+
let response, results = Service.Response.create Results.init_pointer in
Results.file_set results (Some file_cap);
+
Capability.dec_ref file_cap;
Service.return response
method list_impl _ release_param_caps =
···
List.iteri
(fun i e ->
let entry = Capnp.Array.get entries_array i in
+
let file = File.local e.Impl.file in
API.Builder.Directory.Entry.name_set entry e.Impl.name;
-
API.Builder.Directory.Entry.file_set entry
-
(Some (File.local e.Impl.file)))
+
API.Builder.Directory.Entry.file_set entry (Some file))
entries;
Service.return response
-
method open_impl params release_param_caps =
+
method open_impl params _release_param_caps =
let open Directory.Open in
let name = Params.name_get params in
-
release_param_caps ();
let response, results = Service.Response.create Results.init_pointer in
try
let file = Impl.open_ dir name in
-
Results.file_set results (Some (File.local file));
+
let file_cap = File.local file in
+
Results.file_set results (Some file_cap);
Service.return response
with Not_found -> Service.fail "File '%s' not found" name
+9 -1
mvp/ocaml/server/dune
···
(executable
(name server)
-
(libraries bellairs eio_main capnp-rpc logs.fmt capnp-rpc-unix))
+
(libraries
+
bellairs
+
eio_main
+
capnp-rpc
+
logs.fmt
+
fmt.cli
+
logs.cli
+
fmt.tty
+
capnp-rpc-unix))
+16 -3
mvp/ocaml/server/file.ml
···
open Capnp_rpc.Std
+
let int64_of_uint64 n =
+
match Stdint.Int64.of_uint64 n with -1L -> None | i -> Some i
+
let local file =
let module File = API.Service.File in
File.local
···
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
+
let off = int64_of_uint64 (Params.off_get params) in
+
let len = 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
+
let data = Impl.read file ?off ?len in
Results.data_set results data;
+
Service.return response
+
+
method write_impl params release_param_caps =
+
let open File.Write in
+
let off = int64_of_uint64 (Params.off_get params) in
+
let len = int64_of_uint64 (Params.len_get params) in
+
let data = Params.data_get params in
+
release_param_caps ();
+
let response = Service.Response.create_empty () in
+
Impl.write file ?off ?len data;
Service.return response
method size_impl _ release_param_caps =
+23 -6
mvp/ocaml/server/impl.ml
···
(* TODO: put the real implementation here -- for now it's just an
in-memory database *)
-
type file = { content : string; size : int64 }
+
type file = { mutable content : string }
type dir = (string, file) Hashtbl.t
type entry = { name : string; file : file }
let create files name =
-
let file = { content = ""; size = 0L } in
+
let file = { content = "" } in
Hashtbl.add files name file;
file
···
let open_ (files : dir) name = Hashtbl.find files name
let delete files name = Hashtbl.remove files name
-
let read ?(off = 0L) ?(len = Int64.max_int) file =
+
let read ?(off = 0L) ?len 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 len =
+
match len with None -> content_len | Some len -> Int64.to_int 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 write ?(off = 0L) ?len file data =
+
let data_len = String.length data in
+
let off = Int64.to_int off in
+
let len = match len with None -> data_len | Some len -> Int64.to_int len in
+
let required_len = off + len in
+
let file_content =
+
if required_len > String.length file.content then (
+
let new_content = Bytes.make required_len '\000' in
+
String.blit file.content 0 new_content 0 (String.length file.content);
+
let new_content = Bytes.unsafe_to_string new_content in
+
file.content <- new_content;
+
new_content)
+
else file.content
+
in
+
String.blit data 0 (Bytes.unsafe_of_string file_content) off len
+
+
let size file = Int64.of_int (String.length file.content)
let list files =
Hashtbl.fold (fun name file acc -> { name; file } :: acc) files []
+12 -6
mvp/ocaml/server/server.ml
···
let cap_file = "storage.cap"
-
let serve config =
+
let serve () config =
Switch.run @@ fun sw ->
let root_id = Capnp_rpc_unix.Vat_config.derived_id config "root" in
-
let restore = Restorer.single root_id (Directory.local (Impl.root ())) in
+
let root = Impl.root () in
+
let restore = Restorer.single root_id (Directory.local root) in
let vat = Capnp_rpc_unix.serve ~sw ~restore config in
match Capnp_rpc_unix.Cap_file.save_service vat root_id cap_file with
| Error (`Msg m) -> failwith m
···
open Cmdliner
-
let () =
-
Logs.set_level (Some Logs.Warning);
-
Logs.set_reporter (Logs_fmt.reporter ())
+
let setup_log style_renderer level =
+
Fmt_tty.setup_std_outputs ?style_renderer ();
+
Logs.set_level level;
+
Logs.set_reporter (Logs_fmt.reporter ());
+
()
+
+
let setup_log =
+
Term.(const setup_log $ Fmt_cli.style_renderer () $ Logs_cli.level ())
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)
+
Cmd.v info Term.(const serve $ setup_log $ Capnp_rpc_unix.Vat_config.cmd env)
let () = exit @@ Eio_main.run @@ fun env -> Cmd.eval (serve_cmd env)
+6 -3
mvp/schema/storage.capnp
···
interface Directory {
# Represents a directory in the filesystem
-
+
list @0 () -> (entries :List(Entry));
# Lists all entries in the directory
···
interface File {
# Represents a file in the filesystem
-
+
size @0 () -> (size :UInt64);
# Returns the size of the file in bytes
read @1 (off :UInt64 = 0, len :UInt64 = 0xffffffffffffffff) -> (data :Data);
# Reads data from the file, optionally starting at offset and reading up to len bytes
# Default is to read the entire file
+
+
write @2 (off :UInt64 = 0, len :UInt64 = 0xffffffffffffffff, data :Data) -> ();
+
# Write data to the file, optionally starting at offset and reading up to len bytes
+
# Default is to write the entire file
}
-