Source file stored_data.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
open Error_monad
type error += Missing_stored_data of string
let () =
register_error_kind
`Permanent
~id:"stdlib_unix.missing_stored_data"
~title:"Missing stored data"
~description:"Failed to load stored data"
~pp:(fun ppf path ->
Format.fprintf
ppf
"Failed to load on-disk data: no corresponding data found in file %s."
path)
Data_encoding.(obj1 (req "path" string))
(function Missing_stored_data path -> Some path | _ -> None)
(fun path -> Missing_stored_data path)
type 'a file = {
encoding : 'a Data_encoding.t;
eq : 'a -> 'a -> bool;
path : string;
json : bool;
}
let make_file ?(json = false) ~filepath encoding eq =
{encoding; eq; path = filepath; json}
type _ t =
| Stored_data : {
mutable cache : 'a;
file : 'a file;
scheduler : Lwt_idle_waiter.t;
}
-> 'a t
let read_json_file file =
let open Lwt_syntax in
Option.catch_os (fun () ->
let* r = Lwt_utils_unix.Json.read_file file.path in
match r with
| Ok json ->
Lwt.return_some (Data_encoding.Json.destruct file.encoding json)
| _ -> Lwt.return_none)
let read_file file =
Lwt.try_bind
(fun () -> Lwt_utils_unix.read_file file.path)
(fun str ->
Lwt.return (Data_encoding.Binary.of_string_opt file.encoding str))
(fun _ -> Lwt.return_none)
let get (Stored_data v) =
Lwt_idle_waiter.task v.scheduler (fun () -> Lwt.return v.cache)
let write_file file data =
let open Lwt_syntax in
protect (fun () ->
let encoder data =
if file.json then
Data_encoding.Json.construct file.encoding data
|> Data_encoding.Json.to_string
else Data_encoding.Binary.to_string_exn file.encoding data
in
let str = encoder data in
let+ result =
Lwt_utils_unix.with_atomic_open_out file.path (fun fd ->
Lwt_utils_unix.write_string fd str)
in
Result.bind_error result Lwt_utils_unix.tzfail_of_io_error)
let write (Stored_data v) data =
Lwt_idle_waiter.force_idle v.scheduler (fun () ->
if v.file.eq v.cache data then Lwt_result_syntax.return_unit
else
let open Lwt_result_syntax in
let+ () = write_file v.file data in
v.cache <- data)
let create file data =
let open Lwt_result_syntax in
let scheduler = Lwt_idle_waiter.create () in
let* () = write_file file data in
return (Stored_data {cache = data; file; scheduler})
let update_with (Stored_data v) f =
let open Lwt_syntax in
Lwt_idle_waiter.force_idle v.scheduler (fun () ->
let* new_data = f v.cache in
if v.file.eq v.cache new_data then return_ok_unit
else
let open Lwt_result_syntax in
let+ () = write_file v.file new_data in
v.cache <- new_data)
let load file =
let open Lwt_result_syntax in
let*! o = if file.json then read_json_file file else read_file file in
match o with
| Some cache ->
let scheduler = Lwt_idle_waiter.create () in
return (Stored_data {cache; file; scheduler})
| None -> tzfail (Missing_stored_data file.path)
let init file ~initial_data =
let open Lwt_syntax in
let* b = Lwt_unix.file_exists file.path in
match b with true -> load file | false -> create file initial_data