#!/usr/bin/perl -w
-use strict ;
+
+use strict;
# path to your brestore.glade
my $glade_file = 'brestore.glade' ;
- libdbd-mysql-perl or libdbd-pg-perl
- libexpect-perl
+ You have to add brestore_xxx tables to your catalog.
+
To speed up database query you have to create theses indexes
- CREATE INDEX file_pathid on File(PathId);
- ...
=head1 COPYRIGHT
- Copyright (C) 2006 Marc Cousin and Eric Bollengier
+ Bacula® - The Network Backup Solution
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the
- Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- Boston, MA 02111-1307, USA.
-
- Base 64 functions from Karl Hakimian <hakimian@aha.com>
- Integrally copied from recover.pl from bacula source distribution.
+ Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
+
+ Brestore authors are Marc Cousin and Eric Bollengier.
+ The main author of Bacula is Kern Sibbald, with contributions from
+ many others, a complete list can be found in the file AUTHORS.
+
+ This program is Free Software; you can redistribute it and/or
+ modify it under the terms of version two of the GNU General Public
+ License as published by the Free Software Foundation plus additions
+ that are listed in the file LICENSE.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+
+ Bacula® is a registered trademark of John Walker.
+ The licensor of Bacula is the Free Software Foundation Europe
+ (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
+ Switzerland, email:ftf@fsfeurope.org.
+
+ Base 64 functions from Karl Hakimian <hakimian@aha.com>
+ Integrally copied from recover.pl from bacula source distribution.
=cut
-use File::Spec; # portable path manipulations
-use Gtk2 '-init'; # auto-initialize Gtk2
-use Gtk2::GladeXML;
-use Gtk2::SimpleList; # easy wrapper for list views
-use Gtk2::Gdk::Keysyms; # keyboard code constants
use Data::Dumper qw/Dumper/;
-use DBI;
my $debug=0; # can be on brestore.conf
+our ($VERSION) = ('$Revision$' =~ /(\d+)/);
-################################################################
+package Pref;
+use DBI;
+sub new
+{
+ my ($class, $config_file) = @_;
+
+ my $self = bless {
+ config_file => $config_file,
+ password => '', # db passwd
+ username => '', # db username
+ connection_string => '',# db connection string
+ bconsole => 'bconsole', # path and arg to bconsole
+ bsr_dest => '', # destination url for bsr files
+ debug => 0, # debug level 0|1
+ use_ok_bkp_only => 1, # don't use bad backup
+ bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
+ see_all_versions => 0, # display all file versions in FileInfo
+ mozilla => 'mozilla', # mozilla bin
+ default_restore_job => 'restore', # regular expression to select default
+ # restore job
-package DlgFileVersion;
+ # keywords that are used to fill DlgPref
+ chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ],
+ entry_keyword => [ qw/username password bweb mozilla
+ connection_string default_restore_job
+ bconsole bsr_dest glade_file/],
+ };
-sub on_versions_close_clicked
-{
- my ($self, $widget)=@_;
- $self->{version}->destroy();
-}
+ $self->read_config();
-sub on_selection_button_press_event
-{
- print "on_selection_button_press_event()\n";
+ return $self;
}
-sub fileview_data_get
+sub read_config
{
- my ($self, $widget, $context, $data, $info, $time,$string) = @_;
+ my ($self) = @_;
- DlgResto::drag_set_info($widget,
- $self->{cwd},
- $data);
+ # We read the parameters. They come from the configuration files
+ my $cfgfile ; my $tmpbuffer;
+ if (open FICCFG, $self->{config_file})
+ {
+ while(read FICCFG,$tmpbuffer,4096)
+ {
+ $cfgfile .= $tmpbuffer;
+ }
+ close FICCFG;
+ my $refparams;
+ no strict; # I have no idea of the contents of the file
+ eval '$refparams' . " = $cfgfile";
+ use strict;
+
+ for my $p (keys %{$refparams}) {
+ $self->{$p} = $refparams->{$p};
+ }
+
+ } else {
+ # TODO : Force dumb default values and display a message
+ }
}
-sub new
+sub write_config
{
- my ($class, $dbh, $client, $path, $file) = @_;
- my $self = bless {
- cwd => $path,
- version => undef, # main window
- };
+ my ($self) = @_;
+
+ $self->{error} = '';
+ my %parameters;
- # we load version widget of $glade_file
- my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
+ for my $k (@{ $self->{entry_keyword} }) {
+ $parameters{$k} = $self->{$k};
+ }
- # Connect signals magically
- $glade_box->signal_autoconnect_from_package($self);
+ for my $k (@{ $self->{chk_keyword} }) {
+ $parameters{$k} = $self->{$k};
+ }
- $glade_box->get_widget("version_label")
- ->set_markup("<b>File revisions : $client:$path/$file</b>");
+ if (open FICCFG,">$self->{config_file}")
+ {
+ print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
+ close FICCFG;
+ }
+ else
+ {
+ $self->{error} = "Can't write configuration $!";
+ }
+ return $self->{error};
+}
- my $widget = $glade_box->get_widget('version_fileview');
- my $fileview = Gtk2::SimpleList->new_from_treeview(
- $widget,
- 'h_name' => 'hidden',
- 'h_jobid' => 'hidden',
- 'h_type' => 'hidden',
+sub connect_db
+{
+ my $self = shift ;
- 'InChanger' => 'pixbuf',
- 'Volume' => 'text',
- 'JobId' => 'text',
- 'Size' => 'text',
- 'Date' => 'text',
- 'MD5' => 'text',
- );
- DlgResto::init_drag_drop($fileview);
+ if ($self->{dbh}) {
+ $self->{dbh}->disconnect() ;
+ }
- my @v = DlgResto::get_all_file_versions($dbh,
- "$path/",
- $file,
- $client,
- 1);
- for my $ver (@v) {
- my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
- = @{$ver};
- my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
+ delete $self->{dbh};
+ delete $self->{error};
- DlgResto::listview_push($fileview,
- $file, $jobid, 'file',
- $icon, $volname, $jobid, human($size),
- scalar(localtime($mtime)), $md5);
+ if (not $self->{connection_string})
+ {
+ # The parameters have not been set. Maybe the conf
+ # file is empty for now
+ $self->{error} = "No configuration found for database connection. " .
+ "Please set this up.";
+ return 0;
}
-
- $self->{version} = $glade_box->get_widget('dlg_version');
- $self->{version}->show();
- return $self;
+ if (not eval {
+ $self->{dbh} = DBI->connect($self->{connection_string},
+ $self->{username},
+ $self->{password})
+ })
+ {
+ $self->{error} = "Can't open bacula database. " .
+ "Database connect string '" .
+ $self->{connection_string} ."' $!";
+ return 0;
+ }
+ $self->{is_mysql} = ($self->{connection_string} =~ m/dbi:mysql/i);
+ $self->{dbh}->{RowCacheSize}=100;
+ return 1;
}
-sub on_forward_keypress
-{
- return 0;
+sub go_bweb
+{
+ my ($self, $url, $msg) = @_;
+
+ unless ($self->{mozilla} and $self->{bweb}) {
+ new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
+ return -1;
+ }
+
+ if ($^O eq 'MSWin32') {
+ system("start /B $self->{mozilla} \"$self->{bweb}$url\"");
+
+ } elsif (!fork()) {
+ system("$self->{mozilla} -remote 'Ping()'");
+ my $cmd = "$self->{mozilla} '$self->{bweb}$url'";
+ if ($? == 0) {
+ $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
+ }
+ exec($cmd);
+ exit 1;
+ }
+ return ($? == 0);
}
1;
+
################################################################
-package DlgWarn;
+
+package Bbase;
sub new
{
- my ($package, $text) = @_;
+ my ($class, %arg) = @_;
- my $self = bless {};
+ my $self = bless {
+ conf => $arg{conf},
+ }, $class;
- my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
+ return $self;
+}
- # Connect signals magically
- $glade->signal_autoconnect_from_package($self);
- $glade->get_widget('label_warn')->set_text($text);
+use Data::Dumper;
- print "$text\n";
+sub debug
+{
+ my ($self, $what, %arg) = @_;
+
+ if ($self->{conf}->{debug} and defined $what) {
+ my $level=0;
+ if ($arg{up}) {
+ $level++;
+ }
+ my $line = (caller($level))[2];
+ my $func = (caller($level+1))[3] || 'main';
+ print "$func:$line\t";
+ if (ref $what) {
+ print Data::Dumper::Dumper($what);
+ } elsif ($arg{md5}) {
+ print "MD5=", md5_base64($what), " str=", $what,"\n";
+ } else {
+ print $what, "\n";
+ }
+ }
+}
- $self->{window} = $glade->get_widget('dlg_warn');
- $self->{window}->show_all();
- return $self;
+sub dbh_strcat
+{
+ my ($self, @what) = @_;
+ if ($self->{conf}->{connection_string} =~ /dbi:pg/i) {
+ return join(' || ', @what);
+ } else {
+ return 'CONCAT(' . join(',', @what) . ')' ;
+ }
}
-sub on_close_clicked
+sub dbh_prepare
{
- my ($self) = @_;
- $self->{window}->destroy();
+ my ($self, $query) = @_;
+ $self->debug($query, up => 1);
+ return $self->{conf}->{dbh}->prepare($query);
}
-1;
-################################################################
+sub dbh_do
+{
+ my ($self, $query) = @_;
+ $self->debug($query, up => 1);
+ return $self->{conf}->{dbh}->do($query);
+}
-package DlgLaunch;
+sub dbh_selectall_arrayref
+{
+ my ($self, $query) = @_;
+ $self->debug($query, up => 1);
+ return $self->{conf}->{dbh}->selectall_arrayref($query);
+}
-use Bconsole;
+sub dbh_selectrow_arrayref
+{
+ my ($self, $query) = @_;
+ $self->debug($query, up => 1);
+ return $self->{conf}->{dbh}->selectrow_arrayref($query);
+}
-# %arg = (bsr_file => '/path/to/bsr', # on director
-# volumes => [ '00001', '00004']
-# pref => ref Pref
-# );
+sub dbh
+{
+ my ($self) = @_;
+ return $self->{conf}->{dbh};
+}
+
+1;
+
+################################################################
+# Manage preference
+package DlgPref;
+# my $pref = new Pref(config_file => 'brestore.conf');
+# my $dlg = new DlgPref($pref);
+# my $dlg_resto = new DlgResto($pref);
+# $dlg->display($dlg_resto);
sub new
{
- my ($class, %arg) = @_;
+ my ($class, $pref) = @_;
my $self = bless {
- bsr_file => $arg{bsr_file}, # /path/to/bsr on director
- pref => $arg{pref}, # Pref ref
- glade => undef, # GladeXML ref
- bconsole => undef, # Bconsole ref
- };
+ pref => $pref, # Pref ref
+ dlgresto => undef, # DlgResto ref
+ };
- # we load launch widget of $glade_file
- my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
- "dlg_launch");
+ return $self;
+}
- # Connect signals magically
- $glade->signal_autoconnect_from_package($self);
+sub display
+{
+ my ($self, $dlgresto) = @_ ;
- my $widget = $glade->get_widget('volumeview');
- my $volview = Gtk2::SimpleList->new_from_treeview(
- $widget,
- 'InChanger' => 'pixbuf',
- 'Volume' => 'text',
- );
+ unless ($self->{glade}) {
+ $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
+ $self->{glade}->signal_autoconnect_from_package($self);
+ }
- my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
-
- # we replace 0 and 1 by $noicon and $yesicon
- for my $i (@{$infos}) {
- if ($i->[0] == 0) {
- $i->[0] = $DlgResto::noicon;
+ $self->{dlgresto} = $dlgresto;
+
+ my $g = $self->{glade};
+ my $p = $self->{pref};
+
+ for my $k (@{ $p->{entry_keyword} }) {
+ $g->get_widget("entry_$k")->set_text($p->{$k}) ;
+ }
+
+ for my $k (@{ $p->{chk_keyword} }) {
+ $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
+ }
+
+ $g->get_widget("dlg_pref")->show_all() ;
+}
+
+sub on_applybutton_clicked
+{
+ my ($self) = @_;
+ my $glade = $self->{glade};
+ my $pref = $self->{pref};
+
+ for my $k (@{ $pref->{entry_keyword} }) {
+ my $w = $glade->get_widget("entry_$k") ;
+ $pref->{$k} = $w->get_text();
+ }
+
+ for my $k (@{ $pref->{chk_keyword} }) {
+ my $w = $glade->get_widget("chkbp_$k") ;
+ $pref->{$k} = $w->get_active();
+ }
+
+ if (!$pref->write_config() && $pref->connect_db()) {
+ $self->{dlgresto}->set_status('Preferences updated');
+ $self->{dlgresto}->init_server_backup_combobox();
+ $self->{dlgresto}->set_status($pref->{error});
+
+ } else {
+ $self->{dlgresto}->set_status($pref->{error});
+ }
+}
+
+# Handle prefs ok click (apply/dismiss dialog)
+sub on_okbutton_clicked
+{
+ my ($self) = @_;
+ $self->on_applybutton_clicked();
+
+ unless ($self->{pref}->{error}) {
+ $self->on_cancelbutton_clicked();
+ }
+}
+sub on_dialog_delete_event
+{
+ my ($self) = @_;
+ $self->on_cancelbutton_clicked();
+ 1;
+}
+
+sub on_cancelbutton_clicked
+{
+ my ($self) = @_;
+ $self->{glade}->get_widget('dlg_pref')->hide();
+ delete $self->{dlgresto};
+}
+1;
+
+################################################################
+# Display all file revision in a separated window
+package DlgFileVersion;
+
+sub on_versions_close_clicked
+{
+ my ($self, $widget)=@_;
+ $self->{version}->destroy();
+}
+
+sub on_selection_button_press_event
+{
+ print STDERR "on_selection_button_press_event()\n";
+}
+
+sub fileview_data_get
+{
+ my ($self, $widget, $context, $data, $info, $time,$string) = @_;
+
+ DlgResto::drag_set_info($widget,
+ $self->{pwd},
+ $data);
+}
+
+#
+# new DlgFileVersion(Bvfs, "client", pathid, fileid, "/path/to/", "filename");
+#
+sub new
+{
+ my ($class, $bvfs, $client, $pathid, $fileid, $path, $fn) = @_;
+ my $self = bless {
+ pwd => $path,
+ version => undef, # main window
+ };
+
+ # we load version widget of $glade_file
+ my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version");
+
+ # Connect signals magically
+ $glade_box->signal_autoconnect_from_package($self);
+
+ $glade_box->get_widget("version_label")
+ ->set_markup("<b>File revisions : $client:$path$fn</b>");
+
+ my $widget = $glade_box->get_widget('version_fileview');
+ my $fileview = Gtk2::SimpleList->new_from_treeview(
+ $widget,
+ 'h_pathid' => 'hidden',
+ 'h_filenameid' => 'hidden',
+ 'h_name' => 'hidden',
+ 'h_jobid' => 'hidden',
+ 'h_type' => 'hidden',
+
+ 'InChanger' => 'pixbuf',
+ 'Volume' => 'text',
+ 'JobId' => 'text',
+ 'Size' => 'text',
+ 'Date' => 'text',
+ 'MD5' => 'text',
+ );
+ DlgResto::init_drag_drop($fileview);
+
+ my @v = $bvfs->get_all_file_versions($pathid,
+ $fileid,
+ $client,
+ 1);
+ for my $ver (@v) {
+ my (undef,$pid,$fid,$jobid,$fileindex,$mtime,$size,
+ $inchanger,$md5,$volname) = @{$ver};
+ my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon;
+
+ DlgResto::listview_push($fileview,$pid,$fid,
+ $fn, $jobid, 'file',
+ $icon, $volname, $jobid,DlgResto::human($size),
+ scalar(localtime($mtime)), $md5);
+ }
+
+ $self->{version} = $glade_box->get_widget('dlg_version');
+ $self->{version}->show();
+
+ return $self;
+}
+
+sub on_forward_keypress
+{
+ return 0;
+}
+
+1;
+################################################################
+# Display a warning message
+package DlgWarn;
+
+sub new
+{
+ my ($package, $text) = @_;
+
+ my $self = bless {};
+
+ my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn");
+
+ # Connect signals magically
+ $glade->signal_autoconnect_from_package($self);
+ $glade->get_widget('label_warn')->set_text($text);
+
+ print STDERR "$text\n";
+
+ $self->{window} = $glade->get_widget('dlg_warn');
+ $self->{window}->show_all();
+ return $self;
+}
+
+sub on_close_clicked
+{
+ my ($self) = @_;
+ $self->{window}->destroy();
+}
+1;
+
+################################################################
+
+package DlgLaunch;
+
+#use Bconsole;
+
+# %arg = (bsr_file => '/path/to/bsr', # on director
+# volumes => [ '00001', '00004']
+# pref => ref Pref
+# );
+
+sub get_bconsole
+{
+ my ($pref) = (@_);
+
+ if ($pref->{bconsole} =~ /^http/) {
+ return new BwebConsole(pref => $pref);
+ } else {
+ if (eval { require Bconsole; }) {
+ return new Bconsole(pref => $pref);
+ } else {
+ new DlgWarn("Can't use bconsole, verify your setup");
+ return undef;
+ }
+ }
+}
+
+sub new
+{
+ my ($class, %arg) = @_;
+
+ my $self = bless {
+ bsr_file => $arg{bsr_file}, # /path/to/bsr on director
+ pref => $arg{pref}, # Pref ref
+ glade => undef, # GladeXML ref
+ bconsole => undef, # Bconsole ref
+ };
+
+ my $console = $self->{bconsole} = get_bconsole($arg{pref});
+ unless ($console) {
+ return undef;
+ }
+
+ # we load launch widget of $glade_file
+ my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file,
+ "dlg_launch");
+
+ # Connect signals magically
+ $glade->signal_autoconnect_from_package($self);
+
+ my $widget = $glade->get_widget('volumeview');
+ my $volview = Gtk2::SimpleList->new_from_treeview(
+ $widget,
+ 'InChanger' => 'pixbuf',
+ 'Volume' => 'text',
+ );
+
+ my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ;
+
+ # we replace 0 and 1 by $noicon and $yesicon
+ for my $i (@{$infos}) {
+ if ($i->[0] == 0) {
+ $i->[0] = $DlgResto::noicon;
} else {
$i->[0] = $DlgResto::yesicon;
}
# fill volume view
push @{ $volview->{data} }, @{$infos} ;
-
- my $console = $self->{bconsole} = new Bconsole(pref => $arg{pref});
+
+ $console->prepare(qw/list_client list_job list_fileset list_storage/);
# fill client combobox (with director defined clients
my @clients = $console->list_client() ; # get from bconsole
my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status");
+ $self->on_cancel_resto_clicked();
+
if ($ret == -1) {
my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close',
"Your job have been submited to bacula.
$widget->run;
$widget->destroy();
}
+}
- $self->on_cancel_resto_clicked();
+sub on_use_regexp_toggled
+{
+ my ($self,$widget) = @_;
+ my $act = $widget->get_active();
+
+ foreach my $w ('entry_launch_where') {
+ $self->{glade}->get_widget($w)->set_sensitive(!$act);
+ }
+
+ foreach my $w ('entry_add_prefix', 'entry_strip_prefix',
+ 'entry_add_suffix','entry_rwhere','chk_use_regexp')
+ {
+ $self->{glade}->get_widget($w)->set_sensitive($act);
+ }
+
+ if ($act) { # if we activate file relocation, we reset use_regexp
+ $self->{glade}->get_widget('entry_rwhere')->set_sensitive(0);
+ $self->{glade}->get_widget('chk_use_regexp')->set_active(0);
+ }
+}
+
+
+sub on_use_rwhere_toggled
+{
+ my ($self,$widget) = @_;
+ my $act = $widget->get_active();
+
+ foreach my $w ('entry_rwhere') {
+ $self->{glade}->get_widget($w)->set_sensitive($act);
+ }
+
+ foreach my $w ('entry_add_prefix', 'entry_strip_prefix',
+ 'entry_add_suffix')
+ {
+ $self->{glade}->get_widget($w)->set_sensitive(!$act);
+ }
}
sub on_cancel_resto_clicked
$self->{glade}->get_widget('dlg_launch')->destroy();
}
+sub get_where
+{
+ my ($self) = @_ ;
+
+ if ($self->{glade}->get_widget('chk_file_relocation')->get_active()) {
+ # using regexp
+ if ($self->{glade}->get_widget('chk_use_regexp')->get_active()) {
+
+ return ('regexwhere',
+ $self->{glade}->get_widget('entry_rwhere')->get_active());
+ }
+
+ # using regexp utils
+ my @ret;
+ my ($strip_prefix, $add_prefix, $add_suffix) =
+ ($self->{glade}->get_widget('entry_strip_prefix')->get_text(),
+ $self->{glade}->get_widget('entry_add_prefix')->get_text(),
+ $self->{glade}->get_widget('entry_add_suffix')->get_text());
+
+ if ($strip_prefix) {
+ push @ret,"!$strip_prefix!!i";
+ }
+
+ if ($add_prefix) {
+ push @ret,"!^!$add_prefix!";
+ }
+
+ if ($add_suffix) {
+ push @ret,"!([^/])\$!\$1$add_suffix!";
+ }
+
+ return ('regexwhere', join(',', @ret));
+
+ } else { # using where
+ return ('where',
+ $self->{glade}->get_widget('entry_launch_where')->get_text());
+ }
+}
+
sub on_submit_resto_clicked
{
my ($self) = @_ ;
my $storage = $glade->get_widget('combo_launch_storage')
->get_active_text();
- my $where = $glade->get_widget('entry_launch_where')->get_text();
+ my ($where_cmd, $where) = $self->get_where();
+ print "$where_cmd => $where\n";
my $job = $glade->get_widget('combo_launch_job')
->get_active_text();
client => $client,
storage => $storage,
fileset => $fileset,
- where => $where,
+ $where_cmd => $where,
replace => $replace,
priority=> $prio,
bootstrap => $r);
print "$src => $dst\n"
if ($debug);
+ if (!$dst) {
+ return $src;
+ }
+
my $ret=0 ;
my $err ;
my $dstfile;
1;
################################################################
-# preference reader
-package Pref;
-
-sub new
-{
- my ($class, $config_file) = @_;
-
- my $self = bless {
- config_file => $config_file,
- password => '', # db passwd
- username => '', # db username
- connection_string => '',# db connection string
- bconsole => 'bconsole', # path and arg to bconsole
- bsr_dest => '', # destination url for bsr files
- debug => 0, # debug level 0|1
- use_ok_bkp_only => 1, # dont use bad backup
- bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url
- glade_file => $glade_file,
- see_all_versions => 0, # display all file versions in FileInfo
- mozilla => 'mozilla', # mozilla bin
- default_restore_job => 'restore', # regular expression to select default
- # restore job
-
- # keywords that are used to fill DlgPref
- chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ],
- entry_keyword => [ qw/username password bweb mozilla
- connection_string default_restore_job
- bconsole bsr_dest glade_file/],
- };
-
- $self->read_config();
-
- return $self;
-}
-
-sub read_config
-{
- my ($self) = @_;
-
- # We read the parameters. They come from the configuration files
- my $cfgfile ; my $tmpbuffer;
- if (open FICCFG, $self->{config_file})
- {
- while(read FICCFG,$tmpbuffer,4096)
- {
- $cfgfile .= $tmpbuffer;
- }
- close FICCFG;
- my $refparams;
- no strict; # I have no idea of the contents of the file
- eval '$refparams' . " = $cfgfile";
- use strict;
-
- for my $p (keys %{$refparams}) {
- $self->{$p} = $refparams->{$p};
- }
-
- if (defined $self->{debug}) {
- $debug = $self->{debug} ;
- }
- } else {
- # TODO : Force dumb default values and display a message
- }
-}
-
-sub write_config
-{
- my ($self) = @_;
-
- my %parameters;
-
- for my $k (@{ $self->{entry_keyword} }) {
- $parameters{$k} = $self->{$k};
- }
-
- for my $k (@{ $self->{chk_keyword} }) {
- $parameters{$k} = $self->{$k};
- }
-
- if (open FICCFG,">$self->{config_file}")
- {
- print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]);
- close FICCFG;
- }
- else
- {
- # TODO : Display a message
- }
-}
-
-sub connect_db
-{
- my $self = shift ;
-
- if ($self->{dbh}) {
- $self->{dbh}->disconnect() ;
- }
-
- delete $self->{dbh};
- delete $self->{error};
-
- if (not $self->{connection_string})
- {
- # The parameters have not been set. Maybe the conf
- # file is empty for now
- $self->{error} = "No configuration found for database connection. " .
- "Please set this up.";
- return 0;
- }
-
- if (not eval {
- $self->{dbh} = DBI->connect($self->{connection_string},
- $self->{username},
- $self->{password})
- })
- {
- $self->{error} = "Can't open bacula database. " .
- "Database connect string '" .
- $self->{connection_string} ."' $!";
- return 0;
- }
- $self->{dbh}->{RowCacheSize}=100;
- return 1;
-}
-
-sub go_bweb
-{
- my ($self, $url, $msg) = @_;
-
- unless ($self->{mozilla} and $self->{bweb}) {
- new DlgWarn("You must install Bweb and set your mozilla bin to $msg");
- return -1;
- }
-
- system("$self->{mozilla} -remote 'Ping()'");
- if ($? != 0) {
- new DlgWarn("Warning, you must have a running $self->{mozilla} to $msg");
- return 0;
- }
-
- my $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ;
- print "$cmd\n";
- system($cmd);
- return ($? == 0);
-}
-
-1;
-
-################################################################
-# Manage preference
-package DlgPref;
-
-# my $pref = new Pref(config_file => 'brestore.conf');
-# my $dlg = new DlgPref($pref);
-# my $dlg_resto = new DlgResto($pref);
-# $dlg->display($dlg_resto);
-sub new
-{
- my ($class, $pref) = @_;
-
- my $self = bless {
- pref => $pref, # Pref ref
- dlgresto => undef, # DlgResto ref
- };
-
- return $self;
-}
-
-sub display
-{
- my ($self, $dlgresto) = @_ ;
-
- unless ($self->{glade}) {
- $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ;
- $self->{glade}->signal_autoconnect_from_package($self);
- }
-
- $self->{dlgresto} = $dlgresto;
-
- my $g = $self->{glade};
- my $p = $self->{pref};
-
- for my $k (@{ $p->{entry_keyword} }) {
- $g->get_widget("entry_$k")->set_text($p->{$k}) ;
- }
-
- for my $k (@{ $p->{chk_keyword} }) {
- $g->get_widget("chkbp_$k")->set_active($p->{$k}) ;
- }
-
- $g->get_widget("dlg_pref")->show_all() ;
-}
-
-sub on_applybutton_clicked
-{
- my ($self) = @_;
- my $glade = $self->{glade};
- my $pref = $self->{pref};
-
- for my $k (@{ $pref->{entry_keyword} }) {
- my $w = $glade->get_widget("entry_$k") ;
- $pref->{$k} = $w->get_text();
- }
-
- for my $k (@{ $pref->{chk_keyword} }) {
- my $w = $glade->get_widget("chkbp_$k") ;
- $pref->{$k} = $w->get_active();
- }
-
- $pref->write_config();
- if ($pref->connect_db()) {
- $self->{dlgresto}->set_dbh($pref->{dbh});
- $self->{dlgresto}->set_status('Preferences updated');
- $self->{dlgresto}->init_server_backup_combobox();
- } else {
- $self->{dlgresto}->set_status($pref->{error});
- }
-}
-
-# Handle prefs ok click (apply/dismiss dialog)
-sub on_okbutton_clicked
-{
- my ($self) = @_;
- $self->on_applybutton_clicked();
-
- unless ($self->{pref}->{error}) {
- $self->on_cancelbutton_clicked();
- }
-}
-sub on_dialog_delete_event
-{
- my ($self) = @_;
- $self->on_cancelbutton_clicked();
- 1;
-}
-
-sub on_cancelbutton_clicked
-{
- my ($self) = @_;
- $self->{glade}->get_widget('dlg_pref')->hide();
- delete $self->{dlgresto};
-}
-1;
-
-################################################################
-# Main Interface
package DlgResto;
+use base qw/Bbase/;
our $diricon;
our $fileicon;
$noicon = $self->{mainwin}->render_icon('gtk-no', $size);
}
}
-
# init combo (and create ListStore object)
sub init_combo
{
# display Mb/Gb/Kb
sub human
{
- my @unit = qw(b Kb Mb Gb Tb);
+ my @unit = qw(B KB MB GB TB);
my $val = shift;
my $i=0;
my $format = '%i %s';
return sprintf($format, $val, $unit[$i]);
}
-sub set_dbh
-{
- my ($self, $dbh) = @_;
- $self->{dbh} = $dbh;
-}
-
-sub init_drag_drop
+sub get_wanted_job_status
{
- my ($fileview) = shift;
- my $fileview_target_entry = {target => 'STRING',
- flags => ['GTK_TARGET_SAME_APP'],
- info => 40 };
-
- $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
- ['copy'],$fileview_target_entry);
- $fileview->get_selection->set_mode('multiple');
+ my ($ok_only) = @_;
- # set some useful SimpleList properties
- $fileview->set_headers_clickable(0);
- foreach ($fileview->get_columns())
- {
- $_->set_resizable(1);
- $_->set_sizing('grow-only');
+ if ($ok_only) {
+ return "'T'";
+ } else {
+ return "'T', 'A', 'E'";
}
}
-sub new
+# This sub gives a full list of the EndTimes for a ClientId
+# ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
+# ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
+sub get_all_endtimes_for_job
+{
+ my ($self, $client, $ok_only)=@_;
+ my $status = get_wanted_job_status($ok_only);
+ my $query = "
+ SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
+ FROM Job,Client,FileSet
+ WHERE Job.ClientId=Client.ClientId
+ AND Client.Name = '$client'
+ AND Job.Type = 'B'
+ AND JobStatus IN ($status)
+ AND Job.FileSetId = FileSet.FileSetId
+ ORDER BY EndTime desc";
+ my $result = $self->dbh_selectall_arrayref($query);
+
+ return @$result;
+}
+
+sub init_drag_drop
+{
+ my ($fileview) = shift;
+ my $fileview_target_entry = {target => 'STRING',
+ flags => ['GTK_TARGET_SAME_APP'],
+ info => 40 };
+
+ $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'],
+ ['copy'],$fileview_target_entry);
+ $fileview->get_selection->set_mode('multiple');
+
+ # set some useful SimpleList properties
+ $fileview->set_headers_clickable(0);
+ foreach ($fileview->get_columns())
+ {
+ $_->set_resizable(1);
+ $_->set_sizing('grow-only');
+ }
+}
+
+sub new
{
my ($class, $pref) = @_;
my $self = bless {
- pref => $pref,
+ conf => $pref,
dirtree => undef,
CurrentJobIds => [],
location => undef, # location entry widget
fileattrib => {}, # cache file
fileview => undef, # fileview widget SimpleList
fileinfo => undef, # fileinfo widget SimpleList
- cwd => '/',
+ cwd => '/',
client_combobox => undef, # client_combobox widget
restore_backup_combobox => undef, # date combobox widget
list_client => undef, # Gtk2::ListStore
list_backup => undef, # Gtk2::ListStore
+ cache_ppathid => {}, #
+ bvfs => undef, # Bfvs object
};
+ $self->{bvfs} = new Bvfs(conf => $pref);
+
# load menu (to use handler with self reference)
my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu");
$glade->signal_autoconnect_from_package($self);
my $widget = $glade->get_widget('fileview');
my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview(
$widget,
+ 'h_pathid' => 'hidden',
+ 'h_filenameid' => 'hidden',
'h_name' => 'hidden',
'h_jobid' => 'hidden',
'h_type' => 'hidden',
'Size' => 'text',
'Date' => 'text');
init_drag_drop($fileview);
- $fileview->set_search_column(4); # search on File Name
+ $fileview->set_search_column(6); # search on File Name
# Connect glade-restore_list to Gtk2::SimpleList
$widget = $glade->get_widget('restorelist');
my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview(
$widget,
- 'h_name' => 'hidden',
- 'h_jobid' => 'hidden',
- 'h_type' => 'hidden',
- 'h_curjobid' => 'hidden',
-
- '' => 'pixbuf',
- 'File Name' => 'text',
- 'JobId' => 'text',
- 'FileIndex' => 'text');
+ 'h_pathid' => 'hidden', #0
+ 'h_filenameid' => 'hidden',
+ 'h_name' => 'hidden',
+ 'h_jobid' => 'hidden',
+ 'h_type' => 'hidden',
+ 'h_curjobid' => 'hidden', #5
+
+ '' => 'pixbuf',
+ 'File Name' => 'text',
+ 'JobId' => 'text',
+ 'FileIndex' => 'text',
+
+ 'Nb Files' => 'text', #10
+ 'Size' => 'text', #11
+ 'size_b' => 'hidden', #12
+ );
my @restore_list_target_table = ({'target' => 'STRING',
'flags' => [],
$widget = $glade->get_widget('infoview');
my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview(
$widget,
+ 'h_pathid' => 'hidden',
+ 'h_filenameid' => 'hidden',
'h_name' => 'hidden',
'h_jobid' => 'hidden',
'h_type' => 'hidden',
$pref->connect_db() || $self->{dlg_pref}->display($self);
if ($pref->{dbh}) {
- $self->{dbh} = $pref->{dbh};
$self->init_server_backup_combobox();
+ if ($self->{bvfs}->create_brestore_tables()) {
+ new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them.");
+ }
}
+
+ $self->set_status($pref->{error});
}
# set status bar informations
sub set_status
{
my ($self, $string) = @_;
+ return unless ($string);
+
my $context = $self->{status}->get_context_id('Main');
$self->{status}->push($context, $string);
}
{
my $dbh = shift;
my $query = "SELECT Name FROM Client ORDER BY Name";
- print $query,"\n" if $debug;
- my $result = $dbh->selectall_arrayref($query);
- my @return_array;
- foreach my $refrow (@$result)
- {
- push @return_array,($refrow->[0]);
- }
- return @return_array;
-}
-
-sub get_wanted_job_status
-{
- my ($ok_only) = @_;
+ print STDERR $query,"\n" if $debug;
- if ($ok_only) {
- return "'T'";
- } else {
- return "'T', 'A', 'E'";
- }
-}
-
-# This sub gives a full list of the EndTimes for a ClientId
-# ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'],
-# ['Date', 'FileSet', 'Type', 'Status', 'JobId']..)
-sub get_all_endtimes_for_job
-{
- my ($dbh, $client, $ok_only)=@_;
- my $status = get_wanted_job_status($ok_only);
- my $query = "
- SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
- FROM Job,Client,FileSet
- WHERE Job.ClientId=Client.ClientId
- AND Client.Name = '$client'
- AND Job.Type = 'B'
- AND JobStatus IN ($status)
- AND Job.FileSetId = FileSet.FileSetId
- ORDER BY EndTime desc";
- print $query,"\n" if $debug;
my $result = $dbh->selectall_arrayref($query);
- return @$result;
+ return map { $_->[0] } @$result;
}
-
# init infoview widget
sub clear_infoview
{
my $title = "Computing size...\n";
my $txt="";
+
+ # ($pid,$fid,$name,$jobid,'file',$curjobids,
+ # undef, undef, undef, $dirfileindex);
foreach my $entry (@{$self->{restore_list}->{data}})
{
- my ($size, $nb) = $self->estimate_restore_size($entry);
- my $name = unpack('u', $entry->[0]);
+ unless ($entry->[11]) {
+ my ($size, $nb) = $self->{bvfs}->estimate_restore_size($entry,\&refresh_screen);
+ $entry->[12] = $size;
+ $entry->[11] = human($size);
+ $entry->[10] = $nb;
+ }
- $txt .= "\n<i>$name</i> : $nb file(s)/" . human($size) ;
- $widget->set_markup($title . $txt);
+ my $name = unpack('u', $entry->[2]);
- $size_total+=$size;
- $nb_total+=$nb;
+ $txt .= "\n<i>$name</i> : ". $entry->[10] ." file(s)/". $entry->[11] ;
+ $self->debug($title . $txt);
+ $widget->set_markup($title . $txt);
+
+ $size_total+=$entry->[12];
+ $nb_total+=$entry->[10];
refresh_screen();
}
return 0;
}
+
+
+sub on_gen_bsr_clicked
+{
+ my ($self) = @_;
+
+ my @options = ("Choose a bsr file", $self->{mainwin}, 'save',
+ 'gtk-save','ok', 'gtk-cancel', 'cancel');
+
+
+ my $w = new Gtk2::FileChooserDialog ( @options );
+ my $ok = 0;
+ my $save;
+ while (!$ok) {
+ my $a = $w->run();
+ if ($a eq 'cancel') {
+ $ok = 1;
+ }
+
+ if ($a eq 'ok') {
+ my $f = $w->get_filename();
+ if (-f $f) {
+ my $dlg = Gtk2::MessageDialog->new($self->{mainwin},
+ 'destroy-with-parent',
+ 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?');
+ if ($dlg->run() eq 'ok') {
+ $save = $f;
+ }
+ $dlg->destroy();
+ } else {
+ $save = $f;
+ }
+ $ok = 1;
+ }
+ }
+
+ $w->destroy();
+
+ if ($save) {
+ if (open(FP, ">$save")) {
+ my $bsr = $self->create_filelist();
+ print FP $bsr;
+ close(FP);
+ $self->set_status("Dumping BSR to $save ok");
+ } else {
+ $self->set_status("Can't dump BSR to $save: $!");
+ }
+ }
+}
+
+
use File::Temp qw/tempfile/;
sub on_go_button_clicked
{
my $self = shift;
+ unless (scalar(@{$self->{restore_list}->{data}})) {
+ new DlgWarn("No file to restore");
+ return 0;
+ }
my $bsr = $self->create_filelist();
my ($fh, $filename) = tempfile();
$fh->print($bsr);
my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g);
my $vol = [ keys %a ] ; # need only one occurrence of each volume
- new DlgLaunch(pref => $self->{pref},
+ new DlgLaunch(pref => $self->{conf},
volumes => $vol,
bsr_file => $filename,
);
}
+
our $client_list_empty = 'Clients list';
our %type_markup = ('F' => '<b>$label F</b>',
'D' => '$label D',
{
my ($self, $widget) = @_;
return 0 unless defined $self->{fileview};
- my $dbh = $self->{dbh};
$self->{list_backup}->clear();
return 0 ;
}
- my @endtimes=get_all_endtimes_for_job($dbh,
- $self->current_client,
- $self->{pref}->{use_ok_bkp_only});
+ $self->{CurrentJobIds} = [
+ set_job_ids_for_date($self->dbh(),
+ $self->current_client,
+ $self->current_date,
+ $self->{conf}->{use_ok_bkp_only})
+ ];
+
+ my $fs = $self->{bvfs};
+ $fs->set_curjobids(@{$self->{CurrentJobIds}});
+ $fs->ch_dir($fs->get_root());
+ # refresh_fileview will be done by list_backup_changed
+
+
+ my @endtimes=$self->get_all_endtimes_for_job($self->current_client,
+ $self->{conf}->{use_ok_bkp_only});
+
foreach my $endtime (@endtimes)
{
my $i = $self->{list_backup}->append();
);
}
$self->{restore_backup_combobox}->set_active(0);
-
- $self->{CurrentJobIds} = [
- set_job_ids_for_date($dbh,
- $self->current_client,
- $self->current_date,
- $self->{pref}->{use_ok_bkp_only})
- ];
-
- $self->ch_dir('');
-
-# undef $self->{dirtree};
- $self->refresh_fileview();
0;
}
+
sub fill_server_list
{
my ($dbh, $combo, $list) = @_;
sub init_server_backup_combobox
{
my $self = shift ;
- fill_server_list($self->{dbh},
+ fill_server_list($self->{conf}->{dbh},
$self->{client_combobox},
$self->{list_client}) ;
}
my ($self) = @_;
my $fileview = $self->{fileview};
my $client_combobox = $self->{client_combobox};
- my $cwd = $self->{cwd};
+ my $bvfs = $self->{bvfs};
@{$fileview->{data}} = ();
return;
}
- my @dirs = $self->list_dirs($cwd,$client_name);
- # [ [listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
- my $files = $self->list_files($cwd);
- print "CWD : $cwd\n" if ($debug);
+ # [ [dirid, dir_basename, File.LStat, jobid]..]
+ my $list_dirs = $bvfs->ls_dirs();
+ # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..]
+ my $files = $bvfs->ls_files();
my $file_count = 0 ;
my $total_bytes = 0;
# Add directories to view
- foreach my $dir (@dirs) {
- my $time = localtime($self->dir_attrib("$cwd/$dir",'st_mtime'));
+ foreach my $dir_entry (@$list_dirs) {
+ my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime'));
$total_bytes += 4096;
$file_count++;
listview_push($fileview,
- $dir,
- $self->dir_attrib("$cwd/$dir",'jobid'),
+ $dir_entry->[0],
+ 0,
+ $dir_entry->[1],
+ # TODO: voir ce que l'on met la
+ $dir_entry->[3],
'dir',
$diricon,
- $dir,
+ $dir_entry->[1],
"4 Kb",
$time);
}
# Add files to view
foreach my $file (@$files)
{
- my $size = file_attrib($file,'st_size');
- my $time = localtime(file_attrib($file,'st_mtime'));
+ my $size = Bvfs::file_attrib($file,'st_size');
+ my $time = localtime(Bvfs::file_attrib($file,'st_mtime'));
$total_bytes += $size;
$file_count++;
- # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
-
+ # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId]
listview_push($fileview,
- $file->[1],
- $file->[3],
+ $bvfs->{cwdid},
+ $file->[0],
+ $file->[2],
+ $file->[4],
'file',
$fileicon,
- $file->[1],
+ $file->[2],
human($size), $time);
}
$self->set_status("$file_count files/" . human($total_bytes));
-
+ $self->{cwd} = $self->{bvfs}->pwd();
+ $self->{location}->set_text($self->{cwd});
# set a decent default selection (makes keyboard nav easy)
$fileview->select(0);
}
# doc ligne 93
# Ok, we have a corner case :
# path can be empty
- my $file;
- if ($path eq '')
- {
- $file = pack("u", $file_info[0]);
- }
- else
- {
- $file = pack("u", $path . '/' . $file_info[0]);
- }
- push @ret, join(" ; ", $file,
- $file_info[1], # $jobid
- $file_info[2], # $type
+ my $file = pack("u", $path . $file_info[2]);
+
+ push @ret, join(" ; ", $file,
+ $file_info[0], # $pathid
+ $file_info[1], # $filenameid
+ $file_info[3], # $jobid
+ $file_info[4], # $type
);
}
$data->set_text($data_get,-1);
}
+
+
sub fileview_data_get
{
my ($self, $widget, $context, $data, $info, $time,$string) = @_;
{
my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_;
my @ret;
-
+ $self->debug("start\n");
if ($info eq 40 || $info eq 0) # patch for display!=:0
{
foreach my $elt (split(/ :: /, $data->data()))
{
-
- my ($file, $jobid, $type) =
+ my ($file, $pathid, $filenameid, $jobid, $type) =
split(/ ; /, $elt);
$file = unpack("u", $file);
- $self->add_selected_file_to_list($file, $jobid, $type);
+ $self->add_selected_file_to_list($pathid,$filenameid,
+ $file, $jobid, $type);
}
}
+ $self->debug("end\n");
}
sub on_back_button_clicked {
my $self = shift;
- $self->up_dir();
+ $self->{bvfs}->up_dir();
+ $self->refresh_fileview();
}
sub on_location_go_button_clicked
{
{
my $self = shift;
$self->set_status("Open bweb on your browser");
- $self->{pref}->go_bweb('', "go on bweb");
-}
-
-# Change to parent directory
-sub up_dir
-{
- my $self = shift ;
- if ($self->{cwd} eq '/')
- {
- $self->ch_dir('');
- }
- my @dirs = File::Spec->splitdir ($self->{cwd});
- pop @dirs;
- $self->ch_dir(File::Spec->catdir(@dirs));
+ $self->{conf}->go_bweb('', "go on bweb");
}
# Change the current working directory
#
sub ch_dir
{
- my $self = shift;
- $self->{cwd} = shift;
-
- $self->refresh_fileview();
- $self->{location}->set_text($self->{cwd});
-
+ my ($self,$l) = @_;
+
+ my $p = $self->{bvfs}->get_pathid($l);
+ if ($p) {
+ $self->{bvfs}->ch_dir($p);
+ $self->refresh_fileview();
+ } else {
+ $self->set_status("Can't find $l");
+ }
1;
}
my ($list) = shift;
my @selected = $list->get_selected_indices();
if (@selected > 0) {
- my ($name, @other) = @{$list->{data}->[$selected[0]]};
- return (unpack('u', $name), @other);
+ my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]};
+ return ($pid,$fid,unpack('u', $name), @other);
} else {
return undef;
}
my @selected = $list->get_selected_indices();
my @ret;
for my $i (@selected) {
- my ($name, @other) = @{$list->{data}->[$i]};
- push @ret, [unpack('u', $name), @other];
+ my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]};
+ push @ret, [$pid,$fid,unpack('u', $name), @other];
}
return @ret;
}
-
sub listview_push
{
- my ($list, $name, @other) = @_;
- push @{$list->{data}}, [pack('u', $name), @other];
+ my ($list, $pid, $fid, $name, @other) = @_;
+ push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other];
}
-#----------------------------------------------------------------------
+#-----------------------------------------------------------------
# Handle keypress in file-view
# * Translates backspace into a 'cd ..' command
# * All other key presses left for GTK
return 0;
}
if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) {
- $self->up_dir();
+ $self->on_back_button_clicked();
return 1; # eat keypress
}
return 0;
}
-#----------------------------------------------------------------------
+#-------------------------------------------------------------------
# Handle double-click (or enter) on file-view
# * Translates into a 'cd <dir>' command
#
{
my ($self, $widget) = @_;
- my ($name, undef, $type, undef) = listview_get_first($widget);
+ my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget);
if ($type eq 'dir')
{
- if ($self->{cwd} eq '')
- {
- $self->ch_dir($name);
- }
- elsif ($self->{cwd} eq '/')
- {
- $self->ch_dir('/' . $name);
- }
- else
- {
- $self->ch_dir($self->{cwd} . '/' . $name);
- }
-
+ $self->{bvfs}->ch_dir($pid);
+ $self->refresh_fileview();
} else {
- $self->fill_infoview($self->{cwd}, $name);
+ $self->fill_infoview($pid,$fid,$name);
}
return 1; # consume event
sub fill_infoview
{
- my ($self, $path, $file) = @_;
+ my ($self, $path, $file, $fn) = @_;
$self->clear_infoview();
- my @v = get_all_file_versions($self->{dbh},
- "$path/",
- $file,
- $self->current_client,
- $self->{pref}->{see_all_versions});
+ my @v = $self->{bvfs}->get_all_file_versions($path,
+ $file,
+ $self->current_client,
+ $self->{conf}->{see_all_versions});
for my $ver (@v) {
- my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname)
- = @{$ver};
+ my (undef,$pid,$fid,$jobid,$fileindex,$mtime,
+ $size,$inchanger,$md5,$volname) = @{$ver};
my $icon = ($inchanger)?$yesicon:$noicon;
$mtime = localtime($mtime) ;
- listview_push($self->{fileinfo},
- $file, $jobid, 'file',
+ listview_push($self->{fileinfo},$pid,$fid,
+ $fn, $jobid, 'file',
$icon, $volname, $jobid, human($size), $mtime, $md5);
}
}
return 0 unless defined $self->{fileview};
$self->{CurrentJobIds} = [
- set_job_ids_for_date($self->{dbh},
+ set_job_ids_for_date($self->dbh(),
$self->current_client,
$self->current_date,
- $self->{pref}->{use_ok_bkp_only})
+ $self->{conf}->{use_ok_bkp_only})
];
-
+ $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}});
$self->refresh_fileview();
0;
}
my @lst = listview_get_all($self->{fileview});
for my $i (@lst) {
- my ($name, undef) = @{$i};
+ my ($pid,$fid,$name, undef) = @{$i};
- new DlgFileVersion($self->{dbh},
+ new DlgFileVersion($self->{bvfs},
$self->current_client,
- $self->{cwd}, $name);
+ $pid,$fid,$self->{cwd},$name);
}
}
my $type = '';
if (@sel == 1) {
- $type = $sel[0]->[2]; # $type
+ $type = $sel[0]->[4]; # $type
}
my $w;
foreach my $i (@sel)
{
- my ($file, $jobid, $type, undef) = @{$i};
- $file = $self->{cwd} . '/' . $file;
- $self->add_selected_file_to_list($file, $jobid, $type);
+ my ($pid, $fid, $file, $jobid, $type, undef) = @{$i};
+ $file = $self->{cwd} . $file;
+ $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type);
}
}
# Adds a file to the filelist
sub add_selected_file_to_list
{
- my ($self, $name, $jobid, $type)=@_;
+ my ($self, $pid, $fid, $name, $jobid, $type)=@_;
- my $dbh = $self->{dbh};
my $restore_list = $self->{restore_list};
my $curjobids=join(',', @{$self->{CurrentJobIds}});
if ($name and substr $name,-1 ne '/')
{
$name .= '/'; # For bacula
- }
- my $dirfileindex = get_fileindex_from_dir_jobid($dbh,$name,$jobid);
- listview_push($restore_list,
- $name, $jobid, 'dir', $curjobids,
- $diricon, $name,$jobid,$dirfileindex);
- }
- elsif ($type eq 'file')
- {
- my $fileindex = get_fileindex_from_file_jobid($dbh,$name,$jobid);
-
- listview_push($restore_list,
- $name, $jobid, 'file', $curjobids,
- $fileicon, $name, $jobid, $fileindex );
- }
-}
-
-# TODO : we want be able to restore files from a bad ended backup
-# we have JobStatus IN ('T', 'A', 'E') and we must
-
-# Data acces subs from here. Interaction with SGBD and caching
-
-# This sub retrieves the list of jobs corresponding to the jobs selected in the
-# GUI and stores them in @CurrentJobIds
-sub set_job_ids_for_date
-{
- my ($dbh, $client, $date, $only_ok)=@_;
-
- if (!$client or !$date) {
- return ();
- }
-
- my $status = get_wanted_job_status($only_ok);
-
- # The algorithm : for a client, we get all the backups for each
- # fileset, in reverse order Then, for each fileset, we store the 'good'
- # incrementals and differentials until we have found a full so it goes
- # like this : store all incrementals until we have found a differential
- # or a full, then find the full #
-
- my $query = "SELECT JobId, FileSet, Level, JobStatus
- FROM Job, Client, FileSet
- WHERE Job.ClientId = Client.ClientId
- AND FileSet.FileSetId = Job.FileSetId
- AND EndTime <= '$date'
- AND Client.Name = '$client'
- AND Type IN ('B')
- AND JobStatus IN ($status)
- ORDER BY FileSet, JobTDate DESC";
-
- print $query,"\n" if $debug;
- my @CurrentJobIds;
- my $result = $dbh->selectall_arrayref($query);
- my %progress;
- foreach my $refrow (@$result)
- {
- my $jobid = $refrow->[0];
- my $fileset = $refrow->[1];
- my $level = $refrow->[2];
-
- defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
-
- next if $progress{$fileset} eq 'F'; # It's over for this fileset...
-
- if ($level eq 'I')
- {
- next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
- push @CurrentJobIds,($jobid);
- }
- elsif ($level eq 'D')
- {
- next if $progress{$fileset} eq 'D'; # We allready have a differential
- push @CurrentJobIds,($jobid);
- }
- elsif ($level eq 'F')
- {
- push @CurrentJobIds,($jobid);
- }
-
- my $status = $refrow->[3] ;
- if ($status eq 'T') { # good end of job
- $progress{$fileset} = $level;
- }
- }
- print Data::Dumper::Dumper(\@CurrentJobIds) if $debug;
-
- return @CurrentJobIds;
-}
-
-# Lists all directories contained inside a directory.
-# Uses the current dir, the client name, and CurrentJobIds for visibility.
-# Returns an array of dirs
-sub list_dirs
-{
- my ($self,$dir,$client)=@_;
- print "list_dirs($dir, $client)\n";
-
- # Is data allready cached ?
- if (not $self->{dirtree}->{$client})
- {
- $self->cache_dirs($client);
- }
-
- if ($dir ne '' and substr $dir,-1 ne '/')
- {
- $dir .= '/'; # In the db, there is a / at the end of the dirs ...
- }
- # Here, the tree is cached in ram
- my @dir = split('/',$dir,-1);
- pop @dir; # We don't need the empty trailing element
-
- # We have to get the reference of the hash containing $dir contents
- # Get to the root
- my $refdir=$self->{dirtree}->{$client};
-
- # Find the leaf
- foreach my $subdir (@dir)
- {
- if ($subdir eq '')
- {
- $subdir = '/';
- }
- $refdir = $refdir->[0]->{$subdir};
- }
-
- # We reached the directory
- my @return_list;
- DIRLOOP:
- foreach my $dir (sort(keys %{$refdir->[0]}))
- {
- # We return the directory's content : only visible directories
- foreach my $jobid (reverse(sort(@{$self->{CurrentJobIds}})))
- {
- if (defined $refdir->[0]->{$dir}->[1]->{$jobid})
- {
- my $dirname = $refdir->[0]->{$dir}->[2]; # The real dirname...
- push @return_list,($dirname);
- next DIRLOOP; # No need to waste more CPU cycles...
- }
- }
- }
- print "LIST DIR : ", Data::Dumper::Dumper(\@return_list),"\n";
- return @return_list;
-}
-
-
-# List all files in a directory. dir as parameter, CurrentJobIds for visibility
-# Returns an array of dirs
-sub list_files
-{
- my ($self, $dir)=@_;
- my $dbh = $self->{dbh};
-
- my $empty = [];
-
- print "list_files($dir)\n";
-
- if ($dir ne '' and substr $dir,-1 ne '/')
- {
- $dir .= '/'; # In the db, there is a / at the end of the dirs ...
- }
-
- my $query = "SELECT Path.PathId FROM Path WHERE Path.Path = '$dir'";
- print $query,"\n" if $debug;
- my @list_pathid=();
- my $result = $dbh->selectall_arrayref($query);
- foreach my $refrow (@$result)
- {
- push @list_pathid,($refrow->[0]);
- }
-
- if (@list_pathid == 0)
- {
- print "No pathid found for $dir\n" if $debug;
- return $empty;
- }
-
- my $inlistpath = join (',', @list_pathid);
- my $inclause = join (',', @{$self->{CurrentJobIds}});
- if ($inclause eq '')
- {
- return $empty;
- }
-
- $query =
-"SELECT listfiles.id, listfiles.Name, File.LStat, File.JobId
- FROM
- (SELECT Filename.Name, max(File.FileId) as id
- FROM File, Filename
- WHERE File.FilenameId = Filename.FilenameId
- AND Filename.Name != ''
- AND File.PathId IN ($inlistpath)
- AND File.JobId IN ($inclause)
- GROUP BY Filename.Name
- ORDER BY Filename.Name) AS listfiles,
-File
-WHERE File.FileId = listfiles.id";
-
- print $query,"\n" if $debug;
- $result = $dbh->selectall_arrayref($query);
-
- return $result;
-}
-
-sub refresh_screen
-{
- Gtk2->main_iteration while (Gtk2->events_pending);
-}
-
-# For the dirs, because of the db schema, it's inefficient to get the
-# directories contained inside other directories (regexp match or tossing
-# lots of records...). So we load all the tree and cache it. The data is
-# stored in a structure of this form :
-# Each directory is an array.
-# - In this array, the first element is a ref to next dir (hash)
-# - The second element is a hash containing all jobids pointing
-# on an array containing their lstat (or 1 if this jobid is there because
-# of dependencies)
-# - The third is the filename itself (it could get mangled because of
-# the hashing...)
-
-# So it looks like this :
-# $reftree->[ { 'dir1' => $refdir1
-# 'dir2' => $refdir2
-# ......
-# },
-# { 'jobid1' => 'lstat1',
-# 'jobid2' => 'lstat2',
-# 'jobid3' => 1 # This one is here for "visibility"
-# },
-# 'dirname'
-# ]
-
-# Client as a parameter
-# Returns an array of dirs
-sub cache_dirs
-{
- my ($self, $client) = @_;
- print "cache_dirs()\n";
-
- $self->{dirtree}->{$client} = []; # reset cache
- my $dbh = $self->{dbh};
-
- # TODO : If we get here, things could get lenghty ... draw a popup window .
- my $widget = Gtk2::MessageDialog->new($self->{mainwin},
- 'destroy-with-parent',
- 'info', 'none',
- 'Populating cache');
- $widget->show;
-
- # We have to build the tree, as it's the first time it is asked...
-
-
- # First, we only need the jobids of the selected server.
- # It's not the same as @CurrentJobIds (we need ALL the jobs)
- # We get the JobIds first in order to have the best execution
- # plan possible for the big query, with an in clause.
- my $query;
- my $status = get_wanted_job_status($self->{pref}->{use_ok_bkp_only});
- $query =
-"SELECT JobId
- FROM Job,Client
- WHERE Job.ClientId = Client.ClientId
- AND Client.Name = '$client'
- AND Job.JobStatus IN ($status)
- AND Job.Type = 'B'";
-
- print $query,"\n" if $debug;
- my $result = $dbh->selectall_arrayref($query);
- refresh_screen();
-
- my @jobids;
- foreach my $record (@{$result})
- {
- push @jobids,($record->[0]);
- }
- my $inclause = join(',',@jobids);
- if ($inclause eq '')
- {
- $widget->destroy();
- $self->set_status("No previous backup found for $client");
- return ();
- }
-
-# Then, still to help dear mysql, we'll retrieve the PathId from empty Path (directory entries...)
- my @dirids;
- $query =
-"SELECT Filename.FilenameId FROM Filename WHERE Filename.Name=''";
-
- print $query,"\n" if $debug;
- $result = $dbh->selectall_arrayref($query);
- refresh_screen();
-
- foreach my $record (@{$result})
- {
- push @dirids,$record->[0];
- }
- my $dirinclause = join(',',@dirids);
-
- # This query is a bit complicated :
- # whe need to find all dir entries that should be displayed, even
- # if the directory itself has no entry in File table (it means a file
- # is explicitely chosen in the backup configuration)
- # Here's what I wanted to do :
-# $query =
-# "
-# SELECT T1.Path, T2.Lstat, T2.JobId
-# FROM ( SELECT DISTINCT Path.PathId, Path.Path FROM File, Path
-# WHERE File.PathId = Path.PathId
-# AND File.JobId IN ($inclause)) AS T1
-# LEFT JOIN
-# ( SELECT File.Lstat, File.JobId, File.PathId FROM File
-# WHERE File.FilenameId IN ($dirinclause)
-# AND File.JobId IN ($inclause)) AS T2
-# ON (T1.PathId = T2.PathId)
-# ";
- # It works perfectly with postgresql, but mysql doesn't seem to be able
- # to do the hash join correcty, so the performance sucks.
- # So it will be done in 4 steps :
- # o create T1 and T2 as temp tables
- # o create an index on T2.PathId
- # o do the query
- # o remove the temp tables
- $query = "
-CREATE TEMPORARY TABLE T1 AS
-SELECT DISTINCT Path.PathId, Path.Path FROM File, Path
-WHERE File.PathId = Path.PathId
- AND File.JobId IN ($inclause)
-";
- print $query,"\n" if $debug;
- $dbh->do($query);
- refresh_screen();
-
- $query = "
-CREATE TEMPORARY TABLE T2 AS
-SELECT File.Lstat, File.JobId, File.PathId FROM File
-WHERE File.FilenameId IN ($dirinclause)
- AND File.JobId IN ($inclause)
-";
- print $query,"\n" if $debug;
- $dbh->do($query);
- refresh_screen();
-
- $query = "
-CREATE INDEX tmp2 ON T2(PathId)
-";
- print $query,"\n" if $debug;
- $dbh->do($query);
- refresh_screen();
-
- $query = "
-SELECT T1.Path, T2.Lstat, T2.JobId
-FROM T1 LEFT JOIN T2
-ON (T1.PathId = T2.PathId)
-";
- print $query,"\n" if $debug;
- $result = $dbh->selectall_arrayref($query);
- refresh_screen();
-
- my $rcount=0;
- foreach my $record (@{$result})
- {
- if ($rcount > 15000) {
- refresh_screen();
- $rcount=0;
- } else {
- $rcount++;
- }
- # Dirty hack to force the string encoding on perl... we don't
- # want implicit conversions
- my $path = pack "U0C*", unpack "C*",$record->[0];
-
- my @path = split('/',$path,-1);
- pop @path; # we don't need the trailing empty element
- my $lstat = $record->[1];
- my $jobid = $record->[2];
-
- # We're going to store all the data on the cache tree.
- # We find the leaf, then store data there
- my $reftree=$self->{dirtree}->{$client};
- foreach my $dir(@path)
- {
- if ($dir eq '')
- {
- $dir = '/';
- }
- if (not defined($reftree->[0]->{$dir}))
- {
- my @tmparray;
- $reftree->[0]->{$dir}=\@tmparray;
- }
- $reftree=$reftree->[0]->{$dir};
- $reftree->[2]=$dir;
- }
- # We can now add the metadata for this dir ...
-
-# $result = $dbh->selectall_arrayref($query);
- if ($lstat)
- {
- # contains something
- $reftree->[1]->{$jobid}=$lstat;
- }
- else
- {
- # We have a very special case here...
- # lstat is not defined.
- # it means the directory is there because a file has been
- # backuped. so the dir has no entry in File table.
- # That's a rare case, so we can afford to determine it's
- # visibility with a query
- my $select_path=$record->[0];
- $select_path=$dbh->quote($select_path); # gotta be careful
- my $query = "
-SELECT File.JobId
-FROM File, Path
-WHERE File.PathId = Path.PathId
-AND Path.Path = $select_path
-";
- print $query,"\n" if $debug;
- my $result2 = $dbh->selectall_arrayref($query);
- foreach my $record (@{$result2})
- {
- my $jobid=$record->[0];
- $reftree->[1]->{$jobid}=1;
- }
- }
-
- }
- $query = "
-DROP TABLE T1;
-";
- print $query,"\n" if $debug;
- $dbh->do($query);
- $query = "
-DROP TABLE T2;
-";
- print $query,"\n" if $debug;
- $dbh->do($query);
-
-
- list_visible($self->{dirtree}->{$client});
- $widget->destroy();
-
-# print Data::Dumper::Dumper($self->{dirtree});
-}
-
-# Recursive function to calculate the visibility of each directory in the cache
-# tree Working with references to save time and memory
-# For each directory, we want to propagate it's visible jobids onto it's
-# parents directory.
-# A tree is visible if
-# - it's been in a backup pointed by the CurrentJobIds
-# - one of it's subdirs is in a backup pointed by the CurrentJobIds
-# In the second case, the directory is visible but has no metadata.
-# We symbolize this with lstat = 1 for this jobid in the cache.
-
-# Input : reference directory
-# Output : visibility of this dir. Has to know visibility of all subdirs
-# to know it's visibility, hence the recursing.
-sub list_visible
-{
- my ($refdir)=@_;
-
- my %visibility;
- # Get the subdirs array references list
- my @list_ref_subdirs;
- while( my (undef,$ref_subdir) = each (%{$refdir->[0]}))
- {
- push @list_ref_subdirs,($ref_subdir);
- }
-
- # Now lets recurse over these subdirs and retrieve the reference of a hash
- # containing the jobs where they are visible
- foreach my $ref_subdir (@list_ref_subdirs)
- {
- my $ref_list_jobs = list_visible($ref_subdir);
- foreach my $jobid (keys %$ref_list_jobs)
- {
- $visibility{$jobid}=1;
- }
- }
-
- # Ok. Now, we've got the list of those jobs. We are going to update our
- # hash (element 1 of the dir array) containing our jobs Do NOT overwrite
- # the lstat for the known jobids. Put 1 in the new elements... But first,
- # let's store the current jobids
- my @known_jobids;
- foreach my $jobid (keys %{$refdir->[1]})
- {
- push @known_jobids,($jobid);
- }
-
- # Add the new jobs
- foreach my $jobid (keys %visibility)
- {
- next if ($refdir->[1]->{$jobid});
- $refdir->[1]->{$jobid} = 1;
- }
- # Add the known_jobids to %visibility
- foreach my $jobid (@known_jobids)
- {
- $visibility{$jobid}=1;
- }
- return \%visibility;
-}
-
-# Returns the list of media required for a list of jobids.
-# Input : dbh, jobid1, jobid2...
-# Output : reference to array of (joibd, inchanger)
-sub get_required_media_from_jobid
-{
- my ($dbh, @jobids)=@_;
- my $inclause = join(',',@jobids);
- my $query = "
-SELECT DISTINCT JobMedia.MediaId, Media.InChanger
-FROM JobMedia, Media
-WHERE JobMedia.MediaId=Media.MediaId
-AND JobId In ($inclause)
-ORDER BY MediaId";
- my $result = $dbh->selectall_arrayref($query);
- return $result;
-}
-
-# Returns the fileindex from dirname and jobid.
-# Input : dbh, dirname, jobid
-# Output : fileindex
-sub get_fileindex_from_dir_jobid
-{
- my ($dbh, $dirname, $jobid)=@_;
- my $query;
- $query = "SELECT File.FileIndex
- FROM File, Filename, Path
- WHERE File.FilenameId = Filename.FilenameId
- AND File.PathId = Path.PathId
- AND Filename.Name = ''
- AND Path.Path = '$dirname'
- AND File.JobId = '$jobid'
- ";
-
- print $query,"\n" if $debug;
- my $result = $dbh->selectall_arrayref($query);
- return $result->[0]->[0];
-}
-
-# Returns the fileindex from filename and jobid.
-# Input : dbh, filename, jobid
-# Output : fileindex
-sub get_fileindex_from_file_jobid
-{
- my ($dbh, $filename, $jobid)=@_;
-
- my @dirs = File::Spec->splitdir ($filename);
- $filename=pop(@dirs);
- my $dirname = File::Spec->catdir(@dirs) . '/';
-
-
- my $query;
- $query =
-"SELECT File.FileIndex
- FROM File, Filename, Path
- WHERE File.FilenameId = Filename.FilenameId
- AND File.PathId = Path.PathId
- AND Filename.Name = '$filename'
- AND Path.Path = '$dirname'
- AND File.JobId = '$jobid'";
-
- print $query,"\n" if $debug;
- my $result = $dbh->selectall_arrayref($query);
- return $result->[0]->[0];
+ }
+ my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid);
+ listview_push($restore_list,$pid,0,
+ $name, $jobid, 'dir', $curjobids,
+ $diricon, $name,$curjobids,$dirfileindex);
+ }
+ elsif ($type eq 'file')
+ {
+ my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid);
+
+ listview_push($restore_list,$pid,$fid,
+ $name, $jobid, 'file', $curjobids,
+ $fileicon, $name, $jobid, $fileindex );
+ }
}
+# TODO : we want be able to restore files from a bad ended backup
+# we have JobStatus IN ('T', 'A', 'E') and we must
-# Returns list of versions of a file that could be restored
-# returns an array of
-# ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
-# It's the same as entries of restore_list (hidden) + mtime and size and inchanger
-# and volname and md5
-# and of course, there will be only one jobid in the array of jobids...
-sub get_all_file_versions
+# Data acces subs from here. Interaction with SGBD and caching
+
+# This sub retrieves the list of jobs corresponding to the jobs selected in the
+# GUI and stores them in @CurrentJobIds
+sub set_job_ids_for_date
{
- my ($dbh,$path,$file,$client,$see_all)=@_;
-
- defined $see_all or $see_all=0;
+ my ($dbh, $client, $date, $only_ok)=@_;
+
+ if (!$client or !$date) {
+ return ();
+ }
- my @versions;
- my $query;
- $query =
-"SELECT File.JobId, File.FileIndex, File.Lstat,
- File.Md5, Media.VolumeName, Media.InChanger
- FROM File, Filename, Path, Job, Client, JobMedia, Media
- WHERE File.FilenameId = Filename.FilenameId
- AND File.PathId=Path.PathId
- AND File.JobId = Job.JobId
- AND Job.ClientId = Client.ClientId
- AND Job.JobId = JobMedia.JobId
- AND File.FileIndex >= JobMedia.FirstIndex
- AND File.FileIndex <= JobMedia.LastIndex
- AND JobMedia.MediaId = Media.MediaId
- AND Path.Path = '$path'
- AND Filename.Name = '$file'
- AND Client.Name = '$client'";
+ my $status = get_wanted_job_status($only_ok);
- print $query if $debug;
+ # The algorithm : for a client, we get all the backups for each
+ # fileset, in reverse order Then, for each fileset, we store the 'good'
+ # incrementals and differentials until we have found a full so it goes
+ # like this : store all incrementals until we have found a differential
+ # or a full, then find the full #
+
+ my $query = "SELECT JobId, FileSet, Level, JobStatus
+ FROM Job, Client, FileSet
+ WHERE Job.ClientId = Client.ClientId
+ AND FileSet.FileSetId = Job.FileSetId
+ AND EndTime <= '$date'
+ AND Client.Name = '$client'
+ AND Type IN ('B')
+ AND JobStatus IN ($status)
+ ORDER BY FileSet, JobTDate DESC";
+ my @CurrentJobIds;
my $result = $dbh->selectall_arrayref($query);
-
+ my %progress;
foreach my $refrow (@$result)
{
- my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
- my @attribs = parse_lstat($lstat);
- my $mtime = array_attrib('st_mtime',\@attribs);
- my $size = array_attrib('st_size',\@attribs);
+ my $jobid = $refrow->[0];
+ my $fileset = $refrow->[1];
+ my $level = $refrow->[2];
- my @list = ('FILE:', $path.$file, $jobid, $fileindex, $mtime, $size,
- $inchanger, $md5, $volname);
- push @versions, (\@list);
- }
-
- # We have the list of all versions of this file.
- # We'll sort it by mtime desc, size, md5, inchanger desc
- # the rest of the algorithm will be simpler
- # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
- @versions = sort { $b->[4] <=> $a->[4]
- || $a->[5] <=> $b->[5]
- || $a->[7] cmp $a->[7]
- || $b->[6] <=> $a->[6]} @versions;
-
- my @good_versions;
- my %allready_seen_by_mtime;
- my %allready_seen_by_md5;
- # Now we should create a new array with only the interesting records
- foreach my $ref (@versions)
- {
- if ($ref->[7])
+ defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
+
+ next if $progress{$fileset} eq 'F'; # It's over for this fileset...
+
+ if ($level eq 'I')
{
- # The file has a md5. We compare his md5 to other known md5...
- # We take size into account. It may happen that 2 files
- # have the same md5sum and are different. size is a supplementary
- # criterion
-
- # If we allready have a (better) version
- next if ( (not $see_all)
- and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
+ next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
+ push @CurrentJobIds,($jobid);
+ }
+ elsif ($level eq 'D')
+ {
+ next if $progress{$fileset} eq 'D'; # We allready have a differential
+ push @CurrentJobIds,($jobid);
+ }
+ elsif ($level eq 'F')
+ {
+ push @CurrentJobIds,($jobid);
+ }
- # we never met this one before...
- $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
+ my $status = $refrow->[3] ;
+ if ($status eq 'T') { # good end of job
+ $progress{$fileset} = $level;
}
- # Even if it has a md5, we should also work with mtimes
- # We allready have a (better) version
- next if ( (not $see_all)
- and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
- $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
-
- # We reached there. The file hasn't been seen.
- push @good_versions,($ref);
}
-
- # To be nice with the user, we re-sort good_versions by
- # inchanger desc, mtime desc
- @good_versions = sort { $b->[4] <=> $a->[4]
- || $b->[2] <=> $a->[2]} @good_versions;
-
- return @good_versions;
+
+ return @CurrentJobIds;
+}
+
+sub refresh_screen
+{
+ Gtk2->main_iteration while (Gtk2->events_pending);
}
# TODO : bsr must use only good backup or not (see use_ok_bkp_only)
sub create_filelist
{
my $self = shift;
- my $dbh = $self->{dbh};
my %mediainfos;
# This query gets all jobid/jobmedia/media combination.
my $query = "
SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile,
JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex,
JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex,
- Media.Volumename, Media.MediaType
+ Media.VolumeName, Media.MediaType
FROM Job, JobMedia, Media
WHERE Job.JobId = JobMedia.JobId
AND JobMedia.MediaId = Media.MediaId
ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
- my $result = $dbh->selectall_arrayref($query);
+ my $result = $self->dbh_selectall_arrayref($query);
# We will store everything hashed by jobid.
# reminder : restore_list looks like this :
- # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
+ # ($pid,$fid,$name,$jobid,'file',$curjobids,
+ # undef, undef, undef, $dirfileindex);
# Here, we retrieve every file/dir that could be in the restore
# We do as simple as possible for the SQL engine (no crazy joins,
my @select_queries;
foreach my $entry (@{$self->{restore_list}->{data}})
{
- if ($entry->[2] eq 'dir')
+ if ($entry->[4] eq 'dir')
{
- my $dir = unpack('u', $entry->[0]);
- my $inclause = $entry->[3]; #curjobids
-
+ my $dirid = $entry->[0];
+ my $inclause = $entry->[5]; #curjobids
+
my $query =
"(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
FROM File, Path, Filename
WHERE Path.PathId = File.PathId
AND File.FilenameId = Filename.FilenameId
- AND Path.Path LIKE '$dir%'
+ AND Path.Path LIKE
+ (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path
+ WHERE PathId IN ($dirid)
+ )
AND File.JobId IN ($inclause) )";
push @select_queries,($query);
}
{
# It's a file. Great, we allready have most
# of what is needed. Simple and efficient query
- my $file = unpack('u', $entry->[0]);
- my @file = split '/',$file;
- $file = pop @file;
- my $dir = join('/',@file);
+ my $dir = $entry->[0];
+ my $file = $entry->[1];
- my $jobid = $entry->[1];
- my $fileindex = $entry->[7];
- my $inclause = $entry->[3]; # curjobids
+ my $jobid = $entry->[3];
+ my $fileindex = $entry->[9];
+ my $inclause = $entry->[5]; # curjobids
my $query =
"(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId
- FROM File, Path, Filename
- WHERE Path.PathId = File.PathId
+ FROM File,Path,Filename
+ WHERE File.PathId = $dir
+ AND File.PathId = Path.PathId
+ AND File.FilenameId = $file
AND File.FilenameId = Filename.FilenameId
- AND Path.Path = '$dir/'
- AND Filename.Name = '$file'
- AND File.JobId = $jobid)";
+ AND File.JobId = $jobid
+ )
+";
push @select_queries,($query);
}
}
$query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
- print $query,"\n" if $debug;
-
#Now we run the query and parse the result...
# there may be a lot of records, so we better be efficient
# We use the bind column method, working with references...
- my $sth = $dbh->prepare($query);
+ my $sth = $self->dbh_prepare($query);
$sth->execute;
my ($path,$name,$fileindex,$jobid);
or $prev_volfile ne $volfile)
{
# We have to create a new section in the bsr...
- #Â We print the previous one ...
+ # We print the previous one ...
# (before that, save the current range ...)
if ($first_of_current_range != $prev_fileindex)
{
- #Â we are in a range
+ # we are in a range
push @fileindex_ranges,
("$first_of_current_range-$prev_fileindex");
}
return $bsr;
}
-sub print_bsr_section
+sub print_bsr_section
+{
+ my ($ref_fileindex_ranges,$volsessionid,
+ $volsessiontime,$volumename,$volfile,
+ $mediatype,$volblocks,$count)=@_;
+
+ my $bsr='';
+ $bsr .= "Volume=\"$volumename\"\n";
+ $bsr .= "MediaType=\"$mediatype\"\n";
+ $bsr .= "VolSessionId=$volsessionid\n";
+ $bsr .= "VolSessionTime=$volsessiontime\n";
+ $bsr .= "VolFile=$volfile\n";
+ $bsr .= "VolBlock=$volblocks\n";
+
+ foreach my $range (@{$ref_fileindex_ranges})
+ {
+ $bsr .= "FileIndex=$range\n";
+ }
+
+ $bsr .= "Count=$count\n";
+ return $bsr;
+}
+
+1;
+
+################################################################
+
+package Bvfs;
+use base qw/Bbase/;
+
+sub get_pathid
+{
+ my ($self, $dir) = @_;
+ my $query =
+ "SELECT PathId FROM Path WHERE Path = ?";
+ my $sth = $self->dbh_prepare($query);
+ $sth->execute($dir);
+ my $result = $sth->fetchall_arrayref();
+ $sth->finish();
+
+ return join(',', map { $_->[0] } @$result);
+}
+
+
+sub update_cache
+{
+ my ($self) = @_;
+
+ $self->{conf}->{dbh}->begin_work();
+
+ my $query = "
+ SELECT JobId from Job
+ WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) AND JobStatus IN ('T', 'f', 'A') ORDER BY JobId";
+ my $jobs = $self->dbh_selectall_arrayref($query);
+
+ $self->update_brestore_table(map { $_->[0] } @$jobs);
+
+ $self->{conf}->{dbh}->commit();
+ $self->{conf}->{dbh}->begin_work();
+
+ print STDERR "Cleaning path visibility\n";
+
+ my $nb = $self->dbh_do("
+ DELETE FROM brestore_pathvisibility
+ WHERE NOT EXISTS
+ (SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
+
+ print STDERR "$nb rows affected\n";
+ print STDERR "Cleaning known jobid\n";
+
+ $nb = $self->dbh_do("
+ DELETE FROM brestore_knownjobid
+ WHERE NOT EXISTS
+ (SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
+
+ print STDERR "$nb rows affected\n";
+
+ $self->{conf}->{dbh}->commit();
+}
+
+sub get_root
+{
+ my ($self, $dir) = @_;
+ return $self->get_pathid('');
+}
+
+sub ch_dir
+{
+ my ($self, $pathid) = @_;
+ $self->{cwdid} = $pathid;
+}
+
+sub up_dir
+{
+ my ($self) = @_ ;
+ my $query = "
+ SELECT PPathId
+ FROM brestore_pathhierarchy
+ WHERE PathId IN ($self->{cwdid}) ";
+
+ my $all = $self->dbh_selectall_arrayref($query);
+ return unless ($all); # already at root
+
+ my $dir = join(',', map { $_->[0] } @$all);
+ if ($dir) {
+ $self->ch_dir($dir);
+ }
+}
+
+sub pwd
+{
+ my ($self) = @_;
+ return $self->get_path($self->{cwdid});
+}
+
+sub get_path
+{
+ my ($self, $pathid) = @_;
+ $self->debug("Call with pathid = $pathid");
+ my $query =
+ "SELECT Path FROM Path WHERE PathId IN (?)";
+
+ my $sth = $self->dbh_prepare($query);
+ $sth->execute($pathid);
+ my $result = $sth->fetchrow_arrayref();
+ $sth->finish();
+ return $result->[0];
+}
+
+sub set_curjobids
+{
+ my ($self, @jobids) = @_;
+ $self->{curjobids} = join(',', @jobids);
+ $self->update_brestore_table(@jobids);
+}
+
+sub ls_files
+{
+ my ($self) = @_;
+
+ return undef unless ($self->{curjobids});
+
+ my $inclause = $self->{curjobids};
+ my $inlistpath = $self->{cwdid};
+
+ my $query =
+"SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
+ FROM
+ (SELECT Filename.Name, max(File.FileId) as id
+ FROM File, Filename
+ WHERE File.FilenameId = Filename.FilenameId
+ AND Filename.Name != ''
+ AND File.PathId IN ($inlistpath)
+ AND File.JobId IN ($inclause)
+ GROUP BY Filename.Name
+ ORDER BY Filename.Name) AS listfiles,
+File
+WHERE File.FileId = listfiles.id";
+
+ $self->debug($query);
+ my $result = $self->dbh_selectall_arrayref($query);
+ $self->debug($result);
+
+ return $result;
+}
+
+# return ($dirid,$dir_basename,$lstat,$jobid)
+sub ls_dirs
+{
+ my ($self) = @_;
+
+ return undef unless ($self->{curjobids});
+
+ my $pathid = $self->{cwdid};
+ my $jobclause = $self->{curjobids};
+
+ # Let's retrieve the list of the visible dirs in this dir ...
+ # First, I need the empty filenameid to locate efficiently the dirs in the file table
+ my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
+ my $sth = $self->dbh_prepare($query);
+ $sth->execute();
+ my $result = $sth->fetchrow_arrayref();
+ $sth->finish();
+ my $dir_filenameid = $result->[0];
+
+ # Then we get all the dir entries from File ...
+ $query = "
+SELECT PathId, Path, JobId, Lstat FROM (
+
+ SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
+ listfile1.JobId, listfile1.Lstat
+ FROM (
+ SELECT DISTINCT brestore_pathhierarchy1.PathId
+ FROM brestore_pathhierarchy AS brestore_pathhierarchy1
+ JOIN Path AS Path2
+ ON (brestore_pathhierarchy1.PathId = Path2.PathId)
+ JOIN brestore_pathvisibility AS brestore_pathvisibility1
+ ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
+ WHERE brestore_pathhierarchy1.PPathId = $pathid
+ AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
+ JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
+ LEFT JOIN (
+ SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
+ WHERE File1.FilenameId = $dir_filenameid
+ AND File1.JobId IN ($jobclause)) AS listfile1
+ ON (listpath1.PathId = listfile1.PathId)
+ ) AS A ORDER BY 2,3 DESC
+";
+ $self->debug($query);
+ $sth=$self->dbh_prepare($query);
+ $sth->execute();
+ $result = $sth->fetchall_arrayref();
+ my @return_list;
+ my $prev_dir='';
+ foreach my $refrow (@{$result})
+ {
+ my $dirid = $refrow->[0];
+ my $dir = $refrow->[1];
+ my $lstat = $refrow->[3];
+ my $jobid = $refrow->[2] || 0;
+ next if ($dirid eq $prev_dir);
+ # We have to clean up this dirname ... we only want it's 'basename'
+ my $return_value;
+ if ($dir ne '/')
+ {
+ my @temp = split ('/',$dir);
+ $return_value = pop @temp;
+ }
+ else
+ {
+ $return_value = '/';
+ }
+ my @return_array = ($dirid,$return_value,$lstat,$jobid);
+ push @return_list,(\@return_array);
+ $prev_dir = $dirid;
+ }
+ $self->debug(\@return_list);
+ return \@return_list;
+}
+
+# Returns the list of media required for a list of jobids.
+# Input : self, jobid1, jobid2...
+# Output : reference to array of (joibd, inchanger)
+sub get_required_media_from_jobid
+{
+ my ($self, @jobids)=@_;
+ my $inclause = join(',',@jobids);
+ my $query = "
+SELECT DISTINCT JobMedia.MediaId, Media.InChanger
+FROM JobMedia, Media
+WHERE JobMedia.MediaId=Media.MediaId
+AND JobId In ($inclause)
+ORDER BY MediaId";
+ my $result = $self->dbh_selectall_arrayref($query);
+ return $result;
+}
+
+# Returns the fileindex from dirname and jobid.
+# Input : self, dirid, jobid
+# Output : fileindex
+sub get_fileindex_from_dir_jobid
+{
+ my ($self, $dirid, $jobid)=@_;
+ my $query;
+ $query = "SELECT File.FileIndex
+ FROM File, Filename
+ WHERE File.FilenameId = Filename.FilenameId
+ AND File.PathId = $dirid
+ AND Filename.Name = ''
+ AND File.JobId = '$jobid'
+ ";
+
+ $self->debug($query);
+ my $result = $self->dbh_selectall_arrayref($query);
+ return $result->[0]->[0];
+}
+
+# Returns the fileindex from filename and jobid.
+# Input : self, dirid, filenameid, jobid
+# Output : fileindex
+sub get_fileindex_from_file_jobid
{
- my ($ref_fileindex_ranges,$volsessionid,
- $volsessiontime,$volumename,$volfile,
- $mediatype,$volblocks,$count)=@_;
-
- my $bsr='';
- $bsr .= "Volume=\"$volumename\"\n";
- $bsr .= "MediaType=\"$mediatype\"\n";
- $bsr .= "VolSessionId=$volsessionid\n";
- $bsr .= "VolSessionTime=$volsessiontime\n";
- $bsr .= "VolFile=$volfile\n";
- $bsr .= "VolBlock=$volblocks\n";
-
- foreach my $range (@{$ref_fileindex_ranges})
- {
- $bsr .= "FileIndex=$range\n";
- }
+ my ($self, $dirid, $filenameid, $jobid)=@_;
- $bsr .= "Count=$count\n";
- return $bsr;
+ my $query;
+ $query =
+"SELECT File.FileIndex
+ FROM File
+ WHERE File.PathId = $dirid
+ AND File.FilenameId = $filenameid
+ AND File.JobId = $jobid";
+
+ $self->debug($query);
+ my $result = $self->dbh_selectall_arrayref($query);
+ return $result->[0]->[0];
}
# This function estimates the size to be restored for an entry of the restore
sub estimate_restore_size
{
# reminder : restore_list looks like this :
- # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex);
- my $self=shift;
- my ($entry)=@_;
+ # ($pid,$fid,$name,$jobid,'file',$curjobids,
+ # undef, undef, undef, $dirfileindex);
+ my ($self, $entry, $refresh) = @_;
my $query;
- my $dbh = $self->{dbh};
- if ($entry->[2] eq 'dir')
+ if ($entry->[4] eq 'dir')
{
- my $dir = unpack('u', $entry->[0]);
- my $inclause = $entry->[3]; #curjobids
+ my $dir = $entry->[0];
+
+ my $inclause = $entry->[5]; #curjobids
$query =
"SELECT Path.Path, File.FilenameId, File.LStat
FROM File, Path, Job
WHERE Path.PathId = File.PathId
AND File.JobId = Job.JobId
- AND Path.Path LIKE '$dir%'
+ AND Path.Path LIKE
+ (SELECT " . $self->dbh_strcat('Path',"'\%'") . " FROM Path WHERE PathId IN ($dir)
+ )
AND File.JobId IN ($inclause)
ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC";
}
{
# It's a file. Great, we allready have most
# of what is needed. Simple and efficient query
- my $file = unpack('u', $entry->[0]);
- my @file = split '/',$file;
- $file = pop @file;
- my $dir = join('/',@file);
+ my $dir = $entry->[0];
+ my $fileid = $entry->[1];
- my $jobid = $entry->[1];
- my $fileindex = $entry->[7];
- my $inclause = $entry->[3]; # curjobids
+ my $jobid = $entry->[3];
+ my $fileindex = $entry->[9];
+ my $inclause = $entry->[5]; # curjobids
$query =
"SELECT Path.Path, File.FilenameId, File.Lstat
FROM File, Path
WHERE Path.PathId = File.PathId
- AND Path.Path = '$dir/'
- AND Filename.Name = '$file'
+ AND Path.PathId = $dir
+ AND File.FilenameId = $fileid
AND File.JobId = $jobid";
}
- print $query,"\n" if $debug;
my ($path,$nameid,$lstat);
- my $sth = $dbh->prepare($query);
+ my $sth = $self->dbh_prepare($query);
$sth->execute;
$sth->bind_columns(\$path,\$nameid,\$lstat);
my $old_path='';
my $total_size=0;
my $total_files=0;
- refresh_screen();
+ &$refresh();
my $rcount=0;
# We fetch all rows
next if ($nameid eq $old_nameid and $path eq $old_path);
if ($rcount > 15000) {
- refresh_screen();
+ &$refresh();
$rcount=0;
} else {
$rcount++;
$old_path=$path;
$old_nameid=$nameid;
}
+
return ($total_size,$total_files);
}
+# Returns list of versions of a file that could be restored
+# returns an array of
+# ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname)
+# there will be only one jobid in the array of jobids...
+sub get_all_file_versions
+{
+ my ($self,$pathid,$fileid,$client,$see_all)=@_;
+
+ defined $see_all or $see_all=0;
+
+ my @versions;
+ my $query;
+ $query =
+"SELECT File.JobId, File.FileIndex, File.Lstat,
+ File.Md5, Media.VolumeName, Media.InChanger
+ FROM File, Job, Client, JobMedia, Media
+ WHERE File.FilenameId = $fileid
+ AND File.PathId=$pathid
+ AND File.JobId = Job.JobId
+ AND Job.ClientId = Client.ClientId
+ AND Job.JobId = JobMedia.JobId
+ AND File.FileIndex >= JobMedia.FirstIndex
+ AND File.FileIndex <= JobMedia.LastIndex
+ AND JobMedia.MediaId = Media.MediaId
+ AND Job.Type = 'B'
+ AND Client.Name = '$client'";
+
+ $self->debug($query);
+
+ my $result = $self->dbh_selectall_arrayref($query);
+
+ foreach my $refrow (@$result)
+ {
+ my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow;
+ my @attribs = parse_lstat($lstat);
+ my $mtime = array_attrib('st_mtime',\@attribs);
+ my $size = array_attrib('st_size',\@attribs);
+
+ my @list = ('FILE:',$pathid,$fileid,$jobid,
+ $fileindex, $mtime, $size, $inchanger,
+ $md5, $volname);
+ push @versions, (\@list);
+ }
+
+ # We have the list of all versions of this file.
+ # We'll sort it by mtime desc, size, md5, inchanger desc
+ # the rest of the algorithm will be simpler
+ # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
+ @versions = sort { $b->[5] <=> $a->[5]
+ || $a->[6] <=> $b->[6]
+ || $a->[8] cmp $a->[8]
+ || $b->[7] <=> $a->[7]} @versions;
+
+
+ my @good_versions;
+ my %allready_seen_by_mtime;
+ my %allready_seen_by_md5;
+ # Now we should create a new array with only the interesting records
+ foreach my $ref (@versions)
+ {
+ if ($ref->[8])
+ {
+ # The file has a md5. We compare his md5 to other known md5...
+ # We take size into account. It may happen that 2 files
+ # have the same md5sum and are different. size is a supplementary
+ # criterion
+
+ # If we allready have a (better) version
+ next if ( (not $see_all)
+ and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]});
+
+ # we never met this one before...
+ $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1;
+ }
+ # Even if it has a md5, we should also work with mtimes
+ # We allready have a (better) version
+ next if ( (not $see_all)
+ and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]});
+ $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1;
+
+ # We reached there. The file hasn't been seen.
+ push @good_versions,($ref);
+ }
+
+ # To be nice with the user, we re-sort good_versions by
+ # inchanger desc, mtime desc
+ @good_versions = sort { $b->[5] <=> $a->[5]
+ || $b->[3] <=> $a->[3]} @good_versions;
+
+ return @good_versions;
+}
+
+
+sub update_brestore_table
+{
+ my ($self, @jobs) = @_;
+
+ $self->debug(\@jobs);
+
+ foreach my $job (sort {$a <=> $b} @jobs)
+ {
+ my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job";
+ my $retour = $self->dbh_selectrow_arrayref($query);
+ next if ($retour and ($retour->[0] == 1)); # We have allready done this one ...
+
+ print STDERR "Inserting path records for JobId $job\n";
+ $query = "INSERT INTO brestore_pathvisibility (PathId, JobId)
+ (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)";
+
+ $self->dbh_do($query);
+
+ # Now we have to do the directory recursion stuff to determine missing visibility
+ # We try to avoid recursion, to be as fast as possible
+ # We also only work on not allready hierarchised directories...
+
+ print STDERR "Creating missing recursion paths for $job\n";
+
+ $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility
+ JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId)
+ LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId)
+ WHERE brestore_pathvisibility.JobId = $job
+ AND brestore_pathhierarchy.PathId IS NULL
+ ORDER BY Path";
+
+ my $sth = $self->dbh_prepare($query);
+ $sth->execute();
+ my $pathid; my $path;
+ $sth->bind_columns(\$pathid,\$path);
+
+ while ($sth->fetch)
+ {
+ $self->build_path_hierarchy($path,$pathid);
+ }
+ $sth->finish();
+
+ # Great. We have calculated all dependancies. We can use them to add the missing pathids ...
+ # This query gives all parent pathids for a given jobid that aren't stored.
+ # It has to be called until no record is updated ...
+ $query = "
+ INSERT INTO brestore_pathvisibility (PathId, JobId) (
+ SELECT a.PathId,$job
+ FROM
+ (SELECT DISTINCT h.PPathId AS PathId
+ FROM brestore_pathhierarchy AS h
+ JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId)
+ WHERE p.JobId=$job) AS a
+ LEFT JOIN
+ (SELECT PathId
+ FROM brestore_pathvisibility
+ WHERE JobId=$job) AS b
+ ON (a.PathId = b.PathId)
+ WHERE b.PathId IS NULL)";
+
+ my $rows_affected;
+ while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/))
+ {
+ print STDERR "Recursively adding $rows_affected records from $job\n";
+ }
+ # Job's done
+ $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)";
+ $self->dbh_do($query);
+ }
+}
+
+sub parent_dir
+{
+ my ($path) = @_;
+ # Root Unix case :
+ if ($path eq '/')
+ {
+ return '';
+ }
+ # Root Windows case :
+ if ($path =~ /^[a-z]+:\/$/i)
+ {
+ return '';
+ }
+ # Split
+ my @tmp = split('/',$path);
+ # We remove the last ...
+ pop @tmp;
+ my $tmp = join ('/',@tmp) . '/';
+ return $tmp;
+}
+
+sub build_path_hierarchy
+{
+ my ($self, $path,$pathid)=@_;
+ # Does the ppathid exist for this ? we use a memory cache...
+ # In order to avoid the full loop, we consider that if a dir is allready in the
+ # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy
+ while ($path ne '')
+ {
+ if (! $self->{cache_ppathid}->{$pathid})
+ {
+ my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?";
+ my $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
+ $sth2->execute($pathid);
+ # Do we have a result ?
+ if (my $refrow = $sth2->fetchrow_arrayref)
+ {
+ $self->{cache_ppathid}->{$pathid}=$refrow->[0];
+ $sth2->finish();
+ # This dir was in the db ...
+ # It means we can leave, the tree has allready been built for
+ # this dir
+ return 1;
+ } else {
+ $sth2->finish();
+ # We have to create the record ...
+ # What's the current p_path ?
+ my $ppath = parent_dir($path);
+ my $ppathid = $self->return_pathid_from_path($ppath);
+ $self->{cache_ppathid}->{$pathid}= $ppathid;
+
+ $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)";
+ $sth2 = $self->{conf}->{dbh}->prepare_cached($query);
+ $sth2->execute($pathid,$ppathid);
+ $sth2->finish();
+ $path = $ppath;
+ $pathid = $ppathid;
+ }
+ } else {
+ # It's allready in the cache.
+ # We can leave, no time to waste here, all the parent dirs have allready
+ # been done
+ return 1;
+ }
+ }
+ return 1;
+}
+
+
+sub return_pathid_from_path
+{
+ my ($self, $path) = @_;
+ my $query = "SELECT PathId FROM Path WHERE Path = ?";
+
+ #print STDERR $query,"\n" if $debug;
+ my $sth = $self->{conf}->{dbh}->prepare_cached($query);
+ $sth->execute($path);
+ my $result =$sth->fetchrow_arrayref();
+ $sth->finish();
+ if (defined $result)
+ {
+ return $result->[0];
+
+ } else {
+ # A bit dirty : we insert into path, and we have to be sure
+ # we aren't deleted by a purge. We still need to insert into path to get
+ # the pathid, because of mysql
+ $query = "INSERT INTO Path (Path) VALUES (?)";
+ #print STDERR $query,"\n" if $debug;
+ $sth = $self->{conf}->{dbh}->prepare_cached($query);
+ $sth->execute($path);
+ $sth->finish();
+
+ $query = "SELECT PathId FROM Path WHERE Path = ?";
+ #print STDERR $query,"\n" if $debug;
+ $sth = $self->{conf}->{dbh}->prepare_cached($query);
+ $sth->execute($path);
+ $result = $sth->fetchrow_arrayref();
+ $sth->finish();
+ return $result->[0];
+ }
+}
+
+
+sub create_brestore_tables
+{
+ my ($self) = @_;
+ my $ret = 0;
+ my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1";
+
+ unless ($self->dbh_do($verif)) {
+ $ret=1;
+
+ my $req = "
+ CREATE TABLE brestore_knownjobid
+ (
+ JobId int4 NOT NULL,
+ CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId)
+ )";
+ $self->dbh_do($req);
+ }
+
+ $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1";
+ unless ($self->dbh_do($verif)) {
+ $ret=1;
+ my $req = "
+ CREATE TABLE brestore_pathhierarchy
+ (
+ PathId int4 NOT NULL,
+ PPathId int4 NOT NULL,
+ CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId)
+ )";
+ $self->dbh_do($req);
+
+
+ $req = "CREATE INDEX brestore_pathhierarchy_ppathid
+ ON brestore_pathhierarchy (PPathId)";
+ $self->dbh_do($req);
+ }
+
+ $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1";
+ unless ($self->dbh_do($verif)) {
+ $ret=1;
+ my $req = "
+ CREATE TABLE brestore_pathvisibility
+ (
+ PathId int4 NOT NULL,
+ JobId int4 NOT NULL,
+ Size int8 DEFAULT 0,
+ Files int4 DEFAULT 0,
+ CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId)
+ )";
+ $self->dbh_do($req);
+
+ $req = "CREATE INDEX brestore_pathvisibility_jobid
+ ON brestore_pathvisibility (JobId)";
+ $self->dbh_do($req);
+ }
+ return $ret;
+}
# Get metadata
{
}
sub file_attrib
- { # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId]
+ { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
my ($file, $attrib)=@_;
if (defined $attrib_name_id{$attrib}) {
- my @d = split(' ', $file->[2]) ; # TODO : cache this
+ my @d = split(' ', $file->[3]) ; # TODO : cache this
return from_base64($d[$attrib_name_id{$attrib}]);
} elsif ($attrib eq 'jobid') {
- return $file->[3];
+ return $file->[4];
} elsif ($attrib eq 'name') {
- return $file->[1];
+ return $file->[2];
} else {
die "Attribute not known : $attrib.\n";
}
}
-
- # Return the jobid or attribute asked for a dir
- sub dir_attrib
- {
- my ($self,$dir,$attrib)=@_;
-
- my @dir = split('/',$dir,-1);
- my $refdir=$self->{dirtree}->{$self->current_client};
-
- if (not defined $attrib_name_id{$attrib} and $attrib ne 'jobid')
- {
- die "Attribute not known : $attrib.\n";
- }
- # Find the leaf
- foreach my $subdir (@dir)
- {
- $refdir = $refdir->[0]->{$subdir};
- }
-
- # $refdir is now the reference to the dir's array
- # Is the a jobid in @CurrentJobIds where the lstat is
- # defined (we'll search in reverse order)
- foreach my $jobid (reverse(sort {$a <=> $b } @{$self->{CurrentJobIds}}))
- {
- if (defined $refdir->[1]->{$jobid} and $refdir->[1]->{$jobid} ne '1')
- {
- if ($attrib eq 'jobid')
- {
- return $jobid;
- }
- else
- {
- my @attribs = parse_lstat($refdir->[1]->{$jobid});
- return $attribs[$attrib_name_id{$attrib}+1];
- }
- }
- }
-
- return 0; # We cannot get a good attribute.
- # This directory is here for the sake of visibility
- }
sub lstat_attrib
{
my ($lstat,$attrib)=@_;
- if (defined $attrib_name_id{$attrib})
+ if ($lstat and defined $attrib_name_id{$attrib})
{
my @d = split(' ', $lstat) ; # TODO : cache this
return from_base64($d[$attrib_name_id{$attrib}]);
}
+ return 0;
}
}
}
while ($where ne '') {
- $val <<= 6;
+ $val *= 64;
my $d = substr($where, 0, 1);
$val += $base64_map[ord(substr($where, 0, 1))];
$where = substr($where, 1);
return @attribs;
}
}
+
1;
+################################################################
+package BwebConsole;
+use LWP::UserAgent;
+use HTTP::Request::Common;
+
+sub new
+{
+ my ($class, %arg) = @_;
+
+ my $self = bless {
+ pref => $arg{pref}, # Pref object
+ timeout => $arg{timeout} || 20,
+ debug => $arg{debug} || 0,
+ 'list_job' => '',
+ 'list_client' => '',
+ 'list_fileset' => '',
+ 'list_storage' => '',
+ 'run' => '',
+ };
+
+ return $self;
+}
+
+sub prepare
+{
+ my ($self, @what) = @_;
+ my $ua = LWP::UserAgent->new();
+ $ua->agent("Brestore/$VERSION");
+ my $req = POST($self->{pref}->{bconsole},
+ Content_Type => 'form-data',
+ Content => [ map { (action => $_) } @what ]);
+ #$req->authorization_basic('eric', 'test');
+
+ my $res = $ua->request($req);
+
+ if ($res->is_success) {
+ foreach my $l (split(/\n/, $res->content)) {
+ my ($k, $c) = split(/=/,$l,2);
+ $self->{$k} = $c;
+ }
+ } else {
+ $self->{error} = "Can't connect to bweb : " . $res->status_line;
+ new DlgWarn($self->{error});
+ }
+}
+
+sub run
+{
+ my ($self, %arg) = @_;
+
+ my $ua = LWP::UserAgent->new();
+ $ua->agent("Brestore/$VERSION");
+ my $req = POST($self->{pref}->{bconsole},
+ Content_Type => 'form-data',
+ Content => [ job => $arg{job},
+ client => $arg{client},
+ storage => $arg{storage} || '',
+ fileset => $arg{fileset} || '',
+ where => $arg{where} || '',
+ regexwhere => $arg{regexwhere} || '',
+ priority=> $arg{prio} || '',
+ replace => $arg{replace},
+ action => 'run',
+ timeout => 10,
+ bootstrap => [$arg{bootstrap}],
+ ]);
+ #$req->authorization_basic('eric', 'test');
+
+ my $res = $ua->request($req);
+
+ if ($res->is_success) {
+ foreach my $l (split(/\n/, $res->content)) {
+ my ($k, $c) = split(/=/,$l,2);
+ $self->{$k} = $c;
+ }
+ }
+
+ if (!$self->{run}) {
+ new DlgWarn("Can't connect to bweb : " . $res->status_line);
+ }
+
+ unlink($arg{bootstrap});
+
+ return $self->{run};
+}
+
+sub list_job
+{
+ my ($self) = @_;
+ return sort split(/;/, $self->{'list_job'});
+}
+
+sub list_fileset
+{
+ my ($self) = @_;
+ return sort split(/;/, $self->{'list_fileset'});
+}
+
+sub list_storage
+{
+ my ($self) = @_;
+ return sort split(/;/, $self->{'list_storage'});
+}
+sub list_client
+{
+ my ($self) = @_;
+ return sort split(/;/, $self->{'list_client'});
+}
+
+1;
################################################################
package main;
-my $conf = "$ENV{HOME}/.brestore.conf" ;
-my $p = new Pref($conf);
+use Getopt::Long ;
+
+sub HELP_MESSAGE
+{
+ print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n";
+ exit 1;
+}
+
+my $file_conf = (exists $ENV{HOME})? "$ENV{HOME}/.brestore.conf" : undef ;
+my $batch_mod;
+
+GetOptions("conf=s" => \$file_conf,
+ "batch" => \$batch_mod,
+ "debug" => \$debug,
+ "help" => \&HELP_MESSAGE) ;
+
+if (! defined $file_conf) {
+ print STDERR "Could not detect default config and no config file specified\n";
+ HELP_MESSAGE();
+}
+
+my $p = new Pref($file_conf);
-if (! -f $conf) {
+if (! -f $file_conf) {
$p->write_config();
}
-$glade_file = $p->{glade_file};
+if ($batch_mod) {
+ my $vfs = new Bvfs(conf => $p);
+ if ($p->connect_db()) {
+ if ($vfs->create_brestore_tables()) {
+ print "Creating brestore tables\n";
+ }
+ $vfs->update_cache();
+ }
+ exit (0);
+}
+
+# load Gtk libraries only in non batch mode
+eval "
+ use Gtk2;
+ use Gtk2::GladeXML;
+ use Gtk2::SimpleList;
+ use Gtk2::Gdk::Keysyms;
+";
+
+$glade_file = $p->{glade_file} || $glade_file;
foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') {
if (-f "$path/$glade_file") {
}
}
+# gtk have lots of warning on stderr
+if ($^O eq 'MSWin32')
+{
+ close(STDERR);
+ open(STDERR, ">stderr.log");
+}
+
+Gtk2->init();
+
if ( -f $glade_file) {
my $w = new DlgResto($p);
} else {
my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close',
"Can't find your brestore.glade (glade_file => '$glade_file')
-Please, edit your $conf to setup it." );
+Please, edit your $file_conf to setup it." );
$widget->signal_connect('destroy', sub { Gtk2->main_quit() ; });
$widget->run;
exit 0;
-
__END__
+package main;
-TODO :
-
-
-# Code pour trier les colonnes
- my $mod = $fileview->get_model();
- $mod->set_default_sort_func(sub {
- my ($model, $item1, $item2) = @_;
- my $a = $model->get($item1, 1); # récupération de la valeur de la 2ème
- my $b = $model->get($item2, 1); # colonne (indice 1)
- return $a cmp $b;
- }
- );
-
- $fileview->set_headers_clickable(1);
- my $col = $fileview->get_column(1); # la colonne NOM, colonne numéro 2
- $col->signal_connect('clicked', sub {
- my ($colonne, $model) = @_;
- $model->set_sort_column_id (1, 'ascending');
- },
- $mod
- );
+my $p = new Pref("$ENV{HOME}/.brestore.conf");
+$p->{debug} = 1;
+$p->connect_db() || print $p->{error};
+
+my $bvfs = new Bvfs(conf => $p);
+
+$bvfs->debug($bvfs->get_root());
+$bvfs->ch_dir($bvfs->get_root());
+$bvfs->up_dir();
+$bvfs->set_curjobids(268,178,282,281,279);
+$bvfs->ls_files();
+my $dirs = $bvfs->ls_dirs();
+$bvfs->ch_dir(123496);
+$dirs = $bvfs->ls_dirs();
+$bvfs->ls_files();
+map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwdid},312433, "exw3srv3", 1);