| % Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| % use this file except in compliance with the License. You may obtain a copy of |
| % the License at |
| % |
| % http://www.apache.org/licenses/LICENSE-2.0 |
| % |
| % Unless required by applicable law or agreed to in writing, software |
| % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| % License for the specific language governing permissions and limitations under |
| % the License. |
| |
| -module(couch_compress). |
| |
| -export([compress/2, decompress/1, is_compressed/2]). |
| -export([get_compression_method/0]). |
| -export([uncompressed_size/1]). |
| |
| -include_lib("couch/include/couch_db.hrl"). |
| |
| % binaries compressed with snappy have their first byte set to this value |
| -define(SNAPPY_PREFIX, 1). |
| % Term prefixes documented at: |
| % http://www.erlang.org/doc/apps/erts/erl_ext_dist.html |
| -define(TERM_PREFIX, 131). |
| -define(COMPRESSED_TERM_PREFIX, 131, 80). |
| |
| get_compression_method() -> |
| case config:get("couchdb", "file_compression") of |
| undefined -> |
| ?DEFAULT_COMPRESSION; |
| Method1 -> |
| case string:tokens(Method1, "_") of |
| [Method] -> |
| list_to_existing_atom(Method); |
| [Method, Level] -> |
| {list_to_existing_atom(Method), list_to_integer(Level)} |
| end |
| end. |
| |
| compress(<<?SNAPPY_PREFIX, _/binary>> = Bin, snappy) -> |
| Bin; |
| compress(<<?SNAPPY_PREFIX, _/binary>> = Bin, Method) -> |
| compress(decompress(Bin), Method); |
| compress(<<?COMPRESSED_TERM_PREFIX, _/binary>> = Bin, {deflate, _Level}) -> |
| Bin; |
| compress(<<?TERM_PREFIX, _/binary>> = Bin, Method) -> |
| compress(decompress(Bin), Method); |
| compress(Term, none) -> |
| ?term_to_bin(Term); |
| compress(Term, {deflate, Level}) -> |
| term_to_binary(Term, [{minor_version, 1}, {compressed, Level}]); |
| compress(Term, snappy) -> |
| Bin = ?term_to_bin(Term), |
| try |
| {ok, CompressedBin} = snappy:compress(Bin), |
| <<?SNAPPY_PREFIX, CompressedBin/binary>> |
| catch |
| exit:snappy_nif_not_loaded -> |
| Bin |
| end. |
| |
| decompress(<<?SNAPPY_PREFIX, Rest/binary>>) -> |
| {ok, TermBin} = snappy:decompress(Rest), |
| binary_to_term(TermBin); |
| decompress(<<?TERM_PREFIX, _/binary>> = Bin) -> |
| binary_to_term(Bin); |
| decompress(_) -> |
| error(invalid_compression). |
| |
| is_compressed(<<?SNAPPY_PREFIX, _/binary>>, Method) -> |
| Method =:= snappy; |
| is_compressed(<<?COMPRESSED_TERM_PREFIX, _/binary>>, {deflate, _Level}) -> |
| true; |
| is_compressed(<<?COMPRESSED_TERM_PREFIX, _/binary>>, _Method) -> |
| false; |
| is_compressed(<<?TERM_PREFIX, _/binary>>, Method) -> |
| Method =:= none; |
| is_compressed(Term, _Method) when not is_binary(Term) -> |
| false; |
| is_compressed(_, _) -> |
| error(invalid_compression). |
| |
| uncompressed_size(<<?SNAPPY_PREFIX, Rest/binary>>) -> |
| {ok, Size} = snappy:uncompressed_length(Rest), |
| Size; |
| uncompressed_size(<<?COMPRESSED_TERM_PREFIX, Size:32, _/binary>> = _Bin) -> |
| % See http://erlang.org/doc/apps/erts/erl_ext_dist.html |
| % The uncompressed binary would be encoded with <<131, Rest/binary>> |
| % so need to add 1 for 131 |
| Size + 1; |
| uncompressed_size(<<?TERM_PREFIX, _/binary>> = Bin) -> |
| byte_size(Bin); |
| uncompressed_size(_) -> |
| error(invalid_compression). |