rename BackupPC::SearchLib -> BackupPC::Search
[BackupPC.git] / lib / BackupPC / Search.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 use XML::Writer;
12 use IO::File;
13 use BackupPC::Search::Estraier;
14
15 my $on_page = 100;
16 my $pager_pages = 10;
17
18 my $dsn = $Conf{SearchDSN};
19 my $db_user = $Conf{SearchUser} || '';
20
21 my $hest_node_url = $Conf{HyperEstraierIndex};
22
23 my $dbh;
24
25
26
27 sub get_dbh {
28         $dbh ||= DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1 } );
29         return $dbh;
30 }
31
32 sub getUnits() {
33         my @ret;
34
35         my $dbh = get_dbh();
36         my $sth = $dbh->prepare(qq{
37                 SELECT
38                         shares.id       as id,
39                         hosts.name || ':' || shares.name as share
40                 FROM shares
41                 JOIN hosts on hostid = hosts.id
42                 ORDER BY share
43         } );
44         $sth->execute();
45         push @ret, { 'id' => '', 'share' => '-'};       # dummy any
46
47         while ( my $row = $sth->fetchrow_hashref() ) {
48                 push @ret, $row;
49         }
50         return @ret;
51 }
52
53 sub epoch_to_iso {
54         my $t = shift || return;
55         my $iso = BackupPC::Lib::timeStamp(undef, $t);
56         $iso =~ s/\s/ /g;
57         return $iso;
58 }
59
60 sub dates_from_form($) {
61         my $param = shift || return;
62
63         sub mk_epoch_date($$) {
64                 my ($name,$suffix) = @_;
65
66                 my $yyyy = $param->{ $name . '_year_' . $suffix} || return undef;
67                 my $mm .= $param->{ $name . '_month_' . $suffix} ||
68                         ( $suffix eq 'from' ? 1 : 12);
69                 my $dd .= $param->{ $name . '_day_' . $suffix} ||
70                         ( $suffix eq 'from' ? 1 : 31);
71
72                 $yyyy =~ s/\D//g;
73                 $mm =~ s/\D//g;
74                 $dd =~ s/\D//g;
75
76                 my $h = my $m = my $s = 0;
77                 if ($suffix eq 'to') {
78                         $h = 23;
79                         $m = 59;
80                         $s = 59;
81                 }
82
83                 my $dt = new DateTime(
84                         year => $yyyy,
85                         month => $mm,
86                         day => $dd,
87                         hour => $h,
88                         minute => $m,
89                         second => $s,
90                 );
91                 print STDERR "mk_epoch_date($name,$suffix) [$yyyy-$mm-$dd] = " . $dt->ymd . " " . $dt->hms . "\n";
92                 return $dt->epoch || 'NULL';
93         }
94
95         my @ret = (
96                 mk_epoch_date('search_backup', 'from'),
97                 mk_epoch_date('search_backup', 'to'),
98                 mk_epoch_date('search', 'from'),
99                 mk_epoch_date('search', 'to'),
100         );
101
102         return @ret;
103
104 }
105
106
107 sub getWhere($) {
108         my $param = shift || return;
109
110         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
111
112         my @conditions;
113         push @conditions, qq{ backups.date >= $backup_from } if ($backup_from);
114         push @conditions, qq{ backups.date <= $backup_to } if ($backup_to);
115         push @conditions, qq{ files.date >= $files_from } if ($files_from);
116         push @conditions, qq{ files.date <= $files_to } if ($files_to);
117
118         print STDERR "backup: $backup_from - $backup_to files: $files_from - $files_to cond:" . join(" and ",@conditions);
119
120         push( @conditions, ' files.shareid = ' . $param->{'search_share'} ) if ($param->{'search_share'});
121         push (@conditions, " upper(files.path) LIKE upper('%".$param->{'search_filename'}."%')") if ($param->{'search_filename'});
122
123         if ( $param->{burned} ) {
124                 my $is_what = 'is null';
125                 $is_what = '= 1' if ($param->{burned} eq 'burned');
126                 push @conditions, "archive_burned.part $is_what";
127                 push @conditions, "archive_burned.copy $is_what";
128         }
129
130         return join(" and ", @conditions);
131 }
132
133 my $sort_def = {
134         search => {
135                 default => 'date_a',
136                 sql => {
137                         share_d => 'shares.name DESC',
138                         share_a => 'shares.name ASC',
139                         path_d => 'files.path DESC',
140                         path_a => 'files.path ASC',
141                         num_d => 'files.backupnum DESC',
142                         num_a => 'files.backupnum ASC',
143                         size_d => 'files.size DESC',
144                         size_a => 'files.size ASC',
145                         date_d => 'files.date DESC',
146                         date_a => 'files.date ASC',
147                 },
148         }, burn => {
149                 default => 'date_a',
150                 sql => {
151                         share_d => 'host DESC, share DESC',
152                         share_a => 'host ASC, share ASC',
153                         num_d => 'backupnum DESC',
154                         num_a => 'backupnum ASC',
155                         date_d => 'date DESC',
156                         date_a => 'date ASC',
157                         age_d => 'age DESC',
158                         age_a => 'age ASC',
159                         size_d => 'size DESC',
160                         size_a => 'size ASC',
161                         incsize_d => 'inc_size DESC',
162                         incsize_a => 'inc_size ASC',
163                 }
164         }
165 };
166
167 sub getSort($$$) {
168         my ($part,$type, $sort_order) = @_;
169
170         die "unknown part: $part" unless ($sort_def->{$part});
171         die "unknown type: $type" unless ($sort_def->{$part}->{$type});
172
173         $sort_order ||= $sort_def->{$part}->{'default'};
174
175         if (my $ret = $sort_def->{$part}->{$type}->{$sort_order}) {
176                 return $ret;
177         } else {
178                 # fallback to default sort order
179                 return $sort_def->{$part}->{$type}->{ $sort_def->{$part}->{'default'} };
180         }
181 }
182
183 sub getFiles($) {
184         my ($param) = @_;
185
186         my $offset = $param->{'offset'} || 0;
187         $offset *= $on_page;
188
189         my $dbh = get_dbh();
190
191         my $sql_cols = qq{
192                 files.id                        AS fid,
193                 hosts.name                      AS hname,
194                 shares.name                     AS sname,
195                 files.backupnum                 AS backupnum,
196                 files.path                      AS filepath,
197                 files.date                      AS date,
198                 files.type                      AS type,
199                 files.size                      AS size
200         };
201
202         my $sql_from = qq{
203                 FROM files 
204                         INNER JOIN shares       ON files.shareID=shares.ID
205                         INNER JOIN hosts        ON hosts.ID = shares.hostID
206                         INNER JOIN backups      ON backups.num = files.backupnum and backups.hostID = hosts.ID AND backups.shareID = files.shareID
207         };
208
209         my $sql_where;
210         my $where = getWhere($param);
211         $sql_where = " WHERE ". $where if ($where);
212
213         # do we have to add tables for burned media?
214         if ( $param->{burned} ) {
215                 $sql_from .= qq{
216                         LEFT OUTER JOIN archive_backup on archive_backup.backup_id = backups.id
217                         LEFT OUTER JOIN archive_burned on archive_burned.archive_id = archive_backup.archive_id
218                 };
219         }
220
221         my $order = getSort('search', 'sql', $param->{'sort'});
222
223         my $sql_order = qq{
224                 ORDER BY $order
225                 LIMIT $on_page
226                 OFFSET ?
227         };
228
229         my $sql_count = qq{ select count(files.id) $sql_from $sql_where };
230         my $sql_results = qq{ select $sql_cols $sql_from $sql_where $sql_order };
231
232         my $sth = $dbh->prepare($sql_count);
233         $sth->execute();
234         my ($results) = $sth->fetchrow_array();
235
236         $sth = $dbh->prepare($sql_results);
237         $sth->execute( $offset );
238
239         if ($sth->rows != $results) {
240                 my $bug = "$0 BUG: [[ $sql_count ]] = $results while [[ $sql_results ]] = " . $sth->rows;
241                 $bug =~ s/\s+/ /gs;
242                 print STDERR "$bug\n";
243         }
244
245         my @ret;
246       
247         while (my $row = $sth->fetchrow_hashref()) {
248                 push @ret, $row;
249         }
250      
251         $sth->finish();
252         return ($results, \@ret);
253 }
254
255 sub getFilesHyperEstraier($) {
256         my ($param) = @_;
257
258         my $offset = $param->{'offset'} || 0;
259         $offset *= $on_page;
260
261         my $q = $param->{'search_filename'};
262         my $shareid = $param->{'search_share'};
263         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
264
265         return BackupPC::Search::Estraier->new( $hest_node_url )->search(
266                 $offset, $on_page, $param->{sort},
267                 $q, $shareid, $backup_from, $backup_to, $files_from, $files_to
268         );
269                 
270 }
271
272 sub getGzipName($$$)
273 {
274         my ($host, $share, $backupnum) = @_;
275         my $ret = $Conf{GzipSchema};
276         
277         $share =~ s/\//_/g;
278         $ret =~ s/\\h/$host/ge;
279         $ret =~ s/\\s/$share/ge;
280         $ret =~ s/\\n/$backupnum/ge;
281
282         $ret =~ s/__+/_/g;
283
284         return $ret;
285         
286 }
287
288 sub get_tgz_size_by_name($) {
289         my $name = shift;
290
291         my $tgz = $Conf{InstallDir}.'/'.$Conf{GzipTempDir}.'/'.$name;
292         my $size = -1;
293
294         my $Dir = $Conf{InstallDir}."/data/log";
295         $|=1;
296         if (-f "${tgz}.tar.gz") {
297                 $size = (stat("${tgz}.tar.gz"))[7];
298         } elsif (-d $tgz) {
299                 opendir(my $dir, $tgz) || die "can't opendir $tgz: $!";
300                 my @parts = grep { !/^\./ && !/md5/ && -f "$tgz/$_" } readdir($dir);
301                 $size = 0;
302                 foreach my $part (@parts) {
303                         my $currSize =  (stat("$tgz/$part"))[7]; 
304                         $size += (stat("$tgz/$part"))[7] || die "can't stat $tgz/$part: $!";
305                 }
306
307                 closedir $dir;
308         } else {
309                 return -1;
310         }
311
312         return $size;
313 }
314
315 sub getGzipSizeFromBackupID($) {
316         my ($backupID) = @_;
317         my $dbh = get_dbh();
318         my $sql = q{
319                                 SELECT hosts.name  as host,
320                                            shares.name as share,
321                                            backups.num as backupnum
322                                 FROM hosts, backups, shares
323                                 WHERE shares.id=backups.shareid AND
324                                           hosts.id =backups.hostid AND
325                                           backups.id = ?
326         };
327         my $sth = $dbh->prepare($sql);
328         $sth->execute($backupID);
329         my $row = $sth->fetchrow_hashref();
330
331         return get_tgz_size_by_name(
332                 getGzipName($row->{'host'}, $row->{share}, $row->{backupnum})
333         );
334 }
335
336 sub getGzipSize($$)
337 {
338         my ($hostID, $backupNum) = @_;
339         my $sql;
340         my $dbh = get_dbh();
341         
342         $sql = q{ 
343                                 SELECT hosts.name  as host,
344                                            shares.name as share,
345                                            backups.num as backupnum
346                                 FROM hosts, backups, shares
347                                 WHERE shares.id=backups.shareid AND
348                                           hosts.id =backups.hostid AND
349                                           hosts.id=? AND
350                                           backups.num=?
351                         };
352         my $sth = $dbh->prepare($sql);
353         $sth->execute($hostID, $backupNum);
354
355         my $row = $sth->fetchrow_hashref();
356
357         return get_tgz_size_by_name(
358                 getGzipName($row->{'host'}, $row->{share}, $row->{'backupnum'})
359         );
360 }
361
362 sub getVolumes($) {
363         my $id = shift;
364
365         my $max_archive_size = $Conf{MaxArchiveSize} || die "no MaxArchiveSize";
366
367         my $sth = $dbh->prepare(qq{
368                 select
369                         size
370                 from backup_parts
371                 where backup_id = ?
372                 order by part_nr asc
373         });
374
375         $sth->execute($id);
376
377         my $cumulative_size = 0;
378         my $volumes = 1;
379
380         while(my ($size) = $sth->fetchrow_array) {
381                 if ($cumulative_size + $size > $max_archive_size) {
382                         $volumes++;
383                         $cumulative_size = $size;
384                 } else {
385                         $cumulative_size += $size;
386                 }
387         }
388
389         return ($volumes,$cumulative_size);
390 }
391
392 sub getBackupsNotBurned($) {
393
394         my $param = shift;
395         my $dbh = get_dbh();
396
397         my $order = getSort('burn', 'sql', $param->{'sort'});
398
399 print STDERR "## sort=". ($param->{'sort'} || 'no sort param') . " burn sql order: $order\n";
400
401         my $sql = qq{
402                 SELECT 
403                         backups.hostID AS hostID,
404                         hosts.name AS host,
405                         shares.name AS share,
406                         backups.num AS backupnum,
407                         backups.type AS type,
408                         backups.date AS date,
409                         date_part('epoch',now()) - backups.date as age,
410                         backups.size AS size,
411                         backups.id AS id,
412                         backups.inc_size AS inc_size,
413                         backups.parts AS parts
414                 FROM backups 
415                 INNER JOIN shares       ON backups.shareID=shares.ID
416                 INNER JOIN hosts        ON backups.hostID = hosts.ID
417                 LEFT OUTER JOIN archive_backup ON archive_backup.backup_id = backups.id 
418                 WHERE backups.inc_size > 0 AND backups.size > 0 AND backups.inc_deleted is false AND archive_backup.backup_id IS NULL AND backups.parts > 0
419                 GROUP BY
420                         backups.hostID,
421                         hosts.name,
422                         shares.name,
423                         backups.num,
424                         backups.shareid,
425                         backups.id,
426                         backups.type,
427                         backups.date,
428                         backups.size,
429                         backups.inc_size,
430                         backups.parts
431                 ORDER BY $order
432         };
433         my $sth = $dbh->prepare( $sql );
434         my @ret;
435         $sth->execute();
436
437         while ( my $row = $sth->fetchrow_hashref() ) {
438                 $row->{'age'} = sprintf("%0.1f", ( $row->{'age'} / 86400 ) );
439                 #$row->{'age'} = sprintf("%0.1f", ( (time() - $row->{'date'}) / 86400 ) );
440
441                 my $max_archive_size = $Conf{MaxArchiveSize} || die "no MaxArchiveSize";
442                 if ($row->{size} > $max_archive_size) {
443                         ($row->{volumes}, $row->{inc_size_calc}) = getVolumes($row->{id});
444                 }
445
446                 $row->{size} = sprintf("%0.2f", $row->{size} / 1024 / 1024);
447
448                 # do some cluster calculation (approximate)
449                 $row->{inc_size} = int(( ($row->{inc_size} + 1023 ) / 2 )  * 2);
450                 $row->{inc_size_calc} ||= $row->{inc_size};
451                 push @ret, $row;
452         }
453       
454         return @ret;
455 }
456
457 sub displayBackupsGrid($) {
458
459         my $param = shift;
460
461         my $max_archive_size = $Conf{MaxArchiveSize} || die "no MaxArchiveSize";
462         my $max_archive_file_size = $Conf{MaxArchiveFileSize}  || die "no MaxFileInSize";
463
464         my $retHTML .= q{
465                 <form id="forma" method="POST" action="}.$MyURL.q{?action=burn">
466         };
467
468         $retHTML .= <<'EOF3';
469 <style type="text/css">
470 <!--
471 DIV#fixedBox {
472         position: absolute;
473         top: 50em;
474         left: -24%;
475         padding: 0.5em;
476         width: 20%;
477         background-color: #E0F0E0;
478         border: 1px solid #00C000;
479 }
480
481 DIV#fixedBox, DIV#fixedBox INPUT, DIV#fixedBox TEXTAREA {
482         font-size: 10pt;
483 }
484
485 FORM>DIV#fixedBox {
486         position: fixed !important;
487         left: 0.5em !important;
488         top: auto !important;
489         bottom: 1em !important;
490         width: 15% !important;
491 }
492
493 DIV#fixedBox INPUT[type=text], DIV#fixedBox TEXTAREA {
494         border: 1px solid #00C000;
495 }
496
497 DIV#fixedBox #note {
498         display: block;
499         width: 100%;
500 }
501
502 DIV#fixedBox #submitBurner {
503         display: block;
504         width: 100%;
505         margin-top: 0.5em;
506         cursor: pointer;
507 }
508
509 * HTML {
510         overflow-y: hidden;
511 }
512
513 * HTML BODY {
514         overflow-y: auto;
515         height: 100%;
516         font-size: 100%;
517 }
518
519 * HTML DIV#fixedBox {
520         position: absolute;
521 }
522
523 #mContainer, #gradient, #mask, #progressIndicator {
524         display: block;
525         width: 100%;
526         font-size: 10pt;
527         font-weight: bold;
528         text-align: center;
529         vertical-align: middle;
530         padding: 1px;
531 }
532
533 #gradient, #mask, #progressIndicator {
534         left: 0;
535         border-width: 1px;
536         border-style: solid;
537         border-color: #000000;
538         color: #404040;
539         margin: 0.4em;
540         position: absolute;
541         margin-left: -1px;
542         margin-top: -1px;
543         margin-bottom: -1px;
544         overflow: hidden;
545 }
546
547 #mContainer {
548         display: block;
549         position: relative;
550         padding: 0px;
551         margin-top: 0.4em;
552         margin-bottom: 0.5em;
553 }
554
555 #gradient {
556         z-index: 1;
557         background-color: #FFFF00;
558 }
559
560 #mask {
561         z-index: 2;
562         background-color: #FFFFFF;
563 }
564
565 #progressIndicator {
566         z-index: 3;
567         background-color: transparent;
568 }
569
570 #volumes {
571         padding: 0.4em;
572         display: none;
573         width: 100%;
574         font-size: 80%;
575         color: #ff0000;
576         text-align: center;
577 }
578 -->
579 </style>
580 <script type="text/javascript">
581 <!--
582
583 var debug_div;
584 EOF3
585
586         # take maximum archive size from configuration
587         $retHTML .= qq{
588 var media_size = $max_archive_size ;
589 var max_file_size = $max_archive_file_size;
590
591 };
592
593         $retHTML .= <<'EOF3';
594
595 function debug(msg) {
596         return; // Disable debugging
597
598         if (! debug_div) debug_div = document.getElementById('debug');
599
600         // this will create debug div if it doesn't exist.
601         if (! debug_div) {
602                 debug_div = document.createElement('div');
603                 if (document.body) document.body.appendChild(debug_div);
604                 else debug_div = null;
605         }
606         if (debug_div) {
607                 debug_div.appendChild(document.createTextNode(msg));
608                 debug_div.appendChild(document.createElement("br"));
609         }
610 }
611
612
613 var element_id_cache = Array();
614
615 function element_id(name,element) {
616         if (! element_id_cache[name]) {
617                 element_id_cache[name] = self.document.getElementById(name);
618         }
619         return element_id_cache[name];
620 }
621
622 function checkAll(location) {
623         var f = element_id('forma') || null;
624         if (!f) return false;
625
626         var len = f.elements.length;
627         var check_all = element_id('allFiles');
628         var suma = check_all.checked ? (parseInt(f.elements['totalsize'].value) || 0) : 0;
629
630         for (var i = 0; i < len; i++) {
631                 var e = f.elements[i];
632                 if (e.name != 'all' && e.name.substr(0, 3) == 'fcb') {
633                         if (check_all.checked) {
634                                 if (e.checked) continue;
635                                 var el = element_id("fss" + e.name.substr(3));
636                                 var size = parseInt(el.value) || 0;
637                                 debug('suma: '+suma+' size: '+size);
638                                 if ((suma + size) < media_size) {
639                                         suma += size;
640                                         e.checked = true;
641                                 } else {
642                                         break;
643                                 }
644                         } else {
645                                 e.checked = false;
646                         }
647                 }
648         }
649         update_sum(suma);
650 }
651
652 function update_sum(suma, suma_disp) {
653         if (! suma_disp) suma_disp = suma;
654         suma_disp = Math.floor(suma_disp / 1024);
655         element_id('forma').elements['totalsize_kb'].value = suma_disp;
656         element_id('forma').elements['totalsize'].value = suma;
657         pbar_set(suma, media_size);
658         debug('total size: ' + suma);
659 }
660
661 function update_size(name, checked, suma) {
662         var size = parseInt( element_id("fss" + name).value);
663
664         if (checked) {
665                 suma += size;
666         } else {
667                 suma -= size;
668         }
669
670         var volumes = parseInt( element_id("prt" + name).value);
671         debug('update_size('+name+','+checked+') suma: '+suma+' volumes: '+volumes);
672         if (volumes > 1) {
673                 if (checked) {
674                         element_id("volumes").innerHTML = "This will take "+volumes+" mediums!";
675                         element_id("volumes").style.display = 'block';
676                         suma = size;
677                         update_sum(suma);
678                 } else {
679                         suma -= size;
680                         element_id("volumes").style.display = 'none';
681                 }
682         }
683
684         return suma;
685 }
686
687 function sumiraj(e) {
688         var suma = parseInt(element_id('forma').elements['totalsize'].value) || 0;
689         var len = element_id('forma').elements.length;
690         if (e) {
691                 suma = update_size(e.name.substr(3), e.checked, suma);
692                 if (suma < 0) suma = 0;
693         } else {
694                 suma = 0;
695                 for (var i = 0; i < len; i++) {
696                         var fel = element_id('forma').elements[i];
697                         if (fel.name != 'all' && fel.checked && fel.name.substr(0,3) == 'fcb') {
698                                 suma = update_size(fel.name.substr(3), fel.checked, suma);
699                         } 
700                 }
701         }
702         update_sum(suma);
703         return suma;
704 }
705
706 /* progress bar */
707
708 var _pbar_width = null;
709 var _pbar_warn = 10;    // change color in last 10%
710
711 function pbar_reset() {
712         element_id("mask").style.left = "0px";
713         _pbar_width = element_id("mContainer").offsetWidth - 2;
714         element_id("mask").style.width = _pbar_width + "px";
715         element_id("mask").style.display = "block";
716         element_id("progressIndicator").style.zIndex  = 10;
717         element_id("progressIndicator").innerHTML = "0";
718 }
719
720 function dec2hex(d) {
721         var hch = '0123456789ABCDEF';
722         var a = d % 16;
723         var q = (d - a) / 16;
724         return hch.charAt(q) + hch.charAt(a);
725 }
726
727 function pbar_set(amount, max) {
728         debug('pbar_set('+amount+', '+max+')');
729
730         if (_pbar_width == null) {
731                 var _mc = element_id("mContainer");
732                 if (_pbar_width == null) _pbar_width = parseInt(_mc.offsetWidth ? (_mc.offsetWidth - 2) : 0) || null;
733                 if (_pbar_width == null) _pbar_width = parseInt(_mc.clientWidth ? (_mc.clientWidth + 2) : 0) || null;
734                 if (_pbar_width == null) _pbar_width = 0;
735         }
736
737         var pcnt = Math.floor(amount * 100 / max);
738         var p90 = 100 - _pbar_warn;
739         var pcol = pcnt - p90;
740         if (Math.round(pcnt) <= 100) {
741                 if (pcol < 0) pcol = 0;
742                 var e = element_id("submitBurner");
743                 debug('enable_button');
744                 e.disabled = false;
745                 var a = e.getAttributeNode('disabled') || null;
746                 if (a) e.removeAttributeNode(a);
747         } else {
748                 debug('disable button');
749                 pcol = _pbar_warn;
750                 var e = element_id("submitBurner");
751                 if (!e.disabled) e.disabled = true;
752         }
753         var col_g = Math.floor((_pbar_warn - pcol) * 255 / _pbar_warn);
754         var col = '#FF' + dec2hex(col_g) + '00';
755
756         //debug('pcol: '+pcol+' g:'+col_g+' _pbar_warn:'+ _pbar_warn + ' color: '+col);
757         element_id("gradient").style.backgroundColor = col;
758
759         element_id("progressIndicator").innerHTML = pcnt + '%';
760         //element_id("progressIndicator").innerHTML = amount;
761
762         element_id("mask").style.clip = 'rect(' + Array(
763                 '0px',
764                 element_id("mask").offsetWidth + 'px',
765                 element_id("mask").offsetHeight + 'px',
766                 Math.round(_pbar_width * amount / max) + 'px'
767         ).join(' ') + ')';
768 }
769
770 if (!self.body) self.body = new Object();
771 self.onload = self.document.onload = self.body.onload = function() {
772         //pbar_reset();
773         sumiraj();
774 };
775
776 // -->
777 </script>
778 <div id="fixedBox">
779
780 <input type="hidden" name="totalsize"/>
781 Size: <input type="text" name="totalsize_kb" size="7" readonly="readonly" style="text-align:right;" value="0" /> kB
782
783 <div id="mContainer">
784         <div id="gradient">&nbsp;</div>
785         <div id="mask">&nbsp;</div>
786         <div id="progressIndicator">0%</div>
787 </div>
788 <br/>
789
790 <div id="volumes">&nbsp;</div>
791
792 Note:
793 <textarea name="note" cols="10" rows="5" id="note"></textarea>
794
795 <input type="submit" id="submitBurner" value="Burn selected" name="submitBurner" />
796
797 </div>
798 <!--
799 <div id="debug" style="float: right; width: 10em; border: 1px #ff0000 solid; background-color: #ffe0e0; -moz-opacity: 0.7;">
800 no debug output yet
801 </div>
802 -->
803 EOF3
804         $retHTML .= q{
805                         <input type="hidden" value="burn" name="action">
806                         <input type="hidden" value="results" name="search_results">
807                         <table style="fview" border="0" cellspacing="0" cellpadding="2">
808                         <tr class="tableheader">
809                         <td class="tableheader">
810                                 <input type="checkbox" name="allFiles" id="allFiles" onClick="checkAll('allFiles');">
811                         </td>
812         } .
813                 sort_header($param, 'Share', 'share', 'center') .
814                 sort_header($param, '#', 'num', 'center') .
815         qq{
816                         <td align="center">Type</td>
817         } .
818                 sort_header($param, 'Date', 'date', 'center') .
819                 sort_header($param, 'Age/days', 'age', 'center') .
820                 sort_header($param, 'Size/Mb', 'size', 'center') .
821                 sort_header($param, 'gzip size/Kb', 'incsize', 'center') .
822         qq{
823                         <td align="center">medias</td></tr>
824         };
825
826         my @color = (' bgcolor="#e0e0e0"', '');
827
828         my $i = 0;
829         my $host = '';
830
831         foreach my $backup ( getBackupsNotBurned($param) ) {
832
833                 if ($host ne $backup->{'host'}) {
834                         $i++;
835                         $host = $backup->{'host'};
836                 }
837                 my $ftype = "";
838
839                 my $checkbox_key = $backup->{'hostid'}. '_' .$backup->{'backupnum'} . '_' . $backup->{'id'};
840
841                 $retHTML .=
842                         '<tr' . $color[$i %2 ] . '>
843                         <td class="fview">';
844
845                 if (($backup->{'inc_size'} || 0) > 0) {
846                         $retHTML .= '
847                         <input type="checkbox" name="fcb' . $checkbox_key . '" value="' . $checkbox_key . '" onClick="sumiraj(this);">';
848                 }
849
850                 my $img_url = $Conf{CgiImageDirURL};
851
852                 $retHTML .=
853                         '</td>' .
854                         '<td align="right">' . $backup->{'host'} . ':' . $backup->{'share'} . '</td>' .
855                         '<td align="center">' . $backup->{'backupnum'} . '</td>' .
856                         '<td align="center">' . $backup->{'type'} . '</td>' .
857                         '<td align="center">' . epoch_to_iso( $backup->{'date'} ) . '</td>' .
858                         '<td align="center">' . $backup->{'age'} . '</td>' .
859                         '<td align="right">' . $backup->{'size'} . '</td>' .
860                         '<td align="right">' . sprintf("%0.1f", $backup->{'inc_size'} / 1024 ) .
861                         '<input type="hidden" id="fss'.$checkbox_key .'" value="'. $backup->{'inc_size_calc'} .'"></td>' .
862                         '<input type="hidden" id="prt'.$checkbox_key .'" value="'. $backup->{'volumes'} .'"></td>' .
863                         '<td align="left">' . ( qq{<img src="$img_url/icon-cd.gif" alt="media">} x $backup->{volumes} ) . '</td>' .
864
865                         "</tr>\n";
866         }
867
868         $retHTML .= "</table>";
869         $retHTML .= "</form>";
870       
871         return $retHTML;
872 }      
873
874 sub displayGrid($) {
875         my ($param) = @_;
876
877         my $offset = $param->{'offset'};
878         my $hilite = $param->{'search_filename'};
879
880         my $retHTML = "";
881  
882         my $start_t = time();
883
884         my ($results, $files);
885         if ($param->{'use_hest'} && length($hilite) > 0) {
886                 ($results, $files) = getFilesHyperEstraier($param);
887         } else {
888                 ($results, $files) = getFiles($param);
889         }
890
891         my $dur_t = time() - $start_t;
892         my $dur = sprintf("%0.4fs", $dur_t);
893
894         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
895
896         if ($results <= 0) {
897                 $retHTML .= qq{
898                         <p style="color: red;">No results found...</p>
899                 };
900                 return $retHTML;
901         } else {
902                 # DEBUG
903                 #use Data::Dumper;
904                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
905         }
906
907
908         $retHTML .= qq{
909         <div>
910         Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
911         </div>
912         <table style="fview" width="100%" border="0" cellpadding="2" cellspacing="0">
913                 <tr class="fviewheader"> 
914                 <td></td>
915         };
916
917         sub sort_header($$$$) {
918                 my ($param, $display, $name, $align) = @_;
919
920                 my ($sort_what, $sort_direction) = split(/_/,$param->{'sort'},2);
921
922                 my $old_sort = $param->{'sort'};
923
924                 my $html = qq{<td align="$align"};
925                 my $arrow = '';
926
927                 if (lc($sort_what) eq lc($name)) {
928                         my $direction = lc($sort_direction);
929
930                         # swap direction or fallback to default
931                         $direction =~ tr/ad/da/;
932                         $direction = 'a' unless ($direction =~ /[ad]/);
933
934                         $param->{'sort'} = $name . '_' . $direction;
935                         $html .= ' style="border: 1px solid #808080;"';
936                 
937                         # add unicode arrow for direction
938                         $arrow .= '&nbsp;';
939                         $arrow .= $direction eq 'a'  ?  '&#9650;'
940                                 : $direction eq 'd'  ?  '&#9660;'
941                                 :                       ''
942                                 ;
943
944                 } else {
945                         $param->{'sort'} = $name . '_a';
946                 }
947
948                 $html .= '><a href="' . page_uri($param) . '">' . $display . '</a>' . $arrow . '</td>';
949                 $param->{'sort'} = $old_sort;
950
951                 return $html;
952         }
953
954         $retHTML .=
955                 sort_header($param, 'Share', 'share', 'center') .
956                 sort_header($param, 'Type and Name', 'path', 'center') .
957                 sort_header($param, '#', 'num', 'center') .
958                 sort_header($param, 'Size', 'size', 'center') .
959                 sort_header($param, 'Date', 'date', 'center');
960
961         $retHTML .= qq{
962                 <td align="center">Media</td>
963                 </tr>
964         };
965
966         my $file;
967
968         sub hilite_html($$) {
969                 my ($html, $search) = @_;
970                 $html =~ s#($search)#<b>$1</b>#gis;
971                 return $html;
972         }
973
974         sub restore_link($$$$$$) {
975                 my $type = shift;
976                 my $action = 'RestoreFile';
977                 $action = 'browse' if (lc($type) eq 'dir');
978                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
979         }
980
981         my $sth_archived;
982         my %archived_cache;
983
984         sub check_archived($$$) {
985                 my ($host, $share, $num) = @_;
986
987                 if (my $html = $archived_cache{"$host $share $num"}) {
988                         return $html;
989                 }
990
991                 $sth_archived ||= $dbh->prepare(qq{
992                         select
993                                 dvd_nr, note,
994                                 count(archive_burned.copy) as copies
995                         from archive
996                         inner join archive_burned on archive_burned.archive_id = archive.id
997                         inner join archive_backup on archive.id = archive_backup.archive_id
998                         inner join backups on backups.id = archive_backup.backup_id
999                         inner join hosts on hosts.id = backups.hostid
1000                         inner join shares on shares.id = backups.shareid
1001                         where hosts.name = ? and shares.name = ? and backups.num = ?
1002                         group by dvd_nr, note
1003                 });
1004
1005                 my @mediums;
1006
1007                 $sth_archived->execute($host, $share, $num);
1008                 while (my $row = $sth_archived->fetchrow_hashref()) {
1009                         push @mediums, '<abbr title="' .
1010                                 $row->{'note'} .
1011                                 ' [' . $row->{'copies'} . ']' .
1012                                 '">' .$row->{'dvd_nr'} .
1013                                 '</abbr>';
1014                 }
1015
1016                 my $html = join(", ",@mediums);
1017                 $archived_cache{"$host $share $num"} = $html;
1018                 return $html;
1019         }
1020
1021         my $i = $offset * $on_page;
1022
1023         foreach $file (@{ $files }) {
1024                 $i++;
1025
1026                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
1027                 $retHTML .= qq{<tr class="fviewborder">};
1028
1029                 $retHTML .= qq{<td class="fviewborder">$i</td>};
1030
1031                 $retHTML .=
1032                         qq{<td class="fviewborder" align="right">} . $file->{'hname'} . ':' . $file->{'sname'} . qq{</td>} .
1033                         qq{<td class="fviewborder"><img src="$Conf{CgiImageDirURL}/icon-$typeStr.png" alt="$typeStr" align="middle">&nbsp;} . hilite_html( $file->{'filepath'}, $hilite ) . qq{</td>} .
1034                         qq{<td class="fviewborder" align="center">} . restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupnum'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'filepath'} )}, $file->{'backupnum'} ) . qq{</td>} .
1035                         qq{<td class="fviewborder" align="right">} . $file->{'size'} . qq{</td>} .
1036                         qq{<td class="fviewborder">} . epoch_to_iso( $file->{'date'} ) . qq{</td>} .
1037                         qq{<td class="fviewborder">} . check_archived( $file->{'hname'}, $file->{'sname'}, $file->{'backupnum'} ) . qq{</td>};
1038
1039                 $retHTML .= "</tr>";
1040         }
1041         $retHTML .= "</table>";
1042
1043         # all variables which has to be transfered
1044         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/) {
1045                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
1046         }
1047
1048         my $del = '';
1049         my $max_page = int( $results / $on_page );
1050         my $page = 0;
1051
1052         sub page_uri($) {
1053                 my $param = shift || die "no param?";
1054
1055                 my $uri = $MyURL;
1056                 my $del = '?';
1057                 foreach my $k (keys %{ $param }) {
1058                         if ($param->{$k}) {
1059                                 $uri .= $del . $k . '=' . ${EscURI( $param->{$k} )};
1060                                 $del = '&';
1061                         }
1062                 }
1063                 return $uri;
1064         }
1065
1066         sub page_link($$$) {
1067                 my ($param,$page,$display) = @_;
1068
1069                 $param->{'offset'} = $page if (defined($page));
1070
1071                 my $html = '<a href = "' . page_uri($param) . '">' . $display . '</a>';
1072         }
1073
1074         $retHTML .= '<div style="text-align: center;">';
1075
1076         if ($offset > 0) {
1077                 $retHTML .= page_link($param, $offset - 1, '&lt;&lt;') . ' ';
1078         }
1079
1080         while ($page <= $max_page) {
1081                 if ($page == $offset) {
1082                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
1083                 } else {
1084                         $retHTML .= $del . page_link($param, $page, $page + 1);
1085                 }
1086
1087                 if ($page < $offset - $pager_pages && $page != 0) {
1088                         $retHTML .= " ... ";
1089                         $page = $offset - $pager_pages;
1090                         $del = '';
1091                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
1092                         $retHTML .= " ... ";
1093                         $page = $max_page;
1094                         $del = '';
1095                 } else {
1096                         $del = ' | ';
1097                         $page++;
1098                 }
1099         }
1100
1101         if ($offset < $max_page) {
1102                 $retHTML .= ' ' . page_link($param, $offset + 1, '&gt;&gt;');
1103         }
1104
1105         $retHTML .= "</div>";
1106
1107         return $retHTML;
1108 }
1109
1110 1;