1% Licensed under the Apache License, Version 2.0 (the "License"); you may not
2% use this file except in compliance with the License. You may obtain a copy of
3% the License at
4%
5%   http://www.apache.org/licenses/LICENSE-2.0
6%
7% Unless required by applicable law or agreed to in writing, software
8% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10% License for the specific language governing permissions and limitations under
11% the License.
12
13-module(couch_compress).
14
15-export([compress/2, decompress/1, is_compressed/2]).
16-export([get_compression_method/0]).
17-export([uncompressed_size/1]).
18
19-include_lib("couch/include/couch_db.hrl").
20
21% binaries compressed with snappy have their first byte set to this value
22-define(SNAPPY_PREFIX, 1).
23% Term prefixes documented at:
24%      http://www.erlang.org/doc/apps/erts/erl_ext_dist.html
25-define(TERM_PREFIX, 131).
26-define(COMPRESSED_TERM_PREFIX, 131, 80).
27
28
29get_compression_method() ->
30    case config:get("couchdb", "file_compression") of
31    undefined ->
32        ?DEFAULT_COMPRESSION;
33    Method1 ->
34        case string:tokens(Method1, "_") of
35        [Method] ->
36            list_to_existing_atom(Method);
37        [Method, Level] ->
38            {list_to_existing_atom(Method), list_to_integer(Level)}
39        end
40    end.
41
42
43compress(<<?SNAPPY_PREFIX, _/binary>> = Bin, snappy) ->
44    Bin;
45compress(<<?SNAPPY_PREFIX, _/binary>> = Bin, Method) ->
46    compress(decompress(Bin), Method);
47compress(<<?COMPRESSED_TERM_PREFIX, _/binary>> = Bin, {deflate, _Level}) ->
48    Bin;
49compress(<<?TERM_PREFIX, _/binary>> = Bin, Method) ->
50    compress(decompress(Bin), Method);
51compress(Term, none) ->
52    ?term_to_bin(Term);
53compress(Term, {deflate, Level}) ->
54    term_to_binary(Term, [{minor_version, 1}, {compressed, Level}]);
55compress(Term, snappy) ->
56    Bin = ?term_to_bin(Term),
57    try
58        {ok, CompressedBin} = snappy:compress(Bin),
59        <<?SNAPPY_PREFIX, CompressedBin/binary>>
60    catch exit:snappy_nif_not_loaded ->
61        Bin
62    end.
63
64
65decompress(<<?SNAPPY_PREFIX, Rest/binary>>) ->
66    {ok, TermBin} = snappy:decompress(Rest),
67    binary_to_term(TermBin);
68decompress(<<?TERM_PREFIX, _/binary>> = Bin) ->
69    binary_to_term(Bin);
70decompress(_) ->
71    error(invalid_compression).
72
73
74is_compressed(<<?SNAPPY_PREFIX, _/binary>>, Method) ->
75    Method =:= snappy;
76is_compressed(<<?COMPRESSED_TERM_PREFIX, _/binary>>, {deflate, _Level}) ->
77    true;
78is_compressed(<<?COMPRESSED_TERM_PREFIX, _/binary>>, _Method) ->
79    false;
80is_compressed(<<?TERM_PREFIX, _/binary>>, Method) ->
81    Method =:= none;
82is_compressed(Term, _Method) when not is_binary(Term) ->
83    false;
84is_compressed(_, _) ->
85    error(invalid_compression).
86
87
88uncompressed_size(<<?SNAPPY_PREFIX, Rest/binary>>) ->
89    {ok, Size} = snappy:uncompressed_length(Rest),
90    Size;
91uncompressed_size(<<?COMPRESSED_TERM_PREFIX, Size:32, _/binary>> = _Bin) ->
92    % See http://erlang.org/doc/apps/erts/erl_ext_dist.html
93    % The uncompressed binary would be encoded with <<131, Rest/binary>>
94    % so need to add 1 for 131
95    Size + 1;
96uncompressed_size(<<?TERM_PREFIX, _/binary>> = Bin) ->
97    byte_size(Bin);
98uncompressed_size(_) ->
99    error(invalid_compression).
100