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