Skip to content

Commit 4ecee33

Browse files
committed
Add improvements to ZipStore example.
This commit uses features of camplzip v1.13 to implement a fully featured ZipStore that allows deletion and rename operations.
1 parent ddd3428 commit 4ecee33

4 files changed

Lines changed: 175 additions & 109 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,5 +143,5 @@ FilesystemStore.Array.rename store anode "new_name";;
143143
[6]: https://zarr-specs.readthedocs.io/en/latest/v3/core/v3.0.html
144144
[7]: https://zoj613.github.io/zarr-ml/zarr/Zarr/index.html#examples
145145
[8]: https://github.com/ocaml-multicore/eio
146-
[9]: https://github.com/zoj613/zarr-ml/tree/main/examples/inmemory_zipstore.ml
146+
[9]: https://github.com/zoj613/zarr-ml/tree/main/examples/zipstore.ml
147147
[10]: https://github.com/zoj613/zarr-ml/tree/main/examples/picos_fs_store.ml

examples/dune

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
(executable
2-
(name readonly_zipstore)
3-
(modules readonly_zipstore)
2+
(name zipstore)
3+
(modules zipstore)
44
(ocamlopt_flags (:standard -O3))
55
(libraries zarr-eio camlzip))
66

examples/readonly_zipstore.ml

Lines changed: 0 additions & 106 deletions
This file was deleted.

examples/zipstore.ml

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
(* This module implements a Zip file zarr store that uses the Eio library for
2+
non-blocking I/O operations. The main requirement is to implement the signature
3+
of Zarr.Types.IO. Below we show how to implement this custom Zarr Store.
4+
5+
To compile & run this example execute the command
6+
dune exec -- examples/zipstore.exe
7+
in your shell at the root of this project. *)
8+
9+
module ZipStore : sig
10+
include Zarr.Storage.STORE with module Deferred = Zarr_eio.Deferred
11+
val with_open : ?clevel:int -> string -> (t -> 'a) -> 'a
12+
end = struct
13+
14+
module Z = struct
15+
module Deferred = Zarr_eio.Deferred
16+
17+
type t = {path : string; level : int option}
18+
19+
let with_open_in path f =
20+
let ic = Zip.open_in path in
21+
Fun.protect ~finally:(fun () -> Zip.close_in ic) (fun () -> f ic)
22+
23+
let with_open_out path f =
24+
let oc = Zip.open_update path in
25+
Fun.protect ~finally:(fun () -> Zip.close_out oc) (fun () -> f oc)
26+
27+
let is_member t key =
28+
let entry_exists ~key ic = match Zip.find_entry ic key with
29+
| exception Not_found -> false
30+
| _ -> true
31+
in
32+
with_open_in t.path (entry_exists ~key)
33+
34+
let size t key =
35+
let entry_size ~key ic = match Zip.find_entry ic key with
36+
| exception Not_found -> 0
37+
| e -> e.uncompressed_size
38+
in
39+
with_open_in t.path (entry_size ~key)
40+
41+
let get t key =
42+
let read_entry ~key ic = match Zip.find_entry ic key with
43+
| exception Not_found -> raise (Zarr.Storage.Key_not_found key)
44+
| e -> Zip.read_entry ic e
45+
in
46+
with_open_in t.path (read_entry ~key)
47+
48+
let get_partial_values t key ranges =
49+
let read_range ~data ~size (ofs, len) = match len with
50+
| Some l -> String.sub data ofs l
51+
| None -> String.sub data ofs (size - ofs)
52+
in
53+
let data = get t key in
54+
let size = String.length data in
55+
List.map (read_range ~data ~size) ranges
56+
57+
let list t =
58+
let entry_filename = function
59+
| (e : Zip.entry) when not e.is_directory -> Some e.filename
60+
| _ -> None
61+
in
62+
let entries = with_open_in t.path (fun ic -> Zip.entries ic) in
63+
List.filter_map entry_filename entries
64+
65+
let list_dir t prefix =
66+
let module S = Set.Make(String) in
67+
let n = String.length prefix in
68+
let add_entry_with_prefix ((l, r) as acc) = function
69+
| (e : Zip.entry) when e.is_directory -> acc
70+
| e when not (String.starts_with ~prefix e.filename) -> acc
71+
| e when String.contains_from e.filename n '/' ->
72+
let key = e.filename in
73+
let pre = String.sub key 0 (1 + String.index_from key n '/') in
74+
S.add pre l, r
75+
| e -> l, e.filename :: r
76+
in
77+
let entries = with_open_in t.path (fun ic -> Zip.entries ic) in
78+
let prefs, keys = List.fold_left add_entry_with_prefix (S.empty, []) entries in
79+
keys, S.elements prefs
80+
81+
let set t key value =
82+
with_open_out t.path (fun oc -> Zip.add_entry ?level:t.level value oc key)
83+
84+
let set_partial_values t key ?(append=false) rvs =
85+
let ov = try get t key with
86+
| Zarr.Storage.Key_not_found _ -> String.empty
87+
in
88+
let f = if append || ov = String.empty then
89+
fun acc (_, v) -> acc ^ v else
90+
fun acc (rs, v) ->
91+
let s = Bytes.unsafe_of_string acc in
92+
Bytes.blit_string v 0 s rs String.(length v);
93+
Bytes.unsafe_to_string s
94+
in
95+
set t key (List.fold_left f ov rvs)
96+
97+
let add_to_zip ~oc ~level (path, v) = Zip.add_entry ?level v oc path
98+
99+
let rename t prefix new_prefix =
100+
let add_pair ~ic ~prefix ~new_prefix acc = function
101+
| (e : Zip.entry) when not (String.starts_with ~prefix e.filename) ->
102+
(e.filename, Zip.read_entry ic e) :: acc
103+
| e ->
104+
let l = String.length prefix in
105+
let path = new_prefix ^ String.sub e.filename l (String.length e.filename - l) in
106+
(path, Zip.read_entry ic e) :: acc
107+
in
108+
let rename_entries ic =
109+
List.fold_left (add_pair ~ic ~prefix ~new_prefix) [] (Zip.entries ic)
110+
in
111+
let pairs = with_open_in t.path rename_entries in
112+
let oc = Zip.open_out t.path in Zip.close_out oc; (* truncate the old zip file *)
113+
with_open_out t.path @@ fun oc -> List.iter (add_to_zip ~oc ~level:t.level) pairs
114+
115+
let erase t key =
116+
let filter ~ic acc = function
117+
| (e : Zip.entry) when e.filename = key -> acc
118+
| e -> (e.filename, Zip.read_entry ic e) :: acc
119+
in
120+
let filter_entries ic = List.fold_left (filter ~ic) [] (Zip.entries ic) in
121+
let pairs = with_open_in t.path filter_entries in
122+
let oc = Zip.open_out t.path in Zip.close_out oc; (* truncate the old zip file *)
123+
with_open_out t.path @@ fun oc -> List.iter (add_to_zip ~oc ~level:t.level) pairs
124+
125+
let erase_prefix t prefix =
126+
let filter ~ic ~prefix acc = function
127+
| (e : Zip.entry) when String.starts_with ~prefix e.filename -> acc
128+
| e -> (e.filename, Zip.read_entry ic e) :: acc
129+
in
130+
let filter_entries ic = List.fold_left (filter ~ic ~prefix) [] (Zip.entries ic) in
131+
let pairs = with_open_in t.path filter_entries in
132+
let oc = Zip.open_out t.path in Zip.close_out oc; (* truncate the old zip file *)
133+
with_open_out t.path @@ fun oc -> List.iter (add_to_zip ~oc ~level:t.level) pairs
134+
end
135+
136+
include Zarr.Storage.Make(Z)
137+
138+
let with_open ?clevel path f =
139+
if not @@ Sys.file_exists path then begin
140+
Zip.(close_out @@ open_out path)
141+
end;
142+
let level = match clevel with
143+
| Some l when l < 0 || l > 9 ->
144+
raise @@ invalid_arg (Printf.sprintf "wrong compression level: %d" l)
145+
| l -> l
146+
in
147+
f Z.{path; level}
148+
end
149+
150+
let _ =
151+
Eio_main.run @@ fun _ ->
152+
let open Zarr in
153+
let open Zarr.Ndarray in
154+
let open Zarr.Indexing in
155+
156+
let test_functionality store =
157+
let xs, _ = ZipStore.hierarchy store in
158+
let anode = List.hd @@ List.filter
159+
(fun node -> Node.Array.to_path node = "/some/group/name") xs in
160+
let slice = [|R [|0; 20|]; I 10; R [||]|] in
161+
let x = ZipStore.Array.read store anode slice Char in
162+
let x' = Zarr.Ndarray.map (fun _ -> Random.int 256 |> Char.chr) x in
163+
ZipStore.Array.write store anode slice x';
164+
let y = ZipStore.Array.read store anode slice Char in
165+
assert (Zarr.Ndarray.equal x' y);
166+
ZipStore.Array.rename store anode "name2";
167+
let exists = ZipStore.Array.exists store @@ Node.Array.of_path "/some/group/name2" in
168+
assert exists;
169+
ZipStore.clear store (* deletes all zip entries *)
170+
in
171+
ZipStore.with_open "examples/data/testdata.zip" test_functionality;
172+
print_endline "Zip store has been updated."

0 commit comments

Comments
 (0)