~ubuntu-branches/debian/wheezy/couchdb/wheezy

« back to all changes in this revision

Viewing changes to src/CouchDB/couch_file.erl

  • Committer: Bazaar Package Importer
  • Author(s): Noah Slater
  • Date: 2008-02-06 17:03:38 UTC
  • Revision ID: james.westby@ubuntu.com-20080206170338-y411anylx3oplqid
Tags: upstream-0.7.3~svn684
ImportĀ upstreamĀ versionĀ 0.7.3~svn684

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
%   Copyright 2007, 2008 Damien Katz <damien_katz@yahoo.com>
 
2
%
 
3
%   Licensed under the Apache License, Version 2.0 (the "License");
 
4
%   you may not use this file except in compliance with the License.
 
5
%   You may obtain a copy of the License at
 
6
%
 
7
%       http://www.apache.org/licenses/LICENSE-2.0
 
8
%
 
9
%   Unless required by applicable law or agreed to in writing, software
 
10
%   distributed under the License is distributed on an "AS IS" BASIS,
 
11
%   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
12
%   See the License for the specific language governing permissions and
 
13
%   limitations under the License.
 
14
 
 
15
-module(couch_file).
 
16
-behaviour(gen_server).
 
17
 
 
18
-export([open/2, close/1, pread/3, pwrite/3, expand/2, bytes/1, sync/1, truncate/2]).
 
19
-export([append_term/2, pread_term/2]).
 
20
-export([init/1, terminate/2, handle_call/3, handle_cast/2, code_change/3, handle_info/2]).
 
21
 
 
22
%%----------------------------------------------------------------------
 
23
%% Args:   Valid Options are [create] and [create,overwrite].
 
24
%%  Files are opened in read/write mode.
 
25
%% Returns: On success, {ok, Fd}
 
26
%%  or {error, Reason} if the file could not be opened.
 
27
%%----------------------------------------------------------------------
 
28
 
 
29
open(Filepath, Options) ->
 
30
    case gen_server:start_link(couch_file, {Filepath, Options, self()}, []) of
 
31
    {ok, FdPid} ->
 
32
        % we got back an ok, but that doesn't really mean it was successful.
 
33
        % Instead the true status has been sent back to us as a message.
 
34
        % We do this because if the gen_server doesn't initialize properly,
 
35
        % it generates a crash report that will get logged. This avoids
 
36
        % that mess, because we don't want.
 
37
        receive
 
38
        {FdPid, ok} ->
 
39
            {ok, FdPid};
 
40
        {FdPid, Error} ->
 
41
            Error
 
42
        end;
 
43
    Error ->
 
44
        Error
 
45
    end.
 
46
 
 
47
 
 
48
%%----------------------------------------------------------------------
 
49
%% Args:    Pos is the offset from the beginning of the file, Bytes is
 
50
%%  is the number of bytes to read.
 
51
%% Returns: {ok, Binary} where Binary is a binary data from disk
 
52
%%  or {error, Reason}.
 
53
%%----------------------------------------------------------------------
 
54
 
 
55
pread(Fd, Pos, Bytes) when Bytes > 0 ->
 
56
    gen_server:call(Fd, {pread, Pos, Bytes}).
 
57
 
 
58
 
 
59
%%----------------------------------------------------------------------
 
60
%% Args:    Pos is the offset from the beginning of the file, Bin is
 
61
%%  is the binary to write
 
62
%% Returns: ok
 
63
%%  or {error, Reason}.
 
64
%%----------------------------------------------------------------------
 
65
 
 
66
pwrite(Fd, Pos, Bin) ->
 
67
    gen_server:call(Fd, {pwrite, Pos, Bin}).
 
68
 
 
69
%%----------------------------------------------------------------------
 
70
%% Purpose: To append a segment of zeros to the end of the file.
 
71
%% Args:    Bytes is the number of bytes to append to the file.
 
72
%% Returns: {ok, Pos} where Pos is the file offset to the beginning of
 
73
%%  the new segments.
 
74
%%  or {error, Reason}.
 
75
%%----------------------------------------------------------------------
 
76
 
 
77
expand(Fd, Bytes) when Bytes > 0 ->
 
78
    gen_server:call(Fd, {expand, Bytes}).
 
79
 
 
80
 
 
81
%%----------------------------------------------------------------------
 
82
%% Purpose: To append an Erlang term to the end of the file.
 
83
%% Args:    Erlang term to serialize and append to the file.
 
84
%% Returns: {ok, Pos} where Pos is the file offset to the beginning the
 
85
%%  serialized  term. Use pread_term to read the term back.
 
86
%%  or {error, Reason}.
 
87
%%----------------------------------------------------------------------
 
88
 
 
89
append_term(Fd, Term) ->
 
90
    gen_server:call(Fd, {append_term, Term}).
 
91
 
 
92
 
 
93
%%----------------------------------------------------------------------
 
94
%% Purpose: Reads a term from a file that was written with append_term
 
95
%% Args:    Pos, the offset into the file where the term is serialized.
 
96
%% Returns: {ok, Term}
 
97
%%  or {error, Reason}.
 
98
%%----------------------------------------------------------------------
 
99
 
 
100
pread_term(Fd, Pos) ->
 
101
    gen_server:call(Fd, {pread_term, Pos}).
 
102
 
 
103
 
 
104
%%----------------------------------------------------------------------
 
105
%% Purpose: The length of a file, in bytes.
 
106
%% Returns: {ok, Bytes}
 
107
%%  or {error, Reason}.
 
108
%%----------------------------------------------------------------------
 
109
 
 
110
% length in bytes
 
111
bytes(Fd) ->
 
112
    gen_server:call(Fd, bytes).
 
113
 
 
114
%%----------------------------------------------------------------------
 
115
%% Purpose: Truncate a file to the number of bytes.
 
116
%% Returns: ok
 
117
%%  or {error, Reason}.
 
118
%%----------------------------------------------------------------------
 
119
 
 
120
truncate(Fd, Pos) ->
 
121
    gen_server:call(Fd, {truncate, Pos}).
 
122
 
 
123
%%----------------------------------------------------------------------
 
124
%% Purpose: Ensure all bytes written to the file are flushed to disk.
 
125
%% Returns: ok
 
126
%%  or {error, Reason}.
 
127
%%----------------------------------------------------------------------
 
128
 
 
129
sync(Fd) ->
 
130
    gen_server:call(Fd, sync).
 
131
 
 
132
%%----------------------------------------------------------------------
 
133
%% Purpose: Close the file. Is performed asynchronously.
 
134
%% Returns: ok
 
135
%%----------------------------------------------------------------------
 
136
close(Fd) ->
 
137
    gen_server:cast(Fd, close).
 
138
 
 
139
 
 
140
 
 
141
init_status_ok(ReturnPid, Fd) ->
 
142
    ReturnPid ! {self(), ok}, % signal back ok
 
143
    {ok, Fd}.
 
144
 
 
145
init_status_error(ReturnPid, Error) ->
 
146
    ReturnPid ! {self(), Error}, % signal back error status
 
147
    self() ! self_close, % tell ourself to close async
 
148
    {ok, nil}.
 
149
 
 
150
% server functions
 
151
 
 
152
init({Filepath, Options, ReturnPid}) ->
 
153
    case lists:member(create, Options) of
 
154
    true ->
 
155
        filelib:ensure_dir(Filepath),
 
156
        case file:open(Filepath, [read, write, raw, binary]) of
 
157
        {ok, Fd} ->
 
158
            {ok, Length} = file:position(Fd, eof),
 
159
            case Length > 0 of
 
160
            true ->
 
161
                % this means the file already exists and has data.
 
162
                % FYI: We don't differentiate between empty files and non-existant
 
163
                % files here. That could cause issues someday.
 
164
                case lists:member(overwrite, Options) of
 
165
                true ->
 
166
                    {ok, 0} = file:position(Fd, 0),
 
167
                    ok = file:truncate(Fd),
 
168
                    init_status_ok(ReturnPid, Fd);
 
169
                false ->
 
170
                    ok = file:close(Fd),
 
171
                    init_status_error(ReturnPid, {error, file_exists})
 
172
                end;
 
173
            false ->
 
174
                init_status_ok(ReturnPid, Fd)
 
175
            end;
 
176
        Error ->
 
177
            init_status_error(ReturnPid, Error)
 
178
        end;
 
179
    false ->
 
180
        % open in read mode first, so we don't create the file if it doesn't exist.
 
181
        case file:open(Filepath, [read, raw]) of
 
182
        {ok, Fd_Read} ->
 
183
            {ok, Fd} = file:open(Filepath, [read, write, raw, binary]),
 
184
            ok = file:close(Fd_Read),
 
185
            init_status_ok(ReturnPid, Fd);
 
186
        Error ->
 
187
            init_status_error(ReturnPid, Error)
 
188
        end
 
189
    end.
 
190
 
 
191
 
 
192
terminate(_Reason, nil) ->
 
193
    ok;
 
194
terminate(_Reason, Fd) ->
 
195
    file:close(Fd),
 
196
    ok.
 
197
 
 
198
 
 
199
handle_call({pread, Pos, Bytes}, _From, Fd) ->
 
200
    {reply, file:pread(Fd, Pos, Bytes), Fd};
 
201
handle_call({pwrite, Pos, Bin}, _From, Fd) ->
 
202
    {reply, file:pwrite(Fd, Pos, Bin), Fd};
 
203
handle_call({expand, Num}, _From, Fd) ->
 
204
    {ok, Pos} = file:position(Fd, eof),
 
205
    {reply, {file:pwrite(Fd, Pos + Num - 1, <<0>>), Pos}, Fd};
 
206
handle_call(bytes, _From, Fd) ->
 
207
    {reply, file:position(Fd, eof), Fd};
 
208
handle_call(sync, _From, Fd) ->
 
209
    {reply, file:sync(Fd), Fd};
 
210
handle_call({truncate, Pos}, _From, Fd) ->
 
211
    {ok, Pos} = file:position(Fd, Pos),
 
212
    {reply, file:truncate(Fd), Fd};
 
213
handle_call({append_term, Term}, _From, Fd) ->
 
214
    Bin = term_to_binary(Term),
 
215
    TermLen = size(Bin),
 
216
    Bin2 = <<TermLen:32, Bin/binary>>,
 
217
    {ok, Pos} = file:position(Fd, eof),
 
218
    {reply, {file:pwrite(Fd, Pos, Bin2), Pos}, Fd};
 
219
handle_call({pread_term, Pos}, _From, Fd) ->
 
220
    {ok, <<TermLen:32>>}
 
221
        = file:pread(Fd, Pos, 4),
 
222
    {ok, Bin} = file:pread(Fd, Pos + 4, TermLen),
 
223
    {reply, {ok, binary_to_term(Bin)}, Fd}.
 
224
 
 
225
 
 
226
handle_cast(close, Fd) ->
 
227
    {stop,normal,Fd}. % causes terminate to be called
 
228
 
 
229
code_change(_OldVsn, State, _Extra) ->
 
230
    {ok, State}.
 
231
 
 
232
handle_info(self_close, State) ->
 
233
    {stop,normal,State};
 
234
handle_info(_Info, State) ->
 
235
    {noreply, State}.