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; }
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
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()
$('#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();
}
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);
}
--- /dev/null
+test:
+ @ echo Made tests
+
+
+
+
<?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
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
$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
// 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) {
}
}
+// 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);
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
// $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);
}
return false;
}
+function reduceCommand($scale) {
+ if (1 != $scale) {
+ if (onPowerNode()) {
+ return ' | pnmscale -reduce ' . $scale;
+ } else {
+ return ' | pnmscale -nomix -reduce ' . $scale;
+ }
+ } else {
+ return '';
+ }
+}
+
?>
$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!");
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)) {
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) {
}
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
return "<iframe src='" + this.getEmbedURL() + "' width='480px' height='430px'></iframe>";
}
-br.pageW = [
+br.pageW = [
<?
$i=0;
foreach ($scanData->pageData->page as $page) {
?>
];
-br.pageH = [
+br.pageH = [
<?
$totalHeight = 0;
$i=0;
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;?>';
<?
function BRFatal($string) {
+ // $$$ TODO log error
echo "alert('$string')\n";
die(-1);
}
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');
+
+}
+
?>
// $$$ 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;
$foundImageStack = $filename;
}
}
-
+
if ($foundScandata && $foundImageStack) {
return true;
}
// 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!";
<?
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;
$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];
--- /dev/null
+#!/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;
+}
--- /dev/null
+<!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>
--- /dev/null
+// 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);
+});
+
--- /dev/null
+// 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();
+ }
+ );
+});
require_once '/petabox/setup.inc';
$id = $_REQUEST['id'];
+$book = $_REQUEST['book']; // support multiple books within an item
if ("" == $id) {
echo "No identifier specified!";
$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])) {
-<?
+<? require_once '/petabox/setup.inc';
/*
Copyright(c)2008 Internet Archive. Software license AGPL version 3.
*/
-$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, & 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">
</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>
</div>
-<p id="iafoot">
- <a href="http://www.archive.org/about/terms.php">Terms of Use (10 Mar 2001)</a>
-
-</p>
-
-</body>
-</html>
+<?=footer();?>
-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.