move loading of search module into BackupPC::Search
[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 our $search_module;
22
23 sub search_module {
24
25         $search_module = "BackupPC::Search::Estraier";
26         $search_module = "BackupPC::Search::KinoSearch" if $ENV{KINO};
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         my $bpc = BackupPC::Lib->new || die;
35         my %Conf = $bpc->Conf();
36
37         return $search_module->new( %Conf );
38 }
39
40 my $dbh;
41
42 sub get_dbh {
43         $dbh ||= DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1 } );
44         return $dbh;
45 }
46
47 sub getUnits() {
48         my @ret;
49
50         my $dbh = get_dbh();
51         my $sth = $dbh->prepare(qq{
52                 SELECT
53                         shares.id       as id,
54                         hosts.name || ':' || shares.name as share
55                 FROM shares
56                 JOIN hosts on hostid = hosts.id
57                 ORDER BY share
58         } );
59         $sth->execute();
60         push @ret, { 'id' => '', 'share' => '-'};       # dummy any
61
62         while ( my $row = $sth->fetchrow_hashref() ) {
63                 push @ret, $row;
64         }
65         return @ret;
66 }
67
68 sub epoch_to_iso {
69         my $t = shift || return;
70         my $iso = BackupPC::Lib::timeStamp(undef, $t);
71         $iso =~ s/\s/ /g;
72         return $iso;
73 }
74
75 sub dates_from_form($) {
76         my $param = shift || return;
77
78         sub mk_epoch_date($$) {
79                 my ($name,$suffix) = @_;
80
81                 my $yyyy = $param->{ $name . '_year_' . $suffix} || return undef;
82                 my $mm .= $param->{ $name . '_month_' . $suffix} ||
83                         ( $suffix eq 'from' ? 1 : 12);
84                 my $dd .= $param->{ $name . '_day_' . $suffix} ||
85                         ( $suffix eq 'from' ? 1 : 31);
86
87                 $yyyy =~ s/\D//g;
88                 $mm =~ s/\D//g;
89                 $dd =~ s/\D//g;
90
91                 my $h = my $m = my $s = 0;
92                 if ($suffix eq 'to') {
93                         $h = 23;
94                         $m = 59;
95                         $s = 59;
96                 }
97
98                 my $dt = new DateTime(
99                         year => $yyyy,
100                         month => $mm,
101                         day => $dd,
102                         hour => $h,
103                         minute => $m,
104                         second => $s,
105                 );
106                 print STDERR "mk_epoch_date($name,$suffix) [$yyyy-$mm-$dd] = " . $dt->ymd . " " . $dt->hms . "\n";
107                 return $dt->epoch || 'NULL';
108         }
109
110         my @ret = (
111                 mk_epoch_date('search_backup', 'from'),
112                 mk_epoch_date('search_backup', 'to'),
113                 mk_epoch_date('search', 'from'),
114                 mk_epoch_date('search', 'to'),
115         );
116
117         return @ret;
118
119 }
120
121
122 sub getWhere($) {
123         my $param = shift || return;
124
125         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
126
127         my @conditions;
128         push @conditions, qq{ backups.date >= $backup_from } if ($backup_from);
129         push @conditions, qq{ backups.date <= $backup_to } if ($backup_to);
130         push @conditions, qq{ files.date >= $files_from } if ($files_from);
131         push @conditions, qq{ files.date <= $files_to } if ($files_to);
132
133         print STDERR "backup: $backup_from - $backup_to files: $files_from - $files_to cond:" . join(" and ",@conditions);
134
135         push( @conditions, ' files.shareid = ' . $param->{'search_share'} ) if ($param->{'search_share'});
136         push (@conditions, " upper(files.path) LIKE upper('%".$param->{'search_filename'}."%')") if ($param->{'search_filename'});
137
138         if ( $param->{burned} ) {
139                 my $is_what = 'is null';
140                 $is_what = '= 1' if ($param->{burned} eq 'burned');
141                 push @conditions, "archive_burned.part $is_what";
142                 push @conditions, "archive_burned.copy $is_what";
143         }
144
145         return join(" and ", @conditions);
146 }
147
148 my $sort_def = {
149         search => {
150                 default => 'date_a',
151                 sql => {
152                         share_d => 'shares.name DESC',
153                         share_a => 'shares.name ASC',
154                         path_d => 'files.path DESC',
155                         path_a => 'files.path ASC',
156                         num_d => 'files.backupnum DESC',
157                         num_a => 'files.backupnum ASC',
158                         size_d => 'files.size DESC',
159                         size_a => 'files.size ASC',
160                         date_d => 'files.date DESC',
161                         date_a => 'files.date ASC',
162                 },
163         }, burn => {
164                 default => 'date_a',
165                 sql => {
166                         share_d => 'host DESC, share DESC',
167                         share_a => 'host ASC, share ASC',
168                         num_d => 'backupnum DESC',
169                         num_a => 'backupnum ASC',
170                         date_d => 'date DESC',
171                         date_a => 'date ASC',
172                         age_d => 'age DESC',
173                         age_a => 'age ASC',
174                         size_d => 'size DESC',
175                         size_a => 'size ASC',
176                         incsize_d => 'inc_size DESC',
177                         incsize_a => 'inc_size ASC',
178                 }
179         }
180 };
181
182 sub getSort($$$) {
183         my ($part,$type, $sort_order) = @_;
184
185         die "unknown part: $part" unless ($sort_def->{$part});
186         die "unknown type: $type" unless ($sort_def->{$part}->{$type});
187
188         $sort_order ||= $sort_def->{$part}->{'default'};
189
190         if (my $ret = $sort_def->{$part}->{$type}->{$sort_order}) {
191                 return $ret;
192         } else {
193                 # fallback to default sort order
194                 return $sort_def->{$part}->{$type}->{ $sort_def->{$part}->{'default'} };
195         }
196 }
197
198 sub getFiles($) {
199         my ($param) = @_;
200
201         my $offset = $param->{'offset'} || 0;
202         $offset *= $on_page;
203
204         my $dbh = get_dbh();
205
206         my $sql_cols = qq{
207                 files.id                        AS fid,
208                 hosts.name                      AS hname,
209                 shares.name                     AS sname,
210                 files.backupnum                 AS backupnum,
211                 files.path                      AS filepath,
212                 files.date                      AS date,
213                 files.type                      AS type,
214                 files.size                      AS size
215         };
216
217         my $sql_from = qq{
218                 FROM files 
219                         INNER JOIN shares       ON files.shareID=shares.ID
220                         INNER JOIN hosts        ON hosts.ID = shares.hostID
221                         INNER JOIN backups      ON backups.num = files.backupnum and backups.hostID = hosts.ID AND backups.shareID = files.shareID
222         };
223
224         my $sql_where;
225         my $where = getWhere($param);
226         $sql_where = " WHERE ". $where if ($where);
227
228         # do we have to add tables for burned media?
229         if ( $param->{burned} ) {
230                 $sql_from .= qq{
231                         LEFT OUTER JOIN archive_backup on archive_backup.backup_id = backups.id
232                         LEFT OUTER JOIN archive_burned on archive_burned.archive_id = archive_backup.archive_id
233                 };
234         }
235
236         my $order = getSort('search', 'sql', $param->{'sort'});
237
238         my $sql_order = qq{
239                 ORDER BY $order
240                 LIMIT $on_page
241                 OFFSET ?
242         };
243
244         my $sql_count = qq{ select count(files.id) $sql_from $sql_where };
245         my $sql_results = qq{ select $sql_cols $sql_from $sql_where $sql_order };
246
247         my $sth = $dbh->prepare($sql_count);
248         $sth->execute();
249         my ($results) = $sth->fetchrow_array();
250
251         $sth = $dbh->prepare($sql_results);
252         $sth->execute( $offset );
253
254         if ($sth->rows != $results) {
255                 my $bug = "$0 BUG: [[ $sql_count ]] = $results while [[ $sql_results ]] = " . $sth->rows;
256                 $bug =~ s/\s+/ /gs;
257                 print STDERR "$bug\n";
258         }
259
260         my @ret;
261       
262         while (my $row = $sth->fetchrow_hashref()) {
263                 push @ret, $row;
264         }
265      
266         $sth->finish();
267         return ($results, \@ret);
268 }
269
270 sub getFilesHyperEstraier($) {
271         my ($param) = @_;
272
273         my $offset = $param->{'offset'} || 0;
274         $offset *= $on_page;
275
276         my $q = $param->{'search_filename'};
277         my $shareid = $param->{'search_share'};
278         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
279
280         return search_module->search(
281                 $offset, $on_page, $param->{sort},
282                 $q, $shareid, $backup_from, $backup_to, $files_from, $files_to
283         );
284                 
285 }
286
287 sub getGzipName($$$)
288 {
289         my ($host, $share, $backupnum) = @_;
290         my $ret = $Conf{GzipSchema};
291         
292         $share =~ s/\//_/g;
293         $ret =~ s/\\h/$host/ge;
294         $ret =~ s/\\s/$share/ge;
295         $ret =~ s/\\n/$backupnum/ge;
296
297         $ret =~ s/__+/_/g;
298
299         return $ret;
300         
301 }
302
303 sub get_tgz_size_by_name($) {
304         my $name = shift;
305
306         my $tgz = $Conf{InstallDir}.'/'.$Conf{GzipTempDir}.'/'.$name;
307         my $size = -1;
308
309         my $Dir = $Conf{InstallDir}."/data/log";
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{MaxArchiveSize} || die "no MaxArchiveSize";
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{MaxArchiveSize} || die "no MaxArchiveSize";
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{MaxArchiveSize} || die "no MaxArchiveSize";
477         my $max_archive_file_size = $Conf{MaxArchiveFileSize}  || 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', 'share', 'center') .
971                 sort_header($param, 'Type and Name', 'path', 'center') .
972                 sort_header($param, '#', 'num', '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 1;