diff --git a/.gitignore b/.gitignore index b2fd969..a1f33ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ env *.pyc +*.pyo +*.pyd *.gz *.swp *.min.js diff --git a/ctl b/ctl index 88571a3..aa49866 100755 --- a/ctl +++ b/ctl @@ -37,7 +37,7 @@ if [ "$1" == "start" ]; then echo openmedialibrary already running exit 1 fi - python oml server PID & + python2 oml server PID & exit $? fi if [ "$1" == "debug" ]; then @@ -47,7 +47,7 @@ if [ "$1" == "debug" ]; then exit 1 fi shift - python oml server $@ + python2 oml server $@ exit $? fi if [ "$1" == "stop" ]; then @@ -75,5 +75,5 @@ if [ "$1" == "open" ]; then fi cd $BASE/$NAME -python oml $@ +python2 oml $@ exit $? diff --git a/oml/__main__.py b/oml/__main__.py index 318cdac..91d0aab 100644 --- a/oml/__main__.py +++ b/oml/__main__.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division import os import sys diff --git a/oml/app.py b/oml/app.py index 822a2fc..500dabb 100644 --- a/oml/app.py +++ b/oml/app.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division from flask import Flask from flask.ext.script import Manager diff --git a/oml/changelog.py b/oml/changelog.py index 859267b..926e76d 100644 --- a/oml/changelog.py +++ b/oml/changelog.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division import json from datetime import datetime diff --git a/oml/commands.py b/oml/commands.py index cbee014..a9285a7 100644 --- a/oml/commands.py +++ b/oml/commands.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division from flask.ext.script import Command @@ -10,7 +11,10 @@ class Setup(Command): """ def run(self): import setup + import settings setup.create_default_lists() + settings.db.session.connection().execute("PRAGMA journal_mode=WAL") + settings.db.session.commit() class UpdateStatic(Command): """ @@ -28,8 +32,8 @@ class UpdateStatic(Command): oxjs = os.path.join(settings.static_path, 'oxjs') if not os.path.exists(oxjs): r('git', 'clone', 'https://git.0x2620.org/oxjs.git', oxjs) - r('python', os.path.join(oxjs, 'tools', 'build', 'build.py')) - r('python', os.path.join(settings.static_path, 'py', 'build.py')) + r('python2', os.path.join(oxjs, 'tools', 'build', 'build.py')) + r('python2', os.path.join(settings.static_path, 'py', 'build.py')) class Release(Command): """ diff --git a/oml/directory.py b/oml/directory.py index 20c3791..731925b 100644 --- a/oml/directory.py +++ b/oml/directory.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division # DHT placeholder diff --git a/oml/ed25519_utils.py b/oml/ed25519_utils.py index 94283b3..6406588 100644 --- a/oml/ed25519_utils.py +++ b/oml/ed25519_utils.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division + import ed25519 ENCODING='base64' diff --git a/oml/item/add.py b/oml/item/add.py index 96441e3..57fbd41 100644 --- a/oml/item/add.py +++ b/oml/item/add.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division import base64 import models diff --git a/oml/item/api.py b/oml/item/api.py index 4e73a32..96be84b 100644 --- a/oml/item/api.py +++ b/oml/item/api.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division + from datetime import datetime from flask import json diff --git a/oml/item/covers.py b/oml/item/covers.py index 4b19b81..e931651 100644 --- a/oml/item/covers.py +++ b/oml/item/covers.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division import sqlite3 import Image diff --git a/oml/item/migrate.py b/oml/item/migrate.py index 4b237ea..ef3950b 100644 --- a/oml/item/migrate.py +++ b/oml/item/migrate.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division + import models from copy import deepcopy diff --git a/oml/item/models.py b/oml/item/models.py index e3884f5..5118364 100644 --- a/oml/item/models.py +++ b/oml/item/models.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division import os import re @@ -198,6 +199,8 @@ class Item(db.Model): if value: value = unicode(value) value = ox.sort_string(value).lower() + elif isinstance(value, list): #empty list + value = '' setattr(self, 'sort_%s' % key['id'], value) def update_find(self): @@ -295,11 +298,14 @@ class Item(db.Model): def update_cover(self): cover = None - if 'cover' in self.meta: + if 'cover' in self.meta and self.meta['cover']: cover = ox.cache.read_url(self.meta['cover']) #covers[self.id] = requests.get(self.meta['cover']).content if cover: covers[self.id] = cover + else: + if covers[self.id]: + del covers[self.id] path = self.get_path() if not cover and path: cover = self.extract_cover() diff --git a/oml/item/person.py b/oml/item/person.py index 516cfbd..602d642 100644 --- a/oml/item/person.py +++ b/oml/item/person.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division import unicodedata diff --git a/oml/item/query.py b/oml/item/query.py index 59ff044..164f1b6 100644 --- a/oml/item/query.py +++ b/oml/item/query.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division import settings import models diff --git a/oml/item/views.py b/oml/item/views.py index afda07e..e8ea91b 100644 --- a/oml/item/views.py +++ b/oml/item/views.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division import os from datetime import datetime @@ -77,7 +78,7 @@ def cover(id, size=None): if not 'coverRatio' in item.meta: #img = Image.open(StringIO(str(covers[id]))) img = Image.open(StringIO(data)) - item.meta['coverRatio'] = float(img.size[0])/img.size[1] + item.meta['coverRatio'] = img.size[0]/img.size[1] db.session.add(item) db.session.commit() resp = make_response(data) diff --git a/oml/localnodes.py b/oml/localnodes.py index c24d71f..a45bf96 100644 --- a/oml/localnodes.py +++ b/oml/localnodes.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division import socket import thread diff --git a/oml/meta/__init__.py b/oml/meta/__init__.py index 3e46318..2d4191d 100644 --- a/oml/meta/__init__.py +++ b/oml/meta/__init__.py @@ -7,6 +7,7 @@ import loc import lookupbyisbn import openlibrary import worldcat +import google providers = [ ('openlibrary', 'olid'), @@ -17,9 +18,12 @@ providers = [ ] def find(title, author=None, publisher=None, date=None): + results = google.find(title=title, author=author, publisher=publisher, date=date) + ''' results = openlibrary.find(title=title, author=author, publisher=publisher, date=date) for r in results: r['mainid'] = 'olid' + ''' return results def lookup(key, value): diff --git a/oml/meta/google.py b/oml/meta/google.py new file mode 100644 index 0000000..8e66766 --- /dev/null +++ b/oml/meta/google.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division + +import ox.web.google +import stdnum.isbn + +from .utils import find_isbns + + +def find(title, author=None, publisher=None, date=None): + print 'google.find', title, author, publisher, date + query = title + if author: + if isinstance(author, list): + author = ' '.join(author) + query += ' ' + author + query += ' isbn' + isbns = [] + for r in ox.web.google.find(query): + isbns += find_isbns(' '.join(r)) + + results = [] + done = set() + for isbn in isbns: + if isbn not in done: + key = 'isbn%d'%len(isbn) + #r = lookup(key, isbn) + #r['mainid'] = key + r = { + key: isbn, + 'mainid': key + } + results.append(r) + done.add(isbn) + if len(isbn) == 10: + done.add(stdnum.isbn.to_isbn13(isbn)) + return results diff --git a/oml/meta/loc.py b/oml/meta/loc.py index 4c4384a..b36a6ce 100644 --- a/oml/meta/loc.py +++ b/oml/meta/loc.py @@ -33,7 +33,10 @@ def lookup(id): info = { 'lccn': id } - info['title'] = ''.join([e.text for e in mods.findall(ns + 'titleInfo')[0]]) + title = mods.findall(ns + 'titleInfo') + if not title: + return {} + info['title'] = ''.join([e.text for e in title[0]]) origin = mods.findall(ns + 'originInfo') if origin: info['place'] = [] diff --git a/oml/meta/lookupbyisbn.py b/oml/meta/lookupbyisbn.py index 20550b2..7120943 100644 --- a/oml/meta/lookupbyisbn.py +++ b/oml/meta/lookupbyisbn.py @@ -14,6 +14,8 @@ def get_ids(key, value): if m: asin = m[0].split('/')[-3] ids.append(('asin', asin)) + if key == 'isbn10': + ids.append(('isbn13', stdnum.isbn.to_isbn13(value))) if key == 'asin': if stdnum.isbn.is_valid(value): ids.append(('isbn10', value)) @@ -47,14 +49,16 @@ def lookup(id): r[key] = int(r[key]) desc = find_re(data, '

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

', ' ').replace('
', ' ').replace('
', ' ') - r['description'] = desc - if r['description'] == u'Description of this item is not available at this time.': - r['description'] = '' + r['description'] = decode_html(strip_tags(desc)) 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):
+    if 'author' in r and isinstance(r['author'], basestring) and r['author']:
         r['author'] = [r['author']]
+    else:
+        r['author'] = []
+    if r['description'].lower() == u'Description of this item is not available at this time.'.lower():
+        r['description'] = ''
     return r
 
diff --git a/oml/meta/utils.py b/oml/meta/utils.py
index 3812044..869d76a 100644
--- a/oml/meta/utils.py
+++ b/oml/meta/utils.py
@@ -1,5 +1,16 @@
-
+import re
+import stdnum.isbn
 
 def normalize_isbn(value):
     return ''.join([s for s in value if s.isdigit() or s == 'X'])
 
+def find_isbns(text):
+    matches = re.compile('\d[\d\-X\ ]+').findall(text)
+    matches = [normalize_isbn(value) for value in matches]
+    return [isbn for isbn in matches if stdnum.isbn.is_valid(isbn)
+        and len(isbn) in (10, 13)
+        and isbn not in (
+        '0' * 10,
+        '0' * 13,
+    )]
+
diff --git a/oml/utils.py b/oml/utils.py
index 80927ed..a720823 100644
--- a/oml/utils.py
+++ b/oml/utils.py
@@ -8,7 +8,7 @@ import stdnum.isbn
 
 import ox
 
-from meta.utils import normalize_isbn
+from meta.utils import normalize_isbn, find_isbns
 
 def valid_olid(id):
     return id.startswith('OL') and id.endswith('M')
@@ -76,16 +76,6 @@ def sort_title(title):
     title = re.sub(u'[\'!¿¡,\.;\-' - + data.author.join(', ') + '' - : '' - ); + columns: [ + { + format: function(value) { + return Ox.getObjectById(ids, value).title; + }, + id: 'mainid', + visible: true, + width: 64 }, - id: 'index', - visible: true, - width: 192 - Ox.UI.SCROLLBAR_SIZE - }], + { + format: function(value, data) { + return data[data.mainid]; + }, + id: 'index', + visible: true, + width: 128 - Ox.UI.SCROLLBAR_SIZE + } + ], items: items, - keys: ['author', 'title', 'olid'], + keys: ['mainid', 'isbn10', 'isbn13'], min: 1, max: 1, scrollbarVisible: true, - selected: ['0'], - sort: [{key: 'index', operator: '+'}], + sort: [{key: 'mainid', operator: '+'}], unique: 'index' }) .bindEvent({ select: function(data) { - var index = data.ids[0], olid; - if (index == '0') { - $results.replaceElement(1, oml.ui.infoView(data)); - } else { - olid = $list.value(index, 'olid'); - editedData = {olid: olid}; - $results.replaceElement(1, Ox.LoadingScreen().start()); - oml.api.getMetadata({olid: olid}, function(result) { - if (index == $list.options('selected')[0]) { - $results.replaceElement(1, oml.ui.infoView(result.data)); - that.options('buttons')[1].options({disabled: false}); - } - }); - } - + var index = data.ids[0], mainid; + mainid = $list.value(index, 'mainid'); + Ox.print('MAINID', mainid) + titleValue = Ox.extend({}, mainid, $list.value(index, mainid)); + $results.replaceElement(1, Ox.LoadingScreen().start()); + oml.api.getMetadata(titleValue, function(result) { + if (index == $list.options('selected')[0]) { + $results.replaceElement(1, oml.ui.infoView(result.data)); + that.options('buttons')[1].options({disabled: false}); + } + }); } }), $results = Ox.SplitPanel({ elements: [ {element: $list, size: 192}, - {element: oml.ui.infoView(items[0])} + {element: Ox.Element()} ], orientation: 'horizontal' }); @@ -480,7 +484,7 @@ oml.ui.identifyDialog = function(data) { $idButtons.clear.options({disabled: empty}); $idButtons.reset.options({disabled: original}); $idButtons.find.options({disabled: empty}); - that.options('buttons')[1].options({disabled: original}); + that[original ? 'disableButton' : 'enableButton']('update'); } function updateTitleButtons() { @@ -493,7 +497,7 @@ oml.ui.identifyDialog = function(data) { $titleButtons.clear.options({disabled: empty}); $titleButtons.reset.options({disabled: original}); $titleButtons.find.options({disabled: empty}); - that.options('buttons')[1].options({disabled: original}); + that[original ? 'disableButton' : 'enableButton']('update'); } return that; diff --git a/static/js/infoView.js b/static/js/infoView.js index e703bfd..7fe7710 100644 --- a/static/js/infoView.js +++ b/static/js/infoView.js @@ -66,6 +66,12 @@ oml.ui.infoView = function(identifyData) { return '' + str + ''; } + function formatKey(key) { + var item = Ox.getObjectById(oml.config.itemKeys, key); + return '' + + Ox._(Ox.toTitleCase(key)) + ':  '; + } + function formatValue(value, key) { return (Ox.isArray(value) ? value : [value]).map(function(value) { return key ? @@ -234,6 +240,7 @@ oml.ui.infoView = function(identifyData) { $elements = $elements ? Ox.makeArray($elements) : [$cover, $info, $data]; + Ox.print('DEBUG, $ELEMENTS', $elements); (data ? Ox.noop : oml.api.get)({ id: id, @@ -308,8 +315,9 @@ oml.ui.infoView = function(identifyData) { Ox.EditableContent({ clickLink: oml.clickLink, editable: isEditable, + placeholder: formatLight(Ox._('Unknown Title')), tooltip: isEditable ? oml.getEditTooltip() : '', - value: data.title + value: data.title || '' }) .css({ fontSize: '13px', @@ -330,7 +338,9 @@ oml.ui.infoView = function(identifyData) { clickLink: oml.clickLink, editable: isEditable, format: function(value) { - return formatValue(value.split(', '), 'author'); + return !identifyData + ? formatValue(value.split(', '), 'author') + : value; }, placeholder: formatLight(Ox._('Unknown Author')), tooltip: isEditable ? oml.getEditTooltip() : '', @@ -340,23 +350,65 @@ oml.ui.infoView = function(identifyData) { fontSize: '13px', fontWeight: 'bold' }) + .bindEvent({ + // ... + }) ) .appendTo($info); } + if (!isEditable) { + $('
') + .css({ + marginTop: '8px' + }) + .text( + (data.place || '') + + (data.place && (data.publisher || data.date) ? ' : ' : '') + + (data.publisher || '') + + (data.publisher && data.date ? ', ' : '') + + (data.date || '') + ) + .appendTo($info); + } else { + var $div = $('
') + .addClass('OxSelectable') + .css({marginTop: '8px'}) + .appendTo($info); + ['edition', 'publisher', 'date'].forEach(function(key, index) { + index && $('
').css({float: 'left'}).html('; ').appendTo($div); + $('
') + .css({float: 'left'}) + .html(formatKey(key)) + .appendTo($div); + Ox.EditableContent({ + clickLink: oml.clickLink, + format: function(value) { + return formatValue(value.split(', '), key) + }, + placeholder: formatLight('unknown'), + tooltip: oml.getEditTooltip(), + value: data[key] || '' + }) + .css({float: 'left'}) + .bindEvent({ + submit: function(event) { + editMetadata(key, event.value); + } + }) + .appendTo($div); + }); + } - $('
') - .css({ - marginTop: '8px' - }) - .text( - (data.place || '') - + (data.place && (data.publisher || data.date) ? ' : ' : '') - + (data.publisher || '') - + (data.publisher && data.date ? ', ' : '') - + (data.date || '') - ) - .appendTo($info); + if (data.classification) { + $('
') + .css({ + marginTop: '8px', + textAlign: 'justify' + }) + .html(Ox.encodeHTMLEntities(data.classification)) + .appendTo($info); + } if (data.description) { $('
') @@ -445,11 +497,14 @@ oml.ui.infoView = function(identifyData) { }); + // FIXME: identify dialog should call this too function editMetadata(key, value) { var edit; if (value != data[key]) { edit = Ox.extend({id: ui.item}, key, value); oml.api.edit(edit, function(result) { + Ox.Request.clearCache('find'); + oml.$ui.browser.reloadList(); that.update(result.data, $data); }); } @@ -469,7 +524,7 @@ oml.ui.infoView = function(identifyData) { transfer: function(data) { if (data.id == ui.item && data.progress == 1) { Ox.Request.clearCache(); // FIXME: too much - that.update(ui.item, $data); + that.update(ui.item, [$info, $data]); } } }); diff --git a/static/js/usersDialog.js b/static/js/usersDialog.js index 31f271c..adca49c 100644 --- a/static/js/usersDialog.js +++ b/static/js/usersDialog.js @@ -254,8 +254,20 @@ oml.ui.usersDialog = function() { width: 480 }) .bindEvent({ - change: function() { - // ... + change: function(data) { + var value = oml.validateName( + data.value, + users.map(function(user) { + return user.nickname; + }) + ); + this.value(value); + oml.api.editUser({ + id: user.id, + nickname: value + }, function() { + // ... + }); } }) .appendTo($form); @@ -480,6 +492,10 @@ oml.ui.usersDialog = function() { } } + function updateUsers() { + // ... + } + that.update = function() { that.options({ @@ -503,7 +519,6 @@ oml.ui.usersDialog = function() { ); }); - Ox.print('FOLDERS::', folders) folders.forEach(function(folder, index) { $lists.push( ( diff --git a/static/png/oml.png b/static/png/oml.png index bc05b9e..7b7f4e4 100644 Binary files a/static/png/oml.png and b/static/png/oml.png differ