Merge branch 'master' of code.0x2620.org:0x2620/openmedialibrary
This commit is contained in:
commit
ef16e873a0
9 changed files with 142 additions and 68 deletions
2
ctl
2
ctl
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
@ -169,17 +161,17 @@ def info(value):
|
||||||
'publishedDate',
|
'publishedDate',
|
||||||
'publisher',
|
'publisher',
|
||||||
'title',
|
'title',
|
||||||
]:
|
]:
|
||||||
if key in _data:
|
if key in _data:
|
||||||
data[{
|
data[{
|
||||||
'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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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({
|
||||||
|
|
62
static/js/exportAnnotationsDialog.js
Normal file
62
static/js/exportAnnotationsDialog.js
Normal 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;
|
||||||
|
|
||||||
|
};
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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')
|
||||||
})
|
})
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue