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