file.ml 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. (*
  2. * _ _ ____ _
  3. * _| || |_/ ___| ___ _ __ _ __ ___ | |
  4. * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
  5. * |_ _|___) | __/ |_) | |_) | (_) |_|
  6. * |_||_| |____/ \___| .__/| .__/ \___/(_)
  7. * |_| |_|
  8. *
  9. * Personal Social Web.
  10. *
  11. * Copyright (C) The #Seppo contributors. All rights reserved.
  12. *
  13. * This program is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU General Public License as published by
  15. * the Free Software Foundation, either version 3 of the License, or
  16. * (at your option) any later version.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU General Public License
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. *)
  26. (* rather leverage fileutils? *)
  27. let rec find_path_tail predicate ?(prfx = "") ?(sep = "/") lst =
  28. match lst with
  29. | [] -> Error "not found"
  30. | hd :: tl ->
  31. let prfx = sep ^ hd ^ prfx in
  32. match predicate prfx with
  33. | Error _ as e -> e
  34. | Ok true -> Ok prfx
  35. | Ok false -> find_path_tail predicate ~prfx ~sep tl
  36. let mtime_0 ?(default = 0.) fn =
  37. (* Logr.debug (fun m -> m "mtime_0 %s" fn); *)
  38. try (Unix.stat fn).st_mtime
  39. with
  40. | _ -> default
  41. let pDir = 0o755
  42. let pFile = 0o644
  43. let pFileRO = 0o444
  44. let rec mkdir_p perm n =
  45. (* TODO should we block anything starting with / or . ? *)
  46. match Sys.file_exists n with
  47. | true -> Ok n
  48. | false -> (
  49. match n |> Filename.dirname |> mkdir_p perm with
  50. | Ok _ -> (
  51. Unix.(try
  52. mkdir n perm;
  53. Ok n
  54. with Unix_error (n, a, b) ->
  55. Error ((n |> error_message) ^ ": " ^ a ^ " " ^ b)))
  56. | e -> e)
  57. let _chdir f d =
  58. Logr.debug (fun m -> m "%s.%s %s" "File" "chdir" d);
  59. let cwd = Unix.getcwd () in
  60. let _ = mkdir_p pDir d in
  61. Unix.chdir d;
  62. let r = f () in
  63. Unix.chdir cwd;
  64. r
  65. let fold_dir f init dn =
  66. try let dh = dn |> Unix.opendir in
  67. let rec next init =
  68. try
  69. match dh
  70. |> Unix.readdir
  71. |> f init with
  72. | init,false -> init
  73. | init,true -> init |> next
  74. with End_of_file -> init
  75. in
  76. let ret = next init in
  77. dh |> Unix.closedir;
  78. ret
  79. with Unix.(Unix_error(ENOENT, "opendir", _)) -> init
  80. let count_dir ?(max = Int.max_int) ?(pred = (fun f -> not (f = "." || f = ".."))) dn =
  81. fold_dir (fun count fn ->
  82. let count = count + if pred fn
  83. then 1
  84. else 0 in
  85. (count,count < max))
  86. 0 dn
  87. let any pred d : string option =
  88. (* use File.fold_dir? *)
  89. let wa = Unix.opendir d in
  90. let rec loop () =
  91. try
  92. let fn = wa |> Unix.readdir in
  93. if pred fn
  94. then Some fn
  95. else loop ()
  96. with End_of_file -> None
  97. in
  98. let r = loop () in
  99. Unix.closedir wa;
  100. r
  101. let exists = Sys.file_exists
  102. (* evtl. https://rosettacode.org/wiki/Read_entire_file#OCaml *)
  103. let to_bytes (fn : string) : bytes =
  104. try
  105. let len = (Unix.stat fn).st_size in
  106. let ic = open_in_gen [ Open_binary; Open_rdonly ] 0 fn in
  107. let buf = Bytes.create len in
  108. really_input ic buf 0 len;
  109. close_in ic;
  110. buf
  111. with _ -> Bytes.empty
  112. let to_string fn = fn
  113. |> to_bytes
  114. |> Bytes.to_string
  115. let cat fn = try
  116. fn |> to_string |> Result.ok
  117. with
  118. | Sys_error e -> Error e
  119. | Invalid_argument e -> Error e
  120. (* | End_of_file -> Error ("error reading file " ^ fn) *)
  121. let in_channel' rdr fn =
  122. let ic = open_in_gen [ Open_rdonly; Open_binary ] 0 fn in
  123. let ret = rdr ic in
  124. close_in ic;
  125. ret
  126. let in_channel fn rdr =
  127. in_channel' rdr fn
  128. let out_channel' ?(tmp = Some "~") ?(mode = [ Open_append; Open_binary; Open_creat; Open_wronly ]) ?(perm = pFile) wrtr fn =
  129. Logr.debug (fun m -> m "%s.%s %s cwd: %s" "File" "out_channel" fn (Unix.getcwd ()));
  130. let fn' = match tmp with
  131. | None -> fn
  132. | Some "~" -> fn ^ "~"
  133. | Some s -> s in
  134. let oc = open_out_gen mode perm fn' in
  135. let ret = wrtr oc in
  136. oc |> close_out;
  137. if tmp |> Option.is_some then Unix.rename fn' fn;
  138. ret
  139. let out_channel ?(tmp = Some "~") ?(mode = [ Open_append; Open_binary; Open_creat; Open_wronly ]) ?(perm = pFile) fn wrtr =
  140. out_channel' ~tmp ~mode ~perm wrtr fn
  141. let touch fn =
  142. fn
  143. |> open_out_gen [ Open_append; Open_binary; Open_creat; Open_wronly ] pFileRO
  144. |> close_out
  145. let copy_channel ?(buf = 16 * 0x400 |> Bytes.create) oc ic =
  146. (* primitive take copy inspired by
  147. https://sylvain.le-gall.net/ocaml-fileutils.html *)
  148. let len = buf |> Bytes.length in
  149. let r = ref 0 in
  150. while r := input ic buf 0 len;
  151. !r <> 0
  152. do
  153. output oc buf 0 !r
  154. done
  155. let cp' ?(buf = 16 * 0x400 |> Bytes.create) src oc =
  156. let ic = open_in_gen [ Open_rdonly; Open_binary ] 0 src in
  157. ic |> copy_channel ~buf oc;
  158. close_in ic
  159. let restore_static ?(perm = pFile) fn =
  160. if fn |> exists
  161. then None
  162. else
  163. let _ = fn |> Filename.dirname |> mkdir_p pDir in
  164. fn |> out_channel' ~perm (fun oc ->
  165. match Res.read ("static/" ^ fn) with
  166. | None ->
  167. Logr.err (fun m -> m "%s missing %s" E.e1028 fn);
  168. None
  169. | Some str as r ->
  170. str |> output_string oc;
  171. Logr.info (fun m -> m "unpacked %s" fn);
  172. r )
  173. let fold_lines f init ic =
  174. let rec next_line init' =
  175. try
  176. ic
  177. |> input_line
  178. |> f init'
  179. |> next_line
  180. with
  181. | End_of_file -> init'
  182. in
  183. next_line init
  184. let fold_bind_lines f init ic =
  185. let ( let* ) = Result.bind in
  186. let rec next_line init' =
  187. try
  188. let* init' = ic |> input_line |> f init' in
  189. next_line init'
  190. with
  191. | End_of_file -> Ok init'
  192. in
  193. next_line init
  194. module Path = struct
  195. let hd_tl (ch : char) (str : string) : (string * string) option =
  196. Option.bind
  197. (String.index_opt str ch)
  198. (fun len ->
  199. let hd = String.sub str 0 len
  200. and tl = let pp1 = len + 1 in
  201. String.sub str pp1 ((String.length str) - pp1)
  202. in Some (hd, tl))
  203. let hd (ch : char) (str : string) : string option =
  204. Option.bind
  205. (hd_tl ch str)
  206. (fun (s,_) -> Some s)
  207. let tl (ch : char) (str : string) : string option =
  208. Option.bind
  209. (hd_tl ch str)
  210. (fun (_,s) -> Some s)
  211. end