Merge branch 'master' of git@github.com:openlibrary/bookreader into browserlending
[bookreader.git] / BookReaderIA / datanode / BookReaderJSIA.php
old mode 100755 (executable)
new mode 100644 (file)
index 7a29e83..f02a92e
@@ -18,24 +18,32 @@ This file is part of BookReader.
     along with BookReader.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+header('Content-Type: application/javascript');
+
 $id = $_REQUEST['id'];
 $itemPath = $_REQUEST['itemPath'];
 $subPrefix = $_REQUEST['subPrefix'];
 $server = $_REQUEST['server'];
 
+// $$$mang this code has been refactored into BookReaderMeta.inc.php for use e.g. by
+//         BookReaderPreview.php and BookReaderImages.php.  The code below should be
+//         taken out and replaced by calls into BookReaderMeta
+
 // Check if we're on a dev vhost and point to JSIA in the user's public_html on the datanode
+
 // $$$ TODO consolidate this logic
 if (strpos($_SERVER["REQUEST_URI"], "/~mang") === 0) { // Serving out of home dir
     $server .= ':80/~mang';
+} else if (strpos($_SERVER["REQUEST_URI"], "/~rkumar") === 0) { // Serving out of home dir
+    $server .= ':80/~rkumar';
 } else if (strpos($_SERVER["REQUEST_URI"], "/~testflip") === 0) { // Serving out of home dir
     $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 +57,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)) {
@@ -100,6 +118,44 @@ $metaData = simplexml_load_file($metaDataFile);
 //$firstLeaf = $scanData->pageData->page[0]['leafNum'];
 ?>
 
+// Error reporting - this helps us fix errors quickly
+function logError(description,page,line) {
+    if (typeof(archive_analytics) != 'undefined') {
+        var values = {
+            'bookreader': 'error',
+            'description': description,
+            'page': page,
+            'line': line,
+            'itemid': '<?echo $id;?>',
+            'subPrefix': '<?echo $subPrefix;?>',
+            'server': '<?echo $server;?>',
+            'bookPath': '<?echo $subItemPath;?>'
+        };
+
+        // if no referrer set '-' as referrer
+        if (document.referrer == '') {
+            values['referrer'] = '-';
+        } else {
+            values['referrer'] = document.referrer;
+        }
+        
+        if (typeof(br) != 'undefined') {
+            values['itemid'] = br.bookId;
+            values['subPrefix'] = br.subPrefix;
+            values['server'] = br.server;
+            values['bookPath'] = br.bookPath;
+        }
+        
+        var qs = archive_analytics.format_bug(values);
+
+        var error_img = new Image(100,25);
+        error_img.src = archive_analytics.img_src + "?" + qs;
+    }
+
+    return false; // allow browser error handling so user sees there was a problem
+}
+window.onerror=logError;
+
 br = new BookReader();
 
 <?
@@ -150,34 +206,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) {
@@ -242,13 +271,24 @@ br.getPageNum = function(index) {
     }
 }
 
+// Single images in the Internet Archive scandata.xml metadata are (somewhat incorrectly)
+// given a "leaf" number.  Some of these images from the scanning process should not
+// be displayed in the BookReader (for example colour calibration cards).  Since some
+// of the scanned images will not be displayed in the BookReader (those marked with
+// addToAccessFormats false in the scandata.xml) leaf numbers and BookReader page
+// indexes are generally not the same.  This function returns the BookReader page
+// index given a scanned leaf number.
+//
+// This function is used, for example, to map between search results (that use the
+// leaf numbers) and the displayed pages in the BookReader.
 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
@@ -311,24 +351,106 @@ br.cleanupMetadata = function() {
 // getEmbedURL
 //________
 // Returns a URL for an embedded version of the current book
-br.getEmbedURL = function() {
+br.getEmbedURL = function(viewParams) {
     // We could generate a URL hash fragment here but for now we just leave at defaults
     var url = 'http://' + window.location.host + '/stream/'+this.bookId;
     if (this.subPrefix != this.bookId) { // Only include if needed
         url += '/' + this.subPrefix;
     }
     url += '?ui=embed';
+    if (typeof(viewParams) != 'undefined') {
+        url += '#' + this.fragmentFromParams(viewParams);
+    }
     return url;
 }
 
 // getEmbedCode
 //________
 // Returns the embed code HTML fragment suitable for copy and paste
-br.getEmbedCode = function() {
-    return "<iframe src='" + this.getEmbedURL() + "' width='480px' height='430px'></iframe>";
+br.getEmbedCode = function(frameWidth, frameHeight, viewParams) {
+    return "<iframe src='" + this.getEmbedURL(viewParams) + "' width='" + frameWidth + "' height='" + frameHeight + "' frameborder='0' ></iframe>";
+}
+
+// getOpenLibraryRecord
+br.getOpenLibraryRecord = function(callback) {
+    // Try looking up by ocaid first, then by source_record
+    
+    var self = this; // closure
+    
+    var jsonURL = self.olHost + '/query.json?type=/type/edition&*=&ocaid=' + self.bookId;
+    $.ajax({
+        url: jsonURL,
+        success: function(data) {
+            if (data && data.length > 0) {
+                callback(self, data[0]);
+            } else {
+                // try sourceid
+                jsonURL = self.olHost + '/query.json?type=/type/edition&*=&source_records=ia:' + self.bookId;
+                $.ajax({
+                    url: jsonURL,
+                    success: function(data) {
+                        if (data && data.length > 0) {
+                            callback(self, data[0]);
+                        }
+                    },
+                    dataType: 'jsonp'
+                });
+            }
+        },
+        dataType: 'jsonp'
+    });
 }
 
-br.pageW =             [
+br.buildInfoDiv = function(jInfoDiv) {
+    // $$$ it might make more sense to have a URL on openlibrary.org that returns this info
+
+    var escapedTitle = BookReader.util.escapeHTML(this.bookTitle);
+    var domainRe = /(\w+\.(com|org))/;
+    var domainMatch = domainRe.exec(this.bookUrl);
+    var domain = this.bookUrl;
+    if (domainMatch) {
+        domain = domainMatch[1];
+    }
+       
+    // $$$ cover looks weird before it loads
+    jInfoDiv.find('.BRfloatCover').append([
+                    '<div style="height: 140px; min-width: 80px; padding: 0; margin: 0;"><a href="', this.bookUrl, '"><img src="http://www.archive.org/download/', this.bookId, '/page/cover_t.jpg" alt="' + escapedTitle + '" height="140px" /></a></div>'].join('')
+    );
+
+    jInfoDiv.find('.BRfloatMeta').append([
+                    // $$$ description
+                    //'<p>Published ', this.bookPublished,
+                    //, <a href="Open Library Publisher Page">Publisher name</a>',
+                    //'</p>',
+                    //'<p>Written in <a href="Open Library Language page">Language</a></p>',
+                    '<h3>Other Formats</h3>',
+                    '<ul class="links">',
+                        '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '.pdf">PDF</a><span>|</span></li>',
+                        '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '_djvu.txt">Plain Text</a><span>|</span></li>',
+                        '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '_daisy.zip">DAISY</a><span>|</span></li>',
+                        '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '.epub">ePub</a><span>|</span></li>',
+                        '<li><a href="https://www.amazon.com/gp/digital/fiona/web-to-kindle?clientid=IA&itemid=', this.bookId, '&docid=', this.subPrefix, '">Send to Kindle</a></li>',
+                    '</ul>',
+                    '<p class="moreInfo"><span></span>More information on <a href="'+ this.bookUrl + '">' + domain + '</a>  </p>'].join('\n'));
+                    
+    jInfoDiv.find('.BRfloatFoot').append([
+                '<span>|</span>',                
+                '<a href="http://openlibrary.org/contact" class="problem">Report a problem</a>',
+    ].join('\n'));
+                
+    if (domain == 'archive.org') {
+        jInfoDiv.find('.BRfloatMeta p.moreInfo span').css(
+            {'background': 'url(http://www.archive.org/favicon.ico) no-repeat', 'width': 22, 'height': 18 }
+        );
+    }
+    
+    jInfoDiv.find('.BRfloatTitle a').attr({'href': this.bookUrl, 'alt': this.bookTitle}).text(this.bookTitle);
+    var bookPath = (window.location + '').replace('#','%23');
+    jInfoDiv.find('a.problem').attr('href','http://openlibrary.org/contact?path=' + bookPath);
+
+}
+
+br.pageW =  [
             <?
             $i=0;
             foreach ($scanData->pageData->page as $page) {
@@ -341,7 +463,7 @@ br.pageW =          [
             ?>
             ];
 
-br.pageH =             [
+br.pageH =  [
             <?
             $totalHeight = 0;
             $i=0;            
@@ -389,22 +511,45 @@ 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;?>';
 
 <?
 
 # Load some values from meta.xml
 if ('' != $metaData->{'page-progression'}) {
-  echo "br.pageProgression = '" . $metaData->{"page-progression"} . "';";
+  echo "br.pageProgression = '" . $metaData->{"page-progression"} . "';\n";
 } else {
   // Assume page progression is Left To Right
-  echo "br.pageProgression = 'lr';";
+  echo "br.pageProgression = 'lr';\n";
+}
+
+$useOLAuth = false;
+$protected = false;
+foreach ($metaData->xpath('//collection') as $collection) {
+    if('browserlending' == $collection) {
+        $useOLAuth = true;
+        $protected = true;
+    }
+}
+
+echo "br.olHost = 'http://openlibrary.org'\n";
+#echo "br.olHost = 'http://ol-mang:8080'\n";
+
+if ($useOLAuth) {
+    echo "br.olAuth = true;\n";
+} else {
+    echo "br.olAuth = false;\n";
+}
+
+if ($protected) {
+    echo "br.protected = true;\n";
 }
 
 # Special cases
@@ -428,28 +573,213 @@ if (typeof(brConfig) != 'undefined') {
             br.reduce = brConfig['reduce'];
         }
     } else if (brConfig['mode'] == 2) {
-        br.mode = 2;
-      
-<?
-        //$$$mang hack to override request for 2up for books with attribution page
-        //   as first page until we can display that page in 2up
-        $needle = 'goog';
-        if (strrpos($id, $needle) === strlen($id)-strlen($needle)) {
-            print "// override for books with attribution page\n";
-            print "br.mode = 1;\n";
-        }
-?>
+        br.mode = 2;      
+    }
+    
+    if (typeof(brConfig["isAdmin"]) != 'undefined') {
+        br.isAdmin = brConfig["isAdmin"];
+    } else {
+        br.isAdmin = false;
     }
 } // brConfig
 
-br.cleanupMetadata();
-br.init();
 
+function OLAuth() {
+    this.authUrl = br.olHost + '/ia_auth/' + br.bookId;
+    this.olConnect = false;
+    this.loanUUID = false;
+    this.permsToken = false;
+    
+    var cookieRe = /;\s*/;
+    var cookies = document.cookie.split(cookieRe);
+    var length = cookies.length;
+    var i;
+    for (i=0; i<length; i++) {
+        if (0 == cookies[i].indexOf('br-loan-' + br.bookId)) {
+            this.loanUUID = cookies[i].split('=')[1];
+        }
+        if (0 == cookies[i].indexOf('loan-' + br.bookId)) {
+            this.permsToken = cookies[i].split('=')[1];
+        }
+    }
+
+    return this;
+}
+
+OLAuth.prototype.init = function() {
+    var htmlStr =  'Checking loan status with Open Library';
+
+    this.showPopup("#F0EEE2", "#000", htmlStr, 'Please wait as we check the status of this book...');
+    var authUrl = this.authUrl+'?rand='+Math.random();
+    if (false !== this.loanUUID) {
+        authUrl += '&loan='+this.loanUUID
+    }
+    if (false !== this.permsToken) {
+        authUrl += '&token='+this.permsToken
+    }
+    $.ajax({url:authUrl, dataType:'jsonp', jsonpCallback:'olAuth.initCallback'});
+}
+
+OLAuth.prototype.showPopup = function(bgColor, textColor, msg, resolution) {
+    this.popup = document.createElement("div");
+    $(this.popup).css({
+        position: 'absolute',
+        top:      '50px',
+        left:     ($('#BookReader').attr('clientWidth')-400)/2 + 'px',
+        width:    '400px',
+        padding:  "15px",
+        border:   "3px double #999999",
+        zIndex:   3,
+        textAlign: 'center',
+        backgroundColor: bgColor,
+        color: textColor
+    }).appendTo('#BookReader');
+
+    this.setPopupMsg(msg, resolution);
+
+}
+
+OLAuth.prototype.setPopupMsg = function(msg, resolution) {
+    this.popup.innerHTML = ['<p><strong>', msg, '</strong></p><p>', resolution, '</p>'].join('\n');
+}
+
+OLAuth.prototype.showError = function(msg, resolution) {
+   $(this.popup).css({
+        backgroundColor: "#fff",
+        color: "#000"
+    });
+
+    this.setPopupMsg(msg, resolution);
+}
+
+OLAuth.prototype.initCallback = function(obj) {
+    if (false == obj.success) {
+        if (br.isAdmin) {
+            ret = confirm("We couldn't authenticate your loan with Open Library, but since you are an administrator or uploader of this book, you can access this book for QA purposes. Would you like to QA this book?");
+            if (!ret) {
+                this.showError(obj.msg, obj.resolution)
+            } else {
+                br.init();
+            }
+        } else {
+            this.showError(obj.msg, obj.resolution)
+        }       
+    } else {    
+        //user is authenticated
+        this.setCookie(obj.token);
+        this.olConnect = true;
+        this.startPolling();    
+        br.init();
+    }
+}
+
+OLAuth.prototype.callback = function(obj) {
+    if (false == obj.success) {
+        this.showPopup("#F0EEE2", "#000", obj.msg, obj.resolution);
+        clearInterval(this.poller);
+        this.ttsPoller = null;
+    } else {
+        this.olConnect = true;
+        this.setCookie(obj.token);
+    }
+}
+
+OLAuth.prototype.setCookie = function(value) {
+    var date = new Date();
+    date.setTime(date.getTime()+(10*60*1000));  //10 min expiry
+    var expiry = date.toGMTString();
+    var cookie = 'loan-'+br.bookId+'='+value;
+    cookie    += '; expires='+expiry;
+    cookie    += '; path=/; domain=.archive.org;';
+    document.cookie = cookie;
+    this.permsToken = value;
+    
+    //refresh the br-loan uuid cookie with current expiry, if needed
+    if (false !== this.loanUUID) {
+        cookie = 'br-loan-'+br.bookId+'='+this.loanUUID;
+        cookie    += '; expires='+expiry;
+        cookie    += '; path=/; domain=.archive.org;';
+        document.cookie = cookie;
+    }
+}
+
+OLAuth.prototype.deleteCookies = function() {
+    var date = new Date();
+    date.setTime(date.getTime()-(24*60*60*1000));  //one day ago
+    var expiry = date.toGMTString();
+    var cookie = 'loan-'+br.bookId+'=""';
+    cookie    += '; expires='+expiry;
+    cookie    += '; path=/; domain=.archive.org;';
+    document.cookie = cookie;
+    
+    cookie = 'br-loan-'+br.bookId+'=""';
+    cookie    += '; expires='+expiry;
+    cookie    += '; path=/; domain=.archive.org;';
+    document.cookie = cookie;
+}
+
+OLAuth.prototype.startPolling = function () {    
+    var self = this;
+    this.poller=setInterval(function(){
+        if (!self.olConnect) {
+          self.showPopup("#F0EEE2", "#000", 'Connection error', 'The BookReader cannot reach Open Library. This might mean that you are offline or that Open Library is down. Please check your Internet connection and refresh this page or try again later.');
+          clearInterval(self.poller);
+          self.ttsPoller = null;        
+        } else {
+          self.olConnect = false;
+          //be sure to add random param to authUrl to avoid stale cache
+          var authUrl = self.authUrl+'?rand='+Math.random();
+          if (false !== self.loanUUID) {
+              authUrl += '&loan='+self.loanUUID
+          }
+          if (false !== self.permsToken) {
+              authUrl += '&token='+self.permsToken
+          }
+
+          $.ajax({url:authUrl, dataType:'jsonp', jsonpCallback:'olAuth.callback'});
+        }
+    },300000);   //five minute interval
+}
+
+br.cleanupMetadata();
+if (br.olAuth) {
+    var olAuth = new OLAuth();
+    olAuth.init();
+} else {
+    br.init();
+}
 <?
 
 
 function BRFatal($string) {
-    echo "alert('$string')\n";
+    // log error
+    ?>
+    
+    if (typeof(archive_analytics) != 'undefined') {
+        var values = {
+            'bookreader': 'fatal',
+            'description': "<? echo $string; ?>",
+            'itemid': "<? echo $_REQUEST['id']; ?>",
+            'server': "<? echo $_REQUEST['server']; ?>",
+            'request_uri': "<? echo $_SERVER["REQUEST_URI"]; ?>"
+        }
+        
+        if (document.referrer == '') {
+            values['referrer'] = '-';
+        } else {
+            values['referrer'] = document.referrer;
+        }
+        
+        var qs = archive_analytics.format_bug(values);
+
+        var error_img = new Image(100,25);
+        error_img.src = archive_analytics.img_src + "?" + qs;
+    }
+
+    alert("<? echo $string;?>");
+    
+    <?
+    
     die(-1);
 }
 
@@ -467,4 +797,35 @@ 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');
+        
+}
+
 ?>
+