New branched document revision support in the back-end database. Replicator almost...
[couchdbimport.git] / CouchProjects / CouchDb / couch_file.erl
blobe1a746304288d77284d5d2891f82035a8509ea79
1 %% CouchDb
2 %% Copyright (C) 2006 Damien Katz
3 %%
4 %% This program is free software; you can redistribute it and/or
5 %% modify it under the terms of the GNU General Public License
6 %% as published by the Free Software Foundation; either version 2
7 %% of the License, or (at your option) any later version.
8 %%
9 %% This program is distributed in the hope that it will be useful,
10 %% but WITHOUT ANY WARRANTY; without even the implied warranty of
11 %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 %% GNU General Public License for more details.
13 %%
14 %% You should have received a copy of the GNU General Public License
15 %% along with this program; if not, write to the Free Software
16 %% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 -module(couch_file).
19 -behaviour(gen_server).
21 -export([open/2, close/1, pread/3, pwrite/3, expand/2, bytes/1, sync/1, truncate/2]).
22 -export([append_term/2, pread_term/2]).
23 -export([init/1, terminate/2, handle_call/3, handle_cast/2, code_change/3, handle_info/2]).
25 %%----------------------------------------------------------------------
26 %% Args: Valid Options are [create] and [create,overwrite].
27 %% Files are opened in read/write mode.
28 %% Returns: On success, {ok, Fd}
29 %% or {error, Reason} if the file could not be opened.
30 %%----------------------------------------------------------------------
32 open(Filepath, Options) ->
33 case gen_server:start_link(couch_file, {Filepath, Options, self()}, []) of
34 {ok, FdPid} ->
35 % we got back an ok, but that doesn't really mean it was successful.
36 % Instead the true status has been sent back to us as a message.
37 % We do this because if the gen_server doesn't initialize properly,
38 % it generates a crash report that will get logged. This avoids
39 % that mess, because we don't want.
40 receive
41 {FdPid, ok} ->
42 {ok, FdPid};
43 {FdPid, Error} ->
44 Error
45 end;
46 Error ->
47 Error
48 end.
51 %%----------------------------------------------------------------------
52 %% Args: Pos is the offset from the beginning of the file, Bytes is
53 %% is the number of bytes to read.
54 %% Returns: {ok, Binary} where Binary is a binary data from disk
55 %% or {error, Reason}.
56 %%----------------------------------------------------------------------
58 pread(Fd, Pos, Bytes) when Bytes > 0 ->
59 gen_server:call(Fd, {pread, Pos, Bytes}).
62 %%----------------------------------------------------------------------
63 %% Args: Pos is the offset from the beginning of the file, Bin is
64 %% is the binary to write
65 %% Returns: ok
66 %% or {error, Reason}.
67 %%----------------------------------------------------------------------
69 pwrite(Fd, Pos, Bin) ->
70 gen_server:call(Fd, {pwrite, Pos, Bin}).
72 %%----------------------------------------------------------------------
73 %% Purpose: To append a segment of zeros to the end of the file.
74 %% Args: Bytes is the number of bytes to append to the file.
75 %% Returns: {ok, Pos} where Pos is the file offset to the beginning of
76 %% the new segments.
77 %% or {error, Reason}.
78 %%----------------------------------------------------------------------
80 expand(Fd, Bytes) when Bytes > 0 ->
81 gen_server:call(Fd, {expand, Bytes}).
84 %%----------------------------------------------------------------------
85 %% Purpose: To append an Erlang term to the end of the file.
86 %% Args: Erlang term to serialize and append to the file.
87 %% Returns: {ok, Pos} where Pos is the file offset to the beginning the
88 %% serialized term. Use pread_term to read the term back.
89 %% or {error, Reason}.
90 %%----------------------------------------------------------------------
92 append_term(Fd, Term) ->
93 gen_server:call(Fd, {append_term, Term}).
96 %%----------------------------------------------------------------------
97 %% Purpose: Reads a term from a file that was written with append_term
98 %% Args: Pos, the offset into the file where the term is serialized.
99 %% Returns: {ok, Term}
100 %% or {error, Reason}.
101 %%----------------------------------------------------------------------
103 pread_term(Fd, Pos) ->
104 gen_server:call(Fd, {pread_term, Pos}).
107 %%----------------------------------------------------------------------
108 %% Purpose: The length of a file, in bytes.
109 %% Returns: {ok, Bytes}
110 %% or {error, Reason}.
111 %%----------------------------------------------------------------------
113 % length in bytes
114 bytes(Fd) ->
115 gen_server:call(Fd, bytes).
117 %%----------------------------------------------------------------------
118 %% Purpose: Truncate a file to the number of bytes.
119 %% Returns: ok
120 %% or {error, Reason}.
121 %%----------------------------------------------------------------------
123 truncate(Fd, Pos) ->
124 gen_server:call(Fd, {truncate, Pos}).
126 %%----------------------------------------------------------------------
127 %% Purpose: Ensure all bytes written to the file are flushed to disk.
128 %% Returns: ok
129 %% or {error, Reason}.
130 %%----------------------------------------------------------------------
132 sync(Fd) ->
133 gen_server:call(Fd, sync).
135 %%----------------------------------------------------------------------
136 %% Purpose: Close the file. Is performed asynchronously.
137 %% Returns: ok
138 %%----------------------------------------------------------------------
139 close(Fd) ->
140 gen_server:cast(Fd, close).
144 init_status_ok(ReturnPid, Fd) ->
145 ReturnPid ! {self(), ok}, % signal back ok
146 {ok, Fd}.
148 init_status_error(ReturnPid, Error) ->
149 ReturnPid ! {self(), Error}, % signal back error status
150 self() ! self_close, % tell ourself to close async
151 {ok, nil}.
153 % server functions
155 init({Filepath, Options, ReturnPid}) ->
156 case lists:member(create, Options) of
157 true ->
158 filelib:ensure_dir(Filepath),
159 case file:open(Filepath, [read, write, raw, binary]) of
160 {ok, Fd} ->
161 {ok, Length} = file:position(Fd, eof),
162 case Length > 0 of
163 true ->
164 % this means the file already exists and has data.
165 % FYI: We don't differentiate between empty files and non-existant
166 % files here. That could cause issues someday.
167 case lists:member(overwrite, Options) of
168 true ->
169 {ok, 0} = file:position(Fd, 0),
170 ok = file:truncate(Fd),
171 init_status_ok(ReturnPid, Fd);
172 false ->
173 ok = file:close(Fd),
174 init_status_error(ReturnPid, {error, file_exists})
175 end;
176 false ->
177 init_status_ok(ReturnPid, Fd)
178 end;
179 Error ->
180 init_status_error(ReturnPid, Error)
181 end;
182 false ->
183 % open in read mode first, so we don't create the file if it doesn't exist.
184 case file:open(Filepath, [read, raw]) of
185 {ok, Fd_Read} ->
186 {ok, Fd} = file:open(Filepath, [read, write, raw, binary]),
187 ok = file:close(Fd_Read),
188 init_status_ok(ReturnPid, Fd);
189 Error ->
190 init_status_error(ReturnPid, Error)
192 end.
195 terminate(_Reason, nil) ->
197 terminate(_Reason, Fd) ->
198 file:close(Fd),
202 handle_call({pread, Pos, Bytes}, _From, Fd) ->
203 {reply, file:pread(Fd, Pos, Bytes), Fd};
204 handle_call({pwrite, Pos, Bin}, _From, Fd) ->
205 {reply, file:pwrite(Fd, Pos, Bin), Fd};
206 handle_call({expand, Num}, _From, Fd) ->
207 {ok, Pos} = file:position(Fd, eof),
208 {reply, {file:pwrite(Fd, Pos + Num - 1, <<0>>), Pos}, Fd};
209 handle_call(bytes, _From, Fd) ->
210 {reply, file:position(Fd, eof), Fd};
211 handle_call(sync, _From, Fd) ->
212 {reply, file:sync(Fd), Fd};
213 handle_call({truncate, Pos}, _From, Fd) ->
214 {ok, Pos} = file:position(Fd, Pos),
215 {reply, file:truncate(Fd), Fd};
216 handle_call({append_term, Term}, _From, Fd) ->
217 Bin = term_to_binary(Term),
218 TermLen = size(Bin),
219 Bin2 = <<TermLen:32, Bin/binary>>,
220 {ok, Pos} = file:position(Fd, eof),
221 {reply, {file:pwrite(Fd, Pos, Bin2), Pos}, Fd};
222 handle_call({pread_term, Pos}, _From, Fd) ->
223 {ok, <<TermLen:32>>}
224 = file:pread(Fd, Pos, 4),
225 {ok, Bin} = file:pread(Fd, Pos + 4, TermLen),
226 {reply, {ok, binary_to_term(Bin)}, Fd}.
229 handle_cast(close, Fd) ->
230 {stop,normal,Fd}. % causes terminate to be called
232 code_change(_OldVsn, State, _Extra) ->
233 {ok, State}.
235 handle_info(self_close, State) ->
236 {stop,normal,State};
237 handle_info(_Info, State) ->
238 {noreply, State}.