View source with formatted 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
  110
  111/** <module> RDF edit layer
  112This library provides a number of functions on top of the rdf_db module:
  113
  114    * Broadcast modifications
  115    * Provide undo/redo
  116
  117@tbd    This library must be rewritten using rdf_monitor/3.  This allows
  118        using edit layer without having to choose between rdf_ and rdfe_
  119        predicates.
  120
  121@see    rdf_persistency.pl provides reliable persistency, but without
  122        changes boardcasting and undo/redo.
  123*/
  124
  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).
  160
  161%!  rdfe_update(+Subject, +Predicate, +Object, +Action)
  162%
  163%   Update an existing triple.  Possible actions are:
  164%
  165%!          subject(+Subject)
  166%!          predicate(+Predicate)
  167%!          object(+Object)
  168%!          source(+Source)
  169
  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)).
  195
  196%!  rdfe_delete(+Subject)
  197%
  198%   Delete a subject and all we know about it. This is a bit tricky.
  199%   If we are involved in transitive relations, should we re-joint
  200%   these in this module?
  201
  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                 *******************************/
  214
  215%!  rdfe_load(+File) is det.
  216%!  rdfe_load(+File, +Options) is det.
  217%
  218%   Load an RDF file and record this action including version information
  219%   to facilitate reliable reload.
  220
  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)).
  266
  267
  268%!  ensure_snapshot(+Path)
  269%
  270%   Ensure we have a snapshot of Path if we are making a journal, so
  271%   we can always reload the snapshot to ensure exactly the same
  272%   state.
  273
  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(_).
  292
  293
  294%!  load_snapshot(+Source, +Path)
  295%
  296%   Load triples from the given snapshot   file. One of the troubles
  297%   is the time-stamp to avoid rdf_make/0   from reloading the file.
  298%   for the time being we use 1e12, which   is  a lot further in the
  299%   future than this system is going to live.
  300
  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)).
  313
  314
  315%!  snapshot_file(+Path, +MD5, +Access, -File)
  316%
  317%   Find existing snapsnot file or location to save a new one.
  318
  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).
  327
  328
  329%!  rdfe_snapshot_file(-File)
  330%
  331%   Enumerate the MD5 snapshot files required to restore the current
  332%   journal file. Using this  call  we   can  write  a  routine that
  333%   packages the journal file with all required snapshots to restore
  334%   the journal on another computer.
  335
  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.  348
  349%!  rdfe_register_ns(Id, URI)
  350%
  351%   Encapsulation of rdf_register_ns(Id, URI)
  352
  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                 *******************************/
  394
  395%!  rdfe_transaction(:Goal)
  396%
  397%   Run Goal, recording all modifications   as a single transaction.
  398%   If  Goal  raises  an  exception  or    fails,  all  changes  are
  399%   rolled-back.
  400
  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    ).
  419
  420%!  rdfe_begin_transaction
  421%
  422%   Start a transaction.  This is followed by either rdfe_end_transaction
  423%   or rdfe_rollback.  Transactions may be nested.
  424
  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).
  461
  462%!  rollback(+TID)
  463%
  464%   This is the same as undo/1, but it must not record the undone
  465%   actions as rollbacks cannot be `redone'.  Somehow there should
  466%   be a cleaner way to distinguish between transactional operations
  467%   and plain operations.
  468
  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)).
  496
  497%!  undo(+TID)
  498%
  499%   Undo a transaction as well as possible transactions nested into
  500%   it.
  501
  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(_)).
  545
  546%!  rdfe_undo
  547%
  548%   Undo a (toplevel) transaction. More calls do further undo. The
  549%   `Undone' actions are re-added to the undo log, so the user can
  550%   redo them.  Fails if there are no more undo/redo transactions.
  551
  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    !.
  591
  592%!  rdfe_redo
  593%
  594%   Start a redo-session
  595
  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    ).
  608
  609
  610%!  rdfe_can_redo(-TID) is semidet.
  611%!  rdfe_can_undo(-TID) is semidet.
  612%
  613%   Check if we can undo and if so return the id of the transaction
  614%   that will be un/re-done.  A subsequent call to rdfe_transaction_name
  615%   can be used to give a hint in the UI.
  616
  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).
  633
  634%!  rdfe_transaction_name(+TID, -Name)
  635%
  636%   Return name if the transaction is named.
  637
  638rdfe_transaction_name(TID, Name) :-
  639    transaction_name(TID, Name),
  640    Name \== [].
  641
  642%!  rdfe_set_transaction_name(+Name)
  643%
  644%   Set name of the current transaction
  645
  646rdfe_set_transaction_name(Name) :-
  647    current_transaction(TID),
  648    !,
  649    assert(transaction_name(TID, Name)).
  650
  651%!  rdfe_transaction_member(+TID, -Action)
  652%
  653%   Query actions inside a transaction to allow for quick update
  654%   of visualisers.
  655
  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
  683
  684%!  rdfe_set_file_property(+File, +Options)
  685%
  686%   Set properties on the file.  Options is one of
  687%
  688%           * access(ro/rw)
  689%           * default(all/fallback)
  690
  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))).
  703
  704
  705%!  rdfe_get_file_property(+FileOrURL, ?Option).
  706%!  rdfe_get_file_property(-URL, ?Option).
  707%
  708%   Fetch file properties set with rdfe_set_file_property/2.
  709
  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    ).
  739
  740
  741%!  check_file_protection(-Error)
  742%
  743%   Check modification of all protected files
  744
  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    ).
  751
  752
  753%!  to_uri(+Spec, -URL) is det.
  754%
  755%   Convert a specification into a URL.
  756
  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                 *******************************/
  777
  778%!  rdfe_is_modified(?Source)
  779%
  780%   True if facts have been added, deleted or updated that have
  781%   Source as `payload'.
  782
  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)).
  791
  792%!  rdfe_clear_modified(+Graph) is det.
  793%
  794%   Consider the current state of Graph as _unmodified_.
  795
  796rdfe_clear_modified(Graph) :-
  797    rdf_set_graph(Graph, modified(false)).
  798
  799
  800                 /*******************************
  801                 *           WATERMARKS         *
  802                 *******************************/
  803
  804%!  rdfe_set_watermark(Name)
  805%
  806%   Create a watermark for undo and replay journal upto this point.
  807%   The rest of the logic needs to be written later.
  808
  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                 *******************************/
  818
  819%!  rdfe_reset
  820%
  821%   Clear database, undo, namespaces and journalling info.
  822
  823rdfe_reset :-
  824    rdfe_reset_journal,
  825    rdfe_reset_ns,
  826    rdfe_reset_undo,
  827    rdf_reset_db,
  828    broadcast(rdf_reset).
  829
  830%!  rdfe_reset_journal
  831%
  832%   If a journal is open, close it using rdfe_close_journal/0
  833
  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).
  858
  859%!  rdfe_open_journal(+File, +Mode) is det.
  860%
  861%   Open a journal writing to File in Mode.  Mode is one of
  862%
  863%           * read
  864%           Open and replay the journal
  865%
  866%           * write
  867%           Delete current journal and create a fresh one
  868%
  869%           * append
  870%           Read and replay the existing journal and append new
  871%           modifications to the File.
  872
  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]).
  940
  941%!  rdfe_close_journal
  942%
  943%   Close  the  journal.  Automatically  called    from  at  program
  944%   termination from at_halt/1.
  945
  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    ).
  956
  957%!  rdfe_current_journal(-Path)
  958%
  959%   Query the currently open journal
  960
  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]).
  987
  988
  989%!  rdfe_replay_journal(+File)
  990%
  991%   Replay a journal file. For now  this   is  our cheap way to deal
  992%   with save/load. Future versions may be  more clever when dealing
  993%   with the version information stored in the journal.
  994
  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    ).
 1063
 1064
 1065%!  replay_action(+Action)
 1066%
 1067%   Replay actions from the journal. Tricky is rdf_load/3. It should
 1068%   reload the file in the state it  was   in  at  the moment it was
 1069%   created. For now this has been hacked  for files that were empry
 1070%   at the moment they where loaded (e.g. created from `new_file' in
 1071%   our GUI prototype). How to solve this? We could warn if the file
 1072%   appears changed, but this isn't really   easy  as copying and OS
 1073%   differences makes it hard to decide on changes by length as well
 1074%   as modification time. Alternatively we could   save the state in
 1075%   separate quick-load states.
 1076
 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    !.
 1123
 1124%!  make_path(+File, +PWD, -Path)
 1125%
 1126%   Return location of File relative to PWD, Parent of PWD, etc. (TBD)
 1127
 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] ]