/flatula/src/flatula_table.erl

http://flatula.googlecode.com/ · Erlang · 256 lines · 213 code · 34 blank · 9 comment · 2 complexity · ed67f11a30fe79f4cdcfd0e0b098d8d4 MD5 · raw file

  1. -module (flatula_table).
  2. -export ([ start_link/1 ]).
  3. -behaviour (gen_server).
  4. -export ([ init/1,
  5. handle_call/3,
  6. handle_cast/2,
  7. handle_info/2,
  8. terminate/2,
  9. code_change/3 ]).
  10. -include_lib ("kernel/include/file.hrl").
  11. -record (state, { base_path,
  12. max_total_bytes,
  13. max_file_bytes,
  14. cur_file,
  15. cur_fd,
  16. cur_ofs,
  17. lookup,
  18. total_bytes }).
  19. -define (HEADER_LEN, 8).
  20. %
  21. % public
  22. %
  23. start_link (Options) ->
  24. gen_server:start_link (?MODULE, Options, []).
  25. %
  26. % gen_server callbacks
  27. %
  28. init (Options) ->
  29. process_flag (trap_exit, true),
  30. BasePath = option (Options, base_path),
  31. MaxTotalBytes = option (Options, max_total_bytes),
  32. MaxFileBytes = option (Options, max_file_bytes),
  33. State = open (#state { base_path = BasePath,
  34. max_total_bytes = MaxTotalBytes,
  35. max_file_bytes = MaxFileBytes }),
  36. { ok, State }.
  37. handle_call ({ read, Id }, _From, State) ->
  38. { Result, NewState } = read (State, Id),
  39. { reply, Result, NewState };
  40. handle_call ({ write, Term }, _From, State) ->
  41. { Result, NewState } = write (State, Term),
  42. { reply, Result, NewState };
  43. handle_call ({ info, size }, _From, State) ->
  44. { reply, { size, State#state.total_bytes }, State };
  45. handle_call ({ info, max_file_bytes }, _From, State) ->
  46. { reply, { max_file_bytes, State#state.max_file_bytes }, State };
  47. handle_call ({ info, max_total_bytes }, _From, State) ->
  48. { reply, { max_total_bytes, State#state.max_total_bytes }, State };
  49. handle_call (close, _From, State) ->
  50. { stop, normal, ok, State }.
  51. handle_cast (_Request, State) ->
  52. { noreply, State }.
  53. handle_info (_Msg, State) ->
  54. { noreply, State }.
  55. terminate (_Reason, State) ->
  56. close (State),
  57. ok.
  58. code_change (_OldVsn, State, _Extra) ->
  59. process_flag (trap_exit, true),
  60. { ok, State }.
  61. %
  62. % private
  63. %
  64. read (State = #state { cur_file = Cur }, _Id = { F, _, _ }) when F > Cur ->
  65. { not_found, State };
  66. read (State = #state { cur_file = Cur, cur_ofs = CurOfs },
  67. _Id = { Cur, Ofs, Len })
  68. when Ofs + Len > CurOfs ->
  69. { not_found, State };
  70. read (State = #state { cur_file = Cur, cur_fd = Fd, cur_ofs = CurOfs },
  71. _Id = { Cur, Ofs, Len })
  72. when Ofs + Len =< CurOfs ->
  73. Result = read_term (Fd, Ofs, Len),
  74. { Result, State };
  75. read (State = #state { lookup = Lookup }, _Id = { File, Ofs, Len }) ->
  76. case gb_trees:lookup (File, Lookup) of
  77. { value, { Fd, Size } } ->
  78. case Ofs + Len > Size of
  79. true ->
  80. { not_found, State };
  81. false ->
  82. Result = read_term (Fd, Ofs, Len),
  83. { Result, State }
  84. end;
  85. none ->
  86. { not_found, State }
  87. end.
  88. read_term (Fd, Ofs, Len) ->
  89. case file:pread (Fd, Ofs, ?HEADER_LEN + Len) of
  90. { ok, eof } ->
  91. not_found;
  92. { ok, Data } ->
  93. try
  94. Header = header (Len),
  95. << Header:?HEADER_LEN/binary, Binary:Len/binary >> = Data,
  96. { ok, binary_to_term (Binary) }
  97. catch
  98. _:E -> { error, E }
  99. end;
  100. Error ->
  101. Error
  102. end.
  103. write (State = #state { max_file_bytes = Max,
  104. cur_file = Cur,
  105. cur_fd = CurFd,
  106. cur_ofs = CurOfs,
  107. lookup = Lookup }, Term) when CurOfs > Max ->
  108. file:close (CurFd),
  109. NewCurFd = open_read_or_die (State, Cur),
  110. Next = Cur + 1,
  111. NextFd = open_readwrite_or_die (State, Next),
  112. NewLookup = gb_trees:insert (Cur, { NewCurFd, CurOfs }, Lookup),
  113. NewState = State#state { cur_file = Next,
  114. cur_fd = NextFd,
  115. cur_ofs = 0,
  116. lookup = NewLookup },
  117. write_metadata (NewState),
  118. write (NewState, Term);
  119. write (State = #state { max_total_bytes = Max,
  120. total_bytes = Total,
  121. lookup = Lookup }, Term) when Total > Max ->
  122. { First, { Fd, Size }, NewLookup } = gb_trees:take_smallest (Lookup),
  123. file:close (Fd),
  124. NewState = State#state { total_bytes = Total - Size,
  125. lookup = NewLookup },
  126. write_metadata (NewState),
  127. file:delete (path (State, First)),
  128. write (NewState, Term);
  129. write (State = #state { cur_file = Cur,
  130. cur_fd = Fd,
  131. cur_ofs = Ofs,
  132. total_bytes = Total }, Term) ->
  133. B = term_to_binary (Term, [ { minor_version, 1 } ]),
  134. Len = size (B),
  135. case file:pwrite (Fd, Ofs, [ header (Len), B ]) of
  136. ok ->
  137. Id = { Cur, Ofs, Len },
  138. NewState = State#state { cur_ofs = Ofs + Len + ?HEADER_LEN,
  139. total_bytes = Total + Len + ?HEADER_LEN },
  140. { Id, NewState };
  141. Error ->
  142. erlang:error (Error)
  143. end.
  144. write_metadata (State = #state { cur_file = Cur, lookup = Lookup }) ->
  145. List = [ { F, Size } || { F, { _Fd, Size } } <- gb_trees:to_list (Lookup) ],
  146. MetaData = term_to_binary ({ ?MODULE, 1, Cur, List }),
  147. Path = meta_path (State),
  148. case file:write_file (Path, MetaData) of
  149. ok -> ok;
  150. Error -> erlang:error ({ write, Path, Error })
  151. end.
  152. open (State) ->
  153. ok = filelib:ensure_dir (meta_path (State)),
  154. { Cur, Lookup, Total } =
  155. try
  156. { ok, MetaData } = file:read_file (meta_path (State)),
  157. { ?MODULE, 1, OldCur, List } = binary_to_term (MetaData),
  158. Files = case file:read_file_info (path (State, OldCur)) of
  159. { ok, #file_info { size = Size } } ->
  160. [ { OldCur, Size } | List ];
  161. _ ->
  162. List
  163. end,
  164. open_files (State, Files, OldCur + 1, gb_trees:empty (), 0)
  165. catch
  166. _:_ -> { 0, gb_trees:empty (), 0 }
  167. end,
  168. CurFd = open_readwrite_or_die (State, Cur),
  169. NewState = State#state { cur_file = Cur,
  170. cur_fd = CurFd,
  171. cur_ofs = 0,
  172. lookup = Lookup,
  173. total_bytes = Total },
  174. write_metadata (NewState),
  175. NewState.
  176. open_files (_State, [], MaxFile, Lookup, Total) ->
  177. { MaxFile, Lookup, Total };
  178. open_files (State, [ { File, Size } | Files ], MaxFile, Lookup, Total) ->
  179. try
  180. Fd = open_read_or_die (State, File),
  181. NewLookup = gb_trees:insert (File, { Fd, Size }, Lookup),
  182. NewMaxFile = case File > MaxFile of
  183. true -> File;
  184. false -> MaxFile
  185. end,
  186. open_files (State, Files, NewMaxFile, NewLookup, Total + Size)
  187. catch
  188. _:_ ->
  189. open_files (State, Files, MaxFile, Lookup, Total)
  190. end.
  191. close (#state { cur_fd = CurFd, lookup = Lookup }) ->
  192. file:close (CurFd),
  193. Fds = [ Fd || { _File, { Fd, _Size } } <- gb_trees:to_list (Lookup) ],
  194. lists:foreach (fun (Fd) ->
  195. file:close (Fd)
  196. end,
  197. Fds).
  198. open_read_or_die (State, File) ->
  199. open_or_die (State, File, [ read, raw, binary ]).
  200. open_readwrite_or_die (State, File) ->
  201. open_or_die (State, File, [ read, write, raw, binary ]).
  202. open_or_die (State, File, Options) ->
  203. Path = path (State, File),
  204. case file:open (Path, Options) of
  205. { ok, Fd } ->
  206. Fd;
  207. Error ->
  208. erlang:error ({ open, Path, Error })
  209. end.
  210. header (Len) when Len < 16#100000000 ->
  211. << $E, $E, $E, $E, Len:32/big-unsigned-integer >>.
  212. path (State, File) ->
  213. State#state.base_path ++ [ $. | integer_to_list (File) ].
  214. meta_path (State) ->
  215. State#state.base_path ++ ".meta".
  216. option (Options, Name) ->
  217. case lists:keysearch (Name, 1, Options) of
  218. { value, { Name, Value } } ->
  219. Value;
  220. _ ->
  221. erlang:error ({ missing_option, Name })
  222. end.