888a8530cdf24ba1ab6e693ff45adb2346c6429b
[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                 $row->{size} = sprintf("%0.2f", $row->{size} / 1024 / 1024);
462
463                 # do some cluster calculation (approximate)
464                 $row->{inc_size} = int(( ($row->{inc_size} + 1023 ) / 2 )  * 2);
465                 $row->{inc_size_calc} ||= $row->{inc_size};
466                 push @ret, $row;
467         }
468       
469         return @ret;
470 }
471
472 sub displayBackupsGrid($) {
473
474         my $param = shift;
475
476         my $max_archive_size = $Conf{ArchiveMediaSize} || die "no ArchiveMediaSize";
477         my $max_archive_file_size = $Conf{ArchiveChunkSize}  || die "no MaxFileInSize";
478
479         my $retHTML .= q{
480                 <form id="forma" method="POST" action="}.$MyURL.q{?action=burn">
481         };
482
483         $retHTML .= <<'EOF3';
484 <style type="text/css">
485 <!--
486 DIV#fixedBox {
487         position: absolute;
488         top: 50em;
489         left: -24%;
490         padding: 0.5em;
491         width: 20%;
492         background-color: #E0F0E0;
493         border: 1px solid #00C000;
494 }
495
496 DIV#fixedBox, DIV#fixedBox INPUT, DIV#fixedBox TEXTAREA {
497         font-size: 10pt;
498 }
499
500 FORM>DIV#fixedBox {
501         position: fixed !important;
502         left: 0.5em !important;
503         top: auto !important;
504         bottom: 1em !important;
505         width: 15% !important;
506 }
507
508 DIV#fixedBox INPUT[type=text], DIV#fixedBox TEXTAREA {
509         border: 1px solid #00C000;
510 }
511
512 DIV#fixedBox #note {
513         display: block;
514         width: 100%;
515 }
516
517 DIV#fixedBox #submitBurner {
518         display: block;
519         width: 100%;
520         margin-top: 0.5em;
521         cursor: pointer;
522 }
523
524 * HTML {
525         overflow-y: hidden;
526 }
527
528 * HTML BODY {
529         overflow-y: auto;
530         height: 100%;
531         font-size: 100%;
532 }
533
534 * HTML DIV#fixedBox {
535         position: absolute;
536 }
537
538 #mContainer, #gradient, #mask, #progressIndicator {
539         display: block;
540         width: 100%;
541         font-size: 10pt;
542         font-weight: bold;
543         text-align: center;
544         vertical-align: middle;
545         padding: 1px;
546 }
547
548 #gradient, #mask, #progressIndicator {
549         left: 0;
550         border-width: 1px;
551         border-style: solid;
552         border-color: #000000;
553         color: #404040;
554         margin: 0.4em;
555         position: absolute;
556         margin-left: -1px;
557         margin-top: -1px;
558         margin-bottom: -1px;
559         overflow: hidden;
560 }
561
562 #mContainer {
563         display: block;
564         position: relative;
565         padding: 0px;
566         margin-top: 0.4em;
567         margin-bottom: 0.5em;
568 }
569
570 #gradient {
571         z-index: 1;
572         background-color: #FFFF00;
573 }
574
575 #mask {
576         z-index: 2;
577         background-color: #FFFFFF;
578 }
579
580 #progressIndicator {
581         z-index: 3;
582         background-color: transparent;
583 }
584
585 #volumes {
586         padding: 0.4em;
587         display: none;
588         width: 100%;
589         font-size: 80%;
590         color: #ff0000;
591         text-align: center;
592 }
593 -->
594 </style>
595 <script type="text/javascript">
596 <!--
597
598 var debug_div;
599 EOF3
600
601         # take maximum archive size from configuration
602         $retHTML .= qq{
603 var media_size = $max_archive_size ;
604 var max_file_size = $max_archive_file_size;
605
606 };
607
608         $retHTML .= <<'EOF3';
609
610 function debug(msg) {
611         return; // Disable debugging
612
613         if (! debug_div) debug_div = document.getElementById('debug');
614
615         // this will create debug div if it doesn't exist.
616         if (! debug_div) {
617                 debug_div = document.createElement('div');
618                 if (document.body) document.body.appendChild(debug_div);
619                 else debug_div = null;
620         }
621         if (debug_div) {
622                 debug_div.appendChild(document.createTextNode(msg));
623                 debug_div.appendChild(document.createElement("br"));
624         }
625 }
626
627
628 var element_id_cache = Array();
629
630 function element_id(name,element) {
631         if (! element_id_cache[name]) {
632                 element_id_cache[name] = self.document.getElementById(name);
633         }
634         return element_id_cache[name];
635 }
636
637 function checkAll(location) {
638         var f = element_id('forma') || null;
639         if (!f) return false;
640
641         var len = f.elements.length;
642         var check_all = element_id('allFiles');
643         var suma = check_all.checked ? (parseInt(f.elements['totalsize'].value) || 0) : 0;
644
645         for (var i = 0; i < len; i++) {
646                 var e = f.elements[i];
647                 if (e.name != 'all' && e.name.substr(0, 3) == 'fcb') {
648                         if (check_all.checked) {
649                                 if (e.checked) continue;
650                                 var el = element_id("fss" + e.name.substr(3));
651                                 var size = parseInt(el.value) || 0;
652                                 debug('suma: '+suma+' size: '+size);
653                                 if ((suma + size) < media_size) {
654                                         suma += size;
655                                         e.checked = true;
656                                 } else {
657                                         break;
658                                 }
659                         } else {
660                                 e.checked = false;
661                         }
662                 }
663         }
664         update_sum(suma);
665 }
666
667 function update_sum(suma, suma_disp) {
668         if (! suma_disp) suma_disp = suma;
669         suma_disp = Math.floor(suma_disp / 1024);
670         element_id('forma').elements['totalsize_kb'].value = suma_disp;
671         element_id('forma').elements['totalsize'].value = suma;
672         pbar_set(suma, media_size);
673         debug('total size: ' + suma);
674 }
675
676 function update_size(name, checked, suma) {
677         var size = parseInt( element_id("fss" + name).value);
678
679         if (checked) {
680                 suma += size;
681         } else {
682                 suma -= size;
683         }
684
685         var volumes = parseInt( element_id("prt" + name).value);
686         debug('update_size('+name+','+checked+') suma: '+suma+' volumes: '+volumes);
687         if (volumes > 1) {
688                 if (checked) {
689                         element_id("volumes").innerHTML = "This will take "+volumes+" mediums!";
690                         element_id("volumes").style.display = 'block';
691                         suma = size;
692                         update_sum(suma);
693                 } else {
694                         suma -= size;
695                         element_id("volumes").style.display = 'none';
696                 }
697         }
698
699         return suma;
700 }
701
702 function sumiraj(e) {
703         var suma = parseInt(element_id('forma').elements['totalsize'].value) || 0;
704         var len = element_id('forma').elements.length;
705         if (e) {
706                 suma = update_size(e.name.substr(3), e.checked, suma);
707                 if (suma < 0) suma = 0;
708         } else {
709                 suma = 0;
710                 for (var i = 0; i < len; i++) {
711                         var fel = element_id('forma').elements[i];
712                         if (fel.name != 'all' && fel.checked && fel.name.substr(0,3) == 'fcb') {
713                                 suma = update_size(fel.name.substr(3), fel.checked, suma);
714                         } 
715                 }
716         }
717         update_sum(suma);
718         return suma;
719 }
720
721 /* progress bar */
722
723 var _pbar_width = null;
724 var _pbar_warn = 10;    // change color in last 10%
725
726 function pbar_reset() {
727         element_id("mask").style.left = "0px";
728         _pbar_width = element_id("mContainer").offsetWidth - 2;
729         element_id("mask").style.width = _pbar_width + "px";
730         element_id("mask").style.display = "block";
731         element_id("progressIndicator").style.zIndex  = 10;
732         element_id("progressIndicator").innerHTML = "0";
733 }
734
735 function dec2hex(d) {
736         var hch = '0123456789ABCDEF';
737         var a = d % 16;
738         var q = (d - a) / 16;
739         return hch.charAt(q) + hch.charAt(a);
740 }
741
742 function pbar_set(amount, max) {
743         debug('pbar_set('+amount+', '+max+')');
744
745         if (_pbar_width == null) {
746                 var _mc = element_id("mContainer");
747                 if (_pbar_width == null) _pbar_width = parseInt(_mc.offsetWidth ? (_mc.offsetWidth - 2) : 0) || null;
748                 if (_pbar_width == null) _pbar_width = parseInt(_mc.clientWidth ? (_mc.clientWidth + 2) : 0) || null;
749                 if (_pbar_width == null) _pbar_width = 0;
750         }
751
752         var pcnt = Math.floor(amount * 100 / max);
753         var p90 = 100 - _pbar_warn;
754         var pcol = pcnt - p90;
755         if (Math.round(pcnt) <= 100) {
756                 if (pcol < 0) pcol = 0;
757                 var e = element_id("submitBurner");
758                 debug('enable_button');
759                 e.disabled = false;
760                 var a = e.getAttributeNode('disabled') || null;
761                 if (a) e.removeAttributeNode(a);
762         } else {
763                 debug('disable button');
764                 pcol = _pbar_warn;
765                 var e = element_id("submitBurner");
766                 if (!e.disabled) e.disabled = true;
767         }
768         var col_g = Math.floor((_pbar_warn - pcol) * 255 / _pbar_warn);
769         var col = '#FF' + dec2hex(col_g) + '00';
770
771         //debug('pcol: '+pcol+' g:'+col_g+' _pbar_warn:'+ _pbar_warn + ' color: '+col);
772         element_id("gradient").style.backgroundColor = col;
773
774         element_id("progressIndicator").innerHTML = pcnt + '%';
775         //element_id("progressIndicator").innerHTML = amount;
776
777         element_id("mask").style.clip = 'rect(' + Array(
778                 '0px',
779                 element_id("mask").offsetWidth + 'px',
780                 element_id("mask").offsetHeight + 'px',
781                 Math.round(_pbar_width * amount / max) + 'px'
782         ).join(' ') + ')';
783 }
784
785 if (!self.body) self.body = new Object();
786 self.onload = self.document.onload = self.body.onload = function() {
787         //pbar_reset();
788         sumiraj();
789 };
790
791 // -->
792 </script>
793 <div id="fixedBox">
794
795 <input type="hidden" name="totalsize"/>
796 Size: <input type="text" name="totalsize_kb" size="7" readonly="readonly" style="text-align:right;" value="0" /> kB
797
798 <div id="mContainer">
799         <div id="gradient">&nbsp;</div>
800         <div id="mask">&nbsp;</div>
801         <div id="progressIndicator">0%</div>
802 </div>
803 <br/>
804
805 <div id="volumes">&nbsp;</div>
806
807 Note:
808 <textarea name="note" cols="10" rows="5" id="note"></textarea>
809
810 <input type="submit" id="submitBurner" value="Burn selected" name="submitBurner" />
811
812 </div>
813 <!--
814 <div id="debug" style="float: right; width: 10em; border: 1px #ff0000 solid; background-color: #ffe0e0; -moz-opacity: 0.7;">
815 no debug output yet
816 </div>
817 -->
818 EOF3
819         $retHTML .= q{
820                         <input type="hidden" value="burn" name="action">
821                         <input type="hidden" value="results" name="search_results">
822                         <table style="fview" border="0" cellspacing="0" cellpadding="2">
823                         <tr class="tableheader">
824                         <td class="tableheader">
825                                 <input type="checkbox" name="allFiles" id="allFiles" onClick="checkAll('allFiles');">
826                         </td>
827         } .
828                 sort_header($param, 'Share', 'share', 'center') .
829                 sort_header($param, '#', 'num', 'center') .
830         qq{
831                         <td align="center">Type</td>
832         } .
833                 sort_header($param, 'Date', 'date', 'center') .
834                 sort_header($param, 'Age/days', 'age', 'center') .
835                 sort_header($param, 'Size/Mb', 'size', 'center') .
836                 sort_header($param, 'gzip size/Kb', 'incsize', 'center') .
837         qq{
838                         <td align="center">medias</td></tr>
839         };
840
841         my @color = (' bgcolor="#e0e0e0"', '');
842
843         my $i = 0;
844         my $host = '';
845
846         foreach my $backup ( getBackupsNotBurned($param) ) {
847
848                 if ($host ne $backup->{'host'}) {
849                         $i++;
850                         $host = $backup->{'host'};
851                 }
852                 my $ftype = "";
853
854                 my $checkbox_key = $backup->{'hostid'}. '_' .$backup->{'backupnum'} . '_' . $backup->{'id'};
855
856                 $retHTML .=
857                         '<tr' . $color[$i %2 ] . '>
858                         <td class="fview">';
859
860                 if (($backup->{'inc_size'} || 0) > 0) {
861                         $retHTML .= '
862                         <input type="checkbox" name="fcb' . $checkbox_key . '" value="' . $checkbox_key . '" onClick="sumiraj(this);">';
863                 }
864
865                 my $img_url = $Conf{CgiImageDirURL};
866
867                 $retHTML .=
868                         '</td>' .
869                         '<td align="right">' . $backup->{'host'} . ':' . $backup->{'share'} . '</td>' .
870                         '<td align="center">' . $backup->{'backupnum'} . '</td>' .
871                         '<td align="center">' . $backup->{'type'} . '</td>' .
872                         '<td align="center">' . epoch_to_iso( $backup->{'date'} ) . '</td>' .
873                         '<td align="center">' . $backup->{'age'} . '</td>' .
874                         '<td align="right">' . $backup->{'size'} . '</td>' .
875                         '<td align="right">' . sprintf("%0.1f", $backup->{'inc_size'} / 1024 ) .
876                         '<input type="hidden" id="fss'.$checkbox_key .'" value="'. $backup->{'inc_size_calc'} .'"></td>' .
877                         '<input type="hidden" id="prt'.$checkbox_key .'" value="'. $backup->{'volumes'} .'"></td>' .
878                         '<td align="left">' . ( qq{<img src="$img_url/icon-cd.gif" alt="media">} x $backup->{volumes} ) . '</td>' .
879
880                         "</tr>\n";
881         }
882
883         $retHTML .= "</table>";
884         $retHTML .= "</form>";
885       
886         return $retHTML;
887 }      
888
889 sub displayGrid($) {
890         my ($param) = @_;
891
892         my $offset = $param->{'offset'};
893         my $hilite = $param->{'search_filename'};
894
895         my $retHTML = "";
896  
897         my $start_t = time();
898
899         my ($results, $files);
900         if ($param->{'use_hest'} && length($hilite) > 0) {
901                 ($results, $files) = getFilesHyperEstraier($param);
902         } else {
903                 ($results, $files) = getFiles($param);
904         }
905
906         my $dur_t = time() - $start_t;
907         my $dur = sprintf("%0.4fs", $dur_t);
908
909         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
910
911         if ($results <= 0) {
912                 $retHTML .= qq{
913                         <p style="color: red;">No results found...</p>
914                 };
915                 return $retHTML;
916         } else {
917                 # DEBUG
918                 #use Data::Dumper;
919                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
920         }
921
922
923         $retHTML .= qq{
924         <div>
925         Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
926         </div>
927         <table style="fview" width="100%" border="0" cellpadding="2" cellspacing="0">
928                 <tr class="fviewheader"> 
929                 <td></td>
930         };
931
932         sub sort_header($$$$) {
933                 my ($param, $display, $name, $align) = @_;
934
935                 my ($sort_what, $sort_direction) = split(/_/,$param->{'sort'},2);
936
937                 my $old_sort = $param->{'sort'};
938
939                 my $html = qq{<td align="$align"};
940                 my $arrow = '';
941
942                 if (lc($sort_what) eq lc($name)) {
943                         my $direction = lc($sort_direction);
944
945                         # swap direction or fallback to default
946                         $direction =~ tr/ad/da/;
947                         $direction = 'a' unless ($direction =~ /[ad]/);
948
949                         $param->{'sort'} = $name . '_' . $direction;
950                         $html .= ' style="border: 1px solid #808080;"';
951                 
952                         # add unicode arrow for direction
953                         $arrow .= '&nbsp;';
954                         $arrow .= $direction eq 'a'  ?  '&#9650;'
955                                 : $direction eq 'd'  ?  '&#9660;'
956                                 :                       ''
957                                 ;
958
959                 } else {
960                         $param->{'sort'} = $name . '_a';
961                 }
962
963                 $html .= '><a href="' . page_uri($param) . '">' . $display . '</a>' . $arrow . '</td>';
964                 $param->{'sort'} = $old_sort;
965
966                 return $html;
967         }
968
969         $retHTML .=
970                 sort_header($param, 'Share', 'sname', 'center') .
971                 sort_header($param, 'Type and Name', 'filepath', 'center') .
972                 sort_header($param, '#', 'backupnum', 'center') .
973                 sort_header($param, 'Size', 'size', 'center') .
974                 sort_header($param, 'Date', 'date', 'center');
975
976         $retHTML .= qq{
977                 <td align="center">Media</td>
978                 </tr>
979         };
980
981         my $file;
982
983         sub hilite_html($$) {
984                 my ($html, $search) = @_;
985                 $html =~ s#($search)#<b>$1</b>#gis;
986                 return $html;
987         }
988
989         sub restore_link($$$$$$) {
990                 my $type = shift;
991                 my $action = 'RestoreFile';
992                 $action = 'browse' if (lc($type) eq 'dir');
993                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
994         }
995
996         my $sth_archived;
997         my %archived_cache;
998
999         sub check_archived($$$) {
1000                 my ($host, $share, $num) = @_;
1001
1002                 if (my $html = $archived_cache{"$host $share $num"}) {
1003                         return $html;
1004                 }
1005
1006                 $sth_archived ||= $dbh->prepare(qq{
1007                         select
1008                                 dvd_nr, note,
1009                                 count(archive_burned.copy) as copies
1010                         from archive
1011                         inner join archive_burned on archive_burned.archive_id = archive.id
1012                         inner join archive_backup on archive.id = archive_backup.archive_id
1013                         inner join backups on backups.id = archive_backup.backup_id
1014                         inner join hosts on hosts.id = backups.hostid
1015                         inner join shares on shares.id = backups.shareid
1016                         where hosts.name = ? and shares.name = ? and backups.num = ?
1017                         group by dvd_nr, note
1018                 });
1019
1020                 my @mediums;
1021
1022                 $sth_archived->execute($host, $share, $num);
1023                 while (my $row = $sth_archived->fetchrow_hashref()) {
1024                         push @mediums, '<abbr title="' .
1025                                 $row->{'note'} .
1026                                 ' [' . $row->{'copies'} . ']' .
1027                                 '">' .$row->{'dvd_nr'} .
1028                                 '</abbr>';
1029                 }
1030
1031                 my $html = join(", ",@mediums);
1032                 $archived_cache{"$host $share $num"} = $html;
1033                 return $html;
1034         }
1035
1036         my $i = $offset * $on_page;
1037
1038         foreach $file (@{ $files }) {
1039                 $i++;
1040
1041                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
1042                 $retHTML .= qq{<tr class="fviewborder">};
1043
1044                 $retHTML .= qq{<td class="fviewborder">$i</td>};
1045
1046                 $retHTML .=
1047                         qq{<td class="fviewborder" align="right">} . $file->{'hname'} . ':' . $file->{'sname'} . qq{</td>} .
1048                         qq{<td class="fviewborder"><img src="$Conf{CgiImageDirURL}/icon-$typeStr.png" alt="$typeStr" align="middle">&nbsp;} . hilite_html( $file->{'filepath'}, $hilite ) . qq{</td>} .
1049                         qq{<td class="fviewborder" align="center">} . restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupnum'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'filepath'} )}, $file->{'backupnum'} ) . qq{</td>} .
1050                         qq{<td class="fviewborder" align="right">} . $file->{'size'} . qq{</td>} .
1051                         qq{<td class="fviewborder">} . epoch_to_iso( $file->{'date'} ) . qq{</td>} .
1052                         qq{<td class="fviewborder">} . check_archived( $file->{'hname'}, $file->{'sname'}, $file->{'backupnum'} ) . qq{</td>};
1053
1054                 $retHTML .= "</tr>";
1055         }
1056         $retHTML .= "</table>";
1057
1058         # all variables which has to be transfered
1059         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/) {
1060                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
1061         }
1062
1063         my $del = '';
1064         my $max_page = int( $results / $on_page );
1065         my $page = 0;
1066
1067         sub page_uri($) {
1068                 my $param = shift || die "no param?";
1069
1070                 my $uri = $MyURL;
1071                 my $del = '?';
1072                 foreach my $k (keys %{ $param }) {
1073                         if ($param->{$k}) {
1074                                 $uri .= $del . $k . '=' . ${EscURI( $param->{$k} )};
1075                                 $del = '&';
1076                         }
1077                 }
1078                 return $uri;
1079         }
1080
1081         sub page_link($$$) {
1082                 my ($param,$page,$display) = @_;
1083
1084                 $param->{'offset'} = $page if (defined($page));
1085
1086                 my $html = '<a href = "' . page_uri($param) . '">' . $display . '</a>';
1087         }
1088
1089         $retHTML .= '<div style="text-align: center;">';
1090
1091         if ($offset > 0) {
1092                 $retHTML .= page_link($param, $offset - 1, '&lt;&lt;') . ' ';
1093         }
1094
1095         while ($page <= $max_page) {
1096                 if ($page == $offset) {
1097                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
1098                 } else {
1099                         $retHTML .= $del . page_link($param, $page, $page + 1);
1100                 }
1101
1102                 if ($page < $offset - $pager_pages && $page != 0) {
1103                         $retHTML .= " ... ";
1104                         $page = $offset - $pager_pages;
1105                         $del = '';
1106                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
1107                         $retHTML .= " ... ";
1108                         $page = $max_page;
1109                         $del = '';
1110                 } else {
1111                         $del = ' | ';
1112                         $page++;
1113                 }
1114         }
1115
1116         if ($offset < $max_page) {
1117                 $retHTML .= ' ' . page_link($param, $offset + 1, '&gt;&gt;');
1118         }
1119
1120         $retHTML .= "</div>";
1121
1122         return $retHTML;
1123 }
1124
1125 my @units = qw/b k M G/;
1126 sub unit {
1127         my $v = shift;
1128
1129         my $o = 0;
1130
1131         while ( ( $v / 10000 ) >= 1 ) {
1132                 $o++;
1133                 $v /= 1024;
1134         }
1135
1136         if ( $v >= 1 ) {
1137                 return sprintf("%d%s", $v, $units[$o]);
1138         } elsif ( $v == 0 ) {
1139                 return 0;
1140         } else {
1141                 return sprintf("%.1f%s", $v, $units[$o]);
1142         }
1143 }
1144
1145 1;