View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2003-2013, University of Amsterdam
    7                              VU University Amsterdam
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(rdf_edit,
   37          [ rdfe_assert/3,              % Sub, Pred, Obj
   38            rdfe_assert/4,              % Sub, Pred, Obj, PayLoad
   39            rdfe_retractall/3,          % Sub, Pred, Obj
   40            rdfe_retractall/4,          % Sub, Pred, Obj, PayLoad
   41            rdfe_update/4,              % Sub, Pred, Obj, +Action
   42            rdfe_update/5,              % Sub, Pred, Obj, +PayLoad, +Action
   43            rdfe_load/1,                % +File
   44            rdfe_load/2,                % +File, +Options
   45            rdfe_delete/1,              % +Resource
   46
   47            rdfe_register_ns/2,         % +Id, +URI
   48            rdfe_unregister_ns/2,       % +Id, +URI
   49
   50            rdfe_reset/0,               % clear everything
   51
   52            rdfe_transaction/1,         % :Goal
   53            rdfe_transaction/2,         % :Goal, +Name
   54            rdfe_transaction_member/2,  % +Transactions, -Action
   55            rdfe_transaction_name/2,    % +Transactions, -Name
   56            rdfe_set_transaction_name/1,% +Name
   57
   58            rdfe_set_watermark/1,       % +Name
   59
   60            rdfe_undo/0,                %
   61            rdfe_redo/0,
   62            rdfe_can_undo/1,            % -TID
   63            rdfe_can_redo/1,            % -TID
   64
   65            rdfe_set_file_property/2,   % +File, +Property
   66            rdfe_get_file_property/2,   % ?File, ?Property
   67
   68            rdfe_is_modified/1,         % ?File
   69            rdfe_clear_modified/1,      % +File
   70
   71            rdfe_open_journal/2,        % +File, +Mode
   72            rdfe_close_journal/0,
   73            rdfe_replay_journal/1,      % +File
   74            rdfe_current_journal/1,     % -Path
   75
   76            rdfe_snapshot_file/1        % -File
   77          ]).   78:- use_module(library(semweb/rdf_prefixes),
   79              [ (rdf_meta)/1,
   80                op(_,_,rdf_meta)
   81              ]).   82
   83:- autoload(rdf_db,
   84	    [ rdf_assert/4, rdf/4, rdf_retractall/4, rdf_update/4,
   85	      rdf_update/5, rdf_load/2, rdf_statistics/1, rdf_md5/2,
   86	      rdf_unload/1, rdf_save_db/2, rdf_load_db/1, rdf_register_ns/2,
   87	      rdf_source/2, rdf_graph_property/2, rdf_graph/1, rdf_set_graph/2,
   88	      rdf_reset_db/0, rdf_load/1
   89	    ]).   90:- autoload(library(broadcast),[broadcast/1]).   91:- use_module(library(debug),[debug/3,debugging/1]).   92:- autoload(library(gui_tracer),[gtrace/0]).   93:- autoload(library(lists),[append/3]).   94:- autoload(library(uri),[uri_file_name/2,uri_components/2,uri_data/3]).   95
   96:- meta_predicate
   97    rdfe_transaction(0),
   98    rdfe_transaction(0, +).   99
  100:- predicate_options(rdfe_load/2, 2,
  101                     [pass_to(rdf_db:rdf_load/2, 2)]).  102
  103:- dynamic
  104    undo_log/5,                     % TID, Action, Subj, Pred, Obj
  105    current_transaction/1,          % TID
  106    transaction_name/2,             % TID, Name
  107    undo_marker/2,                  % Mode, TID
  108    journal/3,                      % Path, Mode, Stream
  109    snapshot_file/1.                % File

RDF edit layer

This library provides a number of functions on top of the rdf_db module:

See also
- rdf_persistency.pl provides reliable persistency, but without changes boardcasting and undo/redo. */
To be done
- This library must be rewritten using rdf_monitor/3. This allows using edit layer without having to choose between rdf_ and rdfe_ predicates.
  125:- rdf_meta
  126    rdfe_assert(r,r,o),
  127    rdfe_assert(r,r,o,+),
  128    rdfe_retractall(r,r,o),
  129    rdfe_update(r,r,o,t),
  130    rdfe_delete(r),
  131    rdfe_transaction(:),
  132    rdfe_transaction(:, +).  133
  134
  135                 /*******************************
  136                 *     BASIC EDIT OPERATIONS    *
  137                 *******************************/
  138
  139rdfe_assert(Subject, Predicate, Object) :-
  140    rdfe_assert(Subject, Predicate, Object, user).
  141
  142rdfe_assert(Subject, Predicate, Object, PayLoad) :-
  143    rdf_assert(Subject, Predicate, Object, PayLoad),
  144    rdfe_current_transaction(TID),
  145    assert_action(TID, assert(PayLoad), Subject, Predicate, Object),
  146    journal(assert(TID, Subject, Predicate, Object, PayLoad)).
  147
  148rdfe_retractall(Subject, Predicate, Object) :-
  149    rdfe_retractall(Subject, Predicate, Object, _).
  150
  151rdfe_retractall(Subject, Predicate, Object, PayLoad) :-
  152    rdfe_current_transaction(TID),
  153    (   rdf(Subject, Predicate, Object, PayLoad),
  154        assert_action(TID, retract(PayLoad), Subject, Predicate, Object),
  155        journal(retract(TID, Subject, Predicate, Object, PayLoad)),
  156        fail
  157    ;   true
  158    ),
  159    rdf_retractall(Subject, Predicate, Object, PayLoad).
 rdfe_update(+Subject, +Predicate, +Object, +Action)
Update an existing triple. Possible actions are:

! subject(+Subject) ! predicate(+Predicate) ! object(+Object) ! source(+Source)

  170rdfe_update(Subject, Predicate, Object, Action) :-
  171    rdfe_current_transaction(TID),
  172    rdf_update(Subject, Predicate, Object, Action),
  173    (   Action = object(New)
  174    ->  assert_action(TID, object(Object), Subject, Predicate, New)
  175    ;   Action = predicate(New)
  176    ->  assert_action(TID, predicate(Predicate), Subject, New, Object)
  177    ;   Action = subject(New)
  178    ->  assert_action(TID, subject(Subject), New, Predicate, Object)
  179    ;   Action = source(New)
  180    ->  forall(rdf(Subject, Predicate, Object, PayLoad),
  181               assert_action(TID, source(PayLoad, New),
  182                             Subject, Predicate, Object))
  183    ),
  184    journal(update(TID, Subject, Predicate, Object, Action)).
  185
  186rdfe_update(Subject, Predicate, Object, PayLoad, Action) :-
  187    rdfe_current_transaction(TID),
  188    rdf_update(Subject, Predicate, Object, PayLoad, Action),
  189    (   Action = source(New)
  190    ->  assert_action(TID, source(PayLoad, New),
  191                      Subject, Predicate, Object)
  192    ;   throw(tbd)                  % source is used internally
  193    ),
  194    journal(update(TID, Subject, Predicate, Object, PayLoad, Action)).
 rdfe_delete(+Subject)
Delete a subject and all we know about it. This is a bit tricky. If we are involved in transitive relations, should we re-joint these in this module?
  202rdfe_delete(Subject) :-
  203    rdfe_transaction(delete(Subject)).
  204
  205delete(Subject) :-
  206    rdfe_retractall(Subject, _, _),
  207    rdfe_retractall(_, Subject, _),
  208    rdfe_retractall(_, _, Subject).
  209
  210
  211                 /*******************************
  212                 *         FILE HANDLING        *
  213                 *******************************/
 rdfe_load(+File) is det
 rdfe_load(+File, +Options) is det
Load an RDF file and record this action including version information to facilitate reliable reload.
  221rdfe_load(File) :-
  222    rdfe_load(File, []).
  223
  224
  225rdfe_load(File, Options) :-
  226    rdfe_current_transaction(TID),
  227    absolute_file_name(File,
  228                       [ access(read),
  229                         extensions([rdf,rdfs,owl,ttl,nt,''])
  230                       ], Path),
  231    rdf_load(Path,
  232             [ graph(Graph),
  233               modified(Modified)
  234             | Options
  235             ]),
  236    (   Modified == not_modified
  237    ->  true
  238    ;   absolute_file_name('.', PWD),
  239        size_file(Path, Size),
  240        (   Modified = last_modified(Stamp)
  241        ->  true
  242        ;   time_file(Path, Stamp)
  243        ),
  244        SecTime is round(Stamp),
  245        rdf_statistics(triples_by_graph(Graph, Triples)),
  246        rdf_md5(Graph, MD5),
  247        assert_action(TID, load_file(Path), -, -, -),
  248        journal(rdf_load(TID,
  249                         Path,
  250                         [ pwd(PWD),
  251                           size(Size),
  252                           modified(SecTime),
  253                           triples(Triples),
  254                           md5(MD5),
  255                           from(File)
  256                         ])),
  257        ensure_snapshot(Path)
  258    ).
  259
  260
  261rdfe_unload(Path) :-
  262    rdfe_current_transaction(TID),
  263    rdf_unload(Path),
  264    assert_action(TID, unload_file(Path), -, -, -),
  265    journal(rdf_unload(TID, Path)).
 ensure_snapshot(+Path)
Ensure we have a snapshot of Path if we are making a journal, so we can always reload the snapshot to ensure exactly the same state.
  274ensure_snapshot(Path) :-
  275    rdfe_current_journal(_),
  276    rdf_md5(Path, MD5),
  277    (   snapshot_file(Path, MD5,
  278                      [ access(read),
  279                        file_errors(fail)
  280                      ],
  281                      File)
  282    ->  debug(snapshot, 'Existing snapshot for ~w on ~w', [Path, File])
  283    ;   snapshot_file(Path, MD5,
  284                      [ access(write)
  285                      ],
  286                      File),
  287        debug(snapshot, 'Saving snapshot for ~w to ~w', [Path, File]),
  288        rdf_save_db(File, Path)
  289    ),
  290    assert(snapshot_file(File)).
  291ensure_snapshot(_).
 load_snapshot(+Source, +Path)
Load triples from the given snapshot file. One of the troubles is the time-stamp to avoid rdf_make/0 from reloading the file. for the time being we use 1e12, which is a lot further in the future than this system is going to live.
  301load_snapshot(Source, Path) :-
  302    statistics(cputime, T0),
  303    rdf_load_db(Path),
  304    statistics(cputime, T1),
  305    Time is T1 - T0,
  306    rdf_statistics(triples_by_graph(Source, Triples)),
  307    rdf_md5(Source, MD5),
  308                                    % 1e10: modified far in the future
  309    assert(rdf_db:rdf_source(Source, 1e12, Triples, MD5)),
  310    print_message(informational,
  311                  rdf(loaded(Source, Triples, snapshot(Time)))),
  312    assert(snapshot_file(Path)).
 snapshot_file(+Path, +MD5, +Access, -File)
Find existing snapsnot file or location to save a new one.
  319snapshot_file(Path, MD5, Options, SnapShot) :-
  320    file_base_name(Path, Base),
  321    atomic_list_concat([Base, @, MD5], File),
  322    absolute_file_name(snapshot(File),
  323                       [ extensions([trp])
  324                       | Options
  325                       ],
  326                       SnapShot).
 rdfe_snapshot_file(-File)
Enumerate the MD5 snapshot files required to restore the current journal file. Using this call we can write a routine that packages the journal file with all required snapshots to restore the journal on another computer.
  336rdfe_snapshot_file(File) :-
  337    snapshot_file(File).
  338
  339
  340                 /*******************************
  341                 *      NAMESPACE HANDLING      *
  342                 *******************************/
  343
  344:- dynamic
  345    system_ns/2.  346:- volatile
  347    system_ns/2.
 rdfe_register_ns(Id, URI)
Encapsulation of rdf_register_ns(Id, URI)
  353rdfe_register_ns(Id, URI) :-
  354    rdf_db:ns(Id, URI),
  355    !.
  356rdfe_register_ns(Id, URI) :-
  357    save_system_ns,
  358    rdfe_current_transaction(TID),
  359    rdf_register_ns(Id, URI),
  360    broadcast(rdf_ns(register(Id, URI))),
  361    assert_action(TID, ns(register(Id, URI)), -, -, -),
  362    journal(ns(TID, register(Id, URI))).
  363
  364rdfe_unregister_ns(Id, URI) :-
  365    save_system_ns,
  366    rdfe_current_transaction(TID),
  367    retractall(rdf_db:ns(Id, URI)),
  368    broadcast(rdf_ns(unregister(Id, URI))),
  369    assert_action(TID, ns(unregister(Id, URI)), -, -, -),
  370    journal(ns(TID, unregister(Id, URI))).
  371
  372%       rdfe_register_ns/0
  373%
  374%       Reset namespaces to the state they where before usage of the
  375%       rdf_edit layer.
  376
  377rdfe_reset_ns :-
  378    (   system_ns(_, _)
  379    ->  retractall(rdf_db:ns(Id, URI)),
  380        forall(system_ns(Id, URI), assert(rdb_db:ns(Id, URI)))
  381    ;   true
  382    ).
  383
  384save_system_ns :-
  385    system_ns(_, _),
  386    !.             % already done
  387save_system_ns :-
  388    forall(rdf_db:ns(Id, URI), assert(system_ns(Id, URI))).
  389
  390
  391                 /*******************************
  392                 *         TRANSACTIONS         *
  393                 *******************************/
 rdfe_transaction(:Goal)
Run Goal, recording all modifications as a single transaction. If Goal raises an exception or fails, all changes are rolled-back.
  401rdfe_transaction(Goal) :-
  402    rdfe_transaction(Goal, []).
  403rdfe_transaction(Goal, Name) :-
  404    rdfe_begin_transaction(Name),
  405    (   catch(Goal, E, true)
  406    ->  (   var(E)
  407        ->  check_file_protection(Error),
  408            (   var(Error)
  409            ->  rdfe_commit
  410            ;   rdfe_rollback,
  411                throw(Error)
  412            )
  413        ;   rdfe_rollback,
  414            throw(E)
  415        )
  416    ;   rdfe_rollback,
  417        fail
  418    ).
 rdfe_begin_transaction
Start a transaction. This is followed by either rdfe_end_transaction or rdfe_rollback. Transactions may be nested.
  425rdfe_begin_transaction(Name) :-
  426    current_transaction(TID),      % nested transaction
  427    !,
  428    append(TID, [1], TID2),
  429    asserta(current_transaction(TID2)),
  430    assert(transaction_name(TID2, Name)).
  431rdfe_begin_transaction(Name) :-         % toplevel transaction
  432    flag(rdf_edit_tid, TID, TID+1),
  433    asserta(current_transaction([TID])),
  434    assert(transaction_name(TID, Name)).
  435
  436rdfe_current_transaction(TID) :-
  437    current_transaction(TID),
  438    !.
  439rdfe_current_transaction(_) :-
  440    throw(error(existence_error(rdf_transaction, _), _)).
  441
  442rdfe_commit :-
  443    retract(current_transaction(TID)),
  444    !,
  445    retractall(undo_marker(_, _)),
  446    (   rdfe_transaction_member(TID, _)
  447    ->  get_time(Time),             % transaction is not empty
  448        journal(commit(TID, Time)),
  449        (   TID = [Id]
  450        ->  broadcast(rdf_transaction(Id))
  451        ;   true
  452        )
  453    ;   true
  454    ).
  455
  456rdfe_rollback :-
  457    retract(current_transaction(TID)),
  458    !,
  459    journal(rollback(TID)),
  460    rollback(TID).
 rollback(+TID)
This is the same as undo/1, but it must not record the undone actions as rollbacks cannot be `redone'. Somehow there should be a cleaner way to distinguish between transactional operations and plain operations.
  469rollback(TID) :-
  470    append(TID, _, Id),
  471    (   retract(undo_log(Id, Action, Subject, Predicate, Object)),
  472        (   rollback(Action, Subject, Predicate, Object)
  473        ->  fail
  474        ;   print_message(error,
  475                          rdf_undo_failed(undo(Action, Subject,
  476                                               Predicate, Object))),
  477            fail
  478        )
  479    ;   true
  480    ).
  481
  482rollback(assert(PayLoad), Subject, Predicate, Object) :-
  483    !,
  484    rdf_retractall(Subject, Predicate, Object, PayLoad).
  485rollback(retract(PayLoad), Subject, Predicate, Object) :-
  486    !,
  487    rdf_assert(Subject, Predicate, Object, PayLoad).
  488rollback(Action, Subject, Predicate, Object) :-
  489    action(Action),
  490    !,
  491    rdf_update(Subject, Predicate, Object, Action).
  492
  493
  494assert_action(TID, Action, Subject, Predicate, Object) :-
  495    asserta(undo_log(TID, Action, Subject, Predicate, Object)).
 undo(+TID)
Undo a transaction as well as possible transactions nested into it.
  502undo(TID) :-
  503    append(TID, _, Id),
  504    (   retract(undo_log(Id, Action, Subject, Predicate, Object)),
  505        (   undo(Action, Subject, Predicate, Object)
  506        ->  fail
  507        ;   print_message(warning,
  508                          rdf_undo_failed(undo(Action, Subject,
  509                                               Predicate, Object))),
  510            fail
  511        )
  512    ;   true
  513    ).
  514
  515undo(assert(PayLoad), Subject, Predicate, Object) :-
  516    !,
  517    rdfe_retractall(Subject, Predicate, Object, PayLoad).
  518undo(retract(PayLoad), Subject, Predicate, Object) :-
  519    !,
  520    rdfe_assert(Subject, Predicate, Object, PayLoad).
  521undo(source(Old, New), Subject, Predicate, Object) :-
  522    !,
  523    rdfe_update(Subject, Predicate, Object, Old, source(New)).
  524undo(ns(Action), -, -, -) :-
  525    !,
  526    (   Action = register(Id, URI)
  527    ->  rdfe_unregister_ns(Id, URI)
  528    ;   Action = unregister(Id, URI)
  529    ->  rdfe_register_ns(Id, URI)
  530    ).
  531undo(load_file(Path), -, -, -) :-
  532    !,
  533    rdfe_unload(Path).
  534undo(unload_file(Path), -, -, -) :-
  535    !,
  536    rdfe_load(Path).
  537undo(Action, Subject, Predicate, Object) :-
  538    action(Action),
  539    !,
  540    rdfe_update(Subject, Predicate, Object, Action).
  541
  542action(subject(_)).
  543action(predicate(_)).
  544action(object(_)).
 rdfe_undo
Undo a (toplevel) transaction. More calls do further undo. The `Undone' actions are re-added to the undo log, so the user can redo them. Fails if there are no more undo/redo transactions.
  552rdfe_undo :-
  553    undo_marker(undo, TID),
  554    !,
  555    (   undo_previous(TID, UnDone)
  556    ->  retractall(undo_marker(_, _)),
  557        assert(undo_marker(undo, UnDone)),
  558        broadcast(rdf_undo(undo, UnDone))
  559    ;   fail                        % start of undo log
  560    ).
  561rdfe_undo :-
  562    retract(undo_marker(redo, _)),
  563    !,
  564    last_transaction(TID),
  565    undo_previous(TID, UnDone),
  566    assert(undo_marker(undo, UnDone)),
  567    broadcast(rdf_undo(undo, UnDone)).
  568rdfe_undo :-
  569    last_transaction(TID),
  570    undo_previous(TID, UnDone),
  571    assert(undo_marker(undo, UnDone)),
  572    broadcast(rdf_undo(undo, UnDone)).
  573
  574find_previous_undo(-1, _) :-
  575    !,
  576    fail.
  577find_previous_undo(TID, TID) :-
  578    undo_log([TID|_], _, _, _, _),
  579    !.
  580find_previous_undo(TID0, TID) :-
  581    TID1 is TID0 - 1,
  582    find_previous_undo(TID1, TID).
  583
  584undo_previous(TID, Undone) :-
  585    find_previous_undo(TID, Undone),
  586    rdfe_transaction(undo([Undone])).
  587
  588last_transaction(TID) :-
  589    undo_log([TID|_], _, _, _, _),
  590    !.
 rdfe_redo
Start a redo-session
  596rdfe_redo :-
  597    (   retract(undo_marker(undo, _))
  598    ->  last_transaction(TID),
  599        undo_previous(TID, UnDone),
  600        assert(undo_marker(redo, UnDone)),
  601        broadcast(rdf_undo(redo, UnDone))
  602    ;   retract(undo_marker(redo, TID))
  603    ->  undo_previous(TID, UnDone),
  604        assert(undo_marker(redo, UnDone)),
  605        broadcast(rdf_undo(redo, UnDone))
  606    ;   true
  607    ).
 rdfe_can_redo(-TID) is semidet
 rdfe_can_undo(-TID) is semidet
Check if we can undo and if so return the id of the transaction that will be un/re-done. A subsequent call to rdfe_transaction_name can be used to give a hint in the UI.
  617rdfe_can_redo(Redo) :-
  618    undo_marker(undo, _),
  619    !,
  620    last_transaction(TID),
  621    find_previous_undo(TID, Redo).
  622rdfe_can_redo(Redo) :-
  623    undo_marker(redo, TID),
  624    find_previous_undo(TID, Redo).
  625
  626rdfe_can_undo(Undo) :-                  % continue undo
  627    undo_marker(undo, TID),
  628    !,
  629    find_previous_undo(TID, Undo).
  630rdfe_can_undo(Undo) :-                  % start undo
  631    last_transaction(TID),
  632    find_previous_undo(TID, Undo).
 rdfe_transaction_name(+TID, -Name)
Return name if the transaction is named.
  638rdfe_transaction_name(TID, Name) :-
  639    transaction_name(TID, Name),
  640    Name \== [].
 rdfe_set_transaction_name(+Name)
Set name of the current transaction
  646rdfe_set_transaction_name(Name) :-
  647    current_transaction(TID),
  648    !,
  649    assert(transaction_name(TID, Name)).
 rdfe_transaction_member(+TID, -Action)
Query actions inside a transaction to allow for quick update of visualisers.
  656rdfe_transaction_member(TID, Member) :-
  657    (   integer(TID)
  658    ->  Id = [TID|_]
  659    ;   append(TID, _, Id)
  660    ),
  661    undo_log(Id, Action, Subject, Predicate, Object),
  662    user_transaction_member(Action, Subject, Predicate, Object, Member).
  663
  664user_transaction_member(assert(_), Subject, Predicate, Object,
  665                        assert(Subject, Predicate, Object)) :- !.
  666user_transaction_member(retract(_), Subject, Predicate, Object,
  667                        retract(Subject, Predicate, Object)) :- !.
  668user_transaction_member(load_file(Path), -, -, -,
  669                        file(load(Path))) :- !.
  670user_transaction_member(unload_file(Path), -, -, -,
  671                        file(unload(Path))) :- !.
  672user_transaction_member(Update, Subject, Predicate, Object,
  673                        update(Subject, Predicate, Object, Update)).
  674
  675
  676                 /*******************************
  677                 *           PROTECTION         *
  678                 *******************************/
  679
  680:- dynamic
  681    rdf_source_permission/2,        % file, ro/rw
  682    rdf_current_default_file/2.     % file, all/fallback
 rdfe_set_file_property(+File, +Options)
Set properties on the file. Options is one of
  691rdfe_set_file_property(File, access(Access)) :-
  692    !,
  693    to_uri(File, URL),
  694    retractall(rdf_source_permission(URL, _)),
  695    assert(rdf_source_permission(URL, Access)),
  696    broadcast(rdf_file_property(URL, access(Access))).
  697rdfe_set_file_property(File, default(Type)) :-
  698    to_uri(File, URL),
  699    rdfe_set_file_property(URL, access(rw)), % must be writeable
  700    retractall(rdf_current_default_file(_,_)),
  701    assert(rdf_current_default_file(URL, Type)),
  702    broadcast(rdf_file_property(URL, default(Type))).
 rdfe_get_file_property(+FileOrURL, ?Option)
rdfe_get_file_property(-URL, ?Option)
Fetch file properties set with rdfe_set_file_property/2.
  710rdfe_get_file_property(FileOrURL, access(Access)) :-
  711    (   ground(FileOrURL)
  712    ->  to_uri(FileOrURL, URL)
  713    ;   rdf_source(_DB, URL),
  714        FileOrURL = URL
  715    ),
  716    (   rdf_source_permission(URL, Access0)
  717    ->  Access0 = Access
  718    ;   uri_file_name(URL, File),
  719        access_file(File, write)
  720    ->  assert(rdf_source_permission(URL, rw)),
  721        Access = rw
  722    ;   assert(rdf_source_permission(URL, ro)),
  723        Access = ro
  724    ).
  725rdfe_get_file_property(FileOrURL, default(Default)) :-
  726    ground(FileOrURL),
  727    to_uri(FileOrURL, URL),
  728    (   rdf_current_default_file(URL, Default)
  729    ->  true
  730    ;   FileOrURL = user,
  731        Default = fallback
  732    ).
  733rdfe_get_file_property(URL, default(Default)) :-
  734    (   rdf_current_default_file(URL, Default)
  735    ->  true
  736    ;   URL = user,
  737        Default = fallback
  738    ).
 check_file_protection(-Error)
Check modification of all protected files
  745check_file_protection(Error) :-
  746    (   rdfe_get_file_property(File, access(ro)),
  747        rdfe_is_modified(File)
  748    ->  Error = error(permission_error(modify, source, File), triple20)
  749    ;   true
  750    ).
 to_uri(+Spec, -URL) is det
Convert a specification into a URL.
  757to_uri(URL, URL) :-
  758    uri_components(URL, Components),
  759    uri_data(scheme, Components, Scheme),
  760    nonvar(Scheme),
  761    uri_scheme(Scheme),
  762    !.
  763to_uri(File, URL) :-
  764    uri_file_name(URL, File).
  765
  766
  767uri_scheme(file).
  768uri_scheme(http).
  769uri_scheme(https).
  770uri_scheme(ftp).
  771uri_scheme(ftps).
  772
  773
  774                 /*******************************
  775                 *           MODIFIED           *
  776                 *******************************/
 rdfe_is_modified(?Source)
True if facts have been added, deleted or updated that have Source as `payload'.
  783rdfe_is_modified(Source) :-
  784    rdf_source(Graph, Source),
  785    rdf_graph_property(Graph, modified(true)).
  786
  787
  788rdfe_clear_modified :-
  789    forall(rdf_graph(File),
  790           rdfe_clear_modified(File)).
 rdfe_clear_modified(+Graph) is det
Consider the current state of Graph as unmodified.
  796rdfe_clear_modified(Graph) :-
  797    rdf_set_graph(Graph, modified(false)).
  798
  799
  800                 /*******************************
  801                 *           WATERMARKS         *
  802                 *******************************/
 rdfe_set_watermark(Name)
Create a watermark for undo and replay journal upto this point. The rest of the logic needs to be written later.
  809rdfe_set_watermark(Name) :-
  810    rdfe_current_transaction(TID),
  811    assert_action(TID, watermark(Name), -, -, -),
  812    journal(watermark(TID, Name)).
  813
  814
  815                 /*******************************
  816                 *             RESET            *
  817                 *******************************/
 rdfe_reset
Clear database, undo, namespaces and journalling info.
  823rdfe_reset :-
  824    rdfe_reset_journal,
  825    rdfe_reset_ns,
  826    rdfe_reset_undo,
  827    rdf_reset_db,
  828    broadcast(rdf_reset).
 rdfe_reset_journal
If a journal is open, close it using rdfe_close_journal/0
  834rdfe_reset_journal :-
  835    (   rdfe_current_journal(_)
  836    ->  rdfe_close_journal
  837    ;   true
  838    ).
  839
  840rdfe_reset_undo :-
  841    retractall(undo_log(_,_,_,_,_)),
  842    retractall(current_transaction(_)),
  843    retractall(transaction_name(_,_)),
  844    retractall(undo_marker(_,_)),
  845    retractall(snapshot_file(_)).
  846
  847%       close possible open journal at exit.  Using a Prolog hook
  848%       guarantees closure, even for most crashes.
  849
  850:- at_halt(rdfe_reset_journal).  851
  852
  853                 /*******************************
  854                 *          JOURNALLING         *
  855                 *******************************/
  856
  857journal_version(1).
 rdfe_open_journal(+File, +Mode) is det
Open a journal writing to File in Mode. Mode is one of
read
Open and replay the journal
write
Delete current journal and create a fresh one
append
Read and replay the existing journal and append new modifications to the File.
  873rdfe_open_journal(_, _) :-              % already open
  874    journal(_, _, _),
  875    !.
  876rdfe_open_journal(File, read) :-
  877    !,
  878    absolute_file_name(File,
  879                       [ extensions([rdfj, '']),
  880                         access(read)
  881                       ],
  882                       Path),
  883    rdfe_replay_journal(Path),
  884    rdfe_clear_modified.
  885rdfe_open_journal(File, write) :-
  886    !,
  887    absolute_file_name(File,
  888                       [ extensions([rdfj, '']),
  889                         access(write)
  890                       ],
  891                       Path),
  892    open(Path, write, Stream, [close_on_abort(false)]),
  893    assert(journal(Path, write, Stream)),
  894    get_time(T),
  895    journal_open(start, T).
  896rdfe_open_journal(File, append) :-
  897    working_directory(CWD, CWD),
  898    absolute_file_name(File,
  899                       [ extensions([rdfj, '']),
  900                         relative_to(CWD),
  901                         access(write)
  902                       ],
  903                       Path),
  904    (   exists_file(Path)
  905    ->  rdfe_replay_journal(Path),
  906        rdfe_clear_modified,
  907        get_time(T),
  908        assert(journal(Path, append(T), []))
  909    ;   rdfe_open_journal(Path, write)
  910    ).
  911
  912
  913journal_open(Type, Time) :-
  914    journal_comment(Type, Time),
  915    SecTime is round(Time),
  916    journal_version(Version),
  917    Start =.. [ Type, [ time(SecTime),
  918                        version(Version)
  919                      ]
  920              ],
  921    journal(Start),
  922    broadcast(rdf_journal(Start)).
  923
  924journal_comment(start, Time) :-
  925    journal(_, _, Stream),
  926    format_time(string(String), '%+', Time),
  927    format(Stream,
  928           '/* Triple20 Journal File\n\n   \c
  929               Created: ~w\n   \c
  930               Triple20 by Jan Wielemaker <wielemak@science.uva.nl>\n\n   \c
  931               EDIT WITH CARE!\n\c
  932               */~n~n', [String]).
  933journal_comment(resume, Time) :-
  934    journal(_, _, Stream),
  935    format_time(string(String), '%+', Time),
  936    format(Stream,
  937           '\n\c
  938               /* Resumed: ~w\n\c
  939               */~n~n', [String]).
 rdfe_close_journal
Close the journal. Automatically called from at program termination from at_halt/1.
  946rdfe_close_journal :-
  947    get_time(T),
  948    SecTime is round(T),
  949    journal(end([ time(SecTime)
  950                ])),
  951    retract(journal(_, Mode, Stream)),
  952    (   Mode = append(_)
  953    ->  true
  954    ;   close(Stream)
  955    ).
 rdfe_current_journal(-Path)
Query the currently open journal
  961rdfe_current_journal(Path) :-
  962    journal(Path, _Mode, _Stream).
  963
  964journal(Term) :-
  965    journal(Path, append(T), _),
  966    !,
  967    (   Term = end(_)
  968    ->  true
  969    ;   open(Path, append, Stream, [close_on_abort(false)]),
  970        retractall(journal(Path, _, _)),
  971        assert(journal(Path, append, Stream)),
  972        journal_open(resume, T),
  973        journal(Term)
  974    ).
  975journal(Term) :-
  976    (   journal(_, _, Stream)
  977    ->  write_journal(Term, Stream),
  978        flush_output(Stream)
  979    ;   broadcast(rdf_no_journal(Term))
  980    ).
  981
  982write_journal(commit(TID, Time), Stream) :-
  983    !,
  984    format(Stream, 'commit(~q, ~2f).~n~n', [TID, Time]).
  985write_journal(Term, Stream) :-
  986    format(Stream, '~q.~n', [Term]).
 rdfe_replay_journal(+File)
Replay a journal file. For now this is our cheap way to deal with save/load. Future versions may be more clever when dealing with the version information stored in the journal.
  995rdfe_replay_journal(File) :-
  996    absolute_file_name(File,
  997                       [ extensions([rdfj, '']),
  998                         access(read)
  999                       ],
 1000                       Path),
 1001    open(Path, read, Stream),
 1002    replay(Stream),
 1003    close(Stream).
 1004
 1005replay(Stream) :-
 1006    read(Stream, Term),
 1007    replay(Term, Stream).
 1008
 1009replay(end_of_file, _) :- !.
 1010replay(start(_Attributes), Stream) :-
 1011    !,
 1012    read(Stream, Term),
 1013    replay(Term, Stream).
 1014replay(resume(_Attributes), Stream) :-
 1015    !,
 1016    read(Stream, Term),
 1017    replay(Term, Stream).
 1018replay(end(_Attributes), Stream) :-
 1019    !,
 1020    read(Stream, Term),
 1021    replay(Term, Stream).
 1022replay(Term0, Stream) :-
 1023    replay_transaction(Term0, Stream),
 1024    read(Stream, Term),
 1025    replay(Term, Stream).
 1026
 1027replay_transaction(Term0, Stream) :-
 1028    collect_transaction(Term0, Stream, Transaction, Last),
 1029    (   committed_transaction(Last)
 1030    ->  replay_actions(Transaction)
 1031    ;   true
 1032    ).
 1033
 1034collect_transaction(End, _, [], End) :-
 1035    ends_transaction(End),
 1036    !.
 1037collect_transaction(A, Stream, [A|T], End) :-
 1038    read(Stream, Term),
 1039    collect_transaction(Term, Stream, T, End).
 1040
 1041committed_transaction(commit(_)).
 1042committed_transaction(commit(_, _)).
 1043
 1044ends_transaction(end_of_file).
 1045ends_transaction(commit(_)).
 1046ends_transaction(commit(_, _)).
 1047ends_transaction(rollback(_)).
 1048ends_transaction(end(_)).
 1049ends_transaction(start(_)).
 1050
 1051replay_actions([]).
 1052replay_actions([H|T]) :-
 1053    (   replay_action(H)
 1054    ->  replay_actions(T)
 1055    ;   print_message(warning,
 1056                      rdf_replay_failed(H)),
 1057        (   debugging(journal)
 1058        ->  gtrace,
 1059            replay_actions([H|T])
 1060        ;   replay_actions(T)
 1061        )
 1062    ).
 replay_action(+Action)
Replay actions from the journal. Tricky is rdf_load/3. It should reload the file in the state it was in at the moment it was created. For now this has been hacked for files that were empry at the moment they where loaded (e.g. created from `new_file' in our GUI prototype). How to solve this? We could warn if the file appears changed, but this isn't really easy as copying and OS differences makes it hard to decide on changes by length as well as modification time. Alternatively we could save the state in separate quick-load states.
 1077replay_action(retract(_, Subject, Predicate, Object, PayLoad)) :-
 1078    rdf_retractall(Subject, Predicate, Object, PayLoad).
 1079replay_action(assert(_, Subject, Predicate, Object, PayLoad)) :-
 1080    rdf_assert(Subject, Predicate, Object, PayLoad).
 1081replay_action(update(_, Subject, Predicate, Object, Action)) :-
 1082    rdf_update(Subject, Predicate, Object, Action).
 1083replay_action(update(_, Subject, Predicate, Object, Payload, Action)) :-
 1084    rdf_update(Subject, Predicate, Object, Payload, Action).
 1085replay_action(rdf_load(_, File, Options)) :-
 1086    memberchk(md5(MD5), Options),
 1087    snapshot_file(File, MD5,
 1088                  [ access(read),
 1089                    file_errors(fail)
 1090                  ],
 1091                  Path),
 1092    !,
 1093    debug(snapshot, 'Reloading snapshot ~w~n', [Path]),
 1094    load_snapshot(File, Path).
 1095replay_action(rdf_load(_, File, Options)) :-
 1096    find_file(File, Options, Path),
 1097    (   memberchk(triples(0), Options),
 1098        memberchk(modified(Modified), Options)
 1099    ->  rdf_retractall(_,_,_,Path:_),
 1100        retractall(rdf_db:rdf_source(Path, _, _, _)),       % TBD: move
 1101        rdf_md5(Path, MD5),
 1102        assert(rdf_db:rdf_source(Path, Modified, 0, MD5))
 1103    ;   rdf_load(Path)
 1104    ).
 1105replay_action(rdf_unload(_, Source)) :-
 1106    rdf_unload(Source).
 1107replay_action(ns(_, register(ID, URI))) :-
 1108    !,
 1109    rdf_register_ns(ID, URI).
 1110replay_action(ns(_, unregister(ID, URI))) :-
 1111    retractall(rdf_db:ns(ID, URI)).
 1112replay_action(watermark(_, _Name)) :-
 1113    true.
 1114
 1115find_file(File, _, File) :-
 1116    exists_file(File),
 1117    !.
 1118find_file(File, Options, Path) :-
 1119    memberchk(pwd(PWD), Options),
 1120    make_path(File, PWD, Path),
 1121    exists_file(Path),
 1122    !.
 make_path(+File, +PWD, -Path)
Return location of File relative to PWD, Parent of PWD, etc. (TBD)
 1128make_path(File, PWD, Path) :-
 1129    atom_concat(PWD, /, PWD2),
 1130    atom_concat(PWD2, Path, File).
 1131
 1132
 1133                 /*******************************
 1134                 *            MESSAGES          *
 1135                 *******************************/
 1136
 1137:- multifile
 1138    prolog:message/3,
 1139    user:message_hook/3. 1140
 1141%       Catch messages.
 1142
 1143prolog:message(rdf_replay_failed(Term)) -->
 1144    [ 'RDFDB: Replay of ~p failed'-[Term] ].
 1145prolog:message(rdf_undo_failed(Term)) -->
 1146    [ 'RDFDB: Undo of ~p failed'-[Term] ]