Merge branch 'master' of code.0x2620.org:0x2620/openmedialibrary

This commit is contained in:
rlx 2019-02-01 16:26:16 +05:30
commit ef16e873a0
9 changed files with 142 additions and 68 deletions

2
ctl
View file

@ -63,7 +63,7 @@ else
if [ $SYSTEM == "Darwin" ]; then if [ $SYSTEM == "Darwin" ]; then
PLATFORM="darwin64" PLATFORM="darwin64"
PLATFORM_PYTHON=3.7 PLATFORM_PYTHON=3.7
if [ -e $BASE/platform_${PLATFORM}/lib/libunrar.dylib ]; then if [ -e "$BASE/platform_${PLATFORM}/lib/libunrar.dylib" ]; then
export UNRAR_LIB_PATH="$BASE/platform_${PLATFORM}/lib/libunrar.dylib" export UNRAR_LIB_PATH="$BASE/platform_${PLATFORM}/lib/libunrar.dylib"
fi fi
fi fi

View file

@ -17,7 +17,7 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
NAMESPACES = { NAMESPACES = {
'openSearch':'http://a9.com/-/spec/opensearchrss/1.0/', 'openSearch': 'http://a9.com/-/spec/opensearchrss/1.0/',
'atom' : 'http://www.w3.org/2005/Atom', 'atom' : 'http://www.w3.org/2005/Atom',
'dc' : 'http://purl.org/dc/terms', 'dc' : 'http://purl.org/dc/terms',
'gbs' : 'http://schemas.google.com/books/2008', 'gbs' : 'http://schemas.google.com/books/2008',
@ -25,29 +25,6 @@ NAMESPACES = {
} }
XPath = partial(etree.XPath, namespaces=NAMESPACES) XPath = partial(etree.XPath, namespaces=NAMESPACES)
def find_(query):
logger.debug('find %s', query)
query += ' isbn'
isbns = []
for r in ox.web.google.find(query):
isbns += find_isbns(' '.join(r))
logger.debug('isbns', isbns)
results = []
done = set()
for isbn in isbns:
if isbn not in done:
r = {
'isbn': isbn,
'primaryid': ['isbn', isbn]
}
results.append(r)
done.add(isbn)
if len(isbn) == 10:
done.add(stdnum.isbn.to_isbn13(isbn))
if len(isbn) == 13 and isbn.startswith('978'):
done.add(stdnum.isbn.to_isbn10(isbn))
return results
def parse_entry(entry_): def parse_entry(entry_):
entry_id = XPath('descendant::atom:id') entry_id = XPath('descendant::atom:id')
creator = XPath('descendant::dc:creator') creator = XPath('descendant::dc:creator')
@ -90,7 +67,7 @@ def parse_entry(entry_):
info = decode_html_data(info) info = decode_html_data(info)
return info return info
def find(title=None, author=None): def find_feeds(title=None, author=None):
''' '''
parts = [] parts = []
if title: if title:
@ -107,36 +84,48 @@ def find(title=None, author=None):
url = 'http://books.google.com/books/feeds/volumes?' + urlencode({ url = 'http://books.google.com/books/feeds/volumes?' + urlencode({
'q': q.strip(), 'q': q.strip(),
'max-results': 20, 'max-results': 20,
'start-index':1, 'start-index': 1,
'min-viewability':'none', 'min-viewability': 'none',
}) })
data = read_url(url) data = read_url(url)
feed = etree.fromstring(data, feed = etree.fromstring(data, parser=etree.XMLParser(recover=True, no_network=True))
parser=etree.XMLParser(recover=True, no_network=True))
results = [] results = []
isbns = set() isbns = set()
for entry_ in XPath('//atom:entry')(feed): for entry_ in XPath('//atom:entry')(feed):
info = parse_entry(entry_) entry = parse_entry(entry_)
if 'isbn' in info and not 'isbn' in isbns: if 'isbn' in entry and 'isbn' not in isbns:
results.append(info) results.append(info(entry['isbn']))
isbns.add(info['isbn']) isbns.add(info['isbn'])
return results return results
def info_old(isbn): def find(title=None, author=None):
url = 'http://books.google.com/books/feeds/volumes?' + urlencode({ q = ''
'q': 'isnb:' + isbn, if title:
'max-results':1, q += title + ' '
'start-index':1, if author:
'min-viewability':'none', q += author
}) url = 'https://www.googleapis.com/books/v1/volumes?q=%s' % q
data = read_url(url) api_key = settings.server.get('google_api_key')
feed = etree.fromstring(data, if api_key:
parser=etree.XMLParser(recover=True, no_network=True)) url += '&key=' + api_key
for entry_ in XPath('//atom:entry')(feed): if api_limit.error:
info = parse_entry(entry_) raise IOError(url)
info['isbn'] = isbn while not api_limit.consume(1):
return info logger.debug('hitting google api to fast, waiting 1 second')
return {} sleep(1)
r = get_json(url, timeout=-1)
if 'error' in r:
logger.debug('got google api error, dont call for 10 minutes')
store.delete(url)
api_limit.error = True
raise IOError(url, r)
if 'items' not in r:
logger.debug('unknown %s: %s [%s]', key, value, r)
return []
results = []
for item in r['items']:
results.append(parse_item(item))
return results
def info(value): def info(value):
key = 'isbn' key = 'isbn'
@ -155,11 +144,14 @@ def info(value):
store.delete(url) store.delete(url)
api_limit.error = True api_limit.error = True
raise IOError(url, r) raise IOError(url, r)
if not 'items' in r: if 'items' not in r:
logger.debug('unknown %s: %s [%s]', key, value, r) logger.debug('unknown %s: %s [%s]', key, value, r)
return {} return {}
_data = r['items'][0]['volumeInfo'] return parse_item(r['items'][0])
_id = r['items'][0]['id']
def parse_item(item):
_data = item['volumeInfo']
_id = item['id']
data = {} data = {}
for key in [ for key in [
'authors', 'authors',
@ -175,11 +167,11 @@ def info(value):
'authors': 'author', 'authors': 'author',
'pageCount': 'pages', 'pageCount': 'pages',
'publishedDate': 'date', 'publishedDate': 'date',
}.get(key,key)] = _data[key] }.get(key, key)] = _data[key]
if 'subtitle' in _data and _data['subtitle'].strip(): if 'subtitle' in _data and _data['subtitle'].strip():
data['title'] = '{title}: {subtitle}'.format(**_data) data['title'] = '{title}: {subtitle}'.format(**_data)
if r['items'][0]['accessInfo']['viewability'] != 'NO_PAGES': if item['accessInfo']['viewability'] != 'NO_PAGES':
#data['cover'] = 'https://books.google.com/books?id=%s&pg=PP1&img=1&zoom=0&hl=en' % _id #data['cover'] = 'https://books.google.com/books?id=%s&pg=PP1&img=1&zoom=0&hl=en' % _id
data['cover'] = 'https://books.google.com/books/content/images/frontcover/%s?fife=w600-rw' % _id data['cover'] = 'https://books.google.com/books/content/images/frontcover/%s?fife=w600-rw' % _id
@ -191,7 +183,7 @@ def info(value):
if 'industryIdentifiers' in _data: if 'industryIdentifiers' in _data:
for k in _data['industryIdentifiers']: for k in _data['industryIdentifiers']:
if k['type'].startswith('ISBN'): if k['type'].startswith('ISBN'):
if not 'isbn' in data: if 'isbn' not in data:
data['isbn'] = [] data['isbn'] = []
data['isbn'].append(k['identifier']) data['isbn'].append(k['identifier'])
else: else:

View file

@ -504,7 +504,7 @@ class Nodes(Thread):
while not state.shutdown: while not state.shutdown:
args = self._q.get() args = self._q.get()
if args: if args:
logger.debug('processing nodes queue: next: %s, %s entries in queue', args[0], self._q.qsize()) logger.debug('processing nodes queue: next: "%s", %s entries in queue', args[0], self._q.qsize())
if args[0] == 'add': if args[0] == 'add':
self._add(*args[1:]) self._add(*args[1:])
elif args[0] == 'pull': elif args[0] == 'pull':
@ -514,7 +514,7 @@ class Nodes(Thread):
def queue(self, *args): def queue(self, *args):
if args: if args:
logger.debug('add %s to queue, queue currently has %s entries', args[0], self._q.qsize()) logger.debug('queue "%s", %s entries in queue', args[0], self._q.qsize())
self._q.put(list(args)) self._q.put(list(args))
def is_online(self, id): def is_online(self, id):
@ -568,7 +568,7 @@ class Nodes(Thread):
users = [] users = []
with db.session(): with db.session():
from user.models import User from user.models import User
for u in User.query.filter(User.id!=settings.USER_ID).filter_by(peered=True).all(): for u in User.query.filter(User.id != settings.USER_ID).filter_by(peered=True).all():
users.append(u.json(['id', 'index', 'name'])) users.append(u.json(['id', 'index', 'name']))
users.sort(key=user_sort_key) users.sort(key=user_sort_key)
for u in users: for u in users:

View file

@ -31,6 +31,8 @@ oml.ui.annotationPanel = function() {
{id: 'note', title: Ox._('By Note Text'), checked: false}, {id: 'note', title: Ox._('By Note Text'), checked: false},
{id: 'date', title: Ox._('By Date Added'), checked: false} {id: 'date', title: Ox._('By Date Added'), checked: false}
]}, ]},
{},
{id: 'exportAnnotations', title: Ox._('Export Annotations')},
], ],
style: 'square', style: 'square',
title: 'set', title: 'set',
@ -39,7 +41,20 @@ oml.ui.annotationPanel = function() {
}).css({ }).css({
// borderColor: 'transparent', // borderColor: 'transparent',
float: 'right' float: 'right'
}).appendTo($bar); }).appendTo($bar)
.bindEvent({
click: function(data) {
var id = data.id;
if (id == 'exportAnnotations') {
oml.api.get({
id: oml.user.ui.item,
keys: []
}, function(result) {
oml.ui.exportAnnotationsDialog(result.data).open()
})
}
}
});
var ui = oml.user.ui; var ui = oml.user.ui;
var that = Ox.SplitPanel({ var that = Ox.SplitPanel({

View file

@ -0,0 +1,62 @@
'use strict';
oml.ui.exportAnnotationsDialog = function(data) {
var ui = oml.user.ui,
$text = Ox.Input({
type: 'textarea',
style: 'squared',
value: getAnnotationsText(),
width: 640,
height: 480
})
.css({margin: '16px'}),
that = Ox.Dialog({
buttons: [
Ox.Button({
id: 'done',
style: 'squared',
title: Ox._('Done')
})
.bindEvent({
click: function() {
that.close();
}
})
],
closeButton: true,
content: $text,
height: 480 + 2 * 16,
keys: {enter: 'done'},
removeOnClose: true,
title: Ox._('Export Annotations'),
width: 640 + 2* 16
})
.bindEvent({
close: function() {
that.close();
},
open: function() {
// ..
}
});
function getAnnotationsText() {
var annotations = oml.$ui.viewer.getAnnotations()
var text = 'Annotations for ' + data.title + ' (' + data.author.join(', ') + ')\n\n\n\n'
text += annotations.map(function(annotation) {
var text = 'Quote:\n\n' + annotation.text
if (annotation.notes.length) {
text += '\n\nNotes:\n' + annotation.notes.map(function(note) {
return note.value
}).join('\n\n')
}
return text
}).join('\n\n\n\n')
return text
}
return that;
};

View file

@ -143,6 +143,8 @@ oml.ui.viewer = function() {
that.postMessage = function(event, data) { that.postMessage = function(event, data) {
$iframe && $iframe.postMessage(event, data) $iframe && $iframe.postMessage(event, data)
}; };
that.getAnnotations = function() {
return annotations;
}
return that.updateElement(); return that.updateElement();
}; };

View file

@ -19,6 +19,7 @@
"deleteListDialog.js", "deleteListDialog.js",
"editDialog.js", "editDialog.js",
"errorDialog.js", "errorDialog.js",
"exportAnnotationsDialog.js",
"filter.js", "filter.js",
"filtersInnerPanel.js", "filtersInnerPanel.js",
"filtersOuterPanel.js", "filtersOuterPanel.js",

View file

@ -113,9 +113,7 @@ function getText(book, cfiRange, cb) {
function onHighlightClicked(e) { function onHighlightClicked(e) {
console.log("highlight clicked", e.target.dataset.epubcfi); console.log("highlight clicked", e.target.dataset.epubcfi);
if(e.target.classList.contains('selected')) { if(!e.target.classList.contains('selected')) {
e.target.classList.remove('selected')
} else {
document.querySelectorAll('.epubjs-hl.selected').forEach(function(other) { document.querySelectorAll('.epubjs-hl.selected').forEach(function(other) {
other.classList.remove('selected') other.classList.remove('selected')
}) })

View file

@ -22,7 +22,7 @@ Ox.load({
} }
setTimeout(function() { setTimeout(function() {
var el = document.querySelector('.a' + annotation.id); var el = document.querySelector('.a' + annotation.id);
if (el) { if (el && !isInView(el)) {
document.querySelector('#viewerContainer').scrollTop = el.offsetTop + el.parentElement.offsetTop - 64; document.querySelector('#viewerContainer').scrollTop = el.offsetTop + el.parentElement.offsetTop - 64;
} }
}, delay) }, delay)
@ -134,10 +134,7 @@ function renderAnnotation(annotation) {
'width:' + Math.abs(bounds[0] - bounds[2]) + 'px; height:' + Math.abs(bounds[1] - bounds[3]) + 'px;'); 'width:' + Math.abs(bounds[0] - bounds[2]) + 'px; height:' + Math.abs(bounds[1] - bounds[3]) + 'px;');
el.addEventListener('click', function() { el.addEventListener('click', function() {
if (el.classList.contains('selected')) { if (!el.classList.contains('selected')) {
deselectAnnotation(annotation.id)
Ox.$parent.postMessage('selectAnnotation', {id: null})
} else {
selectAnnotation(annotation.id) selectAnnotation(annotation.id)
Ox.$parent.postMessage('selectAnnotation', {id: annotation.id}) Ox.$parent.postMessage('selectAnnotation', {id: annotation.id})
} }
@ -204,3 +201,10 @@ function loadAnnotations(page) {
}) })
} }
function isInView(element) {
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elementTop = $(element).offset().top;
var elementBottom = elementTop + $(element).height();
return elementTop < docViewBottom && elementBottom > docViewTop;
}