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