Merged changes upstream
authorscollett <stephanie.collett@ucop.edu>
Tue, 9 Mar 2010 17:50:23 +0000 (09:50 -0800)
committerscollett <stephanie.collett@ucop.edu>
Tue, 9 Mar 2010 17:50:23 +0000 (09:50 -0800)
14 files changed:
BookReader/BookReader.css
BookReader/BookReader.js
BookReaderIA/Makefile [new file with mode: 0644]
BookReaderIA/datanode/BookReaderImages.php
BookReaderIA/datanode/BookReaderJSIA.php
BookReaderIA/inc/BookReader.inc
BookReaderIA/inc/FlipSearchMap.inc
BookReaderIA/sendtoswarm.pl [new file with mode: 0755]
BookReaderIA/test/index.html [new file with mode: 0644]
BookReaderIA/test/unit/Images.js [new file with mode: 0644]
BookReaderIA/test/unit/JSLocate.js [new file with mode: 0644]
BookReaderIA/www/BookReaderJSLocate.php
BookReaderIA/www/browserunsupported.php
README.txt

index 24191c4..c81dac9 100644 (file)
@@ -157,9 +157,9 @@ a.BRicon {
     vertical-align: middle; 
 }
 
-a.BRwhite                      { color: #fff }
-a.BRwhite:hover                { text-decoration: none; }
-a.BRwhite:visited              { color: #fff }
+a.BRwhite               { color: #fff }
+a.BRwhite:hover         { text-decoration: none; }
+a.BRwhite:visited       { color: #fff }
 
 a.BRblack           { color: #000000 }
 a.BRblack:hover     { text-decoration: none; }
index b24d57e..0c101a3 100644 (file)
@@ -42,9 +42,8 @@ function BookReader() {
     this.ui = 'full'; // UI mode
     this.thumbWidth = 100;
     this.thumbRowBuffer = 3; // number of rows to pre-cache out a view
-
-    this.displayedIndices = [];        
     this.displayedRows=[];
+    this.displayedIndices = [];        
     //this.indicesToDisplay = [];
     this.imgs = {};
     this.prefetchedImgs = {}; //an object with numeric keys cooresponding to page index
@@ -2379,13 +2378,13 @@ BookReader.prototype.search = function(term) {
     term = term.replace(/\//g, ' '); // strip slashes
     this.searchTerm = term;
     $('#BookReaderSearchScript').remove();
-       var script  = document.createElement("script");
-       script.setAttribute('id', 'BookReaderSearchScript');
-       script.setAttribute("type", "text/javascript");
-       script.setAttribute("src", 'http://'+this.server+'/BookReader/flipbook_search_br.php?url='+escape(this.bookPath + '_djvu.xml')+'&term='+term+'&format=XML&callback=br.BRSearchCallback');
-       document.getElementsByTagName('head')[0].appendChild(script);
-       $('#BookReaderSearchBox').val(term);
-       $('#BookReaderSearchResults').html('Searching...');
+    var script  = document.createElement("script");
+    script.setAttribute('id', 'BookReaderSearchScript');
+    script.setAttribute("type", "text/javascript");
+    script.setAttribute("src", 'http://'+this.server+'/BookReader/flipbook_search_br.php?url='+escape(this.bookPath + '_djvu.xml')+'&term='+term+'&format=XML&callback=br.BRSearchCallback');
+    document.getElementsByTagName('head')[0].appendChild(script);
+    $('#BookReaderSearchBox').val(term);
+    $('#BookReaderSearchResults').html('Searching...');
 }
 
 // BRSearchCallback()
@@ -2457,7 +2456,7 @@ BookReader.prototype.BRSearchCallback = function(txt) {
     $('#BookReaderSearchResults').append('</ul>');
 
     // $$$ update again for case of loading search URL in new browser window (search box may not have been ready yet)
-       $('#BookReaderSearchBox').val(this.searchTerm);
+    $('#BookReaderSearchBox').val(this.searchTerm);
 
     this.updateSearchHilites();
 }
@@ -3603,6 +3602,27 @@ BookReader.prototype._getPageURI = function(index, reduce, rotate) {
         return this.imagesBaseURL + "/transparent.png";
     }
     
+    if ('undefined' == typeof(reduce)) {
+        // reduce not passed in
+        var ratio = this.getPageHeight(index) / this.twoPage.height;
+        var scale;
+        // $$$ we make an assumption here that the scales are available pow2 (like kakadu)
+        if (ratio < 2) {
+            scale = 1;
+        } else if (ratio < 4) {
+            scale = 2;
+        } else if (ratio < 8) {
+            scale = 4;
+        } else if (ratio < 16) {
+            scale = 8;
+        } else  if (ratio < 32) {
+            scale = 16;
+        } else {
+            scale = 32;
+        }
+        reduce = scale;
+    }
+    
     return this.getPageURI(index, reduce, rotate);
 }
 
diff --git a/BookReaderIA/Makefile b/BookReaderIA/Makefile
new file mode 100644 (file)
index 0000000..350913b
--- /dev/null
@@ -0,0 +1,6 @@
+test:
+       @ echo Made tests
+
+
+
+
index 8f4e7fe..2895724 100644 (file)
@@ -1,9 +1,14 @@
 <?php
 
 /*
-Copyright(c)2008 Internet Archive. Software license AGPL version 3.
+Copyright(c) 2008-2010 Internet Archive. Software license AGPL version 3.
 
-This file is part of BookReader.
+This file is part of BookReader.  The full source code can be found at GitHub:
+http://github.com/openlibrary/bookreader
+
+The canonical short name of an image type is the same as in the MIME type.
+For example both .jpeg and .jpg are considered to have type "jpeg" since
+the MIME type is "image/jpeg".
 
     BookReader is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -19,11 +24,173 @@ This file is part of BookReader.
     along with BookReader.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-$MIMES = array('jpg' => 'image/jpeg',
-               'png' => 'image/png');
+$MIMES = array('gif' => 'image/gif',
+               'jp2' => 'image/jp2',
+               'jpg' => 'image/jpeg',
+               'jpeg' => 'image/jpeg',
+               'png' => 'image/png',
+               'tif' => 'image/tiff',
+               'tiff' => 'image/tiff');
+               
+$EXTENSIONS = array('gif' => 'gif',
+                    'jp2' => 'jp2',
+                    'jpeg' => 'jpeg',
+                    'jpg' => 'jpeg',
+                    'png' => 'png',
+                    'tif' => 'tiff',
+                    'tiff' => 'tiff');
+               
+$exiftool = '/petabox/sw/books/exiftool/exiftool';
 
+// Process some of the request parameters
 $zipPath  = $_REQUEST['zip'];
 $file     = $_REQUEST['file'];
+if (isset($_REQUEST['ext'])) {
+    $ext = $_REQUEST['ext'];
+} else {
+    // Default to jpg
+    $ext = 'jpeg';
+}
+if (isset($_REQUEST['callback'])) {
+    // validate callback is valid JS identifier (only)
+    $callback = $_REQUEST['callback'];
+    $identifierPatt = '/^[[:alpha:]$_]([[:alnum:]$_])*$/';
+    if (! preg_match($identifierPatt, $callback)) {
+        BRfatal('Invalid callback');
+    }
+} else {
+    $callback = null;
+}
+
+/*
+ * Approach:
+ * 
+ * Get info about requested image (input)
+ * Get info about requested output format
+ * Determine processing parameters
+ * Process image
+ * Return image data
+ * Clean up temporary files
+ */
+function getUnarchiveCommand($archivePath, $file)
+{
+    $lowerPath = strtolower($archivePath);
+    if (preg_match('/\.([^\.]+)$/', $lowerPath, $matches)) {
+        $suffix = $matches[1];
+        
+        if ($suffix == 'zip') {
+            return 'unzip -p '
+                . escapeshellarg($archivePath)
+                . ' ' . escapeshellarg($file);
+        } else if ($suffix == 'tar') {
+            return '7z e -so '
+                . escapeshellarg($archivePath)
+                . ' ' . escapeshellarg($file);
+        } else {
+            BRfatal('Incompatible archive format');
+        }
+
+    } else {
+        BRfatal('Bad image stack path');
+    }
+    
+    BRfatal('Bad image stack path or archive format');
+    
+}
+
+/*
+ * Returns the image type associated with the file extension.
+ */
+function imageExtensionToType($extension)
+{
+    global $EXTENSIONS;
+    
+    if (array_key_exists($extension, $EXTENSIONS)) {
+        return $EXTENSIONS[$extension];
+    } else {
+        BRfatal('Unknown image extension');
+    }            
+}
+
+/*
+ * Get the image width, height and depth from a jp2 file in zip.
+ */
+function getImageInfo($zipPath, $file)
+{
+    global $exiftool;
+    
+    $fileExt = strtolower(pathinfo($file, PATHINFO_EXTENSION));
+    $type = imageExtensionToType($fileExt);
+     
+    // We look for all the possible tags of interest then act on the
+    // ones presumed present based on the file type
+    $tagsToGet = ' -ImageWidth -ImageHeight -FileType'        // all formats
+                 . ' -BitsPerComponent -ColorSpace'          // jp2
+                 . ' -BitDepth'                              // png
+                 . ' -BitsPerSample';                        // tiff
+                        
+    $cmd = getUnarchiveCommand($zipPath, $file)
+        . ' | '. $exiftool . ' -S -fast' . $tagsToGet . ' -';
+    exec($cmd, $output);
+    
+    $tags = Array();
+    foreach ($output as $line) {
+        $keyValue = explode(": ", $line);
+        $tags[$keyValue[0]] = $keyValue[1];
+    }
+    
+    $width = intval($tags["ImageWidth"]);
+    $height = intval($tags["ImageHeight"]);
+    $type = strtolower($tags["FileType"]);
+    
+    switch ($type) {
+        case "jp2":
+            $bits = intval($tags["BitsPerComponent"]);
+            break;
+        case "tiff":
+            $bits = intval($tags["BitsPerSample"]);
+            break;
+        case "jpeg":
+            $bits = 8;
+            break;
+        case "png":
+            $bits = intval($tags["BitDepth"]);
+            break;
+        default:
+            BRfatal("Unsupported image type");
+            break;
+    }
+   
+   
+    $retval = Array('width' => $width, 'height' => $height,
+        'bits' => $bits, 'type' => $type);
+    
+    return $retval;
+}
+
+/*
+ * Output JSON given the imageInfo associative array
+ */
+function outputJSON($imageInfo, $callback)
+{
+    header('Content-type: text/plain');
+    $jsonOutput = json_encode($imageInfo);
+    if ($callback) {
+        $jsonOutput = $callback . '(' . $jsonOutput . ');';
+    }
+    echo $jsonOutput;
+}
+
+// Get the image size and depth
+$imageInfo = getImageInfo($zipPath, $file);
+
+// Output json if requested
+if ('json' == $ext) {
+    // $$$ we should determine the output size first based on requested scale
+    outputJSON($imageInfo, $callback);
+    exit;
+}
 
 // Unfortunately kakadu requires us to know a priori if the
 // output file should be .ppm or .pgm.  By decompressing to
@@ -38,13 +205,6 @@ if ($decompressToBmp) {
   $stdoutLink = '/tmp/stdout.ppm';
 }
 
-if (isset($_REQUEST['ext'])) {
-  $ext = $_REQUEST['ext'];
-} else {
-  // Default to jpg
-  $ext = 'jpg';
-}
-
 $fileExt = strtolower(pathinfo($file, PATHINFO_EXTENSION));
 
 // Rotate is currently only supported for jp2 since it does not add server load
@@ -60,7 +220,7 @@ $jpegOptions = '-quality 75';
 
 // The pbmreduce reduction factor produces an image with dimension 1/n
 // The kakadu reduction factor produceds an image with dimension 1/(2^n)
-
+// $$$ handle continuous values for scale
 if (isset($_REQUEST['height'])) {
     $ratio = floatval($_REQUEST['origHeight']) / floatval($_REQUEST['height']);
     if ($ratio <= 2) {
@@ -97,6 +257,22 @@ if (isset($_REQUEST['height'])) {
     }
 }
 
+// Override depending on source image format
+// $$$ consider doing a 302 here instead, to make better use of the browser cache
+// Limit scaling for 1-bit images.  See https://bugs.edge.launchpad.net/bookreader/+bug/486011
+if (1 == $imageInfo['bits']) {
+    if ($scale > 1) {
+        $scale /= 2;
+        $powReduce -= 1;
+        
+        // Hard limit so there are some black pixels to use!
+        if ($scale > 4) {
+            $scale = 4;
+            $powReduce = 2;
+        }
+    }
+}
+
 if (!file_exists($stdoutLink)) 
 {  
   system('ln -s /dev/stdout ' . $stdoutLink);  
@@ -105,39 +281,40 @@ if (!file_exists($stdoutLink))
 
 putenv('LD_LIBRARY_PATH=/petabox/sw/lib/kakadu');
 
-$unzipCmd  = 'unzip -p ' . 
-        escapeshellarg($zipPath) .
-        ' ' . escapeshellarg($file);
-        
-if ('jp2' == $fileExt) {
-    $decompressCmd = 
-        " | /petabox/sw/bin/kdu_expand -no_seek -quiet -reduce $powReduce -rotate $rotate -i /dev/stdin -o " . $stdoutLink;
-    if ($decompressToBmp) {
-        $decompressCmd .= ' | bmptopnm ';
-    }
-    
-} else if ('tif' == $fileExt) {
-    // We need to create a temporary file for tifftopnm since it cannot
-    // work on a pipe (the file must be seekable).
-    // We use the BookReaderTiff prefix to give a hint in case things don't
-    // get cleaned up.
-    $tempFile = tempnam("/tmp", "BookReaderTiff");
-    
-    if (1 != $scale) {
-        if (onPowerNode()) {
-            $pbmReduce = ' | pnmscale -reduce ' . $scale;
-        } else {
-            $pbmReduce = ' | pnmscale -nomix -reduce ' . $scale;
+$unzipCmd  = getUnarchiveCommand($zipPath, $file);
+
+switch ($imageInfo['type']) {
+    case 'jp2':
+        $decompressCmd = 
+            " | /petabox/sw/bin/kdu_expand -no_seek -quiet -reduce $powReduce -rotate $rotate -i /dev/stdin -o " . $stdoutLink;
+        if ($decompressToBmp) {
+            $decompressCmd .= ' | bmptopnm ';
         }
-    } else {
-        $pbmReduce = '';
-    }
+        break;
+
+    case 'tiff':
+        // We need to create a temporary file for tifftopnm since it cannot
+        // work on a pipe (the file must be seekable).
+        // We use the BookReaderTiff prefix to give a hint in case things don't
+        // get cleaned up.
+        $tempFile = tempnam("/tmp", "BookReaderTiff");
     
-    $decompressCmd = 
-        ' > ' . $tempFile . ' ; tifftopnm ' . $tempFile . ' 2>/dev/null' . $pbmReduce;
+        // $$$ look at bit depth when reducing
+        $decompressCmd = 
+            ' > ' . $tempFile . ' ; tifftopnm ' . $tempFile . ' 2>/dev/null' . reduceCommand($scale);
+        break;
+    case 'jpeg':
+        $decompressCmd = ' | jpegtopnm ' . reduceCommand($scale);
+        break;
 
-} else {
-    BRfatal('Unknown source file extension: ' . $fileExt);
+    case 'png':
+        $decompressCmd = ' | pngtopnm ' . reduceCommand($scale);
+        break;
+        
+    default:
+        BRfatal('Unknown source file extension: ' . $fileExt);
+        break;
 }
        
 // Non-integer scaling is currently disabled on the cluster
@@ -145,27 +322,41 @@ if ('jp2' == $fileExt) {
 //     $cmd .= " | pnmscale -height {$_REQUEST['height']} ";
 // }
 
-if ('jpg' == $ext) {
-    $compressCmd = ' | pnmtojpeg ' . $jpegOptions;
-} else if ('png' == $ext) {
-    $compressCmd = ' | pnmtopng ' . $pngOptions;
+switch ($ext) {
+    case 'png':
+        $compressCmd = ' | pnmtopng ' . $pngOptions;
+        break;
+        
+    case 'jpeg':
+    case 'jpg':
+    default:
+        $compressCmd = ' | pnmtojpeg ' . $jpegOptions;
+        $ext = 'jpeg'; // for matching below
+        break;
+
+}
+
+if (($ext == $fileExt) && ($scale == 1) && ($rotate === "0")) {
+    // Just pass through original data if same format and size
+    $cmd = $unzipCmd;
+} else {
+    $cmd = $unzipCmd . $decompressCmd . $compressCmd;
 }
 
-$cmd = $unzipCmd . $decompressCmd . $compressCmd;
+# print $cmd;
 
-//print $cmd;
 
+// $$$ investigate how to flush cache when this file is changed
 header('Content-type: ' . $MIMES[$ext]);
 header('Cache-Control: max-age=15552000');
-
-passthru ($cmd);
+passthru ($cmd); # cmd returns image data
 
 if (isset($tempFile)) {
-  unlink($tempFile);
+    unlink($tempFile);
 }
 
 function BRFatal($string) {
-    echo "alert('$string')\n";
+    echo "alert('$string');\n";
     die(-1);
 }
 
@@ -183,6 +374,18 @@ function onPowerNode() {
     return false;
 }
 
+function reduceCommand($scale) {
+    if (1 != $scale) {
+        if (onPowerNode()) {
+            return ' | pnmscale -reduce ' . $scale;
+        } else {
+            return ' | pnmscale -nomix -reduce ' . $scale;
+        }
+    } else {
+        return '';
+    }
+}
+
 
 ?>
 
index 7a29e83..730249a 100755 (executable)
@@ -31,11 +31,10 @@ if (strpos($_SERVER["REQUEST_URI"], "/~mang") === 0) { // Serving out of home di
     $server .= ':80/~testflip';
 }
 
-if ($subPrefix) {
-    $subItemPath = $itemPath . '/' . $subPrefix;
-} else {
-    $subItemPath = $itemPath . '/' . $id;
+if (! $subPrefix) {
+    $subPrefix = $id;
 }
+$subItemPath = $itemPath . '/' . $subPrefix;
 
 if ("" == $id) {
     BRFatal("No identifier specified!");
@@ -49,28 +48,38 @@ if ("" == $server) {
     BRFatal("No server specified!");
 }
 
-if (!preg_match("|^/[0-3]/items/{$id}$|", $itemPath)) {
+if (!preg_match("|^/\d+/items/{$id}$|", $itemPath)) {
     BRFatal("Bad id!");
 }
 
 // XXX check here that subitem is okay
 
-$imageFormat = 'unknown';
-$zipFile = "${subItemPath}_jp2.zip";
+$filesDataFile = "$itemPath/${id}_files.xml";
 
-if (file_exists($zipFile)) {
-    $imageFormat = 'jp2';
+if (file_exists($filesDataFile)) {
+    $filesData = simplexml_load_file("$itemPath/${id}_files.xml");
 } else {
-  $zipFile = "${subItemPath}_tif.zip";
-  if (file_exists($zipFile)) {
-    $imageFormat = 'tif';
-  }
+    BRfatal("File metadata not found!");
+}
+
+$imageStackInfo = findImageStack($subPrefix, $filesData);
+if ($imageStackInfo['imageFormat'] == 'unknown') {
+    BRfatal('Couldn\'t find image stack');
 }
 
+$imageFormat = $imageStackInfo['imageFormat'];
+$archiveFormat = $imageStackInfo['archiveFormat'];
+$imageStackFile = $itemPath . "/" . $imageStackInfo['imageStackFile'];
+
 if ("unknown" == $imageFormat) {
   BRfatal("Unknown image format");
 }
 
+if ("unknown" == $archiveFormat) {
+  BRfatal("Unknown archive format");
+}
+
+
 $scanDataFile = "${subItemPath}_scandata.xml";
 $scanDataZip  = "$itemPath/scandata.zip";
 if (file_exists($scanDataFile)) {
@@ -150,34 +159,7 @@ br.getPageURI = function(index, reduce, rotate) {
     var file = this._getPageFile(index);
         
     // $$$ add more image stack formats here
-    if (1==this.mode) {
-        var url = 'http://'+this.server+'/BookReader/BookReaderImages.php?zip='+this.zip+'&file='+file+'&scale='+_reduce+'&rotate='+_rotate;
-    } else {
-        if ('undefined' == typeof(reduce)) {
-            // reduce not passed in
-            var ratio = this.getPageHeight(index) / this.twoPage.height;
-            var scale;
-            // $$$ we make an assumption here that the scales are available pow2 (like kakadu)
-            if (ratio < 2) {
-                scale = 1;
-            } else if (ratio < 4) {
-                scale = 2;
-            } else if (ratio < 8) {
-                scale = 4;
-            } else if (ratio < 16) {
-                scale = 8;
-            } else  if (ratio < 32) {
-                scale = 16;
-            } else {
-                scale = 32;
-            }
-            _reduce = scale;
-        }
-    
-        var url = 'http://'+this.server+'/BookReader/BookReaderImages.php?zip='+this.zip+'&file='+file+'&scale='+_reduce+'&rotate='+_rotate;
-        
-    }
-    return url;
+    return 'http://'+this.server+'/BookReader/BookReaderImages.php?zip='+this.zip+'&file='+file+'&scale='+_reduce+'&rotate='+_rotate;
 }
 
 br._getPageFile = function(index) {
@@ -243,12 +225,13 @@ br.getPageNum = function(index) {
 }
 
 br.leafNumToIndex = function(leafNum) {
-    var index = jQuery.inArray(leafNum, this.leafMap);
-    if (-1 == index) {
-        return null;
-    } else {
-        return index;
+    for (var index = 0; index < this.leafMap.length; index++) {
+        if (this.leafMap[index] == leafNum) {
+            return index;
+        }
     }
+    
+    return null;
 }
 
 // This function returns the left and right indices for the user-visible
@@ -328,7 +311,7 @@ br.getEmbedCode = function() {
     return "<iframe src='" + this.getEmbedURL() + "' width='480px' height='430px'></iframe>";
 }
 
-br.pageW =             [
+br.pageW =  [
             <?
             $i=0;
             foreach ($scanData->pageData->page as $page) {
@@ -341,7 +324,7 @@ br.pageW =          [
             ?>
             ];
 
-br.pageH =             [
+br.pageH =  [
             <?
             $totalHeight = 0;
             $i=0;            
@@ -389,13 +372,14 @@ br.pageNums = [
 br.numLeafs = br.pageW.length;
 
 br.bookId   = '<?echo $id;?>';
-br.zip      = '<?echo $zipFile;?>';
+br.zip      = '<?echo $imageStackFile;?>';
 br.subPrefix = '<?echo $subPrefix;?>';
 br.server   = '<?echo $server;?>';
 br.bookTitle= '<?echo preg_replace("/\'/", "\\'", $metaData->title);?>';
 br.bookPath = '<?echo $subItemPath;?>';
 br.bookUrl  = '<?echo "http://www.archive.org/details/$id";?>';
 br.imageFormat = '<?echo $imageFormat;?>';
+br.archiveFormat = '<?echo $archiveFormat;?>';
 
 <?
 
@@ -449,6 +433,7 @@ br.init();
 
 
 function BRFatal($string) {
+    // $$$ TODO log error
     echo "alert('$string')\n";
     die(-1);
 }
@@ -467,4 +452,34 @@ function shouldAddPage($page) {
     return true;
 }
 
+// Returns { 'imageFormat' => , 'archiveFormat' => '} given a sub-item prefix and loaded xml data
+function findImageStack($subPrefix, $filesData) {
+
+    // $$$ The order of the image formats determines which will be returned first
+    $imageFormats = array('JP2' => 'jp2', 'TIFF' => 'tif', 'JPEG' => 'jpg');
+    $archiveFormats = array('ZIP' => 'zip', 'Tar' => 'tar');
+    $imageGroup = implode('|', array_keys($imageFormats));
+    $archiveGroup = implode('|', array_keys($archiveFormats));
+    // $$$ Currently only return processed images
+    $imageStackRegex = "/Single Page (Processed) (${imageGroup}) (${archiveGroup})/";
+        
+    foreach ($filesData->file as $file) {        
+        if (strpos($file['name'], $subPrefix) === 0) { // subprefix matches beginning
+            if (preg_match($imageStackRegex, $file->format, $matches)) {
+            
+                // Make sure we have a regular image stack
+                $imageFormat = $imageFormats[$matches[2]];
+                if (strpos($file['name'], $subPrefix . '_' . $imageFormat) === 0) {            
+                    return array('imageFormat' => $imageFormat,
+                                 'archiveFormat' => $archiveFormats[$matches[3]],
+                                 'imageStackFile' => $file['name']);
+                }
+            }
+        }
+    }
+    
+    return array('imageFormat' => 'unknown', 'archiveFormat' => 'unknown', 'imageStackFile' => 'unknown');
+        
+}
+
 ?>
index d61de5e..1b2606f 100644 (file)
@@ -19,12 +19,12 @@ class BookReader
     // $$$ TODO add support for jpg and tar stacks
     // https://bugs.edge.launchpad.net/gnubook/+bug/323003
     // https://bugs.edge.launchpad.net/gnubook/+bug/385397
-    $imageFormatRegex = '@' . preg_quote($prefix, '@') . '_(jp2|tif)\.zip$@';
+    $imageFormatRegex = '@' . preg_quote($prefix, '@') . '_(jp2|tif|jpg)\.(zip|tar)$@';
     
     $baseLength = strlen($item->metadataGrabber->mainDir . '/');
     foreach ($item->getFiles() as $location => $fileInfo) {
         $filename = substr($location, $baseLength);
-
+        
         if ($checkOldScandata) {
             if ($filename == 'scandata.xml' || $filename == 'scandata.zip') {
                 $foundScandata = $filename;
@@ -39,7 +39,7 @@ class BookReader
             $foundImageStack = $filename;
         }
     }
-        
+    
     if ($foundScandata && $foundImageStack) {
         return true;
     }
@@ -81,7 +81,7 @@ class BookReader
     // manually update with Launchpad version number at each checkin so that browsers
     // do not use old cached version
     // see https://bugs.launchpad.net/gnubook/+bug/330748
-    $version = "0.9.18";
+    $version = "0.9.21";
     
     if ("" == $id) {
         echo "No identifier specified!";
@@ -163,22 +163,29 @@ class BookReader
   <?
     exit;
   }
+
   
-  public static function jsMetadataURL($server, $identifier, $mainDir, $subPrefix = '')
+  public static function serverBaseURL($server)
   {
-    $serverBaseURL = $server;
-    
-    // Check if we're on a dev vhost and point to JSIA in the user's public_html on the datanode
+    // Check if we're on a dev vhost and point to JSIA in the user's public_html
+    // on the datanode
     if (preg_match("/^www-(\w+)/", $_SERVER["SERVER_NAME"], $match)) {
       // $$$ the remapping isn't totally automatic yet and requires user to
       //     ln -s ~/petabox/www/datanode/BookReader ~/public_html/BookReader
       //     so we enable it only for known hosts
-      $devhosts = array('mang', 'testflip');
+      $devhosts = array('mang', 'testflip', 'rkumar');
       if (in_array($match[1], $devhosts)) {
-        $serverBaseURL = $serverBaseURL . ":81/~" . $match[1];
+        $server = $server . "/~" . $match[1];
       }
     }
-    
+    return $server;
+  }
+  
+  
+  public static function jsMetadataURL($server, $identifier, $mainDir, $subPrefix = '')
+  {
+    $serverBaseURL = BookReader::serverBaseURL($server);
+
     $params = array( 'id' => $identifier, 'itemPath' => $mainDir, 'server' => $server );
     if ($subPrefix) {
         $params['subPrefix'] = $subPrefix;
index e9345ca..182572b 100644 (file)
@@ -138,9 +138,9 @@ class FlipSearchMap {
     $url = urldecode($searchUrl);
     //Another way is for the javascript code to pass in server, itemdir, and identifier directly
     //For now we'll parse the $url passed us.
-    #if (!preg_match('|http://\w+.archive.org(/[0-9]/items/\w+)/(\w+)_djvu.xml$|', $url, $match))
+    #if (!preg_match('|http://\w+.archive.org(/[0-9]+/items/\w+)/(\w+)_djvu.xml$|', $url, $match))
     #if (!preg_match('|(\w+)/(\w+)_djvu.xml$|', $url, $match))      
-    if (!preg_match('|(/[0-9]/items/[\w-]+)/([\w-]+)_djvu.xml$|', $url, $match))
+    if (!preg_match('|(/[0-9]+/items/[\w-]+)/([\w-]+)_djvu.xml$|', $url, $match))
       fatal("Can't get server and identifier from url $url");
     $bookDir = $match[1];
     $identifier = $match[2];
diff --git a/BookReaderIA/sendtoswarm.pl b/BookReaderIA/sendtoswarm.pl
new file mode 100755 (executable)
index 0000000..0485106
--- /dev/null
@@ -0,0 +1,207 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+# CONFIGURE
+
+# The location of the TestSwarm that you're going to run against.
+
+my $SWARM = "http://dev01.sf.archive.org:8080";
+my $SWARM_INJECT = "/js/inject.js";
+
+# Your TestSwarm username.
+
+my $USER = "testflip";
+
+# Your authorization token.
+
+# my $AUTH_TOKEN = "";
+open(AUTHFILE, "/home/testflip/.testswarm");
+my $AUTH_TOKEN = <AUTHFILE>;
+chomp($AUTH_TOKEN);
+
+# The maximum number of times you want the tests to be run.
+
+my $MAX_RUNS = 5;
+
+# The type of revision control system being used.
+# Currently "svn" or "git" are supported.
+
+my $RCS_TYPE = "git";
+
+# The URL from which a copy will be checked out.
+
+my $RCS_URL = "/home/testflip/bookreader/.git";
+
+# The directory in which the checkouts will occur.
+
+my $BASE_DIR = "/home/testflip/public_html/changeset";
+
+# A script tag loading in the TestSwarm injection script will
+# be added at the bottom of the <head> in the following file.
+
+my $INJECT_FILE = "BookReaderIA/test/index.html";
+
+# Any build commands that need to happen.
+
+my $BUILD = "(cd BookReaderIA; make test)";
+
+# The name of the job that will be submitted
+# (pick a descriptive, but short, name to make it easy to search)
+
+# Note: The string {REV} will be replaced with the current
+#       commit number/hash.
+
+my $JOB_NAME = "BookReader www-testflip #{REV}";
+
+# The browsers you wish to run against. Options include:
+#  - "all" all available browsers.
+#  - "popular" the most popular browser (99%+ of all browsers in use)
+#  - "current" the current release of all the major browsers
+#  - "gbs" the browsers currently supported in Yahoo's Graded Browser Support
+#  - "beta" upcoming alpha/beta of popular browsers
+#  - "popularbeta" the most popular browser and their upcoming releases
+
+my $BROWSERS = "popularbeta";
+
+# All the suites that you wish to run within this job
+# (can be any number of suites)
+
+#my %SUITES = ('first' => 'http://test.archive.org/changeset/{REV}/test/index.html');
+my %SUITES = ();
+
+# Comment these out if you wish to define a custom set of SUITES above
+#my $SUITE = "http://dev.jquery.com/~john/changeset/{REV}";
+my $SUITE = "http://home.us.archive.org/~testflip/changeset/{REV}/BookReaderIA";
+sub BUILD_SUITES {
+       %SUITES = map { /(\w+).js$/; $1 => "$SUITE/test/?$1%20module"; } glob("BookReaderIA/test/unit/*.js");
+}
+
+########### NO NEED TO CONFIGURE BELOW HERE ############
+
+my $DEBUG = 1;
+my $curdate = time;
+my $co_dir = "tmp-$curdate";
+
+print "chdir $BASE_DIR\n" if ( $DEBUG );
+chdir( $BASE_DIR );
+
+# Check out a specific revision
+if ( $RCS_TYPE eq "svn" ) {
+       print "svn co $RCS_URL $co_dir\n" if ( $DEBUG );
+       `svn co $RCS_URL $co_dir`;
+} elsif ( $RCS_TYPE eq "git" ) {
+       print "git clone $RCS_URL $co_dir\n" if ( $DEBUG );
+       `git clone $RCS_URL $co_dir`;
+}
+
+if ( ! -e $co_dir ) {
+       die "Problem checking out source.";
+}
+
+print "chdir $co_dir\n" if ( $DEBUG );
+chdir( $co_dir );
+
+my $rev;
+
+# Figure out the revision of the checkout
+if ( $RCS_TYPE eq "svn" ) {
+       print "svn info | grep Revision\n" if ( $DEBUG );
+       $rev = `svn info | grep Revision`;
+       $rev =~ s/Revision: //;
+} elsif ( $RCS_TYPE eq "git" ) {
+    my $cmd = "git rev-parse --short HEAD";
+    print "$cmd\n" if ( $DEBUG );
+    $rev = `$cmd`;
+}
+
+$rev =~ s/\s*//g;
+
+print "Revision: $rev\n" if ( $DEBUG );
+
+if ( ! $rev ) {
+       remove_tmp();
+       die "Revision information not found.";
+
+} elsif ( ! -e "../$rev" ) {
+       print "chdir $BASE_DIR\n" if ( $DEBUG );
+       chdir( $BASE_DIR );
+
+       print "rename $co_dir $rev\n" if ( $DEBUG );
+       rename( $co_dir, $rev );
+
+       print "chdir $rev\n" if ( $DEBUG );
+       chdir ( $rev );
+
+       if ( $BUILD ) {
+               print "$BUILD\n" if ( $DEBUG );
+               `$BUILD`;
+       }
+
+       if ( exists &BUILD_SUITES ) {
+               &BUILD_SUITES();
+       }
+
+       foreach my $file ( glob($INJECT_FILE) ) {
+               my $inject_file = `cat $file`;
+
+               # Inject the TestSwarm injection script into the test suite
+               $inject_file =~ s/<\/head>/<script>document.write("<scr" + "ipt src='$SWARM$SWARM_INJECT?" + (new Date).getTime() + "'><\/scr" + "ipt>");<\/script><\/head>/;
+
+               open( my $fh, '>', $file ) or die "$file : $!";
+               print $fh $inject_file;
+               close( $fh );
+       }
+
+       my %props = (
+               "state" => "addjob",
+               "output" => "dump",
+               "user" => $USER,
+               "max" => $MAX_RUNS,
+               "job_name" => $JOB_NAME,
+               "browsers" => $BROWSERS,
+               "auth" => $AUTH_TOKEN
+       );
+
+       my $query = "";
+
+       foreach my $prop ( keys %props ) {
+               $query .= ($query ? "&" : "") . $prop . "=" . clean($props{$prop});
+       }
+
+       foreach my $suite ( sort keys %SUITES ) {
+               $query .= "&suites[]=" . clean($suite) .
+                         "&urls[]=" . clean($SUITES{$suite});
+       }
+
+       print "curl -d \"$query\" $SWARM\n" if ( $DEBUG );
+       my $results = `curl -d "$query" $SWARM`;
+
+       print "Results: $results\n" if ( $DEBUG );
+
+       if ( $results ) {
+               open( my $fh, '>', "results.txt" ) or die "$rev/results.txt : $!";
+               print $fh "$SWARM$results";
+               close( $fh );
+
+       } else {
+               die "Job not submitted properly.";
+       }
+
+# Otherwise, give up and clean up
+} else {
+       remove_tmp();
+}
+
+sub remove_tmp {
+       chdir( $BASE_DIR );
+       `rm -rf $co_dir`;
+}
+
+sub clean {
+  my $str = shift;
+       $str =~ s/{REV}/$rev/g;
+       $str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
+       $str;
+}
diff --git a/BookReaderIA/test/index.html b/BookReaderIA/test/index.html
new file mode 100644 (file)
index 0000000..1de9221
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
+                    "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+  <script src="http://code.jquery.com/jquery-latest.js"></script>
+  <link rel="stylesheet" href="http://dev01.sf.archive.org:8080/qunit/qunit.css" type="text/css" media="screen" />
+<script type="text/javascript" src="http://dev01.sf.archive.org:8080/qunit/qunit.js"></script>
+<script type="text/javascript" src="unit/JSLocate.js"></script>
+<script type="text/javascript" src="unit/Images.js"></script>
+
+</head>
+<body>
+  <h1 id="qunit-header">BookReader QUnit Tests</h1>
+ <h2 id="qunit-banner"></h2>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+</body>
+</html>
diff --git a/BookReaderIA/test/unit/Images.js b/BookReaderIA/test/unit/Images.js
new file mode 100644 (file)
index 0000000..e2fabb9
--- /dev/null
@@ -0,0 +1,208 @@
+// Tests for BookReaderImages.php
+
+// $$$ TODO -- make the test host configurable/automagic
+
+module("Images");
+
+// $$$ set to test host
+var testHost = 'http://www-testflip.archive.org';
+
+// Returns locator URL for the given id
+function jsLocateURL(identifier, book) {
+    var bookURL = testHost + '/bookreader/BookReaderJSLocate.php?id=' + identifier;
+    if (book) {
+        bookURL += '&book=' + book;
+    }
+    return bookURL;
+}
+
+// Set up dummy BookReader class for JSLocate
+function BookReader() {
+};
+
+BookReader.prototype.init = function() {
+    return true;
+};
+
+
+// Test image info - jpeg
+asyncTest("JSLocate for armageddonafter00couruoft - jpeg", function() {
+    expect(1);
+    $.getScript( jsLocateURL('armageddonafter00couruoft'), function() {
+        equals(br.bookTitle, 'Armageddon and after', 'Title');
+        start();
+    });
+});
+
+asyncTest("Image info for jpeg", function() {
+    expect(3);
+    var expected = {"width":1349,"height":2105,"bits":8,"type":"jpeg"};
+    var imageInfoURL = br.getPageURI(8) + '&ext=json&callback=?';
+    
+    $.getJSON(imageInfoURL, function(data) {
+        equals(data != null, true, 'data is not null');
+        if (data != null) {
+            equals(data.width, expected.width, 'Image width');
+            same(data, expected, 'Image info object');
+        }
+        start();
+    });
+});
+
+
+
+// Test image info
+asyncTest("JSLocate for zc-f-c-b-4 - 1-bit jp2", function() {
+    expect(1);
+    $.getScript( jsLocateURL('zc-f-c-b-4', 'concept-of-infection'), function() {
+        equals(br.numLeafs, 13, 'numLeafs');
+        start();
+    });
+});
+
+asyncTest("Image info for 1-bit jp2", function() {
+    expect(3);
+    var expected = {"width":3295,"height":2561,"bits":1,"type":"jp2"};
+    var imageInfoURL = br.getPageURI(0) + '&ext=json&callback=?';
+    
+    $.getJSON(imageInfoURL, function(data) {
+        equals(data != null, true, 'data is not null');
+        if (data != null) {
+            equals(data.width, expected.width, 'Image width');
+            same(data, expected, 'Image info object');
+        }
+        start();
+    });
+});
+
+/// windwavesatseabr00bige - jp2 zip
+asyncTest("JSLocate for windwavesatseabr00bige - Scribe jp2.zip book", function() {
+    expect(1);
+    $.getScript( jsLocateURL('windwavesatseabr00bige'), function(data, textStatus) {
+        equals(br.numLeafs, 224, 'JSLocate successful. numLeafs');
+        start();
+    });
+});
+    
+test("Image URI for windwavesatseabr00bige page index 5", function() {
+    expect(1);
+    var index = 5;
+    var expectedEnding = "file=windwavesatseabr00bige_jp2/windwavesatseabr00bige_0006.jp2&scale=1&rotate=0";
+    var pageURI = br.getPageURI(index);
+    var reg = new RegExp('file=.*$');
+    var actualEnding = reg.exec(pageURI);
+    equals(actualEnding, expectedEnding, 'URI for page index 5 ends with');
+});
+
+asyncTest("Load windwavesatseabr00bige image 5", function() {
+    var pageURI = br.getPageURI(5);
+    var img = new Image();
+    $(img).bind( 'load error', function(eventObj) {
+        equals(eventObj.type, 'load', 'Load image (' + pageURI + '). Event handler called');
+        start();
+    })
+    // Actually load the image
+    .attr('src', pageURI);
+});
+
+
+/// nybc200109 - 1-bit tiff zip
+asyncTest("JSLocate for nybc200109 - 1-bit tiff.zip book", function() {
+    expect(1);
+    $.getScript( jsLocateURL('nybc200109'), function() {
+        equals(br.numLeafs,
+               694,
+               'Number of pages');
+        start();
+    });
+});
+
+asyncTest("Image info for 1-bit tiff", function() {
+    expect(3);
+    var expected = {"width":5081,"height":6592,"bits":1,"type":"tiff"};
+    var imageInfoURL = br.getPageURI(0) + '&ext=json&callback=?';
+    
+    $.getJSON(imageInfoURL, function(data) {
+        equals(data != null, true, 'data is not null');
+        if (data != null) {
+            equals(data.width, expected.width, 'Image width');
+            same(data, expected, 'Image info object');
+        }
+        start();
+    });
+});
+
+asyncTest("Load 1-bit tiff image from zip", function() {
+    expect(2);
+    var pageURI = br.getPageURI(6, 16);
+    var img = new Image();
+    $(img).bind( 'load error', function(eventObj) {
+        equals(eventObj.type, 'load', 'Load image (' + pageURI + '). Event handler called');
+        equals(this.width, 1272, 'Image width');
+        start();
+    })
+    .attr('src', pageURI);
+});
+
+
+
+/// asamoandictiona00pragoog - tiff zip
+asyncTest("JSLocate for asamoandictiona00pragoog - tiff.zip book", function() {
+    expect(1);
+    $.getScript( jsLocateURL('asamoandictiona00pragoog'), function() {
+        equals(br.bookTitle,
+               'A Samoan dictionary: English and Samoan, and Samoan and English;',
+               'Book title');
+        start();
+    });
+});
+
+asyncTest("Image info for 8-bit tiff", function() {
+    expect(3);
+    var expected = {"width":1275,"height":1650,"bits":8,"type":"tiff"};
+    var imageInfoURL = br.getPageURI(0) + '&ext=json&callback=?';
+    
+    $.getJSON(imageInfoURL, function(data) {
+        equals(data != null, true, 'data is not null');
+        if (data != null) {
+            equals(data.width, expected.width, 'Image width');
+            same(data, expected, 'Image info object');
+        }
+        start();
+    });
+});
+
+asyncTest("Load tiff image from zip", function() {
+    expect(2);
+    var pageURI = br.getPageURI(23, 8);
+    var img = new Image();
+    $(img).bind( 'load error', function(eventObj) {
+        equals(eventObj.type, 'load', 'Load image (' + pageURI + '). Event handler called');
+        equals(this.width, 702, 'Image width');
+        start();
+    })
+    .attr('src', pageURI);
+});
+
+
+/// hccapp56191900uoft - jpeg tar
+asyncTest("JSLocate for hccapp56191900uoft - jpg.tar", function() {
+    expect(1);
+    $.getScript( jsLocateURL('hccapp56191900uoft'), function() {
+        equals(br.numLeafs, 1101, 'Number of pages');
+        start();
+    });
+});
+
+asyncTest('Load jpg image from tar file - https://bugs.launchpad.net/bookreader/+bug/323003', function() {
+    expect(2);
+    var pageURI = br.getPageURI(6, 8);
+    var img = new Image();
+    $(img).bind( 'load error', function(eventObj) {
+        equals(eventObj.type, 'load', 'Load image (' + pageURI + '). Event handler called');
+        equals(this.width, 244, 'Image width');
+        start();
+    })
+    .attr('src', pageURI);
+});
+
diff --git a/BookReaderIA/test/unit/JSLocate.js b/BookReaderIA/test/unit/JSLocate.js
new file mode 100644 (file)
index 0000000..17136c7
--- /dev/null
@@ -0,0 +1,50 @@
+// Tests for BookReaderJSLocate.php
+
+// $$$ TODO -- make the test host configurable/automagic
+
+module("JSLocate");
+
+testHost = 'http://www-testflip.archive.org';
+
+// Returns locator URL for the given id
+function jsLocateURL(bookId) {
+    return testHost + '/bookreader/BookReaderJSLocate.php?id=' + bookId;
+}
+
+// Set up dummy BookReader class for JSLocate
+function BookReader() {
+};
+
+BookReader.prototype.init = function() {
+    return true;
+};
+
+asyncTest("JSLocate for notesonsubmarine00grea", function() {
+    expect(1);
+    $.getScript( jsLocateURL('notesonsubmarine00grea'),
+        function(data, textStatus) {
+            equals(window.br.titleLeaf, 5, 'Metadata loaded.  See https://bugs.launchpad.net/bookreader/+bug/517424. Title leaf');
+            start();
+        }
+    );
+});
+
+asyncTest("JSLocate for photographingclo00carprich", function() {
+    expect(1);
+    $.getScript( jsLocateURL('photographingclo00carprich'),
+        function(data, textStatus) {
+            equals(window.br.bookTitle, 'Photographing clouds from an airplane',  'Title of book');
+            start();
+        }
+    );
+});
+
+asyncTest("JSLocate for salmoncookbookho00panaiala", function() {
+    expect(1);
+    $.getScript( jsLocateURL('salmoncookbookho00panaiala'),
+        function(data, textStatus) {
+            equals(window.br.numLeafs, 40,  'Number of pages');
+            start();
+        }
+    );
+});
index 0b18e2e..c71ffeb 100644 (file)
@@ -21,6 +21,7 @@ This file is part of BookReader.
 require_once '/petabox/setup.inc';
 
 $id = $_REQUEST['id'];
+$book = $_REQUEST['book']; // support multiple books within an item
 
 if ("" == $id) {
     echo "No identifier specified!";
@@ -31,20 +32,13 @@ $locator      = new Locator();
 
 $results = $locator->locateUDP($id, 1, false);
 
-$serverBaseURL = $results[0][0];
-
-// Check if we're on a dev vhost and point to JSIA in the user's public_html on the datanode
-if (preg_match("/^www-(\w+)/", $_SERVER["SERVER_NAME"], $match)) {
-    // $$$ the remapping isn't totally automatic yet and requires user to
-    //     ln -s ~/petabox/www/datanode/BookReader ~/public_html/BookReader
-    //     so we enable it only for known hosts
-    $devhosts = array('mang', 'testflip', 'rkumar');
-    if (in_array($match[1], $devhosts)) {
-        $serverBaseURL = $serverBaseURL . ":81/~" . $match[1];
-    }
-}
+$server = $results[0][0];
+$serverBaseURL = BookReader::serverBaseURL($server);
 
-$url = "http://{$serverBaseURL}/BookReader/BookReaderJSIA.php?id={$id}&itemPath={$results[0][1]}&server={$serverBaseURL}";
+$url = "http://{$serverBaseURL}/BookReader/BookReaderJSIA.php?id=" . urlencode($id) . "&itemPath={$results[0][1]}&server={$server}";
+if ($book) {
+    $url .= "&subPrefix=" . urlencode($book);
+}
 
 
 if (("" != $results[0][0]) && ("" != $results[0][1])) {
index 1e8680e..c098789 100644 (file)
@@ -1,4 +1,4 @@
-<?
+<? require_once '/petabox/setup.inc';
 /*
 Copyright(c)2008 Internet Archive. Software license AGPL version 3.
 
@@ -19,68 +19,14 @@ This file is part of BookReader.
 */
 
 
-$id = $_REQUEST['id'];
+$id = urlencode($_REQUEST['id']); // Sanitize since we put this value in the page
 
-// Sanitize since we put this value in the page
-$id = urlencode($id);
-
-$flippyURL = "http://" . $_SERVER['SERVER_NAME'] . "/texts/flipbook/flippy.php?id=" . $id;
+$flippyURL = "/texts/flipbook/flippy.php?id=$id";
 
+Nav::bar('Unsupported browser', 'home', null, null, 1, null,
+         '<meta http-equiv="refresh" content="10;url=http://www.archive.org'.$flippyURL.'">',
+         '', true);
 ?>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html>
-<head>
-    <title>Unsupported browser</title>
-    <meta http-equiv="refresh" content="10;url=<? echo($flippyURL); ?>">
-    <link rel="stylesheet" href="http://www.archive.org/stylesheets/archive.css?v=1.99" type="text/css">
-</head>
-<body>
-
-<table style="background-color:white " cellspacing="0" width="100%" border="0" cellpadding="0">
-  <tbody>
-    <tr> 
-      <td style="width:100px; height:72px; vertical-align:top; background-color:#000000;">
-
-        <a href="http://www.archive.org/"><img style="border:0;" alt="(logo)" src="http://www.archive.org/images/logo.jpg" width="84" height="70"/></a>
-      </td>
-      <td valign="bottom" style="background-image:url(http://www.archive.org/images/blendbar.jpg);">
-        <table width="100%" border="0" cellpadding="5">
-          <tr> 
-            <td class="level1Header">
-              <div class="tab"><a href="http://www.archive.org/web/web.php">Web</a></div><div class="tab"><a href="http://www.archive.org/details/movies">Moving Images</a></div><div class="tab"><a href="http://www.archive.org/details/texts">Texts</a></div><div class="tab"><a href="http://www.archive.org/details/audio">Audio</a></div><div class="tab"><a href="http://www.archive.org/details/software">Software</a></div><div class="tab"><a href="http://www.archive.org/details/education">Education</a></div><div class="tab"><a href="http://www.archive.org/account/login.changepw.php">Patron Info</a></div><div class="tab"><a href="http://www.archive.org/about/about.php">About IA</a></div>            </td>
-
-          </tr>
-                    
-        </table>
-      </td>
-      <td style="width:80px; height:72px; vertical-align:top; text-align: right">
-        <a href="http://www.archive.org/about/about.php"><img alt="(navigation image)" src="http://www.archive.org/images/main-header.jpg" style="margin:0; border:0; vertical-align:top;" /></a>      </td>
-    </tr>
-  </tbody>
-</table>
-
-<!--BEGIN HEADER 2-->
-<table width="100%" class="level2Header">
-  <tbody>
-    <tr>
-      <td align="left" valign="top" class="level2HeaderLeft">
-        <a class="level2Header" href="http://www.archive.org/">Home</a>
-      </td>
-      <td style="width:100%;" class="level2Header">
-        <a href="http://www.archive.org/donate/index.php">Donate</a> | 
-
-<a href="http://www.archive.org/iathreads/forums.php">Forums</a> | 
-<a href="http://www.archive.org/about/faqs.php">FAQs</a> | 
-<a href="http://www.archive.org/contribute.php">Contributions</a> | 
-<a href="http://www.archive.org/about/terms.php">Terms, Privacy, &amp; Copyright</a> | 
-<a href="http://www.archive.org/about/contact.php">Contact</a> | 
-<a href="http://www.archive.org/about/jobs.php">Jobs</a> | 
-
-<a href="http://www.archive.org/about/bios.php">Bios</a>
-      </td>
-    </tr>
-  </tbody>
-</table>
 
 
 <div class="box">
@@ -89,9 +35,9 @@ Sorry, but our new viewer does not work in this browser yet.
 </p>
 
 <p>
-Either go to our <a href="<? echo($flippyURL); ?>">old viewer</a>,
+Either go to our <a href="<?=$flippyURL?>">old viewer</a>,  
 or download <a href="http://www.mozilla.com/en-US/firefox/">Firefox</a> or
-<a href="http://www.microsoft.com/windows/internet-explorer/download-ie.aspx">IE7 (or higher)</a>.
+  <a href="http://www.microsoft.com/windows/internet-explorer/download-ie.aspx">IE7 (or higher)</a>.
 </p>
 
 <p>
@@ -100,10 +46,4 @@ You will be automatically redirected to the older viewer in 10 seconds.
 
 </div>
 
-<p id="iafoot">
-  <a href="http://www.archive.org/about/terms.php">Terms of Use (10 Mar 2001)</a>
-
-</p>
-
-</body>
-</html>
+<?=footer();?>
index 83b6a25..b081997 100644 (file)
@@ -1 +1,10 @@
-openlibrary book reader
+The Internet Archive BookReader is used to view books from the Internet Archive
+online and can also be used to view other books.
+
+Developer documentation:
+http://openlibrary.org/dev/docs/bookreader
+
+Hosted source code:
+http://github.com/openlibrary/bookreader
+
+The source code license is AGPL v3, as described in the LICENSE file.