better calculation of inc_size
[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
12 my $on_page = 100;
13 my $pager_pages = 10;
14
15 my $dsn = $Conf{SearchDSN};
16 my $db_user = $Conf{SearchUser} || '';
17
18 my $hest_index_path = $Conf{HyperEstraierIndex};
19
20 my $dbh;
21
22 sub get_dbh {
23         $dbh ||= DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1 } );
24         return $dbh;
25 }
26
27 sub getUnits() {
28         my @ret;
29
30         my $dbh = get_dbh();
31         my $sth = $dbh->prepare(qq{
32                 SELECT
33                         shares.id       as id,
34                         hosts.name || ':' || shares.name as share
35                 FROM shares
36                 JOIN hosts on hostid = hosts.id
37                 ORDER BY share
38         } );
39         $sth->execute();
40         push @ret, { 'id' => '', 'share' => '-'};       # dummy any
41
42         while ( my $row = $sth->fetchrow_hashref() ) {
43                 push @ret, $row;
44         }
45         return @ret;
46 }
47
48 sub epoch_to_iso {
49         my $t = shift || return;
50         my $iso = BackupPC::Lib::timeStamp(undef, $t);
51         $iso =~ s/\s/ /g;
52         return $iso;
53 }
54
55 sub dates_from_form($) {
56         my $param = shift || return;
57
58         sub mk_epoch_date($$) {
59                 my ($name,$suffix) = @_;
60
61                 my $yyyy = $param->{ $name . '_year_' . $suffix} || return undef;
62                 my $mm .= $param->{ $name . '_month_' . $suffix} ||
63                         ( $suffix eq 'from' ? 1 : 12);
64                 my $dd .= $param->{ $name . '_day_' . $suffix} ||
65                         ( $suffix eq 'from' ? 1 : 31);
66
67                 $yyyy =~ s/\D//g;
68                 $mm =~ s/\D//g;
69                 $dd =~ s/\D//g;
70
71                 my $dt = new DateTime(
72                         year => $yyyy,
73                         month => $mm,
74                         day => $dd
75                 );
76                 print STDERR "mk_epoch_date($name,$suffix) [$yyyy-$mm-$dd] = " . $dt->ymd . " " . $dt->hms . "\n";
77                 return $dt->epoch || 'NULL';
78         }
79
80         my @ret = (
81                 mk_epoch_date('search_backup', 'from'),
82                 mk_epoch_date('search_backup', 'to'),
83                 mk_epoch_date('search', 'from'),
84                 mk_epoch_date('search', 'to'),
85         );
86
87         return @ret;
88
89 }
90
91
92 sub getWhere($) {
93         my $param = shift || return;
94
95         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
96
97         my @conditions;
98         push @conditions, qq{ backups.date >= $backup_from } if ($backup_from);
99         push @conditions, qq{ backups.date <= $backup_to } if ($backup_to);
100         push @conditions, qq{ files.date >= $files_from } if ($files_from);
101         push @conditions, qq{ files.date <= $files_to } if ($files_to);
102
103         print STDERR "backup: $backup_from - $backup_to files: $files_from - $files_to cond:" . join(" | ",@conditions);
104
105         push( @conditions, ' files.shareid = ' . $param->{'search_share'} ) if ($param->{'search_share'});
106         push (@conditions, " upper(files.path) LIKE upper('%".$param->{'search_filename'}."%')") if ($param->{'search_filename'});
107
108         return join(" and ", @conditions);
109 }
110
111
112 sub getFiles($) {
113         my ($param) = @_;
114
115         my $offset = $param->{'offset'} || 0;
116         $offset *= $on_page;
117
118         my $dbh = get_dbh();
119
120         my $sql_cols = qq{
121                 files.id                        AS fid,
122                 hosts.name                      AS hname,
123                 shares.name                     AS sname,
124                 files.backupnum                 AS backupnum,
125                 files.path                      AS filepath,
126                 files.date                      AS date,
127                 files.type                      AS type,
128                 files.size                      AS size
129         };
130
131         my $sql_from = qq{
132                 FROM files 
133                         INNER JOIN shares       ON files.shareID=shares.ID
134                         INNER JOIN hosts        ON hosts.ID = shares.hostID
135                         INNER JOIN backups      ON backups.num = files.backupnum and backups.hostID = hosts.ID AND backups.shareID = files.shareID
136         };
137
138         my $sql_where;
139         my $where = getWhere($param);
140         $sql_where = " WHERE ". $where if ($where);
141
142         my $sql_order = qq{
143                 ORDER BY files.date
144                 LIMIT $on_page
145                 OFFSET ?
146         };
147
148         my $sql_count = qq{ select count(files.id) $sql_from $sql_where };
149         my $sql_results = qq{ select $sql_cols $sql_from $sql_where $sql_order };
150
151         my $sth = $dbh->prepare($sql_count);
152         $sth->execute();
153         my ($results) = $sth->fetchrow_array();
154
155         $sth = $dbh->prepare($sql_results);
156         $sth->execute( $offset );
157
158         if ($sth->rows != $results) {
159                 my $bug = "$0 BUG: [[ $sql_count ]] = $results while [[ $sql_results ]] = " . $sth->rows;
160                 $bug =~ s/\s+/ /gs;
161                 print STDERR "$bug\n";
162         }
163
164         my @ret;
165       
166         while (my $row = $sth->fetchrow_hashref()) {
167                 push @ret, $row;
168         }
169      
170         $sth->finish();
171         return ($results, \@ret);
172 }
173
174 sub getHyperEstraier_url($) {
175         my ($use_hest) = @_;
176
177         return unless $use_hest;
178
179         use HyperEstraier;
180         my ($index_path, $index_node_url);
181
182         if ($use_hest =~ m#^http://#) {
183                 $index_node_url = $use_hest;
184         } else {
185                 $index_path = $TopDir . '/' . $index_path;
186                 $index_path =~ s#//#/#g;
187         }
188         return ($index_path, $index_node_url);
189 }
190
191 sub getFilesHyperEstraier($) {
192         my ($param) = @_;
193
194         my $offset = $param->{'offset'} || 0;
195         $offset *= $on_page;
196
197         die "no index_path?" unless ($hest_index_path);
198
199         use HyperEstraier;
200
201         my ($index_path, $index_node_url) = getHyperEstraier_url($hest_index_path);
202
203         # open the database
204         my $db;
205         if ($index_path) {
206                 $db = HyperEstraier::Database->new();
207                 $db->open($index_path, $HyperEstraier::ESTDBREADER);
208         } elsif ($index_node_url) {
209                 $db ||= HyperEstraier::Node->new($index_node_url);
210                 $db->set_auth('admin', 'admin');
211         } else {
212                 die "BUG: unimplemented";
213         }
214
215         # create a search condition object
216         my $cond = HyperEstraier::Condition->new();
217
218         my $q = $param->{'search_filename'};
219         my $shareid = $param->{'search_share'};
220
221         if (length($q) > 0) {
222                 # exact match
223                 $cond->add_attr("filepath ISTRINC $q");
224
225                 $q =~ s/(.)/$1 /g;
226                 # set the search phrase to the search condition object
227                 $cond->set_phrase($q);
228         }
229
230         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
231
232         $cond->add_attr("backup_date NUMGE $backup_from") if ($backup_from);
233         $cond->add_attr("backup_date NUMLE $backup_to") if ($backup_to);
234
235         $cond->add_attr("date NUMGE $files_from") if ($files_from);
236         $cond->add_attr("date NUMLE $files_to") if ($files_to);
237
238         $cond->add_attr("shareid NUMEQ $shareid") if ($shareid);
239
240 #       $cond->set_max( $offset + $on_page );
241         $cond->set_options( $HyperEstraier::Condition::SURE );
242         $cond->set_order( 'date NUMA' );
243
244         # get the result of search
245         my @res;
246         my ($result, $hits);
247
248         if ($index_path) {
249                 $result = $db->search($cond, 0);
250                 $hits = $result->size;
251         } elsif ($index_node_url) {
252                 $result = $db->search($cond, 0);
253                 $hits = $result->doc_num;
254         } else {
255                 die "BUG: unimplemented";
256         }
257
258         # for each document in result
259         for my $i ($offset .. ($offset + $on_page - 1)) {
260                 last if ($i >= $hits);
261
262                 my $doc;
263                 if ($index_path) {
264                         my $id = $result->get($i);
265                         $doc = $db->get_doc($id, 0);
266                 } elsif ($index_node_url) {
267                         $doc = $result->get_doc($i);
268                 } else {
269                         die "BUG: unimplemented";
270                 }
271
272                 my $row;
273                 foreach my $c (qw/fid hname sname backupnum fiilename filepath date type size/) {
274                         $row->{$c} = $doc->attr($c);
275                 }
276                 push @res, $row;
277         }
278
279         return ($hits, \@res);
280 }
281
282 sub getGzipName($$$)
283 {
284         my ($host, $share, $backupnum) = @_;
285         my $ret = $Conf{GzipSchema};
286         
287         $share =~ s/\//_/g;
288         $ret =~ s/\\h/$host/ge;
289         $ret =~ s/\\s/$share/ge;
290         $ret =~ s/\\n/$backupnum/ge;
291
292         $ret =~ s/__+/_/g;
293
294         return $ret;
295         
296 }
297
298 sub getGzipSize($$)
299 {
300         my ($hostID, $backupNum) = @_;
301         my $ret;
302         my $sql;
303         my $dbh = get_dbh();
304         
305         $sql = q{ 
306                                 SELECT hosts.name  as host,
307                                            shares.name as share,
308                                            backups.num as backupnum
309                                 FROM hosts, backups, shares
310                                 WHERE shares.id=backups.shareid AND
311                                           hosts.id =backups.hostid AND
312                                           hosts.id=? AND
313                                           backups.num=?
314                         };
315         my $sth = $dbh->prepare($sql);
316         $sth->execute($hostID, $backupNum);
317
318         my $row = $sth->fetchrow_hashref();
319         
320         my (undef,undef,undef,undef,undef,undef,undef,$ret,undef,undef,undef,undef,undef) = 
321                         stat( $Conf{InstallDir}.'/'.$Conf{GzipTempDir}.'/'.
322                                 getGzipName($row->{'host'}, $row->{share}, $row->{'backupnum'}));
323         
324         return $ret;    
325 }
326
327 sub getBackupsNotBurned() {
328
329         my $dbh = get_dbh();
330
331         my $sql = q{
332                 SELECT 
333                         backups.hostID AS hostID,
334                         hosts.name AS host,
335                         shares.name AS share,
336                         backups.num AS backupnum,
337                         backups.type AS type,
338                         backups.date AS date,
339                         backups.size AS size,
340                         backups.id AS id,
341                         backups.inc_size AS inc_size
342                 FROM backups 
343                 INNER JOIN shares       ON backups.shareID=shares.ID
344                 INNER JOIN hosts        ON backups.hostID = hosts.ID
345                 LEFT OUTER JOIN archive_backup ON archive_backup.backup_id = backups.id 
346                 WHERE backups.size > 0 AND backups.inc_size > 0 AND backups.inc_deleted is false AND archive_backup.backup_id IS NULL
347                 GROUP BY
348                         backups.hostID,
349                         hosts.name,
350                         shares.name,
351                         backups.num,
352                         backups.shareid,
353                         backups.id,
354                         backups.type,
355                         backups.date,
356                         backups.size,
357                         backups.inc_size
358                 ORDER BY backups.date
359         };
360         my $sth = $dbh->prepare( $sql );
361         my @ret;
362         $sth->execute();
363
364         while ( my $row = $sth->fetchrow_hashref() ) {
365                 $row->{'age'} = sprintf("%0.1f", ( (time() - $row->{'date'}) / 86400 ) );
366                 $row->{'size'} = sprintf("%0.2f", $row->{'size'} / 1024 / 1024);
367
368                 # do some cluster calculation (approximate) and convert to kB
369                 $row->{'inc_size'} = int(($row->{'inc_size'} + 1023 ) / ( 2 * 1024 ) * 2);
370                 push @ret, $row;
371         }
372       
373         return @ret;      
374 }
375
376 sub displayBackupsGrid() {
377
378         my $retHTML .= q{
379                 <form id="forma" method="POST" action="}.$MyURL.q{?action=burn">
380         };
381
382         $retHTML .= <<'EOF3';
383 <style type="text/css">
384 <!--
385 DIV#fixedBox {
386         position: absolute;
387         top: 50em;
388         left: -24%;
389         padding: 0.5em;
390         width: 20%;
391         background-color: #E0F0E0;
392         border: 1px solid #00C000;
393 }
394
395 DIV#fixedBox, DIV#fixedBox INPUT, DIV#fixedBox TEXTAREA {
396         font-size: 10pt;
397 }
398
399 FORM>DIV#fixedBox {
400         position: fixed !important;
401         left: 0.5em !important;
402         top: auto !important;
403         bottom: 1em !important;
404         width: 15% !important;
405 }
406
407 DIV#fixedBox INPUT[type=text], DIV#fixedBox TEXTAREA {
408         border: 1px solid #00C000;
409 }
410
411 DIV#fixedBox #note {
412         display: block;
413         width: 100%;
414 }
415
416 DIV#fixedBox #submitBurner {
417         display: block;
418         width: 100%;
419         margin-top: 0.5em;
420         cursor: pointer;
421 }
422
423 * HTML {
424         overflow-y: hidden;
425 }
426
427 * HTML BODY {
428         overflow-y: auto;
429         height: 100%;
430         font-size: 100%;
431 }
432
433 * HTML DIV#fixedBox {
434         position: absolute;
435 }
436
437 #mContainer, #gradient, #mask, #progressIndicator {
438         display: block;
439         width: 100%;
440         font-size: 10pt;
441         font-weight: bold;
442         text-align: center;
443         vertical-align: middle;
444         padding: 1px;
445 }
446
447 #gradient, #mask, #progressIndicator {
448         left: 0;
449         border-width: 1px;
450         border-style: solid;
451         border-color: #000000;
452         color: #404040;
453         margin: 0.4em;
454         position: absolute;
455         margin-left: -1px;
456         margin-top: -1px;
457         margin-bottom: -1px;
458         overflow: hidden;
459 }
460
461 #mContainer {
462         display: block;
463         position: relative;
464         padding: 0px;
465         margin-top: 0.4em;
466         margin-bottom: 0.5em;
467 }
468
469 #gradient {
470         z-index: 1;
471         background-color: #FFFF00;
472 }
473
474 #mask {
475         z-index: 2;
476         background-color: #FFFFFF;
477 }
478
479 #progressIndicator {
480         z-index: 3;
481         background-color: transparent;
482 }
483 -->
484 </style>
485 <script type="text/javascript">
486 <!--
487
488 var debug_div = null;
489 EOF3
490
491         # take maximum archive size from configuration
492         $retHTML .= 'var media_size = '. $Conf{MaxArchiveSize} .';';
493
494         $retHTML .= <<'EOF3';
495
496 function debug(msg) {
497 //      return; // Disable debugging
498
499         if (! debug_div) debug_div = document.getElementById('debug');
500
501         // this will create debug div if it doesn't exist.
502         if (! debug_div) {
503                 debug_div = document.createElement('div');
504                 if (document.body) document.body.appendChild(debug_div);
505                 else debug_div = null;
506         }
507         if (debug_div) {
508                 debug_div.appendChild(document.createTextNode(msg));
509                 debug_div.appendChild(document.createElement("br"));
510         }
511 }
512
513
514 var element_id_cache = Array();
515
516 function element_id(name,element) {
517         if (! element_id_cache[name]) {
518                 element_id_cache[name] = self.document.getElementById(name);
519         }
520         return element_id_cache[name];
521 }
522
523 function checkAll(location) {
524         var f = element_id('forma') || null;
525         if (!f) return false;
526
527         var len = f.elements.length;
528         var check_all = element_id('allFiles');
529         var suma = check_all.checked ? (parseInt(f.elements['totalsize'].value) || 0) : 0;
530
531         for (var i = 0; i < len; i++) {
532                 var e = f.elements[i];
533                 if (e.name != 'all' && e.name.substr(0, 3) == 'fcb') {
534                         if (check_all.checked) {
535                                 if (e.checked) continue;
536                                 var el = element_id("fss" + e.name.substr(3));
537                                 var size = parseInt(el.value) || 0;
538                                 debug('suma: '+suma+' size: '+size);
539                                 if ((suma + size) < media_size) {
540                                         suma += size;
541                                         e.checked = true;
542                                 } else {
543                                         break;
544                                 }
545                         } else {
546                                 e.checked = false;
547                         }
548                 }
549         }
550         update_sum(suma);
551 }
552
553 function update_sum(suma) {
554         element_id('forma').elements['totalsize'].value = suma;
555         pbar_set(suma, media_size);
556         debug('total size: ' + suma);
557 }
558
559 function sumiraj(e) {
560         var suma = parseInt(element_id('forma').elements['totalsize'].value) || 0;
561         var len = element_id('forma').elements.length;
562         if (e) {
563                 var size = parseInt( element_id("fss" + e.name.substr(3)).value);
564                 if (e.checked) {
565                         suma += size;
566                 } else {
567                         suma -= size;
568                 }
569         } else {
570                 suma = 0;
571                 for (var i = 0; i < len; i++) {
572                         var e = element_id('forma').elements[i];
573                         if (e.name != 'all' && e.checked && e.name.substr(0,3) == 'fcb') {
574                                 var el = element_id("fss" + e.name.substr(3));
575                                 if (el && el.value) suma += parseInt(el.value) || 0;
576                         }
577                 }
578         }
579         update_sum(suma);
580         return suma;
581 }
582
583 /* progress bar */
584
585 var _pbar_width = null;
586 var _pbar_warn = 10;    // change color in last 10%
587
588 function pbar_reset() {
589         element_id("mask").style.left = "0px";
590         _pbar_width = element_id("mContainer").offsetWidth - 2;
591         element_id("mask").style.width = _pbar_width + "px";
592         element_id("mask").style.display = "block";
593         element_id("progressIndicator").style.zIndex  = 10;
594         element_id("progressIndicator").innerHTML = "0";
595 }
596
597 function dec2hex(d) {
598         var hch = '0123456789ABCDEF';
599         var a = d % 16;
600         var q = (d - a) / 16;
601         return hch.charAt(q) + hch.charAt(a);
602 }
603
604 function pbar_set(amount, max) {
605         debug('pbar_set('+amount+', '+max+')');
606
607         if (_pbar_width == null) {
608                 var _mc = element_id("mContainer");
609                 if (_pbar_width == null) _pbar_width = parseInt(_mc.offsetWidth ? (_mc.offsetWidth - 2) : 0) || null;
610                 if (_pbar_width == null) _pbar_width = parseInt(_mc.clientWidth ? (_mc.clientWidth + 2) : 0) || null;
611                 if (_pbar_width == null) _pbar_width = 0;
612         }
613
614         var pcnt = Math.floor(amount * 100 / max);
615         var p90 = 100 - _pbar_warn;
616         var pcol = pcnt - p90;
617         if (Math.round(pcnt) <= 100) {
618                 if (pcol < 0) pcol = 0;
619                 var e = element_id("submitBurner");
620                 debug('enable_button');
621                 e.disabled = false;
622                 var a = e.getAttributeNode('disabled') || null;
623                 if (a) e.removeAttributeNode(a);
624         } else {
625                 debug('disable button');
626                 pcol = _pbar_warn;
627                 var e = element_id("submitBurner");
628                 if (!e.disabled) e.disabled = true;
629         }
630         var col_g = Math.floor((_pbar_warn - pcol) * 255 / _pbar_warn);
631         var col = '#FF' + dec2hex(col_g) + '00';
632
633         //debug('pcol: '+pcol+' g:'+col_g+' _pbar_warn:'+ _pbar_warn + ' color: '+col);
634         element_id("gradient").style.backgroundColor = col;
635
636         element_id("progressIndicator").innerHTML = pcnt + '%';
637         //element_id("progressIndicator").innerHTML = amount;
638
639         element_id("mask").style.clip = 'rect(' + Array(
640                 '0px',
641                 element_id("mask").offsetWidth + 'px',
642                 element_id("mask").offsetHeight + 'px',
643                 Math.round(_pbar_width * amount / max) + 'px'
644         ).join(' ') + ')';
645 }
646
647 if (!self.body) self.body = new Object();
648 self.onload = self.document.onload = self.body.onload = function() {
649         //pbar_reset();
650         sumiraj();
651 };
652
653 // -->
654 </script>
655 <div id="fixedBox">
656
657 Size: <input type="text" name="totalsize" size="7" readonly="readonly" style="text-align:right;" value="0" /> kB
658
659 <div id="mContainer">
660         <div id="gradient">&nbsp;</div>
661         <div id="mask">&nbsp;</div>
662         <div id="progressIndicator">0%</div>
663 </div>
664 <br/>
665
666 Note:
667 <textarea name="note" cols="10" rows="5" id="note"></textarea>
668
669 <input type="submit" id="submitBurner" value="Burn selected" name="submitBurner" />
670
671 </div>
672 <!--
673 <div id="debug" style="float: right; width: 10em; border: 1px #ff0000 solid; background-color: #ffe0e0; -moz-opacity: 0.7;">
674 no debug output yet
675 </div>
676 -->
677 EOF3
678         $retHTML .= q{
679                         <input type="hidden" value="burn" name="action">
680                         <input type="hidden" value="results" name="search_results">
681                         <table style="fview" border="0" cellspacing="0" cellpadding="2">
682                         <tr class="tableheader">
683                         <td class="tableheader">
684                                 <input type="checkbox" name="allFiles" id="allFiles" onClick="checkAll('allFiles');">
685                         </td>
686                         <td align="center">Share</td>
687                         <td align="center">Backup no</td>
688                         <td align="center">Type</td>
689                         <td align="center">date</td>
690                         <td align="center">age/days</td>
691                         <td align="center">size/MB</td>
692                         <td align="center">gzip size/kB</td>
693                         </tr>
694
695         };
696
697         my @color = (' bgcolor="#e0e0e0"', '');
698
699         my $i = 0;
700         my $host = '';
701
702         foreach my $backup ( getBackupsNotBurned() ) {
703
704                 if ($host ne $backup->{'host'}) {
705                         $i++;
706                         $host = $backup->{'host'};
707                 }
708                 my $ftype = "";
709
710                 my $checkbox_key = $backup->{'hostid'}. '_' .$backup->{'backupnum'} . '_' . $backup->{'id'};
711
712                 $retHTML .=
713                         '<tr' . $color[$i %2 ] . '>
714                         <td class="fview">';
715
716                 if (($backup->{'inc_size'} || 0) > 0) {
717                         $retHTML .= '
718                         <input type="checkbox" name="fcb' . $checkbox_key . '" value="' . $checkbox_key . '" onClick="sumiraj(this);">';
719                 }
720
721                 $retHTML .=
722                         '</td>' .
723                         '<td align="right">' . $backup->{'host'} . ':' . $backup->{'share'} . '</td>' .
724                         '<td align="center">' . $backup->{'backupnum'} . '</td>' .
725                         '<td align="center">' . $backup->{'type'} . '</td>' .
726                         '<td align="center">' . epoch_to_iso( $backup->{'date'} ) . '</td>' .
727                         '<td align="center">' . $backup->{'age'} . '</td>' .
728                         '<td align="right">' . $backup->{'size'} . '</td>' .
729                         '<td align="right">' . $backup->{'inc_size'} .
730                         '<input type="hidden" iD="fss'.$checkbox_key .'" value="'. $backup->{'inc_size'} .'"></td>' .
731
732                         "</tr>\n";
733         }
734
735         $retHTML .= "</table>";
736         $retHTML .= "</form>";
737       
738         return $retHTML;
739 }      
740
741 sub displayGrid($) {
742         my ($param) = @_;
743
744         my $offset = $param->{'offset'};
745         my $hilite = $param->{'search_filename'};
746
747         my $retHTML = "";
748  
749         my $start_t = time();
750
751         my ($results, $files);
752         if ($param->{'use_hest'} && length($hilite) > 0) {
753                 ($results, $files) = getFilesHyperEstraier($param);
754         } else {
755                 ($results, $files) = getFiles($param);
756         }
757
758         my $dur_t = time() - $start_t;
759         my $dur = sprintf("%0.4fs", $dur_t);
760
761         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
762
763         if ($results <= 0) {
764                 $retHTML .= qq{
765                         <p style="color: red;">No results found...</p>
766                 };
767                 return $retHTML;
768         } else {
769                 # DEBUG
770                 #use Data::Dumper;
771                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
772         }
773
774
775         $retHTML .= qq{
776         <div>
777         Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
778         </div>
779         <table style="fview" width="100%" border="0" cellpadding="2" cellspacing="0">
780                 <tr class="fviewheader"> 
781                 <td></td>
782                 <td align="center">Share</td>
783                 <td align="center">Type and Name</td>
784                 <td align="center">#</td>
785                 <td align="center">Size</td>
786                 <td align="center">Date</td>
787                 <td align="center">Media</td>
788                 </tr>
789         };
790
791         my $file;
792
793         sub hilite_html($$) {
794                 my ($html, $search) = @_;
795                 $html =~ s#($search)#<b>$1</b>#gis;
796                 return $html;
797         }
798
799         sub restore_link($$$$$$) {
800                 my $type = shift;
801                 my $action = 'RestoreFile';
802                 $action = 'browse' if (lc($type) eq 'dir');
803                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
804         }
805
806         my $i = $offset * $on_page;
807
808         foreach $file (@{ $files }) {
809                 $i++;
810
811                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
812                 $retHTML .= qq{<tr class="fviewborder">};
813
814                 $retHTML .= qq{<td class="fviewborder">$i</td>};
815
816                 $retHTML .=
817                         qq{<td class="fviewborder" align="right">} . $file->{'hname'} . ':' . $file->{'sname'} . qq{</td>} .
818                         qq{<td class="fviewborder"><img src="$Conf{CgiImageDirURL}/icon-$typeStr.gif" alt="$typeStr" align="middle">&nbsp;} . hilite_html( $file->{'filepath'}, $hilite ) . qq{</td>} .
819                         qq{<td class="fviewborder" align="center">} . restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupnum'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'filepath'} )}, $file->{'backupnum'} ) . qq{</td>} .
820                         qq{<td class="fviewborder" align="right">} . $file->{'size'} . qq{</td>} .
821                         qq{<td class="fviewborder">} . epoch_to_iso( $file->{'date'} ) . qq{</td>} .
822                         qq{<td class="fviewborder">} . '?' . qq{</td>};
823
824                 $retHTML .= "</tr>";
825         }
826         $retHTML .= "</table>";
827
828         # all variables which has to be transfered
829         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/) {
830                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
831         }
832
833         my $del = '';
834         my $max_page = int( $results / $on_page );
835         my $page = 0;
836
837         sub page_link($$$) {
838                 my ($param,$page,$display) = @_;
839
840                 $param->{'offset'} = $page;
841
842                 my $html = '<a href = "' . $MyURL;
843                 my $del = '?';
844                 foreach my $k (keys %{ $param }) {
845                         if ($param->{$k}) {
846                                 $html .= $del . $k . '=' . ${EscURI( $param->{$k} )};
847                                 $del = '&';
848                         }
849                 }
850                 $html .= '">' . $display . '</a>';
851         }
852
853         $retHTML .= '<div style="text-align: center;">';
854
855         if ($offset > 0) {
856                 $retHTML .= page_link($param, $offset - 1, '&lt;&lt;') . ' ';
857         }
858
859         while ($page <= $max_page) {
860                 if ($page == $offset) {
861                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
862                 } else {
863                         $retHTML .= $del . page_link($param, $page, $page + 1);
864                 }
865
866                 if ($page < $offset - $pager_pages && $page != 0) {
867                         $retHTML .= " ... ";
868                         $page = $offset - $pager_pages;
869                         $del = '';
870                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
871                         $retHTML .= " ... ";
872                         $page = $max_page;
873                         $del = '';
874                 } else {
875                         $del = ' | ';
876                         $page++;
877                 }
878         }
879
880         if ($offset < $max_page) {
881                 $retHTML .= ' ' . page_link($param, $offset + 1, '&gt;&gt;');
882         }
883
884         $retHTML .= "</div>";
885
886         return $retHTML;
887 }
888
889 1;