/elibs/mnesia_storage.erl

http://github.com/cliffmoon/dynomite · Erlang · 233 lines · 201 code · 22 blank · 10 comment · 1 complexity · c84f9163b99c26cecd4b333ccb751be1 MD5 · raw file

  1. -module (mnesia_storage).
  2. -export ([open/2, close/1, get/2, put/4, has_key/2, delete/2, fold/3]).
  3. %% largest value size allowed in RAM storage
  4. -define(MEM_VALUE_LIMIT, 1024).
  5. -record(entry, {
  6. key,
  7. values,
  8. path,
  9. context,
  10. updated
  11. %% expires -- someday
  12. }).
  13. open(Directory, Name) ->
  14. ok = filelib:ensure_dir(Directory ++ "/"),
  15. TableName = list_to_atom(lists:concat([Name, '_', node()])),
  16. ok = ensure_table(TableName),
  17. {ok, {Directory, TableName}}.
  18. % noop
  19. close({_Directory, Table}) -> ok.
  20. fold(Fun, {_Directory, Table}, AccIn) when is_function(Fun) ->
  21. InnerFoldFun = fun(#entry{key=Key, values=V, path=P, context=C}, Acc) ->
  22. Values = case V of
  23. undefined ->
  24. read_file(P);
  25. _ ->
  26. V
  27. end,
  28. Fun({Key, C, Values}, Acc)
  29. end,
  30. FoldFun = fun() ->
  31. mnesia:foldl(InnerFoldFun, AccIn, Table)
  32. end,
  33. mnesia:ets(FoldFun).
  34. put(Key, Context, Values, {Directory, Table}) ->
  35. Vlist = if
  36. is_list(Values) -> Values;
  37. true -> [Values]
  38. end,
  39. Old = mnesia:ets(fun() ->
  40. case mnesia:read(Table, Key, read) of
  41. [Entry] ->
  42. Entry;
  43. _ ->
  44. #entry{}
  45. end
  46. end),
  47. case value_size(Values) of
  48. large ->
  49. put_values_in_file(
  50. Key, Context, Vlist, Directory, Table, Old#entry.path);
  51. _ ->
  52. Write = fun() ->
  53. Entry = #entry{key=Key,
  54. context=Context,
  55. values=Vlist,
  56. updated=erlang:now()},
  57. mnesia:write(Table, Entry, write)
  58. end,
  59. %% can't use ets access here, since we're writing
  60. ok = mnesia:async_dirty(Write),
  61. %% clean up any old path
  62. case Old#entry.path of
  63. undefined ->
  64. ok;
  65. P when is_list(P) ->
  66. ok = file:delete(P)
  67. end,
  68. {ok, {Directory, Table}}
  69. end.
  70. get(Key, {_Directory, Table}) ->
  71. Read = fun() ->
  72. mnesia:read(Table, Key, read)
  73. end,
  74. case mnesia:ets(Read) of
  75. [] ->
  76. {ok, not_found};
  77. [#entry{key=Key, context=C, path=P}] when is_list(P) ->
  78. {ok, {C, read_file(P)}};
  79. [#entry{key=Key, values=V, context=C}] ->
  80. {ok, {C, V}};
  81. Other ->
  82. Other
  83. end.
  84. has_key(Key, {_Directory, Table}) ->
  85. Read = fun () ->
  86. mnesia:read(Table, Key, read)
  87. end,
  88. case mnesia:ets(Read) of
  89. [] ->
  90. {ok, false};
  91. [_Record] ->
  92. {ok, true};
  93. Other ->
  94. {ok, false}
  95. end.
  96. delete(Key, {Directory, Table}) ->
  97. Del = fun () ->
  98. case mnesia:read(Table, Key, read) of
  99. [#entry{path=P}] ->
  100. {mnesia:delete(Table, Key, write), P};
  101. _ ->
  102. {ok, not_found}
  103. end
  104. end,
  105. %% can't use ets access mode here, since we're writing
  106. case mnesia:async_dirty(Del) of
  107. {ok, not_found} ->
  108. {ok, {Directory, Table}};
  109. {ok, undefined} ->
  110. {ok, {Directory, Table}};
  111. {ok, Path} when is_list(Path) ->
  112. ok = file:delete(Path),
  113. {ok, {Directory, Table}};
  114. Other ->
  115. Other
  116. end.
  117. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  118. % internal functions
  119. read_file(Path) ->
  120. {ok, Binary} = file:read_file(Path),
  121. Values = case (catch binary_to_term(Binary)) of
  122. {'EXIT', _} -> [Binary];
  123. Terms -> Terms
  124. end,
  125. Values.
  126. put_values_in_file(Key, Context, Values, Directory, Table, Path) ->
  127. Read = fun() ->
  128. mnesia:read(Table, Key, read)
  129. end,
  130. HashedFilename = case Path of
  131. undefined ->
  132. create_filename(Directory, Key);
  133. _ ->
  134. Path
  135. end,
  136. ToWrite = term_to_binary(Values),
  137. Write = fun() ->
  138. Entry = #entry{key=Key,
  139. context=Context,
  140. path=HashedFilename,
  141. updated=erlang:now()},
  142. mnesia:write(Table, Entry, write)
  143. end,
  144. %% can't use ets access mode here, since we're writing
  145. ok = mnesia:async_dirty(Write),
  146. ok = file:write_file(HashedFilename, ToWrite, [raw]),
  147. {ok, {Directory, Table}}.
  148. create_filename(Directory, Key) ->
  149. Hash = lists:concat(
  150. lists:map(
  151. fun(Int) -> erlang:integer_to_list(Int, 16) end,
  152. binary_to_list(
  153. crypto:sha(
  154. list_to_binary(Key))))),
  155. Filename = ensure_against_collisions(Directory, Hash),
  156. ok = filelib:ensure_dir(Filename),
  157. Filename.
  158. ensure_against_collisions(Directory, Hash) ->
  159. ensure_against_collisions(Directory, Hash, 0).
  160. ensure_against_collisions(Directory, Hash, Append) ->
  161. Filename = case Append > 0 of
  162. true -> hash_to_directory(Directory, Hash) ++ "-" ++ integer_to_list(Append);
  163. false -> hash_to_directory(Directory, Hash)
  164. end,
  165. case filelib:is_file(Filename) of
  166. true -> ensure_against_collisions(Directory, Hash, Append+1);
  167. false -> Filename
  168. end.
  169. hash_to_directory(Directory, Hash) ->
  170. hash_to_directory(Directory, Hash, Hash, 3).
  171. hash_to_directory(Directory, _Left, Original, 0) ->
  172. lists:concat([Directory, '/', Original]);
  173. hash_to_directory(Directory, [Char|Left], Original, Depth) ->
  174. hash_to_directory(lists:concat([Directory, '/', [Char]]), Left, Original, Depth-1).
  175. value_size(V) ->
  176. Len = iolist_size(V),
  177. if Len >= ?MEM_VALUE_LIMIT ->
  178. large;
  179. true ->
  180. small
  181. end.
  182. ensure_table(Table) ->
  183. %% ensure that schema is on disc
  184. case mnesia:start() of
  185. ok ->
  186. ok;
  187. {error, already_started} ->
  188. ok;
  189. Error ->
  190. exit(Error)
  191. end,
  192. case mnesia:change_table_copy_type(schema, node(), disc_copies) of
  193. {atomic, ok} ->
  194. ok;
  195. {aborted, {already_exists, _, _, _}} ->
  196. ok;
  197. Fail ->
  198. exit(Fail)
  199. end,
  200. case mnesia:create_table(Table,
  201. [{record_name, entry},
  202. {attributes, record_info(fields, entry)},
  203. {disc_copies, [node()]},
  204. {local_content, true}]) of
  205. {atomic, ok} ->
  206. ok;
  207. {aborted, {already_exists, _}} ->
  208. ok;
  209. Other ->
  210. Other
  211. end.