0166748474b2753e7f5ba0400aac6cf13b98389a
[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         return $ret;
293         
294 }
295
296 sub getBackupsNotBurned() {
297
298         my $dbh = get_dbh();
299
300         my $sql = q{
301                 SELECT 
302                         backups.hostID AS hostID,
303                         hosts.name AS host,
304                         shares.name AS share,
305                         backups.id AS backupnum,
306                         backups.type AS type,
307                         backups.date AS date,
308                         backups.size AS size
309                 FROM backups 
310                 INNER JOIN shares       ON backups.shareID=shares.ID
311                 INNER JOIN hosts        ON backups.hostID = hosts.ID
312                 LEFT OUTER JOIN archive_backup ON archive_backup.backup_id = backups.id AND archive_backup.backup_id IS NULL
313                 WHERE backups.size > 0
314                 GROUP BY
315                         backups.hostID,
316                         hosts.name,
317                         shares.name,
318                         backups.num,
319                         backups.shareid,
320                         backups.id,
321                         backups.type,
322                         backups.date,
323                         backups.size
324                 ORDER BY backups.date
325         };
326         my $sth = $dbh->prepare( $sql );
327         my @ret;
328         $sth->execute();
329
330         while ( my $row = $sth->fetchrow_hashref() ) {
331                 $row->{'age'} = sprintf("%0.1f", ( (time() - $row->{'date'}) / 86400 ) );
332                 $row->{'size'} = sprintf("%0.2f", $row->{'size'} / 1024 / 1024);
333                 my (undef,undef,undef,undef,undef,undef,undef,$fs_size,undef,undef,undef,undef,undef) = 
334                         stat( $Conf{InstallDir}.'/'.$Conf{GzipTempDir}.'/'.
335                                 getGzipName($row->{'host'}, $row->{share}, $row->{'backupnum'}));
336                 $row->{'fs_size'} = $fs_size;
337                 push @ret, $row;
338         }
339       
340         return @ret;      
341 }
342
343 sub displayBackupsGrid() {
344
345         my $retHTML .= q{
346                 <form id="forma" method="POST" action=};
347                 $retHTML .= "\"".$MyURL."\"";
348                 $retHTML .= q{?action=burn>
349         };
350
351         $retHTML .= <<'EOF3';
352 <style>
353 <!--
354
355 div#fixedBox {
356         position: absolute;
357         bottom: 1em;
358         left: 0.5em;
359         padding: 0.5em;
360         width: 10em;
361         background: #e0f0e0;
362         border: 1px solid #00ff00;
363 }
364 @media screen {
365         div#fixedBox {
366                 position: fixed;
367         }
368         /* Don't do this at home */
369         * html {
370                 overflow-y: hidden;
371         }
372         * html body {
373                 overflow-y: auto;
374                 height: 100%;
375                 padding: 0 1em 0 12em;
376                 font-size: 100%;
377         }
378         * html div#fixedBox {
379                 position: absolute;     
380         }
381         /* All done. */
382 }
383
384 #mContainer {
385         position: relative;
386         width: 100%;
387         height: 1.1em;
388         padding: 0px;
389         border: 1px solid #000000;
390 }
391
392 #gradient {
393         position: absolute;
394         top: 0px;
395         left: 0px;
396         width: 100%;
397         height: 100%;
398         display: block;
399         background-color: #ffff00;
400 }
401
402 #mask {
403         position: absolute;
404         top: 0px;
405         left: 0px;
406         width: 100%;
407         height: 100%;
408         display: block;
409         font-size: 1px;
410         background-color: #FFFFFF;
411         overflow: hidden;
412 }
413
414 #progressIndicator {
415         position: absolute;
416         top: 0px;
417         left: 0px;
418         width: 100%;
419         height: 100%;
420         display: block;
421         font-weight: bold;
422         color: #404040;
423         font-size: 10pt;
424         text-align: center;
425 }
426
427 -->
428 </style>
429 <script language="javascript" type="text/javascript">
430 <!--
431
432 var debug_div = null;
433 var media_size = 4400 * 1024;
434
435 function debug(msg) {
436 //      return; // Disable debugging
437
438         if (! debug_div) debug_div = document.getElementById('debug');
439
440         // this will create debug div if it doesn't exist.
441         if (! debug_div) {
442                 debug_div = document.createElement('div');
443                 if (document.body) document.body.appendChild(debug_div);
444                 else debug_div = null;
445         }
446         if (debug_div) {
447                 debug_div.appendChild(document.createTextNode(msg));
448                 debug_div.appendChild(document.createElement("br"));
449         }
450 }
451
452
453 var element_id_cache = Array();
454
455 function element_id(name,element) {
456         if (! element_id_cache[name]) {
457                 element_id_cache[name] = self.document.getElementById(name);
458         }
459         return element_id_cache[name];
460 }
461
462 function checkAll(location) {
463         var len = element_id('forma').elements.length;
464         var check_all = element_id('allFiles');
465         var suma = 0;
466
467         for (var i = 0; i < len; i++) {
468
469                 var e = element_id('forma').elements[i];
470                 if (e.name != 'all' && e.name.substr(0,3) == 'fcb') {
471                         if (check_all.checked) {
472                                 var el = element_id("fss" + e.name.substr(3));
473                                 var size = parseInt(el.value) || 0;
474                                 debug('suma: '+suma+' size: '+size);
475                                 if ((suma + size) < media_size) {
476                                         suma += size;
477                                         e.checked = true;
478                                 } else {
479                                         break;
480                                 }
481                         } else {
482                                 e.checked = false;
483                         }
484                 }
485         }
486         update_sum(suma);
487 }
488
489 function update_sum(suma) {
490         element_id('forma').totalsize.value = suma;
491         pbar_set(suma, media_size);
492         debug('total size: '+suma);
493 }
494
495 function sumiraj(e) {
496         var suma = parseInt(element_id('forma').totalsize.value) || 0;
497         var len = element_id('forma').elements.length;
498         if (e) {
499                 var size = parseInt( element_id("fss" + e.name.substr(3)).value );
500                 if (e.checked) {
501                         suma += size;
502                 } else {
503                         suma -= size;
504                 }
505         } else {
506                 suma = 0;
507                 for (var i = 0; i < len; i++) {
508                         var e = element_id('forma').elements[i];
509                         if (e.name != 'all' && e.checked && e.name.substr(0,3) == 'fcb') {
510                                 var el = element_id("fss" + e.name.substr(3));
511                                 if (el && el.value) suma += parseInt(el.value) || 0;
512                         }
513                 }
514         }
515         update_sum(suma);
516         return suma;
517 }
518
519 /* progress bar */
520
521 var _pbar_width = 0;
522 var _pbar_warn = 10;    // change color in last 10%
523
524 function pbar_reset() {
525         element_id("mask").style.left = "0px";
526         _pbar_width = element_id("mContainer").offsetWidth - 2;
527         element_id("mask").style.width = _pbar_width + "px";
528         element_id("progressIndicator").style.zIndex  = 10;
529         element_id("mask").style.display = "block";
530         element_id("progressIndicator").innerHTML = "0";
531 }
532
533 function dec2hex(d) {
534         var hch="0123456789ABCDEF";
535         var a=d%16;
536         var q=(d-a)/16;
537         return hch.charAt(q)+hch.charAt(a);
538 }
539
540
541 function pbar_set(amount, max) {
542
543         debug('pbar_set( '+amount+' , '+max+' )');
544
545         curWidth = parseInt(element_id("mask").offsetWidth);
546         curLeft = parseInt(element_id("mask").offsetLeft);
547
548
549         var pcnt = Math.floor( amount * 100 / max );
550         var p90 = 100 - _pbar_warn;
551         var pcol = pcnt - p90;
552         if (pcol < _pbar_warn) {
553                 if (pcol < 0) pcol = 0;
554                 var e = element_id("submitBurner");
555                 if (e && e.disabled) {
556                         debug('enable_button');
557                         var a = e.getAttributeNode('disabled') || null;
558                         if (a) e.removeAttributeNode(a);
559                 }
560         } else if (pcol > _pbar_warn) {
561                 debug('disable button');
562                 pcol = _pbar_warn;
563                 var e = element_id("submitBurner");
564                 if (! e.disabled) e.disabled = true;
565         }
566         var col_g = Math.floor( ( _pbar_warn - pcol ) * 255 / _pbar_warn );
567         var col = '#ff' + dec2hex( col_g ) + '00';
568
569         //debug('pcol: '+pcol+' g:'+col_g+' _pbar_warn:'+ _pbar_warn + ' color: '+col);
570         element_id("gradient").style.backgroundColor = col;
571
572         var size = parseInt( _pbar_width * amount / max );
573
574         curWidth = _pbar_width - size;
575         curLeft = size ;
576
577         //debug('size: '+size+' curWidth '+curWidth+' curLeft: '+curLeft);
578
579         element_id("progressIndicator").innerHTML = pcnt + '%';
580         //element_id("progressIndicator").innerHTML = amount;
581
582         if (curLeft > _pbar_width) {
583                 element_id("mask").style.display = "none";
584                 return;
585         } else {
586                 element_id("mask").style.display = "";
587         }
588
589         //if(parseInt(element_id("mask").offsetWidth)>10)
590         element_id("mask").style.width = curWidth + "px";
591         element_id("mask").style.left = curLeft + "px";
592
593 }
594
595 if (!self.body) self.body = new Object();
596 self.onload = self.document.onload = self.body.onload = function() {
597         pbar_reset();
598         sumiraj();
599 }
600
601 //-->
602 </script>
603 <div id="fixedBox">
604
605 Size:
606 <input type="text" name="totalsize" size="7" readonly>
607
608 <div id="mContainer">
609         <div id="gradient"></div>
610         <div id="mask"></div>
611         <div id="progressIndicator">&nbsp;</div>
612 </div>
613
614 <br/>
615 Note:
616 <br/>
617 <textarea name="note" cols="10" rows="5">
618 </textarea>
619 <br/>
620 <input type="submit" id="submitBurner" value="Burn selected" name="submitBurner">
621
622 </div>
623 <div id="debug" style="float: right; width: 10em; border: 1px #ff0000 solid; background-color: #ffe0e0; -moz-opacity: 0.7;">
624 no debug output yet
625 </div>
626 EOF3
627         $retHTML .= q{
628                         <input type="hidden" value="burn" name="action">
629                         <input type="hidden" value="results" name="search_results">
630                         <table style="fview" border="0" cellspacing="0" cellpadding="2">
631                         <tr class="tableheader">
632                         <td class="tableheader">
633                                 <input type="checkbox" name="allFiles" id="allFiles" onClick="checkAll('allFiles');">
634                         </td>
635                         <td align="center">Share</td>
636                         <td align="center">Backup no</td>
637                         <td align="center">Type</td>
638                         <td align="center">date</td>
639                         <td align="center">age/days</td>
640                         <td align="center">size/MB</td>
641                         <td align="center">gzip size</td>
642                         </tr>
643
644         };
645
646         my @color = (' bgcolor="#e0e0e0"', '');
647
648         my $i = 0;
649         my $host = '';
650
651         foreach my $backup ( getBackupsNotBurned() ) {
652
653                 if ($host ne $backup->{'host'}) {
654                         $i++;
655                         $host = $backup->{'host'};
656                 }
657                 my $ftype = "";
658
659                 $retHTML .=
660                         '<tr' . $color[$i %2 ] . '>
661                         <td class="fview">';
662                 # FIXME
663                 $backup->{'fs_size'} = int($backup->{'size'} * 1024);
664                 if (($backup->{'fs_size'} || 0) > 0) {
665                         $retHTML .= '
666                         <input type="checkbox" name="fcb' .
667                         $backup->{'hostid'}.'_'.$backup->{'backupnum'} . 
668                         '" value="' . $backup->{'hostid'}.'_'.$backup->{'backupnum'} .
669                         '" onClick="sumiraj(this);">';
670                 }
671                 $retHTML .=
672                         '</td>' .
673                         '<td align="right">' . $backup->{'host'} . ':' . $backup->{'share'} . '</td>' .
674                         '<td align="center">' . $backup->{'backupnum'} . '</td>' .
675                         '<td align="center">' . $backup->{'type'} . '</td>' .
676                         '<td align="center">' . epoch_to_iso( $backup->{'date'} ) . '</td>' .
677                         '<td align="center">' . $backup->{'age'} . '</td>' .
678                         '<td align="right">' . $backup->{'size'} . '</td>' .
679                         '<td align="right">' . $backup->{'fs_size'} .
680                         '<input type="hidden" iD="fss'.$backup->{'hostid'}.'_'.$backup->{'backupnum'} . '" value="'. $backup->{'fs_size'} .'"></td>' .
681
682                         "</tr>\n";
683         }
684
685         $retHTML .= "</table>";
686         $retHTML .= "</form>";
687       
688         return $retHTML;
689 }      
690
691 sub displayGrid($) {
692         my ($param) = @_;
693
694         my $offset = $param->{'offset'};
695         my $hilite = $param->{'search_filename'};
696
697         my $retHTML = "";
698  
699         my $start_t = time();
700
701         my ($results, $files);
702         if ($param->{'use_hest'} && length($hilite) > 0) {
703                 ($results, $files) = getFilesHyperEstraier($param);
704         } else {
705                 ($results, $files) = getFiles($param);
706         }
707
708         my $dur_t = time() - $start_t;
709         my $dur = sprintf("%0.4fs", $dur_t);
710
711         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
712
713         if ($results <= 0) {
714                 $retHTML .= qq{
715                         <p style="color: red;">No results found...</p>
716                 };
717                 return $retHTML;
718         } else {
719                 # DEBUG
720                 #use Data::Dumper;
721                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
722         }
723
724
725         $retHTML .= qq{
726         <div>
727         Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
728         </div>
729         <table style="fview" width="100%" border="0" cellpadding="2" cellspacing="0">
730                 <tr class="fviewheader"> 
731                 <td></td>
732                 <td align="center">Share</td>
733                 <td align="center">Type and Name</td>
734                 <td align="center">#</td>
735                 <td align="center">Size</td>
736                 <td align="center">Date</td>
737                 <td align="center">Media</td>
738                 </tr>
739         };
740
741         my $file;
742
743         sub hilite_html($$) {
744                 my ($html, $search) = @_;
745                 $html =~ s#($search)#<b>$1</b>#gis;
746                 return $html;
747         }
748
749         sub restore_link($$$$$$) {
750                 my $type = shift;
751                 my $action = 'RestoreFile';
752                 $action = 'browse' if (lc($type) eq 'dir');
753                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
754         }
755
756         my $i = $offset * $on_page;
757
758         foreach $file (@{ $files }) {
759                 $i++;
760
761                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
762                 $retHTML .= qq{<tr class="fviewborder">};
763
764                 $retHTML .= qq{<td class="fviewborder">$i</td>};
765
766                 $retHTML .=
767                         qq{<td class="fviewborder" align="right">} . $file->{'hname'} . ':' . $file->{'sname'} . qq{</td>} .
768                         qq{<td class="fviewborder"><img src="$Conf{CgiImageDirURL}/icon-$typeStr.gif" alt="$typeStr" align="middle">&nbsp;} . hilite_html( $file->{'filepath'}, $hilite ) . qq{</td>} .
769                         qq{<td class="fviewborder" align="center">} . restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupnum'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'filepath'} )}, $file->{'backupnum'} ) . qq{</td>} .
770                         qq{<td class="fviewborder" align="right">} . $file->{'size'} . qq{</td>} .
771                         qq{<td class="fviewborder">} . epoch_to_iso( $file->{'date'} ) . qq{</td>} .
772                         qq{<td class="fviewborder">} . '?' . qq{</td>};
773
774                 $retHTML .= "</tr>";
775         }
776         $retHTML .= "</table>";
777
778         # all variables which has to be transfered
779         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/) {
780                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
781         }
782
783         my $del = '';
784         my $max_page = int( $results / $on_page );
785         my $page = 0;
786
787         sub page_link($$$) {
788                 my ($param,$page,$display) = @_;
789
790                 $param->{'offset'} = $page;
791
792                 my $html = '<a href = "' . $MyURL;
793                 my $del = '?';
794                 foreach my $k (keys %{ $param }) {
795                         if ($param->{$k}) {
796                                 $html .= $del . $k . '=' . ${EscURI( $param->{$k} )};
797                                 $del = '&';
798                         }
799                 }
800                 $html .= '">' . $display . '</a>';
801         }
802
803         $retHTML .= '<div style="text-align: center;">';
804
805         if ($offset > 0) {
806                 $retHTML .= page_link($param, $offset - 1, '&lt;&lt;') . ' ';
807         }
808
809         while ($page <= $max_page) {
810                 if ($page == $offset) {
811                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
812                 } else {
813                         $retHTML .= $del . page_link($param, $page, $page + 1);
814                 }
815
816                 if ($page < $offset - $pager_pages && $page != 0) {
817                         $retHTML .= " ... ";
818                         $page = $offset - $pager_pages;
819                         $del = '';
820                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
821                         $retHTML .= " ... ";
822                         $page = $max_page;
823                         $del = '';
824                 } else {
825                         $del = ' | ';
826                         $page++;
827                 }
828         }
829
830         if ($offset < $max_page) {
831                 $retHTML .= ' ' . page_link($param, $offset + 1, '&gt;&gt;');
832         }
833
834         $retHTML .= "</div>";
835
836         return $retHTML;
837 }
838
839 1;