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