+
+ return 0;
+}
+
+sub on_see_all_version
+{
+ my ($self) = @_;
+
+ my @lst = listview_get_all($self->{fileview});
+
+ for my $i (@lst) {
+ my ($pid,$fid,$name, undef) = @{$i};
+
+ new DlgFileVersion($self->{bvfs},
+ $self->current_client,
+ $pid,$fid,$self->{cwd},$name);
+ }
+}
+
+sub on_right_click_filelist
+{
+ my ($self,$widget,$event) = @_;
+ # I need to know what's selected
+ my @sel = listview_get_all($self->{fileview});
+
+ my $type = '';
+
+ if (@sel == 1) {
+ $type = $sel[0]->[4]; # $type
+ }
+
+ my $w;
+
+ if (@sel >=2 or $type eq 'dir')
+ {
+ # We have selected more than one or it is a directories
+ $w = $self->{filelist_dir_menu};
+ }
+ else
+ {
+ $w = $self->{filelist_file_menu};
+ }
+ $w->popup(undef,
+ undef,
+ undef,
+ undef,
+ $event->button, $event->time);
+}
+
+sub context_add_to_filelist
+{
+ my ($self) = @_;
+
+ my @sel = listview_get_all($self->{fileview});
+
+ foreach my $i (@sel)
+ {
+ 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, $pid, $fid, $name, $jobid, $type)=@_;
+
+ my $restore_list = $self->{restore_list};
+
+ my $curjobids=join(',', @{$self->{CurrentJobIds}});
+
+ if ($type eq 'dir')
+ {
+ # dirty hack
+ $name =~ s!^//+!/!;
+
+ if ($name and substr $name,-1 ne '/')
+ {
+ $name .= '/'; # For bacula
+ }
+ 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
+
+# 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";
+
+ 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;
+ }
+ }
+
+ 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)
+# This sub creates a BSR from the information in the restore_list
+# Returns the BSR as a string
+sub create_filelist
+{
+ my $self = shift;
+ 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
+FROM Job, JobMedia, Media
+WHERE Job.JobId = JobMedia.JobId
+ AND JobMedia.MediaId = Media.MediaId
+ ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex";
+
+
+ my $result = $self->dbh_selectall_arrayref($query);
+
+ # We will store everything hashed by jobid.
+
+ foreach my $refrow (@$result)
+ {
+ my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile,
+ $firstindex, $lastindex, $startblock, $endblock,
+ $volindex, $volumename, $mediatype) = @{$refrow};
+
+ # We just have to deal with the case where starfile != endfile
+ # In this case, we concatenate both, for the bsr
+ if ($startfile != $endfile) {
+ $startfile = $startfile . '-' . $endfile;
+ }
+
+ my @tmparray =
+ ($jobid, $volsessionid, $volsessiontime, $startfile,
+ $firstindex, $lastindex, $startblock .'-'. $endblock,
+ $volindex, $volumename, $mediatype);
+
+ push @{$mediainfos{$refrow->[0]}},(\@tmparray);
+ }
+
+
+ # reminder : restore_list looks like this :
+ # ($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,
+ # no pseudo join (>= FirstIndex ...), etc ...
+ # We do a SQL union of all the files/dirs specified in the restore_list
+ my @select_queries;
+ foreach my $entry (@{$self->{restore_list}->{data}})
+ {
+ if ($entry->[4] eq 'dir')
+ {
+ 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
+ (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path
+ WHERE PathId IN ($dirid)
+ )
+ AND File.JobId IN ($inclause) )";
+ push @select_queries,($query);
+ }
+ else
+ {
+ # It's a file. Great, we allready have most
+ # of what is needed. Simple and efficient query
+ my $dir = $entry->[0];
+ my $file = $entry->[1];
+
+ 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 File.PathId = $dir
+ AND File.PathId = Path.PathId
+ AND File.FilenameId = $file
+ AND File.FilenameId = Filename.FilenameId
+ AND File.JobId = $jobid
+ )
+";
+ push @select_queries,($query);
+ }
+ }
+ $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n";
+
+ #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 = $self->dbh_prepare($query);
+ $sth->execute;
+
+ my ($path,$name,$fileindex,$jobid);
+ $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid);
+
+ # The temp place we're going to save all file
+ # list to before the real list
+ my @temp_list;
+
+ RECORD_LOOP:
+ while ($sth->fetchrow_arrayref())
+ {
+ # This may look dumb, but we're going to do a join by ourselves,
+ # to save memory and avoid sending a complex query to mysql
+ my $complete_path = $path . $name;
+ my $is_dir = 0;
+
+ if ( $name eq '')
+ {
+ $is_dir = 1;
+ }
+
+ # Remove trailing slash (normalize file and dir name)
+ $complete_path =~ s/\/$//;
+
+ # Let's find the ref(s) for the %mediainfo element(s)
+ # containing the data for this file
+ # There can be several matches. It is the pseudo join.
+ my $med_idx=0;
+ my $max_elt=@{$mediainfos{$jobid}}-1;
+ MEDIA_LOOP:
+ while($med_idx <= $max_elt)
+ {
+ my $ref = $mediainfos{$jobid}->[$med_idx];
+ # First, can we get rid of the first elements of the
+ # array ? (if they don't contain valuable records
+ # anymore
+ if ($fileindex > $ref->[5])
+ {
+ # It seems we don't need anymore
+ # this entry in %mediainfo (the input data
+ # is sorted...)
+ # We get rid of it.
+ shift @{$mediainfos{$jobid}};
+ $max_elt--;
+ next MEDIA_LOOP;
+ }
+ # We will do work on this elt. We can ++
+ # $med_idx for next loop
+ $med_idx++;
+
+ # %mediainfo row looks like :
+ # (jobid,VolsessionId,VolsessionTime,File,FirstIndex,
+ # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,
+ # MediaType)
+
+ # We are in range. We store and continue looping
+ # in the medias
+ if ($fileindex >= $ref->[4])
+ {
+ my @data = ($complete_path,$is_dir,
+ $fileindex,$ref);
+ push @temp_list,(\@data);
+ next MEDIA_LOOP;
+ }
+
+ # We are not in range. No point in continuing looping
+ # We go to next record.
+ next RECORD_LOOP;
+ }
+ }
+ # Now we have the array.
+ # We're going to sort it, by
+ # path, volsessiontime DESC (get the most recent file...)
+ # The array rows look like this :
+ # complete_path,is_dir,fileindex,
+ #\81 ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,
+ # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType)
+ @temp_list = sort {$a->[0] cmp $b->[0]
+ || $b->[3]->[2] <=> $a->[3]->[2]
+ } @temp_list;
+
+ my @restore_list;
+ my $prev_complete_path='////'; # Sure not to match
+ my $prev_is_file=1;
+ my $prev_jobid;
+
+ while (my $refrow = shift @temp_list)
+ {
+ # For the sake of readability, we load $refrow
+ # contents in real scalars
+ my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow};
+ my $jobid= $refother->[0]; # We don't need the rest...
+
+ # We skip this entry.
+ # We allready have a newer one and this
+ # isn't a continuation of the same file
+ next if ($complete_path eq $prev_complete_path
+ and $jobid != $prev_jobid);
+
+
+ if ($prev_is_file
+ and $complete_path =~ m|^\Q$prev_complete_path\E/|)
+ {
+ # We would be recursing inside a file.
+ # Just what we don't want (dir replaced by file
+ # between two backups
+ next;
+ }
+ elsif ($is_dir)
+ {
+ # It is a directory
+ push @restore_list,($refrow);
+
+ $prev_complete_path = $complete_path;
+ $prev_jobid = $jobid;
+ $prev_is_file = 0;
+ }
+ else
+ {
+ # It is a file
+ push @restore_list,($refrow);
+
+ $prev_complete_path = $complete_path;
+ $prev_jobid = $jobid;
+ $prev_is_file = 1;
+ }
+ }
+ # We get rid of @temp_list... save memory
+ @temp_list=();
+
+ # Ok everything is in the list. Let's sort it again in another way.
+ # This time it will be in the bsr file order
+
+ # we sort the results by
+ # volsessiontime, volsessionid, volindex, fileindex
+ # to get all files in right order...
+ # Reminder : The array rows look like this :
+ # complete_path,is_dir,fileindex,
+ # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex,
+ # StartBlock-EndBlock,VolIndex,Volumename,MediaType)
+
+ @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2]
+ || $a->[3]->[1] <=> $b->[3]->[1]
+ || $a->[3]->[7] <=> $b->[3]->[7]
+ || $a->[2] <=> $b->[2] }
+ @restore_list;
+
+ # Now that everything is ready, we create the bsr
+ my $prev_fileindex=-1;
+ my $prev_volsessionid=-1;
+ my $prev_volsessiontime=-1;
+ my $prev_volumename=-1;
+ my $prev_volfile=-1;
+ my $prev_mediatype;
+ my $prev_volblocks;
+ my $count=0;
+ my $first_of_current_range=0;
+ my @fileindex_ranges;
+ my $bsr='';
+
+ foreach my $refrow (@restore_list)
+ {
+ my (undef,undef,$fileindex,$refother)=@{$refrow};
+ my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef,
+ $volblocks,undef,$volumename,$mediatype)=@{$refother};
+
+ # We can specifiy the number of files in each section of the
+ # bsr to speedup restore (bacula can then jump over the
+ # end of tape files.
+ $count++;
+
+
+ if ($prev_volumename eq '-1')
+ {
+ # We only have to start the new range...
+ $first_of_current_range=$fileindex;
+ }
+ elsif ($prev_volsessionid != $volsessionid
+ or $prev_volsessiontime != $volsessiontime
+ or $prev_volumename ne $volumename
+ or $prev_volfile ne $volfile)
+ {
+ # We have to create a new section in the bsr...
+ #\81Â\81 We print the previous one ...
+ # (before that, save the current range ...)
+ if ($first_of_current_range != $prev_fileindex)
+ {
+ #\81Â\81 we are in a range
+ push @fileindex_ranges,
+ ("$first_of_current_range-$prev_fileindex");
+ }
+ else
+ {
+ # We are out of a range,
+ # but there is only one element in the range
+ push @fileindex_ranges,
+ ("$first_of_current_range");
+ }
+
+ $bsr.=print_bsr_section(\@fileindex_ranges,
+ $prev_volsessionid,
+ $prev_volsessiontime,
+ $prev_volumename,
+ $prev_volfile,
+ $prev_mediatype,
+ $prev_volblocks,
+ $count-1);
+ $count=1;
+ # Reset for next loop
+ @fileindex_ranges=();
+ $first_of_current_range=$fileindex;
+ }
+ elsif ($fileindex-1 != $prev_fileindex)
+ {
+ # End of a range of fileindexes
+ if ($first_of_current_range != $prev_fileindex)
+ {
+ #we are in a range
+ push @fileindex_ranges,
+ ("$first_of_current_range-$prev_fileindex");
+ }
+ else
+ {
+ # We are out of a range,
+ # but there is only one element in the range
+ push @fileindex_ranges,
+ ("$first_of_current_range");
+ }
+ $first_of_current_range=$fileindex;
+ }
+ $prev_fileindex=$fileindex;
+ $prev_volsessionid = $volsessionid;
+ $prev_volsessiontime = $volsessiontime;
+ $prev_volumename = $volumename;
+ $prev_volfile=$volfile;
+ $prev_mediatype=$mediatype;
+ $prev_volblocks=$volblocks;
+
+ }
+
+ # Ok, we're out of the loop. Alas, there's still the last record ...
+ if ($first_of_current_range != $prev_fileindex)
+ {
+ # we are in a range
+ push @fileindex_ranges,("$first_of_current_range-$prev_fileindex");
+
+ }
+ else
+ {
+ # We are out of a range,
+ # but there is only one element in the range
+ push @fileindex_ranges,("$first_of_current_range");
+
+ }
+ $bsr.=print_bsr_section(\@fileindex_ranges,
+ $prev_volsessionid,
+ $prev_volsessiontime,
+ $prev_volumename,
+ $prev_volfile,
+ $prev_mediatype,
+ $prev_volblocks,
+ $count);
+
+ return $bsr;
+}
+
+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;