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