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