rename one patch, added blocks fix
[Fuse-DBI] / DBI.pm
diff --git a/DBI.pm b/DBI.pm
index bb42967..c360e3c 100755 (executable)
--- a/DBI.pm
+++ b/DBI.pm
@@ -13,7 +13,7 @@ use Carp;
 use Data::Dumper;
 
 
 use Data::Dumper;
 
 
-our $VERSION = '0.03';
+our $VERSION = '0.07';
 
 =head1 NAME
 
 
 =head1 NAME
 
@@ -24,15 +24,15 @@ Fuse::DBI - mount your database as filesystem and use it
   use Fuse::DBI;
   Fuse::DBI->mount( ... );
 
   use Fuse::DBI;
   Fuse::DBI->mount( ... );
 
-See C<run> below for examples how to set parametars.
+See C<run> below for examples how to set parameters.
 
 =head1 DESCRIPTION
 
 This module will use C<Fuse> module, part of C<FUSE (Filesystem in USErspace)>
 
 =head1 DESCRIPTION
 
 This module will use C<Fuse> module, part of C<FUSE (Filesystem in USErspace)>
-available at L<http://sourceforge.net/projects/avf> to mount
+available at L<http://fuse.sourceforge.net/> to mount
 your database as file system.
 
 your database as file system.
 
-That will give you posibility to use normal file-system tools (cat, grep, vi)
+That will give you possibility to use normal file-system tools (cat, grep, vi)
 to manipulate data in database.
 
 It's actually opposite of Oracle's intention to put everything into database.
 to manipulate data in database.
 
 It's actually opposite of Oracle's intention to put everything into database.
@@ -46,15 +46,69 @@ It's actually opposite of Oracle's intention to put everything into database.
 
 Mount your database as filesystem.
 
 
 Mount your database as filesystem.
 
+Let's suppose that your database have table C<files> with following structure:
+
+ id:           int
+ filename:     text
+ size:         int
+ content:      text
+ writable:     boolean
+
+Following is example how to mount table like that to C</mnt>:
+
   my $mnt = Fuse::DBI->mount({
   my $mnt = Fuse::DBI->mount({
-       filenames => 'select name from files_table as filenames',
-       read => 'sql read',
-       update => 'sql update',
-       dsn => 'DBI:Pg:dbname=webgui',
-       user => 'database_user',
-       password => 'database_password'
+       'filenames' => 'select id,filename,size,writable from files',
+       'read' => 'select content from files where id = ?',
+       'update' => 'update files set content = ? where id = ?',
+       'dsn' => 'DBI:Pg:dbname=test_db',
+       'user' => 'database_user',
+       'password' => 'database_password',
+       'invalidate' => sub { ... },
   });
 
   });
 
+Options:
+
+=over 5
+
+=item filenames
+
+SQL query which returns C<id> (unique id for that row), C<filename>,
+C<size> and C<writable> boolean flag.
+
+=item read
+
+SQL query which returns only one column with content of file and has
+placeholder C<?> for C<id>.
+
+=item update
+
+SQL query with two pace-holders, one for new content and one for C<id>.
+
+=item dsn
+
+C<DBI> dsn to connect to (contains database driver and name of database).
+
+=item user
+
+User with which to connect to database
+
+=item password
+
+Password for connecting to database
+
+=item invalidate
+
+Optional anonymous code reference which will be executed when data is updated in
+database. It can be used as hook to delete cache (for example on-disk-cache)
+which is created from data edited through C<Fuse::DBI>.
+
+=item fork
+
+Optional flag which forks after mount so that executing script will continue
+running. Implementation is experimental.
+
+=back
+
 =cut
 
 my $dbh;
 =cut
 
 my $dbh;
@@ -84,7 +138,6 @@ sub mount {
        # save (some) arguments in self
        foreach (qw(mount invalidate)) {
                $self->{$_} = $arg->{$_};
        # save (some) arguments in self
        foreach (qw(mount invalidate)) {
                $self->{$_} = $arg->{$_};
-               $fuse_self->{$_} = $arg->{$_};
        }
 
        foreach (qw(filenames read update)) {
        }
 
        foreach (qw(filenames read update)) {
@@ -99,19 +152,34 @@ sub mount {
                die "fork() failed: $!" unless defined $pid;
                # child will return to caller
                if ($pid) {
                die "fork() failed: $!" unless defined $pid;
                # child will return to caller
                if ($pid) {
-                       return $self;
+                       my $counter = 4;
+                       while ($counter && ! $self->is_mounted) {
+                               select(undef, undef, undef, 0.5);
+                               $counter--;
+                       }
+                       if ($self->is_mounted) {
+                               return $self;
+                       } else {
+                               return undef;
+                       }
                }
        }
 
        $dbh = DBI->connect($arg->{'dsn'},$arg->{'user'},$arg->{'password'}, {AutoCommit => 0, RaiseError => 1}) || die $DBI::errstr;
 
                }
        }
 
        $dbh = DBI->connect($arg->{'dsn'},$arg->{'user'},$arg->{'password'}, {AutoCommit => 0, RaiseError => 1}) || die $DBI::errstr;
 
-       $sth->{filenames} = $dbh->prepare($arg->{'filenames'}) || die $dbh->errstr();
+       $sth->{'filenames'} = $dbh->prepare($arg->{'filenames'}) || die $dbh->errstr();
 
        $sth->{'read'} = $dbh->prepare($arg->{'read'}) || die $dbh->errstr();
        $sth->{'update'} = $dbh->prepare($arg->{'update'}) || die $dbh->errstr();
 
 
        $sth->{'read'} = $dbh->prepare($arg->{'read'}) || die $dbh->errstr();
        $sth->{'update'} = $dbh->prepare($arg->{'update'}) || die $dbh->errstr();
 
+
+       $self->{'sth'} = $sth;
+
+       $self->{'read_filenames'} = sub { $self->read_filenames };
        $self->read_filenames;
 
        $self->read_filenames;
 
+       $fuse_self = \$self;
+
        Fuse::main(
                mountpoint=>$arg->{'mount'},
                getattr=>\&e_getattr,
        Fuse::main(
                mountpoint=>$arg->{'mount'},
                getattr=>\&e_getattr,
@@ -123,15 +191,42 @@ sub mount {
                utime=>\&e_utime,
                truncate=>\&e_truncate,
                unlink=>\&e_unlink,
                utime=>\&e_utime,
                truncate=>\&e_truncate,
                unlink=>\&e_unlink,
+               rmdir=>\&e_unlink,
                debug=>0,
        );
                debug=>0,
        );
-
+       
        exit(0) if ($arg->{'fork'});
 
        return 1;
 
 };
 
        exit(0) if ($arg->{'fork'});
 
        return 1;
 
 };
 
+=head2 is_mounted
+
+Check if fuse filesystem is mounted
+
+  if ($mnt->is_mounted) { ... }
+
+=cut
+
+sub is_mounted {
+       my $self = shift;
+
+       my $mounted = 0;
+       my $mount = $self->{'mount'} || confess "can't find mount point!";
+       if (open(MTAB, "/etc/mtab")) {
+               while(<MTAB>) {
+                       $mounted = 1 if (/ $mount fuse /i);
+               }
+               close(MTAB);
+       } else {
+               warn "can't open /etc/mtab: $!";
+       }
+
+       return $mounted;
+}
+
+
 =head2 umount
 
 Unmount your database as filesystem.
 =head2 umount
 
 Unmount your database as filesystem.
@@ -146,20 +241,31 @@ database to filesystem.
 sub umount {
        my $self = shift;
 
 sub umount {
        my $self = shift;
 
-       system "fusermount -u ".$self->{'mount'} || croak "umount error: $!";
+       if ($self->{'mount'} && $self->is_mounted) {
+               system "fusermount -u ".$self->{'mount'}." 2>&1 >/dev/null" || return 0;
+               return 1;
+       }
 
 
-       return 1;
+       return 0;
 }
 
 }
 
-#$SIG{'INT'} = sub {
-#      print STDERR "umount called by SIG INT\n";
-#      umount;
-#};
+$SIG{'INT'} = sub {
+       if ($fuse_self && $$fuse_self->umount) {
+               print STDERR "umount called by SIG INT\n";
+       }
+};
+
+$SIG{'QUIT'} = sub {
+       if ($fuse_self && $$fuse_self->umount) {
+               print STDERR "umount called by SIG QUIT\n";
+       }
+};
 
 sub DESTROY {
        my $self = shift;
 
 sub DESTROY {
        my $self = shift;
-       print STDERR "umount called by DESTROY\n";
-       $self->umount;
+       if ($self->umount) {
+               print STDERR "umount called by DESTROY\n";
+       }
 }
 
 =head2 fuse_module_loaded
 }
 
 =head2 fuse_module_loaded
@@ -169,7 +275,7 @@ Checks if C<fuse> module is loaded in kernel.
   die "no fuse module loaded in kernel"
        unless (Fuse::DBI::fuse_module_loaded);
 
   die "no fuse module loaded in kernel"
        unless (Fuse::DBI::fuse_module_loaded);
 
-This function in called by L<mount>, but might be useful alone also.
+This function in called by C<mount>, but might be useful alone also.
 
 =cut
 
 
 =cut
 
@@ -189,12 +295,18 @@ my %dirs;
 sub read_filenames {
        my $self = shift;
 
 sub read_filenames {
        my $self = shift;
 
+       my $sth = $self->{'sth'} || die "no sth argument";
+
        # create empty filesystem
        (%files) = (
                '.' => {
                        type => 0040,
                        mode => 0755,
                },
        # create empty filesystem
        (%files) = (
                '.' => {
                        type => 0040,
                        mode => 0755,
                },
+               '..' => {
+                       type => 0040,
+                       mode => 0755,
+               },
        #       a => {
        #               cont => "File 'a'.\n",
        #               type => 0100,
        #       a => {
        #               cont => "File 'a'.\n",
        #               type => 0100,
@@ -273,7 +385,7 @@ sub e_getdir {
        my %out;
        foreach my $f (sort keys %files) {
                if ($dirname) {
        my %out;
        foreach my $f (sort keys %files) {
                if ($dirname) {
-                       if ($f =~ s/^\E$dirname\Q\///) {
+                       if ($f =~ s/^\Q$dirname\E\///) {
                                $out{$f}++ if ($f =~ /^[^\/]+$/);
                        }
                } else {
                                $out{$f}++ if ($f =~ /^[^\/]+$/);
                        }
                } else {
@@ -295,6 +407,8 @@ sub read_content {
 
        $sth->{'read'}->execute($id) || die $sth->{'read'}->errstr;
        $files{$file}{cont} = $sth->{'read'}->fetchrow_array;
 
        $sth->{'read'}->execute($id) || die $sth->{'read'}->errstr;
        $files{$file}{cont} = $sth->{'read'}->fetchrow_array;
+       # I should modify ctime only if content in database changed
+       #$files{$file}{ctime} = time() unless ($files{$file}{ctime});
        print "file '$file' content [",length($files{$file}{cont})," bytes] read in cache\n";
 }
 
        print "file '$file' content [",length($files{$file}{cont})," bytes] read in cache\n";
 }
 
@@ -340,6 +454,7 @@ sub clear_cont {
        print "invalidate all cached content\n";
        foreach my $f (keys %files) {
                delete $files{$f}{cont};
        print "invalidate all cached content\n";
        foreach my $f (keys %files) {
                delete $files{$f}{cont};
+               delete $files{$f}{ctime};
        }
        print "begin new transaction\n";
        #$dbh->begin_work || die $dbh->errstr;
        }
        print "begin new transaction\n";
        #$dbh->begin_work || die $dbh->errstr;
@@ -368,7 +483,7 @@ sub update_db {
                }
                print "updated '$file' [",$files{$file}{id},"]\n";
 
                }
                print "updated '$file' [",$files{$file}{id},"]\n";
 
-               $fuse_self->{'invalidate'}->() if (ref $fuse_self->{'invalidate'});
+               $$fuse_self->{'invalidate'}->() if (ref $$fuse_self->{'invalidate'});
        }
        return 1;
 }
        }
        return 1;
 }
@@ -428,13 +543,18 @@ sub e_statfs { return 255, 1, 1, 1, 1, 2 }
 sub e_unlink {
        my $file = filename_fixup(shift);
 
 sub e_unlink {
        my $file = filename_fixup(shift);
 
-       return -ENOENT() unless exists($files{$file});
-
-       print "unlink '$file' will invalidate cache\n";
-
-       read_content($file,$files{$file}{id});
+       if (exists( $dirs{$file} )) {
+               print "unlink '$file' will re-read template names\n";
+               print Dumper($fuse_self);
+               $$fuse_self->{'read_filenames'}->();
+               return 0;
+       } elsif (exists( $files{$file} )) {
+               print "unlink '$file' will invalidate cache\n";
+               read_content($file,$files{$file}{id});
+               return 0;
+       }
 
 
-       return 0;
+       return -ENOENT();
 }
 1;
 __END__
 }
 1;
 __END__
@@ -443,10 +563,20 @@ __END__
 
 Nothing.
 
 
 Nothing.
 
+=head1 BUGS
+
+Size information (C<ls -s>) is wrong. It's a problem in upstream Fuse module
+(for which I'm to blame lately), so when it gets fixes, C<Fuse::DBI> will
+automagically pick it up.
+
 =head1 SEE ALSO
 
 C<FUSE (Filesystem in USErspace)> website
 =head1 SEE ALSO
 
 C<FUSE (Filesystem in USErspace)> website
-L<http://sourceforge.net/projects/avf>
+L<http://fuse.sourceforge.net/>
+
+Example for WebGUI which comes with this distribution in
+directory C<examples/webgui.pl>. It also contains a lot of documentation
+about design of this module, usage and limitations.
 
 =head1 AUTHOR
 
 
 =head1 AUTHOR