X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FText%2FTradition%2FDirectory.pm;h=f01d19bf14ecfa9335c24fed245dca9ad4253216;hb=027d819cfec7c990f32bf810203481c9f7dc1f60;hp=3487d90f8e9751de9d4890837c592aa833ffd0e4;hpb=12523041b91eb6461a56355b2184978f0d6aa7f3;p=scpubgit%2Fstemmatology.git diff --git a/lib/Text/Tradition/Directory.pm b/lib/Text/Tradition/Directory.pm index 3487d90..f01d19b 100644 --- a/lib/Text/Tradition/Directory.pm +++ b/lib/Text/Tradition/Directory.pm @@ -3,8 +3,10 @@ package Text::Tradition::Directory; use strict; use warnings; use Moose; +use KiokuDB::GC::Naive; use KiokuDB::TypeMap; use KiokuDB::TypeMap::Entry::Naive; +use Text::Tradition::Error; extends 'KiokuX::Model'; @@ -21,14 +23,11 @@ Text::Tradition::Directory - a KiokuDB interface for storing and retrieving trad ); my $tradition = Text::Tradition->new( @args ); + my $stemma = $tradition->add_stemma( dotfile => $dotfile ); $d->save_tradition( $tradition ); - my $stemma = Text::Tradition::Stemma->new( - 'dot' => $dotfile, 'collation' => $tradition->collation ); - $d->save_stemma( $stemma ); foreach my $id ( $d->traditions ) { print $d->tradition( $id )->name; - print $d->stemma( $id )->as_svg; } =head1 DESCRIPTION @@ -39,14 +38,7 @@ Text::Tradition::Directory is an interface for storing and retrieving text tradi =head2 new -Returns a Directory object. Apart from those documented in L, -options include: - -=over - -=item * preload - Load all traditions and stemmata into memory upon instantiation. Defaults to true. (TODO manage on-demand loading) - -=back +Returns a Directory object. =head2 tradition_ids @@ -56,89 +48,96 @@ Returns the ID of all traditions in the database. Returns the Text::Tradition object of the given ID. -=head2 stemma( $id ) - -Returns the Text::Tradition::Stemma object associated with the given tradition ID. - -=head2 save_tradition( $tradition ) - -Writes the given tradition to the database, returning its UUID. - -=head2 save_stemma( $stemma ) +=head2 save( $tradition ) -Writes the given stemma to the database, returning its UUID. +Writes the given tradition to the database, returning its ID. =begin testing +use TryCatch; use File::Temp; use Text::Tradition; -use Text::Tradition::Stemma; use_ok 'Text::Tradition::Directory'; my $fh = File::Temp->new(); my $file = $fh->filename; $fh->close; my $dsn = "dbi:SQLite:dbname=$file"; +my $uuid; +my $t = Text::Tradition->new( + 'name' => 'inline', + 'input' => 'Tabular', + 'file' => 't/data/simple.txt', + ); -my $d = Text::Tradition::Directory->new( 'dsn' => $dsn, - 'extra_args' => { 'create' => 1 } ); -is( ref $d, 'Text::Tradition::Directory', "Got directory object" ); +{ + my $d = Text::Tradition::Directory->new( 'dsn' => $dsn, + 'extra_args' => { 'create' => 1 } ); + is( ref $d, 'Text::Tradition::Directory', "Got directory object" ); + + my $scope = $d->new_scope; + $uuid = $d->save( $t ); + ok( $uuid, "Saved test tradition" ); + + my $s = $t->add_stemma( dotfile => 't/data/simple.dot' ); + ok( $d->save( $t ), "Updated tradition with stemma" ); + is( $d->tradition( $uuid ), $t, "Correct tradition returned for id" ); + is( $d->tradition( $uuid )->stemma(0), $s, "...and it has the correct stemma" ); + try { + $d->save( $s ); + } catch( Text::Tradition::Error $e ) { + is( $e->ident, 'database error', "Got exception trying to save stemma directly" ); + like( $e->message, qr/Cannot directly save non-Tradition object/, + "Exception has correct message" ); + } +} +my $nt = Text::Tradition->new( + 'name' => 'CX', + 'input' => 'CollateX', + 'file' => 't/data/Collatex-16.xml', + ); +is( ref( $nt ), 'Text::Tradition', "Made new tradition" ); + +{ + my $f = Text::Tradition::Directory->new( 'dsn' => $dsn ); + my $scope = $f->new_scope; + is( scalar $f->tradition_ids, 1, "Directory index has our tradition" ); + my $nuuid = $f->save( $nt ); + ok( $nuuid, "Stored second tradition" ); + is( scalar $f->tradition_ids, 2, "Directory index has both traditions" ); + my $tf = $f->tradition( $uuid ); + is( $tf->name, $t->name, "Retrieved the tradition from a new directory" ); + my $sid = $f->object_to_id( $tf->stemma(0) ); + try { + $f->tradition( $sid ); + } catch( Text::Tradition::Error $e ) { + is( $e->ident, 'database error', "Got exception trying to fetch stemma directly" ); + like( $e->message, qr/not a Text::Tradition/, "Exception has correct message" ); + } + try { + $f->delete( $sid ); + } catch( Text::Tradition::Error $e ) { + is( $e->ident, 'database error', "Got exception trying to delete stemma directly" ); + like( $e->message, qr/Cannot directly delete non-Tradition object/, + "Exception has correct message" ); + } + $f->delete( $uuid ); + ok( !$f->exists( $uuid ), "Object is deleted from DB" ); + ok( !$f->exists( $sid ), "Object stemma also deleted from DB" ); + is( scalar $f->tradition_ids, 1, "Object is deleted from index" ); +} -my $t = Text::Tradition->new( - 'name' => 'inline', - 'input' => 'Tabular', - 'file' => 't/data/simple.txt', - ); -my $uuid = $d->save_tradition( $t ); -ok( $uuid, "Saved test tradition" ); - -my $s = Text::Tradition::Stemma->new( - 'collation' => $t->collation, - 'dotfile' => 't/data/simple.dot' ); -my $sid = $d->save_stemma( $s ); -ok( $sid, "Saved test stemma" ); - -is( $d->tradition( $uuid ), $t, "Correct tradition returned for id" ); -is( $d->stemma( $uuid ), $s, "Correct stemma returned for id" ); -is( scalar $d->tradition_ids, 1, "Only one tradition in DB" ); - -# Connect to a new instance -my $e = Text::Tradition::Directory->new( 'dsn' => $dsn ); -is( scalar $e->tradition_ids, 1, "One tradition preloaded from DB" ); -my $te = $e->tradition( $uuid ); -is( $te->name, $t->name, "New instance returns correct tradition" ); -my $se = $e->stemma( $uuid ); -is( $se->graph, $s->graph, "New instance returns correct stemma" ); -is( $e->tradition( 'NOT-A-UUID' ), undef, "Undef returned for non-tradition" ); -is( $e->stemma( 'NOT-A-UUID' ), undef, "Undef returned for non-stemma" ); -$te->name( "Changed name" ); -my $new_id = $e->save_tradition( $te ); -is( $new_id, $uuid, "Updated tradition ID did not change" ); - -my $f = Text::Tradition::Directory->new( 'dsn' => $dsn, 'preload' => 0 ); -is( scalar $f->tradition_ids, 0, "No traditions preloaded from DB" ); -### TODO This doesn't work, as I cannot get an object scope in the -### 'tradition' wrapper. -# my $tf = $f->tradition( $uuid ); -# is( $tf->name, $t->name, "Next instance returns correct tradition" ); -# is( $tf->name, "Changed name", "Change to tradition carried through" ); +SKIP: { + skip 'Have yet to figure out garbage collection', 1; + my $g = Text::Tradition::Directory->new( 'dsn' => $dsn ); + my $scope = $g->new_scope; + is( scalar $g->tradition_ids, 1, "Now one object in new directory index" ); +} =end testing =cut -has data_hash => ( - traits => ['Hash'], - default => sub { {} }, - handles => { - tradition => 'get', - stemma => 'get', - add_tradition => 'set', - add_stemma => 'set', - tradition_ids => 'keys', - }, -); - has +typemap => ( is => 'rw', isa => 'KiokuDB::TypeMap', @@ -152,143 +151,68 @@ has +typemap => ( }, ); -has preload => ( - is => 'ro', - isa => 'Bool', - default => 1, - ); - -around 'tradition' => sub { - my( $orig, $self, @arg ) = @_; - my $data = $self->$orig( @arg ); - unless( $data ) { - # Connect to the DB and fetch the thing. - $self->new_scope; - my $id = shift @arg; - my $trad = $self->lookup( $id ); - if( ref( $trad ) eq 'Text::Tradition' ) { - $self->add_tradition( $id => $trad ); - return $trad; - } - # If we got this far... - return undef; - } - return $data->{'object'}; -}; - -around 'stemma' => sub { - my( $orig, $self, @arg ) = @_; - my $data = $self->$orig( @arg ); - unless( $data ) { - # Connect to the DB and fetch the thing. - $self->new_scope; - my $id = shift @arg; - my $trad = $self->lookup( $id ); - if( ref( $trad ) eq 'Text::Tradition' ) { - # Add it - $self->add_tradition( $id => $trad ); - # Find the stemma whose collation belongs to $trad - my $ret = $self->grep( sub { $_->collation eq $trad->collation } ); - my $stemma; - until ( $ret->is_done ) { - foreach my $st ( $ret->items ) { - warn "Found two saved stemmas for tradition $id" if $stemma; - $stemma = $st; - } - } - if( $stemma ) { - $self->add_stemma( $stemma ); - return $stemma; +before [ qw/ store update insert delete / ] => sub { + my $self = shift; + my @nontrad; + foreach my $obj ( @_ ) { + if( ref( $obj ) && ref( $obj ) ne 'Text::Tradition' ) { + # Is it an id => Tradition hash? + if( ref( $obj ) eq 'HASH' && keys( %$obj ) == 1 ) { + my( $k ) = keys %$obj; + next if ref( $obj->{$k} ) eq 'Text::Tradition'; } - } - # If we got this far... - return undef; + push( @nontrad, $obj ); + } + } + if( @nontrad ) { + throw( "Cannot directly save non-Tradition object of type " + . ref( $nontrad[0] ) ); } - return $data->{'stemma'}; -}; - -around 'add_tradition' => sub { - my( $orig, $self, $id, $obj ) = @_; - $self->$orig( $id => { 'object' => $obj } ); }; -around 'add_stemma' => sub { - my( $orig, $self, $id, $obj ) = @_; - $self->{data_hash}->{$id}->{'stemma'} = $obj; +# If a tradition is deleted, remove it from the index. +after delete => sub { + my $self = shift; + my $gc = KiokuDB::GC::Naive->new( backend => $self->directory->backend ); + $self->directory->backend->delete( $gc->garbage->members ); }; -# Load all the relevant data from the DSN we were passed. - -sub BUILD { +sub save { my $self = shift; - my $args = shift; - - $self->fetch_all if( $self->dsn && $self->preload ); + return $self->store( @_ ); } -# Connect to self, get the traditions and stemmas, and save them -# in the directory. -sub fetch_all { - my $self = shift; - my $scope = $self->new_scope; - my $stream = $self->root_set; - my %stemmata; - until( $stream->is_done ) { - foreach my $obj ( $stream->items ) { - my $uuid = $self->object_to_id( $obj ); - if( ref( $obj ) eq 'Text::Tradition' ) { - $self->add_tradition( $uuid => $obj ); - } elsif( ref( $obj ) eq 'Text::Tradition::Stemma' ) { - $stemmata{$obj->collation} = $obj; - } else { - warn "Found root object in DB that is neither tradition nor stemma: $obj"; - } - } - } - # Now match the stemmata to their traditions. - foreach my $id ( $self->tradition_ids ) { - my $c = $self->tradition( $id )->collation; - if( exists $stemmata{$c} ) { - $self->add_stemma( $id => $stemmata{$c} ); - } +sub tradition { + my( $self, $id ) = @_; + my $obj = $self->lookup( $id ); + unless( ref( $obj ) eq 'Text::Tradition' ) { + throw( "Retrieved object is a " . ref( $obj ) . ", not a Text::Tradition" ); } + return $obj; } - -sub save_tradition { - my( $self, $tradition ) = @_; - # Write the thing to the db and return its ID. - unless( ref( $tradition ) eq 'Text::Tradition' ) { - warn "Object $tradition is not a Text::Tradition"; - return undef; - } - my $scope = $self->new_scope; - my $uuid = $self->store( $tradition ); - $self->add_tradition( $uuid => $tradition ); - return $uuid; +sub tradition_ids { + my $self = shift; + my @ids; + $self->scan( sub { push( @ids, $self->object_to_id( @_ ) ) } ); + return @ids; } -sub save_stemma { - my( $self, $stemma ) = @_; - unless( ref( $stemma ) eq 'Text::Tradition::Stemma' ) { - warn "Object $stemma is not a Text::Tradition::Stemma"; - return undef; - } - my $scope = $self->new_scope; - # Get the tradition to which this stemma belongs. - my $tradition = $stemma->collation->tradition; - # Make sure the tradition is in the DB. - my $tid = $self->save_tradition( $tradition ); - unless( $tid ) { - warn "Could not access this stemma's tradition; aborting"; - return undef; - } - my $sid = $self->store( $stemma ); - $self->add_stemma( $tid => $stemma ); - return $tid; +sub throw { + Text::Tradition::Error->throw( + 'ident' => 'database error', + 'message' => $_[0], + ); } - 1; - \ No newline at end of file +=head1 LICENSE + +This package is free software and is provided "as is" without express +or implied warranty. You can redistribute it and/or modify it under +the same terms as Perl itself. + +=head1 AUTHOR + +Tara L Andrews Eaurum@cpan.orgE