diff --git a/README b/README index c7add47..adacb44 100644 --- a/README +++ b/README @@ -4,6 +4,23 @@ Open Media Library soon +== Networking == + +At this time you need a working IPv6 connection to use Open Media Libary. +If you dont have native IPv6 you can use Teredo/Miredo (apt-get install miredo) +or get a tunnel Hurricane Electric (https://tunnelbroker.net/) +or SixSS (https://sixxs.net). + +== Platform == + +If you install Open Media Library on a architecture thats not directly supported, +you need a working python 2.7.x installation and the following packages: + + apt-get install \ + python-pypdf python-stdnum python-html5lib python-chardet python-openssl \ + python-simplejson python-lxml + pip install -r requirements.txt + == Development == mkdir client diff --git a/config.json b/config.json index 0ad448e..f218671 100644 --- a/config.json +++ b/config.json @@ -26,6 +26,7 @@ "columnRequired": true, "columnWidth": 192, "filter": true, + "find": true, "sort": true, "sortType": "person" }, diff --git a/ctl b/ctl index 0560fe7..88571a3 100755 --- a/ctl +++ b/ctl @@ -25,6 +25,9 @@ export PATH PYTHONPATH="$PLATFORM_ENV/lib/python2.7/site-packages:$SHARED_ENV/lib/python2.7/site-packages:$BASE/$NAME" export PYTHONPATH +oxCACHE="$BASE/config/ox" +export oxCACHE + #must be called to update commands in $PATH hash -r 2>/dev/null diff --git a/oml/item/api.py b/oml/item/api.py index 7ace6d1..4e73a32 100644 --- a/oml/item/api.py +++ b/oml/item/api.py @@ -14,6 +14,8 @@ from changelog import Changelog import re import state +import meta + import utils @returns_json @@ -108,7 +110,7 @@ actions.register(edit, cache=False) @returns_json -def identify(request): +def findMetadata(request): ''' takes { title: string, @@ -124,26 +126,22 @@ def identify(request): ''' response = {} data = json.loads(request.form['data']) if 'data' in request.form else {} - response = { - 'items': [ - { - u'title': u'Cinema', - u'author': [u'Gilles Deleuze'], - u'date': u'1986-10', - u'publisher': u'University of Minnesota Press', - u'isbn10': u'0816613990', - }, - { - u'title': u'How to Change the World: Reflections on Marx and Marxism', - u'author': [u'Eric Hobsbawm'], - u'date': u'2011-09-06', - u'publisher': u'Yale University Press', - u'isbn13': u'9780300176162', - } - ] - } + print 'findMetadata', data + response['items'] = meta.find(**data) return response -actions.register(identify) +actions.register(findMetadata) + +@returns_json +def getMetadata(request): + data = json.loads(request.form['data']) if 'data' in request.form else {} + print 'getMetadata', data + key, value = data.iteritems().next() + if key in ('isbn10', 'isbn13'): + value = utils.normalize_isbn(value) + response = meta.lookup(key, value) + response['mainid'] = key + return response +actions.register(getMetadata) @returns_json def download(request): diff --git a/oml/meta/__init__.py b/oml/meta/__init__.py index 7a933a3..3e46318 100644 --- a/oml/meta/__init__.py +++ b/oml/meta/__init__.py @@ -16,8 +16,11 @@ providers = [ ('abebooks', 'isbn10') ] -def find(title, author=None, publisher=None, year=None): - return [] +def find(title, author=None, publisher=None, date=None): + results = openlibrary.find(title=title, author=author, publisher=publisher, date=date) + for r in results: + r['mainid'] = 'olid' + return results def lookup(key, value): data = {key: value} @@ -32,16 +35,16 @@ def lookup(key, value): if not kv in ids: ids.append(kv) done = False - print ids + print 'lookup %s=%s =>' % ids[0], ids for k, v in ids: for provider, id in providers: - if id == k: + if id == k and provider not in provider_data: provider_data[provider] = globals()[provider].lookup(v) for provider in sorted( provider_data.keys(), key=lambda x: -len(provider_data[x]) ): - print provider, len(provider_data[provider]) + print provider, len(provider_data[provider]), provider_data[provider].keys() for k_, v_ in provider_data[provider].iteritems(): if not k_ in data: data[k_] = v_ diff --git a/oml/meta/lookupbyisbn.py b/oml/meta/lookupbyisbn.py index 15da05c..20550b2 100644 --- a/oml/meta/lookupbyisbn.py +++ b/oml/meta/lookupbyisbn.py @@ -1,6 +1,7 @@ from ox.cache import read_url -from ox import find_re, strip_tags +from ox import find_re, strip_tags, decode_html import re +import stdnum.isbn base = 'http://www.lookupbyisbn.com' @@ -13,6 +14,9 @@ def get_ids(key, value): if m: asin = m[0].split('/')[-3] ids.append(('asin', asin)) + if key == 'asin': + if stdnum.isbn.is_valid(value): + ids.append(('isbn10', value)) if ids: print 'lookupbyisbn.get_ids', key, value print ids @@ -43,10 +47,13 @@ def lookup(id): r[key] = int(r[key]) desc = find_re(data, '

Description:<\/h2>(.*?)

', ' ').replace('
', ' ').replace('
', ' ') - r['description'] = strip_tags(desc).strip() + r['description'] = desc if r['description'] == u'Description of this item is not available at this time.': r['description'] = '' r['cover'] = find_re(data, 'Book cover').replace('._SL160_', '')
+    for key in r:
+        if isinstance(r[key], basestring):
+            r[key] = decode_html(strip_tags(r[key])).strip()
     if 'author' in r and isinstance(r['author'], basestring):
         r['author'] = [r['author']]
     return r
diff --git a/oml/meta/openlibrary.py b/oml/meta/openlibrary.py
index 8b9d343..5ad8c79 100644
--- a/oml/meta/openlibrary.py
+++ b/oml/meta/openlibrary.py
@@ -2,12 +2,60 @@
 # vi:si:et:sw=4:sts=4:ts=4
 from __future__ import division
 
+from urllib import urlencode
 from ox.cache import read_url
 import json
 
 from marc_countries import COUNTRIES
 from utils import normalize_isbn
 
+KEYS = {
+    'authors': 'author',
+    'covers': 'cover',
+    'dewey_decimal_class': 'classification',
+    'isbn_10': 'isbn10',
+    'isbn_13': 'isbn13',
+    'languages': 'language',
+    'lccn': 'lccn',
+    'number_of_pages': 'pages',
+    'oclc_numbers': 'oclc',
+    'publish_country': 'country',
+    'publish_date': 'date',
+    'publishers': 'publisher',
+    'publish_places': 'place',
+    'series': 'series',
+    'title': 'title',
+}
+
+def find(*args, **kargs):
+    args = [a.replace(':', ' ') for a in args]
+    for k in ('date', 'publisher'):
+        if k in kargs:
+            print 'ignoring %s on openlibrary' % k, kargs[k]
+            del kargs[k]
+    for k, v in kargs.iteritems():
+        key = KEYS.keys()[KEYS.values().index(k)]
+        if v:
+            if not isinstance(v, list):
+                v = [v]
+            #v = ['%s:=0.13.1 diff --git a/static/js/identifyDialog.js b/static/js/identifyDialog.js index 7d5d6fe..d6cfaf9 100644 --- a/static/js/identifyDialog.js +++ b/static/js/identifyDialog.js @@ -9,108 +9,130 @@ oml.ui.identifyDialog = function(data) { ].map(function(id) { return { id: id, - title: Ox._(Ox.getObjectById(oml.config.itemKeys, id).title) + title: Ox.getObjectById(oml.config.itemKeys, id).title }; }), keys = [ 'title', 'author', 'publisher', 'date' ].map(function(id) { + var key = Ox.getObjectById(oml.config.sortKeys, id); return { + format: key.format, id: id, - title: Ox._(Ox.getObjectById(oml.config.itemKeys, id).title) + operator: key.operator, + width: { + title: 288, + author: 224, + publisher: 160, + date: 96 - Ox.UI.SCROLLBAR_SIZE + }[id], + title: key.title, + visible: true }; }), - $input = Ox.FormElementGroup({ - elements: [ - Ox.Select({ - items: ids, - overlap: 'right', - value: 'isbn10', - width: 128 - }), - Ox.Input({ - value: data['isbn10'] || '', - width: 610 - }) - ] - }) - .css({margin: '16px'}), + originalData = Ox.clone(data, true), - $preview = Ox.Element(), + $idForm = renderIdForm(data), + + $preview = data.mainid + ? oml.ui.infoView(data) + : Ox.Element(), $idPanel = Ox.SplitPanel({ elements: [ - {element: Ox.Element().append($input), size: 48}, + {element: Ox.Element().append($idForm), size: 96}, {element: $preview} ], orientation: 'vertical' }), - $form = Ox.Form({ - items: keys.map(function(key) { - return Ox.Input({ - id: key.id, - labelWidth: 128, - label: key.title, - value: key == 'author' - ? (data[key.id] || []).join(', ') - : data[key.id], - width: 736 - }); - }) - }) - .css({padding: '16px'}) - .bindEvent({ - change: function(data) { - Ox.print('FORM CHANGE', data); - } - }), + $titleForm = Ox.Element(), - $list = Ox.TableList({ - columns: [ - { - id: 'index' - }, - { - id: 'title', - visible: true, - width: 288, - }, - { - id: 'author', - visible: true, - width: 224 - }, - { - id: 'publisher', - visible: true, - width: 160 - }, - { - id: 'date', - visible: true, - width: 96 + $inputs = keys.map(function(key, index) { + return Ox.Input({ + label: Ox._(key.title), + labelWidth: 64, + value: data[key.id], + width: 360 + }) + .css({ + position: 'absolute', + left: index < 2 ? '16px' : '392px', + top: index % 2 == 0 ? '16px' : '40px' + }) + .bindEvent({ + submit: function(data) { + $findButton.triggerEvent('click'); } - ], - items: [], - max: 1, - sort: [{key: 'index', operator: '+'}], - unique: 'index' + }) + .appendTo($titleForm); + }), + + $clearButton = Ox.Button({ + title: Ox._('Clear'), + width: 64 + }) + .css({ + position: 'absolute', + right: '160px', + top: '64px' }) .bindEvent({ - select: function(data) { - $that.options('buttons')[1].options({ - disabled: data.ids.length == 0 + click: function() { + keys.forEach(function(key) { + inputValue(key.id, ''); }); + updateButtons(); } - }), + }) + .appendTo($titleForm), + + $resetButton = Ox.Button({ + disabled: true, + title: Ox._('Reset'), + width: 64 + }) + .css({ + position: 'absolute', + right: '88px', + top: '64px' + }) + .bindEvent({ + click: function() { + keys.forEach(function(key) { + inputValue(key.id, originalData[key.id]); + }); + updateButtons(); + } + }) + .appendTo($titleForm), + + $findButton = Ox.Button({ + title: Ox._('Find'), + width: 64 + }) + .css({ + position: 'absolute', + right: '16px', + top: '64px' + }) + .bindEvent({ + click: function() { + var data = {}; + keys.forEach(function(key) { + data[key.id] = inputValue(key.id); + }); + findMetadata(data); + } + }) + .appendTo($titleForm), $titlePanel = Ox.SplitPanel({ elements: [ - {element: Ox.Element().append($form), size: 120}, - {element: $list} + {element: $titleForm, size: 96}, + {element: renderResults([Ox.extend({index: '0'}, data)])} ], orientation: 'vertical' }), @@ -186,6 +208,233 @@ oml.ui.identifyDialog = function(data) { width: 768 }); + function findMetadata(data) { + $titlePanel.replaceElement(1, Ox.LoadingScreen().start()); + oml.api.findMetadata(data, function(result) { + Ox.print('GOT RESULTS', result.data); + var items = result.data.items.map(function(item, index) { + return Ox.extend({index: index.toString()}, item); + }).concat([ + Ox.extend({index: result.data.items.length.toString()}, data) + ]); + $titlePanel.replaceElement(1, renderResults(items)); + }); + } + + function getMetadata(key, value) { + $idPanel.replaceElement(1, Ox.LoadingScreen().start()); + oml.api.getMetadata(Ox.extend({}, key, value), function(result) { + Ox.print('GOT RESULT', result.data); + $idForm = renderIdForm(result.data); + $preview = oml.ui.infoView(result.data); + $idPanel + .replaceElement(0, $idForm) + .replaceElement(1, $preview); + }); + } + + function inputValue(key, value) { + // FIXME: UNELEGANT + Ox.print('INPUTVALUE', key, value) + var $input = $inputs[[ + 'title', 'author', 'publisher', 'date' + ].indexOf(key)]; + if (Ox.isUndefined(value)) { + value = $input.value(); + if (key == 'author') { + value = value ? value.split(', ') : []; + } + } else { + $input.value( + key == 'author' ? (value || []).join(', ') : value + ); + } + return value; + } + + function isEmpty(data) { + return Ox.every(data, Ox.isEmpty); + } + + function isOriginal(data) { + return Ox.every(data, function(value, key) { + return value == originalData[key]; + }); + } + + function renderIdForm(data) { + var $element = Ox.Element(), + $elements = ids.map(function(id, index) { + return Ox.FormElementGroup({ + elements: [ + Ox.Checkbox({ + overlap: 'right', + title: Ox._(id.title), + value: id.id == data.mainid, + width: 80 + }) + .bindEvent({ + change: function(data) { + var value = $elements[index].options('elements')[1].value() + if (data.value) { + if (value) { + $elements.forEach(function($element, i) { + if (i != index) { + $elements[i].options('elements')[0].value(false); + } + }); + getMetadata(id.id, value, function() { + // ... + }); + } else { + this.value(false); + } + } else { + this.value(true); + } + } + }), + Ox.Input({ + value: data[id.id] || '', + width: 160 + }) + .bindEvent({ + submit: function(data) { + if (data.value) { + $elements.forEach(function($element, i) { + $element.options('elements')[0].options({ + disabled: true, + value: i == index + }); + $element.options('elements')[1].options({ + disabled: true + }); + }); + getMetadata(id.id, data.value, function() { + // ... + }); + } + } + }) + ], + float: 'left' + }) + .css({ + position: 'absolute', + left: 16 + Math.floor(index / 2) * 248 + 'px', + top: 16 + (index % 2) * 24 + 'px' + }) + .appendTo($element); + }), + $resetButton = Ox.Button({ + disabled: true, + title: Ox._('Reset'), + width: 64 + }) + .css({ + position: 'absolute', + right: '16px', + top: '64px' + }) + .bindEvent({ + click: function() { + /* + keys.forEach(function(key) { + inputValue(key.id, originalData[key.id]); + }); + updateButtons(); + */ + } + }) + .appendTo($element); + return $element; + Ox.print('???', data.mainid) + return Ox.Form({ + items: Ox.flatten(ids.map(function(id) { + return [ + Ox.Checkbox({ + disabled: !data[id.id] || id.id == data.mainid, + id: id.id + 'Checkbox', + title: Ox._(id.title), + value: id.id == data.mainid, + width: 128 + }) + .bindEvent({ + change: function() { + getMetadata(id.id, data[id.id]); + } + }), + Ox.Input({ + id: id.id + 'Input', + value: data[id.id] || '', + width: 128 + }) + .css({marginBottom: '16px'}) + .bindEvent({ + change: function(data) { + if (data.value) { + getMetadata(id.id, data.value, function() { + //... + }); + } else { + Ox.print('this', this) + } + } + }) + ]; + })) + }) + .css({margin: '16px'}); + return $form; + } + + function renderResults(items) { + Ox.print('LIST ITEMS::::', items); + var $list = Ox.TableList({ + columns: Ox.clone(keys, true), + items: items, + min: 1, + max: 1, + scrollbarVisible: true, + selected: ['0'], + sort: [{key: 'index', operator: '+'}], + unique: 'index' + }) + .bindEvent({ + select: function(data) { + var index = data.ids[0]; + data = Ox.getObject(items, 'index', index); + $results.replaceElement(1, Ox.LoadingScreen().start()); + Ox.print('OLID', data.olid); + oml.api.getMetadata({olid: data.olid}, function(result) { + Ox.print('#### GOT DATA', result.data); + $results.replaceElement(1, oml.ui.infoView(result.data)); + that.options('buttons')[1].options({disabled: false}); + }); + } + }), + $results = Ox.SplitPanel({ + elements: [ + {element: $list, size: 80}, + {element: oml.ui.infoView(items[0])} + ], + orientation: 'vertical' + }); + return $results; + } + + function updateButtons() { + var data = {}, empty, original; + keys.forEach(function(key) { + data[key.id] = inputValue(key.id); + }); + empty = isEmpty(data); + original = isOriginal(data); + $clearButton.options({disabled: empty}); + $resetButton.options({disabled: original}); + $findButton.options({disabled: empty}); + } + return that; }; \ No newline at end of file diff --git a/static/js/infoView.js b/static/js/infoView.js index 42dcacb..5204d1b 100644 --- a/static/js/infoView.js +++ b/static/js/infoView.js @@ -1,6 +1,6 @@ 'use strict'; -oml.ui.infoView = function() { +oml.ui.infoView = function(identifyData) { var ui = oml.user.ui, @@ -34,11 +34,14 @@ oml.ui.infoView = function() { .css({ position: 'absolute', left: '288px', - right: '176px', + right: !identifyData ? '176px' : 16 + Ox.UI.SCROLLBAR_SIZE + 'px', top: '16px' }) .appendTo(that), + $data; + + if (!identifyData) { $data = Ox.Element() .addClass('OxSelectable') .css({ @@ -48,6 +51,7 @@ oml.ui.infoView = function() { width: '128px' }) .appendTo(that); + } function formatLight(str) { return '' + str + ''; @@ -218,7 +222,9 @@ oml.ui.infoView = function() { width = Math.round(ratio >= 1 ? size : size * ratio), height = Math.round(ratio <= 1 ? size : size / ratio), left = Math.floor((size - width) / 2), - src = '/' + data.id + '/cover256.jpg', + src = !identifyData + ? '/' + data.id + '/cover256.jpg' + : data.cover, reflectionSize = Math.round(size / 2); $elements.forEach(function($element) { @@ -334,6 +340,7 @@ oml.ui.infoView = function() { .appendTo($info); } + $('
').css({height: '16px'}).appendTo($info); } else if ($element == $data) { @@ -424,7 +431,11 @@ oml.ui.infoView = function() { }; - ui.item && that.update(ui.item); + if (!identifyData) { + ui.item && that.update(ui.item); + } else { + that.update(identifyData, [$cover, $info]); + } oml.bindEvent({ transfer: function(data) { diff --git a/static/js/oml.js b/static/js/oml.js index d5ca508..8ffa751 100644 --- a/static/js/oml.js +++ b/static/js/oml.js @@ -73,8 +73,8 @@ ? Ox['format' + Ox.toTitleCase(key.format.type)].apply( this, [value].concat(key.format.args || []) ) - : Ox.isArray(key.type) ? value.join(', ') - : value; + : Ox.isArray(key.type) ? (value || []).join(', ') + : (value || ''); } }); })