remove checkboxes for hosts without created increments
[BackupPC.git] / lib / BackupPC / SearchLib.pm
1 #!/usr/bin/perl
2 package BackupPC::SearchLib;
3
4 use strict;
5 use BackupPC::CGI::Lib qw(:all);
6 use BackupPC::Attrib qw(:all);
7 use DBI;
8 use DateTime;
9 use vars qw(%In $MyURL);
10 use Time::HiRes qw/time/;
11
12 my $on_page = 100;
13 my $pager_pages = 10;
14
15 my $dsn = $Conf{SearchDSN};
16 my $db_user = $Conf{SearchUser} || '';
17
18 my $hest_index_path = $Conf{HyperEstraierIndex};
19
20 my $dbh;
21
22 sub get_dbh {
23         $dbh ||= DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1 } );
24         return $dbh;
25 }
26
27 sub getUnits() {
28         my @ret;
29
30         my $dbh = get_dbh();
31         my $sth = $dbh->prepare(qq{
32                 SELECT
33                         shares.id       as id,
34                         hosts.name || ':' || shares.name as share
35                 FROM shares
36                 JOIN hosts on hostid = hosts.id
37                 ORDER BY share
38         } );
39         $sth->execute();
40         push @ret, { 'id' => '', 'share' => '-'};       # dummy any
41
42         while ( my $row = $sth->fetchrow_hashref() ) {
43                 push @ret, $row;
44         }
45         return @ret;
46 }
47
48 sub epoch_to_iso {
49         my $t = shift || return;
50         my $iso = BackupPC::Lib::timeStamp(undef, $t);
51         $iso =~ s/\s/ /g;
52         return $iso;
53 }
54
55 sub dates_from_form($) {
56         my $param = shift || return;
57
58         sub mk_epoch_date($$) {
59                 my ($name,$suffix) = @_;
60
61                 my $yyyy = $param->{ $name . '_year_' . $suffix} || return undef;
62                 my $mm .= $param->{ $name . '_month_' . $suffix} ||
63                         ( $suffix eq 'from' ? 1 : 12);
64                 my $dd .= $param->{ $name . '_day_' . $suffix} ||
65                         ( $suffix eq 'from' ? 1 : 31);
66
67                 $yyyy =~ s/\D//g;
68                 $mm =~ s/\D//g;
69                 $dd =~ s/\D//g;
70
71                 my $dt = new DateTime(
72                         year => $yyyy,
73                         month => $mm,
74                         day => $dd
75                 );
76                 print STDERR "mk_epoch_date($name,$suffix) [$yyyy-$mm-$dd] = " . $dt->ymd . " " . $dt->hms . "\n";
77                 return $dt->epoch || 'NULL';
78         }
79
80         my @ret = (
81                 mk_epoch_date('search_backup', 'from'),
82                 mk_epoch_date('search_backup', 'to'),
83                 mk_epoch_date('search', 'from'),
84                 mk_epoch_date('search', 'to'),
85         );
86
87         return @ret;
88
89 }
90
91
92 sub getWhere($) {
93         my $param = shift || return;
94
95         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
96
97         my @conditions;
98         push @conditions, qq{ backups.date >= $backup_from } if ($backup_from);
99         push @conditions, qq{ backups.date <= $backup_to } if ($backup_to);
100         push @conditions, qq{ files.date >= $files_from } if ($files_from);
101         push @conditions, qq{ files.date <= $files_to } if ($files_to);
102
103         print STDERR "backup: $backup_from - $backup_to files: $files_from - $files_to cond:" . join(" | ",@conditions);
104
105         push( @conditions, ' files.shareid = ' . $param->{'search_share'} ) if ($param->{'search_share'});
106         push (@conditions, " upper(files.path) LIKE upper('%".$param->{'search_filename'}."%')") if ($param->{'search_filename'});
107
108         return join(" and ", @conditions);
109 }
110
111
112 sub getFiles($) {
113         my ($param) = @_;
114
115         my $offset = $param->{'offset'} || 0;
116         $offset *= $on_page;
117
118         my $dbh = get_dbh();
119
120         my $sql_cols = qq{
121                 files.id                        AS fid,
122                 hosts.name                      AS hname,
123                 shares.name                     AS sname,
124                 files.backupnum                 AS backupnum,
125                 files.path                      AS filepath,
126                 files.date                      AS date,
127                 files.type                      AS type,
128                 files.size                      AS size
129         };
130
131         my $sql_from = qq{
132                 FROM files 
133                         INNER JOIN shares       ON files.shareID=shares.ID
134                         INNER JOIN hosts        ON hosts.ID = shares.hostID
135                         INNER JOIN backups      ON backups.num = files.backupnum and backups.hostID = hosts.ID AND backups.shareID = files.shareID
136         };
137
138         my $sql_where;
139         my $where = getWhere($param);
140         $sql_where = " WHERE ". $where if ($where);
141
142         my $sql_order = qq{
143                 ORDER BY files.date
144                 LIMIT $on_page
145                 OFFSET ?
146         };
147
148         my $sql_count = qq{ select count(files.id) $sql_from $sql_where };
149         my $sql_results = qq{ select $sql_cols $sql_from $sql_where $sql_order };
150
151         my $sth = $dbh->prepare($sql_count);
152         $sth->execute();
153         my ($results) = $sth->fetchrow_array();
154
155         $sth = $dbh->prepare($sql_results);
156         $sth->execute( $offset );
157
158         if ($sth->rows != $results) {
159                 my $bug = "$0 BUG: [[ $sql_count ]] = $results while [[ $sql_results ]] = " . $sth->rows;
160                 $bug =~ s/\s+/ /gs;
161                 print STDERR "$bug\n";
162         }
163
164         my @ret;
165       
166         while (my $row = $sth->fetchrow_hashref()) {
167                 push @ret, $row;
168         }
169      
170         $sth->finish();
171         return ($results, \@ret);
172 }
173
174 sub getHyperEstraier_url($) {
175         my ($use_hest) = @_;
176
177         return unless $use_hest;
178
179         use HyperEstraier;
180         my ($index_path, $index_node_url);
181
182         if ($use_hest =~ m#^http://#) {
183                 $index_node_url = $use_hest;
184         } else {
185                 $index_path = $TopDir . '/' . $index_path;
186                 $index_path =~ s#//#/#g;
187         }
188         return ($index_path, $index_node_url);
189 }
190
191 sub getFilesHyperEstraier($) {
192         my ($param) = @_;
193
194         my $offset = $param->{'offset'} || 0;
195         $offset *= $on_page;
196
197         die "no index_path?" unless ($hest_index_path);
198
199         use HyperEstraier;
200
201         my ($index_path, $index_node_url) = getHyperEstraier_url($hest_index_path);
202
203         # open the database
204         my $db;
205         if ($index_path) {
206                 $db = HyperEstraier::Database->new();
207                 $db->open($index_path, $HyperEstraier::ESTDBREADER);
208         } elsif ($index_node_url) {
209                 $db ||= HyperEstraier::Node->new($index_node_url);
210                 $db->set_auth('admin', 'admin');
211         } else {
212                 die "BUG: unimplemented";
213         }
214
215         # create a search condition object
216         my $cond = HyperEstraier::Condition->new();
217
218         my $q = $param->{'search_filename'};
219         my $shareid = $param->{'search_share'};
220
221         if (length($q) > 0) {
222                 # exact match
223                 $cond->add_attr("filepath ISTRINC $q");
224
225                 $q =~ s/(.)/$1 /g;
226                 # set the search phrase to the search condition object
227                 $cond->set_phrase($q);
228         }
229
230         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
231
232         $cond->add_attr("backup_date NUMGE $backup_from") if ($backup_from);
233         $cond->add_attr("backup_date NUMLE $backup_to") if ($backup_to);
234
235         $cond->add_attr("date NUMGE $files_from") if ($files_from);
236         $cond->add_attr("date NUMLE $files_to") if ($files_to);
237
238         $cond->add_attr("shareid NUMEQ $shareid") if ($shareid);
239
240 #       $cond->set_max( $offset + $on_page );
241         $cond->set_options( $HyperEstraier::Condition::SURE );
242         $cond->set_order( 'date NUMA' );
243
244         # get the result of search
245         my @res;
246         my ($result, $hits);
247
248         if ($index_path) {
249                 $result = $db->search($cond, 0);
250                 $hits = $result->size;
251         } elsif ($index_node_url) {
252                 $result = $db->search($cond, 0);
253                 $hits = $result->doc_num;
254         } else {
255                 die "BUG: unimplemented";
256         }
257
258         # for each document in result
259         for my $i ($offset .. ($offset + $on_page - 1)) {
260                 last if ($i >= $hits);
261
262                 my $doc;
263                 if ($index_path) {
264                         my $id = $result->get($i);
265                         $doc = $db->get_doc($id, 0);
266                 } elsif ($index_node_url) {
267                         $doc = $result->get_doc($i);
268                 } else {
269                         die "BUG: unimplemented";
270                 }
271
272                 my $row;
273                 foreach my $c (qw/fid hname sname backupnum fiilename filepath date type size/) {
274                         $row->{$c} = $doc->attr($c);
275                 }
276                 push @res, $row;
277         }
278
279         return ($hits, \@res);
280 }
281
282 sub getGzipName($$$)
283 {
284         my ($host, $share, $backupnum) = @_;
285         my $ret = $Conf{GzipSchema};
286         
287         $share =~ s/\//_/g;
288         $ret =~ s/\\h/$host/ge;
289         $ret =~ s/\\s/$share/ge;
290         $ret =~ s/\\n/$backupnum/ge;
291         
292         return $ret;
293         
294 }
295
296 sub getBackupsNotBurned() {
297
298         my $dbh = get_dbh();
299
300         my $sql = q{
301                 SELECT 
302                         backups.hostID AS hostID,
303                         hosts.name AS host,
304                         shares.name AS share,
305                         backups.id AS backupnum,
306                         backups.type AS type,
307                         backups.date AS date,
308                         backups.size AS size
309                 FROM backups 
310                 INNER JOIN shares       ON backups.shareID=shares.ID
311                 INNER JOIN hosts        ON backups.hostID = hosts.ID
312                 LEFT OUTER JOIN archive_backup ON archive_backup.backup_id = backups.id AND archive_backup.backup_id IS NULL
313                 WHERE backups.size > 0
314                 GROUP BY
315                         backups.hostID,
316                         hosts.name,
317                         shares.name,
318                         backups.num,
319                         backups.shareid,
320                         backups.id,
321                         backups.type,
322                         backups.date,
323                         backups.size
324                 ORDER BY backups.date
325         };
326         my $sth = $dbh->prepare( $sql );
327         my @ret;
328         $sth->execute();
329
330         while ( my $row = $sth->fetchrow_hashref() ) {
331                 $row->{'age'} = sprintf("%0.1f", ( (time() - $row->{'date'}) / 86400 ) );
332                 $row->{'size'} = sprintf("%0.2f", $row->{'size'} / 1024 / 1024);
333                 my (undef,undef,undef,undef,undef,undef,undef,$fs_size,undef,undef,undef,undef,undef) = 
334                         stat( $Conf{InstallDir}.'/'.$Conf{GzipTempDir}.'/'.
335                                 getGzipName($row->{'host'}, $row->{share}, $row->{'backupnum'}));
336                 $row->{'fs_size'} = $fs_size;
337                 push @ret, $row;
338         }
339       
340         return @ret;      
341 }
342
343 sub displayBackupsGrid()
344   {
345       my $retHTML = "";
346       
347         $retHTML .= <<EOF3;
348 <script language="javascript" type="text/javascript">
349 <!--
350
351     function checkAll(location)
352     {
353       for (var i=0;i<document.forma.elements.length;i++)
354       {
355         var e = document.forma.elements[i];
356         if ((e.checked || !e.checked) && e.name != \'all\') {
357             if (eval("document.forma."+location+".checked")) {
358                 e.checked = true;
359             } else {
360                 e.checked = false;
361             }
362         }
363       }
364     }
365     
366     function sumiraj()
367     {
368         var suma = 0;
369         for (var i = 0; i < document.forma.elements.length; i++)    
370                 {
371                         var e = document.forma.elements[i];
372                         if ((e.checked || !e.checked) && e.name != \'all\')
373                         {
374                                 if (e.checked)
375                                 {
376                                         var ret = e.name.match("fcb(.*)");
377                                         suma += parseInt(eval("document.forma.fss"+ret[1]+".value") || 0);
378                                         
379                                 }
380                         }
381         }
382         document.forma.totalsize.value = suma;
383         return suma;
384     }
385 //-->
386 </script>      
387 EOF3
388         $retHTML .= q{
389                 <form name="forma" method="GET" action=};
390                 $retHTML .= "\"".$MyURL."\"";
391                 $retHTML .= q{?action=burn>
392                         <input type="hidden" value="burn" name="action">
393                         <input type="hidden" value="results" name="search_results">
394                         <table style="fview" border="0" cellspacing="0" cellpadding="2">
395                         <tr class="tableheader">
396                         <td class="tableheader">
397                                 <input type="checkbox" name="allFiles" onClick="checkAll('allFiles');">
398                         </td>
399                         <td align="center">Share</td>
400                         <td align="center">Backup no</td>
401                         <td align="center">Type</td>
402                         <td align="center">date</td>
403                         <td align="center">age/days</td>
404                         <td align="center">size/MB</td>
405                         <td align="center">gzip size</td>
406                         </tr>
407
408                         <tr><td colspan=7 style="tableheader">
409                         <input type="submit" value="Burn selected backups on medium" name="submitBurner">
410                         </td></tr>
411         };
412
413         my @color = (' bgcolor="#e0e0e0"', '');
414
415         my $i = 0;
416         my $host = '';
417
418         foreach my $backup ( getBackupsNotBurned() ) {
419
420                 if ($host ne $backup->{'host'}) {
421                         $i++;
422                         $host = $backup->{'host'};
423                 }
424                 my $ftype = "";
425
426                 $retHTML .=
427                         '<tr' . $color[$i %2 ] . '>
428                         <td class="fview">';
429                 if (($backup->{'fs_size'} || 0) > 0) {
430                         $retHTML .= '
431                         <input type="checkbox" name="fcb' .
432                         $backup->{'hostid'}.'_'.$backup->{'backupnum'} . 
433                         '" value="' . $backup->{'hostid'}.'_'.$backup->{'backupnum'} .
434                         '" onClick="sumiraj();">';
435                 }
436                 $retHTML .=
437                         '</td>' .
438                         '<td align="right">' . $backup->{'host'} . ':' . $backup->{'share'} . '</td>' .
439                         '<td align="center">' . $backup->{'backupnum'} . '</td>' .
440                         '<td align="center">' . $backup->{'type'} . '</td>' .
441                         '<td align="center">' . epoch_to_iso( $backup->{'date'} ) . '</td>' .
442                         '<td align="center">' . $backup->{'age'} . '</td>' .
443                         '<td align="right">' . $backup->{'size'} . '</td>' .
444                         '<td align="right">' . $backup->{'fs_size'} .
445                         '<input type="hidden" name="fss'.$backup->{'hostid'}.'_'.$backup->{'backupnum'} . '"'.
446                         'value="'. $backup->{'fs_size'} .'"'.'</td>' .
447
448                         "</tr>\n";
449         }
450
451         $retHTML .= "</table>";
452         $retHTML .= "total gzip size:<input type=\"text\" name=\"totalsize\"><br>";
453         $retHTML .= "Note:<input type=\"text\" name=\"note\">";
454         $retHTML .= "</form>";
455       
456         return $retHTML;
457 }      
458
459 sub displayGrid($) {
460         my ($param) = @_;
461
462         my $offset = $param->{'offset'};
463         my $hilite = $param->{'search_filename'};
464
465         my $retHTML = "";
466  
467         my $start_t = time();
468
469         my ($results, $files);
470         if ($param->{'use_hest'} && length($hilite) > 0) {
471                 ($results, $files) = getFilesHyperEstraier($param);
472         } else {
473                 ($results, $files) = getFiles($param);
474         }
475
476         my $dur_t = time() - $start_t;
477         my $dur = sprintf("%0.4fs", $dur_t);
478
479         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
480
481         if ($results <= 0) {
482                 $retHTML .= qq{
483                         <p style="color: red;">No results found...</p>
484                 };
485                 return $retHTML;
486         } else {
487                 # DEBUG
488                 #use Data::Dumper;
489                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
490         }
491
492
493         $retHTML .= qq{
494         <div>
495         Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
496         </div>
497         <table style="fview" width="100%" border="0" cellpadding="2" cellspacing="0">
498                 <tr class="fviewheader"> 
499                 <td></td>
500                 <td align="center">Share</td>
501                 <td align="center">Type and Name</td>
502                 <td align="center">#</td>
503                 <td align="center">Size</td>
504                 <td align="center">Date</td>
505                 <td align="center">Media</td>
506                 </tr>
507         };
508
509         my $file;
510
511         sub hilite_html($$) {
512                 my ($html, $search) = @_;
513                 $html =~ s#($search)#<b>$1</b>#gis;
514                 return $html;
515         }
516
517         sub restore_link($$$$$$) {
518                 my $type = shift;
519                 my $action = 'RestoreFile';
520                 $action = 'browse' if (lc($type) eq 'dir');
521                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
522         }
523
524         my $i = $offset * $on_page;
525
526         foreach $file (@{ $files }) {
527                 $i++;
528
529                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
530                 $retHTML .= qq{<tr class="fviewborder">};
531
532                 $retHTML .= qq{<td class="fviewborder">$i</td>};
533
534                 $retHTML .=
535                         qq{<td class="fviewborder" align="right">} . $file->{'hname'} . ':' . $file->{'sname'} . qq{</td>} .
536                         qq{<td class="fviewborder"><img src="$Conf{CgiImageDirURL}/icon-$typeStr.gif" alt="$typeStr" align="middle">&nbsp;} . hilite_html( $file->{'filepath'}, $hilite ) . qq{</td>} .
537                         qq{<td class="fviewborder" align="center">} . restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupnum'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'filepath'} )}, $file->{'backupnum'} ) . qq{</td>} .
538                         qq{<td class="fviewborder" align="right">} . $file->{'size'} . qq{</td>} .
539                         qq{<td class="fviewborder">} . epoch_to_iso( $file->{'date'} ) . qq{</td>} .
540                         qq{<td class="fviewborder">} . '?' . qq{</td>};
541
542                 $retHTML .= "</tr>";
543         }
544         $retHTML .= "</table>";
545
546         # all variables which has to be transfered
547         foreach my $n (qw/search_day_from search_month_from search_year_from search_day_to search_month_to search_year_to search_backup_day_from search_backup_month_from search_backup_year_from search_backup_day_to search_backup_month_to search_backup_year_to search_filename offset/) {
548                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
549         }
550
551         my $del = '';
552         my $max_page = int( $results / $on_page );
553         my $page = 0;
554
555         sub page_link($$$) {
556                 my ($param,$page,$display) = @_;
557
558                 $param->{'offset'} = $page;
559
560                 my $html = '<a href = "' . $MyURL;
561                 my $del = '?';
562                 foreach my $k (keys %{ $param }) {
563                         if ($param->{$k}) {
564                                 $html .= $del . $k . '=' . ${EscURI( $param->{$k} )};
565                                 $del = '&';
566                         }
567                 }
568                 $html .= '">' . $display . '</a>';
569         }
570
571         $retHTML .= '<div style="text-align: center;">';
572
573         if ($offset > 0) {
574                 $retHTML .= page_link($param, $offset - 1, '&lt;&lt;') . ' ';
575         }
576
577         while ($page <= $max_page) {
578                 if ($page == $offset) {
579                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
580                 } else {
581                         $retHTML .= $del . page_link($param, $page, $page + 1);
582                 }
583
584                 if ($page < $offset - $pager_pages && $page != 0) {
585                         $retHTML .= " ... ";
586                         $page = $offset - $pager_pages;
587                         $del = '';
588                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
589                         $retHTML .= " ... ";
590                         $page = $max_page;
591                         $del = '';
592                 } else {
593                         $del = ' | ';
594                         $page++;
595                 }
596         }
597
598         if ($offset < $max_page) {
599                 $retHTML .= ' ' . page_link($param, $offset + 1, '&gt;&gt;');
600         }
601
602         $retHTML .= "</div>";
603
604         return $retHTML;
605 }
606
607 1;