View source with raw comments or as raw
    1/*  Part of SWISH
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2015, VU University Amsterdam
    7    All rights reserved.
    8
    9    Redistribution and use in source and binary forms, with or without
   10    modification, are permitted provided that the following conditions
   11    are met:
   12
   13    1. Redistributions of source code must retain the above copyright
   14       notice, this list of conditions and the following disclaimer.
   15
   16    2. Redistributions in binary form must reproduce the above copyright
   17       notice, this list of conditions and the following disclaimer in
   18       the documentation and/or other materials provided with the
   19       distribution.
   20
   21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32    POSSIBILITY OF SUCH DAMAGE.
   33*/
   34
   35:- module(gitty_driver_bdb,
   36	  [ gitty_close/1,		% +Store
   37	    gitty_file/3,		% +Store, ?Name, ?Hash
   38
   39	    gitty_update_head/4,	% +Store, +Name, +OldCommit, +NewCommit
   40	    delete_head/2,		% +Store, +Name
   41	    set_head/3,			% +Store, +Name, +Hash
   42	    store_object/4,		% +Store, +Hash, +Header, +Data
   43	    delete_object/2,		% +Store, +Hash
   44
   45	    gitty_hash/2,		% +Store, ?Hash
   46	    load_plain_commit/3,	% +Store, +Hash, -Meta
   47	    load_object/5		% +Store, +Hash, -Data, -Type, -Size
   48	  ]).   49:- use_module(library(zlib)).   50:- use_module(library(dcg/basics)).   51:- use_module(library(memfile)).   52:- use_module(library(bdb)).

Gitty BDB driver

This version of the driver uses library(bdb), the BerkeyDB database. This driver is particularly suited for large numbers of files. The store uses less disk space and starts much faster on large numbers of files.

The BDB database file contains two databases:

*/

   67:- dynamic
   68	bdb_env/2,			% Store, Env
   69	bdb_db/3.			% Store, Database, Handle
   70:- volatile
   71	bdb_env/2,
   72	bdb_db/3.   73
   74
   75bdb_handle(Store, Database, Handle) :-
   76	bdb_db(Store, Database, Handle), !.
   77bdb_handle(Store, Database, Handle) :-
   78	with_mutex(gitty_bdb, bdb_handle_sync(Store, Database, Handle)).
   79
   80bdb_handle_sync(Store, Database, Handle) :-
   81	bdb_db(Store, Database, Handle), !.
   82bdb_handle_sync(Store, Database, Handle) :-
   83	bdb_store(Store, Env),
   84	db_types(Database, KeyType, ValueType),
   85	bdb_open(Database, update, Handle,
   86		 [ environment(Env),
   87		   key(KeyType),
   88		   value(ValueType)
   89		 ]),
   90	asserta(bdb_db(Store, Database, Handle)).
   91
   92db_types(heads,   atom, atom).		% Name --> Hash
   93db_types(objects, atom, c_blob).	% Hash --> Blob
 bdb_store(+Store, -Env) is det
Get the BDB environment for Store.
   99bdb_store(Store, Env) :-
  100	bdb_env(Store, Env), !.
  101bdb_store(Store, Env) :-
  102	with_mutex(gitty_bdb, bdb_store_sync(Store, Env)).
  103
  104bdb_store_sync(Store, Env) :-
  105	bdb_env(Store, Env), !.
  106bdb_store_sync(Store, Env) :-
  107	ensure_directory(Store),
  108	bdb_init(Env,
  109		 [ home(Store),
  110		   create(true),
  111		   thread(true),
  112		   init_txn(true),
  113		   recover(true),
  114		   register(true)
  115		 ]),
  116	asserta(bdb_env(Store, Env)).
  117
  118ensure_directory(Dir) :-
  119	exists_directory(Dir), !.
  120ensure_directory(Dir) :-
  121	make_directory(Dir).
 gitty_close(+Store) is det
Close the BDB environment associated with a gitty store
  127gitty_close(Store) :-
  128	with_mutex(gitty_bdb, gitty_close_sync(Store)).
  129
  130gitty_close_sync(Store) :-
  131	(   retract(bdb_env(Store, Env))
  132	->  bdb_close_environment(Env)
  133	;   true
  134	).
 gitty_file(+Store, ?File, ?Head) is nondet
True when File entry in the gitty store and Head is the HEAD revision.
  142gitty_file(Store, Head, Hash) :-
  143	bdb_handle(Store, heads, H),
  144	(   nonvar(Head)
  145	->  bdb_get(H, Head, Hash)
  146	;   bdb_enum(H, Head, Hash)
  147	).
 gitty_update_head(+Store, +Name, +OldCommit, +NewCommit) is det
Update the head of a gitty store for Name. OldCommit is the current head and NewCommit is the new head. If Name is created, and thus there is no head, OldCommit must be -.

This operation can fail because another writer has updated the head. This can both be in-process or another process.

Errors
- gitty(file_exists(Name) if the file already exists
- gitty(not_at_head(Name, OldCommit) if the head was moved by someone else.
  162gitty_update_head(Store, Name, OldCommit, NewCommit) :-
  163	bdb_store(Store, Env),
  164	bdb_transaction(
  165	    Env,
  166	    gitty_update_head_sync(Store, Name, OldCommit, NewCommit)).
  167
  168gitty_update_head_sync(Store, Name, OldCommit, NewCommit) :-
  169	bdb_handle(Store, heads, BDB),
  170	(   OldCommit == (-)
  171	->  (   bdb_get(BDB, Name, _)
  172	    ->	throw(error(gitty(file_exists(Name),_)))
  173	    ;	bdb_put(BDB, Name, NewCommit)
  174	    )
  175	;   (   bdb_get(BDB, Name, OldCommit)
  176	    ->	bdb_put(BDB, Name, NewCommit)
  177	    ;	throw(error(gitty(not_at_head(Name, OldCommit)), _))
  178	    )
  179	).
 delete_head(+Store, +Name) is det
Delete the named head.
  185delete_head(Store, Name) :-
  186	bdb_handle(Store, heads, BDB),
  187	bdb_del(BDB, Name, _Old).
 set_head(+Store, +File, +Hash) is det
Set the head of the given File to Hash
  193set_head(Store, File, Hash) :-
  194	bdb_handle(Store, heads, BDB),
  195	bdb_put(BDB, File, Hash).
 load_plain_commit(+Store, +Hash, -Meta:dict) is semidet
Load the commit data as a dict. Fails if Hash does not exist or is not a commit.
  202load_plain_commit(Store, Hash, Meta) :-
  203	load_object(Store, Hash, String, commit, _Size),
  204	term_string(Meta, String, []).
 store_object(+Store, +Hash, +Header:string, +Data:string) is det
Store the actual object. The store must associate Hash with the concatenation of Hdr and Data.
  211store_object(Store, Hash, Hdr, Data) :-
  212	compress_string(Hdr, Data, Object),
  213	bdb_handle(Store, objects, BDB),
  214	bdb_put(BDB, Hash, Object).
  215
  216compress_string(Header, Data, String) :-
  217	setup_call_cleanup(
  218	    new_memory_file(MF),
  219	    ( setup_call_cleanup(
  220		  open_memory_file(MF, write, Out, [encoding(utf8)]),
  221		  setup_call_cleanup(
  222		      zopen(Out, OutZ, [ format(gzip),
  223					 close_parent(false)
  224				       ]),
  225		      format(OutZ, '~s~s', [Header, Data]),
  226		    close(OutZ)),
  227		  close(Out)),
  228	      memory_file_to_string(MF, String, octet)
  229	    ),
  230	    free_memory_file(MF)).
 load_object(+Store, +Hash, -Data, -Type, -Size) is det
Load an object given its Hash. Data holds the content as a string, Type is the object type (an atom) and Size is the size of the object in bytes.
  238load_object(Store, Hash, Data, Type, Size) :-
  239	bdb_handle(Store, objects, BDB),
  240	bdb_get(BDB, Hash, Blob),
  241	setup_call_cleanup(
  242	    open_string(Blob, In),
  243	    setup_call_cleanup(
  244		zopen(In, InZ, [ format(gzip),
  245				 close_parent(false)
  246			       ]),
  247		( set_stream(InZ, encoding(utf8)),
  248		  read_object(InZ, Data, Type, Size)
  249		),
  250		close(InZ)),
  251	    close(In)).
  252
  253read_object(In, Data, Type, Size) :-
  254	get_code(In, C0),
  255	read_hdr(C0, In, Hdr),
  256	phrase((nonblanks(TypeChars), " ", integer(Size)), Hdr),
  257	atom_codes(Type, TypeChars),
  258	read_string(In, _, Data).
  259
  260read_hdr(C, In, [C|T]) :-
  261	C > 0, !,
  262	get_code(In, C1),
  263	read_hdr(C1, In, T).
  264read_hdr(_, _, []).
 gitty_hash(+Store, ?Hash) is nondet
True when Hash is an object in the store.
  270gitty_hash(Store, Hash) :-
  271	bdb_handle(Store, objects, BDB),
  272	(   nonvar(Hash)
  273	->  bdb_get(BDB, Hash, _)
  274	;   bdb_enum(BDB, Hash, _)
  275	).
 delete_object(+Store, +Hash)
Delete an existing object
  281delete_object(Store, Hash) :-
  282	bdb_handle(Store, objects, BDB),
  283	bdb_del(BDB, Hash, _)