allow custom metadata

This commit is contained in:
j 2014-05-19 22:58:00 +02:00
parent ed7053c0cb
commit 996a754db5
12 changed files with 126 additions and 144 deletions

View file

@ -161,13 +161,17 @@ class Changelog(db.Model):
i = Item.get(itemid) i = Item.get(itemid)
if i.timestamp > timestamp: if i.timestamp > timestamp:
return True return True
key = meta.keys()[0] keys = filter(lambda k: k in Item.id_keys, meta.keys())
if not meta[key] and i.meta.get('mainid') == key: if keys:
logger.debug('remove id mapping %s currenrlt %s', key, meta[key], i.meta[key]) key = keys[0]
i.update_mainid(key, meta[key]) if not meta[key] and i.meta.get('mainid') == key:
elif meta[key] and (i.meta.get('mainid') != key or meta[key] != i.meta.get(key)): logger.debug('remove id mapping %s currently %s', key, meta[key], i.meta[key])
logger.debug('new mapping %s %s currently %s %s', key, meta[key], i.meta.get('mainid'), i.meta.get(i.meta.get('mainid'))) i.update_mainid(key, meta[key])
i.update_mainid(key, meta[key]) elif meta[key] and (i.meta.get('mainid') != key or meta[key] != i.meta.get(key)):
logger.debug('new mapping %s %s currently %s %s', key, meta[key], i.meta.get('mainid'), i.meta.get(i.meta.get('mainid')))
i.update_mainid(key, meta[key])
else:
i.update_meta(meta)
i.modified = datetime.fromtimestamp(float(timestamp)) i.modified = datetime.fromtimestamp(float(timestamp))
i.save() i.save()
return True return True

View file

@ -125,12 +125,11 @@ def edit(data):
item.update_mainid(key, data[key]) item.update_mainid(key, data[key])
response = item.json() response = item.json()
elif not item.meta.get('mainid'): elif not item.meta.get('mainid'):
logger.debug('chustom data %s', data) logger.debug('setting chustom metadata %s', data)
for key in ('title', 'author', 'date', 'publisher', 'edition'): item.update_meta(data)
if key in data: response = item.json()
item.meta[key] = data[key] else:
item.update() logger.debug('invalid metadata %s', data)
logger.debug('FIXME: custom metadata not published to changelog!!!')
else: else:
logger.info('can only edit available items') logger.info('can only edit available items')
return response return response

View file

@ -18,8 +18,6 @@ import ox
import settings import settings
from settings import db, config from settings import db, config
from user.models import User
from person import get_sort_name from person import get_sort_name
import media import media
@ -250,12 +248,17 @@ class Item(db.Model):
db.session.add(f) db.session.add(f)
def update(self): def update(self):
for key in ('mediastate', 'coverRatio'):
if key in self.meta:
if key not in self.info:
self.info[key] = self.meta[key]
del self.meta[key]
users = map(str, list(self.users)) users = map(str, list(self.users))
self.meta['mediastate'] = 'available' # available, unavailable, transferring self.info['mediastate'] = 'available' # available, unavailable, transferring
if self.transferadded and self.transferprogress < 1: if self.transferadded and self.transferprogress < 1:
self.meta['mediastate'] = 'transferring' self.info['mediastate'] = 'transferring'
else: else:
self.meta['mediastate'] = 'available' if settings.USER_ID in users else 'unavailable' self.info['mediastate'] = 'available' if settings.USER_ID in users else 'unavailable'
self.update_sort() self.update_sort()
self.update_find() self.update_find()
self.update_lists() self.update_lists()
@ -266,6 +269,15 @@ class Item(db.Model):
db.session.add(self) db.session.add(self)
db.session.commit() db.session.commit()
def update_meta(self, data):
self.meta = data
self.update()
self.modified = datetime.now()
self.save()
user = state.user()
if user in self.users:
Changelog.record(user, 'edititem', self.id, data)
def update_mainid(self, key, id): def update_mainid(self, key, id):
record = {} record = {}
if id: if id:
@ -317,7 +329,7 @@ class Item(db.Model):
covers[self.id] = cover covers[self.id] = cover
if cover: if cover:
img = Image.open(StringIO(cover)) img = Image.open(StringIO(cover))
self.meta['coverRatio'] = img.size[0]/img.size[1] self.info['coverRatio'] = img.size[0]/img.size[1]
for p in (':128', ':256', ':512'): for p in (':128', ':256', ':512'):
del covers['%s%s' % (self.id, p)] del covers['%s%s' % (self.id, p)]
return cover return cover

View file

@ -73,10 +73,10 @@ def cover(id, size=None):
if size: if size:
data = covers['%s:%s' % (id, size)] = resize_image(data, size=size) data = covers['%s:%s' % (id, size)] = resize_image(data, size=size)
data = str(data) data = str(data)
if not 'coverRatio' in item.meta: if not 'coverRatio' in item.info:
#img = Image.open(StringIO(str(covers[id]))) #img = Image.open(StringIO(str(covers[id])))
img = Image.open(StringIO(data)) img = Image.open(StringIO(data))
item.meta['coverRatio'] = img.size[0]/img.size[1] item.info['coverRatio'] = img.size[0]/img.size[1]
db.session.add(item) db.session.add(item)
db.session.commit() db.session.commit()
resp = make_response(data) resp = make_response(data)

View file

@ -24,7 +24,11 @@ providers = [
('abebooks', 'isbn10') ('abebooks', 'isbn10')
] ]
def find(title, author=None, publisher=None, date=None): def find(**kargs):
title = kargs.get('title')
author = kargs.get('author')
publisher = kargs.get('publisher')
date = kargs.get('date')
#results = google.find(title=title, author=author, publisher=publisher, date=date) #results = google.find(title=title, author=author, publisher=publisher, date=date)
results = duckduckgo.find(title=title, author=author, publisher=publisher, date=date) results = duckduckgo.find(title=title, author=author, publisher=publisher, date=date)
''' '''

View file

@ -38,7 +38,7 @@ def lookup(id):
title = mods.findall(ns + 'titleInfo') title = mods.findall(ns + 'titleInfo')
if not title: if not title:
return {} return {}
info['title'] = ''.join([e.text for e in title[0]]) info['title'] = ''.join([': ' + e.text.strip() if e.tag == ns + 'subTitle' else ' ' + e.text.strip() for e in title[0]]).strip()
origin = mods.findall(ns + 'originInfo') origin = mods.findall(ns + 'originInfo')
if origin: if origin:
info['place'] = [] info['place'] = []
@ -70,6 +70,9 @@ def lookup(id):
if a.attrib.get('usage') == 'primary': if a.attrib.get('usage') == 'primary':
info['author'].append(' '.join([e.text for e in a.findall(ns + 'namePart') if not e.attrib.get('type') in ('date', )])) info['author'].append(' '.join([e.text for e in a.findall(ns + 'namePart') if not e.attrib.get('type') in ('date', )]))
info['author'] = [ox.normalize_name(a) for a in info['author']] info['author'] = [ox.normalize_name(a) for a in info['author']]
toc = mods.findall(ns + 'tableOfContents')
if toc:
info['description'] = toc[0].text.strip()
for key in info.keys(): for key in info.keys():
if not info[key]: if not info[key]:
del info[key] del info[key]

View file

@ -5,12 +5,13 @@ from __future__ import division, with_statement
import inspect import inspect
import sys import sys
import json import json
import logging
from flask import request, Blueprint from flask import request, Blueprint
from .shortcuts import render_to_json_response, json_response from .shortcuts import render_to_json_response, json_response
import logging
logger = logging.getLogger('oxflask.api') logger = logging.getLogger('oxflask.api')
app = Blueprint('oxflask', __name__) app = Blueprint('oxflask', __name__)
@app.route('/api/', methods=['POST', 'OPTIONS']) @app.route('/api/', methods=['POST', 'OPTIONS'])

View file

@ -29,7 +29,7 @@ oml.ui.browser = function() {
borderWidth: Math.round(size / 64) + 'px 0', borderWidth: Math.round(size / 64) + 'px 0',
borderStyle: 'solid', borderStyle: 'solid',
borderColor: 'rgba(' + color[2].join(', ') + ')', borderColor: 'rgba(' + color[2].join(', ') + ')',
margin: Math.round(size / 18) + 'px ' + Math.round(width / 3) + 'px', margin: Math.round(size / 18) + 'px ' + Math.round(width / 2 - 14) + 'px',
fontSize: Math.round(size / 16) + 'px', fontSize: Math.round(size / 16) + 'px',
textAlign: 'center', textAlign: 'center',
color: 'rgba(' + color[2].join(', ') + ')', color: 'rgba(' + color[2].join(', ') + ')',

View file

@ -28,7 +28,7 @@ oml.ui.gridView = function() {
borderWidth: Math.round(size / 64) + 'px 0', borderWidth: Math.round(size / 64) + 'px 0',
borderStyle: 'solid', borderStyle: 'solid',
borderColor: 'rgba(' + color[2].join(', ') + ')', borderColor: 'rgba(' + color[2].join(', ') + ')',
margin: Math.round(size / 18) + 'px ' + Math.round(width / 3) + 'px', margin: Math.round(size / 18) + 'px ' + Math.round(width / 2 - 14) + 'px',
fontSize: Math.round(size / 16) + 'px', fontSize: Math.round(size / 16) + 'px',
textAlign: 'center', textAlign: 'center',
color: 'rgba(' + color[2].join(', ') + ')', color: 'rgba(' + color[2].join(', ') + ')',

View file

@ -14,19 +14,12 @@ oml.ui.identifyDialog = function(data) {
}), }),
keys = [ keys = [
'title', 'author', 'publisher', 'date' 'title', 'author', 'publisher', 'date', 'edition', 'language'
].map(function(id) { ].map(function(id) {
var key = Ox.getObjectById(oml.config.sortKeys, id); var key = Ox.getObjectById(oml.config.itemKeys, id);
return { return {
format: key.format, format: key.format,
id: id, id: id,
operator: key.operator,
width: {
title: 288,
author: 224,
publisher: 160,
date: 96 - Ox.UI.SCROLLBAR_SIZE
}[id],
title: key.title, title: key.title,
visible: true visible: true
}; };
@ -61,7 +54,7 @@ oml.ui.identifyDialog = function(data) {
$titlePanel = Ox.SplitPanel({ $titlePanel = Ox.SplitPanel({
elements: [ elements: [
{element: $titleForm, size: 96}, {element: $titleForm, size: 96},
{element: Ox.Element()} {element: renderResults()}
], ],
orientation: 'vertical' orientation: 'vertical'
}), }),
@ -134,12 +127,13 @@ oml.ui.identifyDialog = function(data) {
); );
that.options({content: Ox.LoadingScreen().start()}); that.options({content: Ox.LoadingScreen().start()});
that.disableButtons(); that.disableButtons();
Ox.print('VALUE SENT:', edit)
oml.api.edit(edit, function(result) { oml.api.edit(edit, function(result) {
that.close(); that.close();
Ox.Request.clearCache('find'); Ox.Request.clearCache('find');
oml.$ui.browser.reloadList(true); oml.$ui.browser.reloadList(true);
Ox.Request.clearCache(data.id); Ox.Request.clearCache(data.id);
oml.$ui.infoView.updateElement(result.data); oml.$ui.infoView.updateElement(data.id);
}); });
} }
}) })
@ -163,7 +157,6 @@ oml.ui.identifyDialog = function(data) {
disableButtons(); disableButtons();
$titlePanel.replaceElement(1, Ox.LoadingScreen().start()); $titlePanel.replaceElement(1, Ox.LoadingScreen().start());
oml.api.findMetadata(data, function(result) { oml.api.findMetadata(data, function(result) {
// FIXME: CONCAT HERE
var items = result.data.items.map(function(item, index) { var items = result.data.items.map(function(item, index) {
return Ox.extend({index: (index + 1).toString()}, item); return Ox.extend({index: (index + 1).toString()}, item);
}); });
@ -352,42 +345,48 @@ oml.ui.identifyDialog = function(data) {
var $list = Ox.TableList({ var $list = Ox.TableList({
columns: [ columns: [
{ {
format: function(value) { format: function(value, data) {
return Ox.getObjectById(ids, value).title; return value
? '<b>' + Ox.getObjectById(ids, value).title
+ ':</b> ' + data[data.mainid]
: '<b>No ID</b>'
}, },
id: 'mainid', id: 'mainid',
visible: true, visible: true,
width: 64 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, items: [{
'index': '0',
'mainid': ''
}].concat(items || []),
keys: ['mainid', 'isbn10', 'isbn13'], keys: ['mainid', 'isbn10', 'isbn13'],
min: 1, min: 1,
max: 1, max: 1,
scrollbarVisible: true, scrollbarVisible: true,
sort: [{key: 'mainid', operator: '+'}], sort: [{key: 'index', operator: '+'}],
unique: 'index' unique: 'index'
}) })
.bindEvent({ .bindEvent({
select: function(data) { select: function(data) {
var index = data.ids[0], mainid; var index = data.ids[0],
mainid = $list.value(index, 'mainid'); mainid = $list.value(index, 'mainid');
titleValue = Ox.extend({}, mainid, $list.value(index, mainid)); if (!mainid) {
$results.replaceElement(1, Ox.LoadingScreen().start()); titleValue = {};
oml.api.getMetadata(titleValue, function(result) { keys.forEach(function(key) {
if (index == $list.options('selected')[0]) { titleValue[key.id] = titleInputValue(key.id);
$results.replaceElement(1, oml.ui.infoView(result.data)); });
that.options('buttons')[1].options({disabled: false}); $results.replaceElement(1, oml.ui.infoView(titleValue));
} } else {
}); 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({ $results = Ox.SplitPanel({
@ -405,14 +404,14 @@ oml.ui.identifyDialog = function(data) {
$titleInputs = keys.map(function(key, index) { $titleInputs = keys.map(function(key, index) {
return Ox.Input({ return Ox.Input({
label: Ox._(key.title), label: Ox._(key.title),
labelWidth: 64, labelWidth: 80,
value: data[key.id], value: data[key.id],
width: 360 width: 240
}) })
.css({ .css({
position: 'absolute', position: 'absolute',
left: index < 2 ? '16px' : '392px', left: 16 + Math.floor(index / 2) * 248 + 'px',
top: index % 2 == 0 ? '16px' : '40px' top: 16 + (index % 2) * 24 + 'px'
}) })
.bindEvent({ .bindEvent({
submit: function(data) { submit: function(data) {

View file

@ -4,7 +4,9 @@ oml.ui.infoView = function(identifyData) {
var ui = oml.user.ui, var ui = oml.user.ui,
css = getCSS(ui.coverSize, oml.config.coverRatio), coverSize = identifyData ? 256 : ui.coverSize,
css = getCSS(coverSize, oml.config.coverRatio),
that = Ox.Element() that = Ox.Element()
.addClass('OxTextPage') .addClass('OxTextPage')
@ -39,7 +41,7 @@ oml.ui.infoView = function(identifyData) {
right: !identifyData ? '176px' : 16 + Ox.UI.SCROLLBAR_SIZE + 'px', right: !identifyData ? '176px' : 16 + Ox.UI.SCROLLBAR_SIZE + 'px',
top: '16px' top: '16px'
}) })
[ui.coverSize == 512 ? 'hide' : 'show']() [coverSize == 512 ? 'hide' : 'show']()
.appendTo(that), .appendTo(that),
$data, $data,
@ -226,8 +228,9 @@ oml.ui.infoView = function(identifyData) {
} }
function toggleCoverSize(ratio) { function toggleCoverSize(ratio) {
var coverSize = ui.coverSize == 256 ? 512 : 256, var css;
css = getCSS(coverSize, ratio); coverSize = coverSize == 256 ? 512 : 256,
css = getCSS(coverSize, ratio);
//$cover.animate(css.cover, 250); //$cover.animate(css.cover, 250);
$info.animate(css.info, 250); $info.animate(css.info, 250);
$image.animate(css.image, 250); $image.animate(css.image, 250);
@ -237,7 +240,7 @@ oml.ui.infoView = function(identifyData) {
} }
function updateCover(ratio) { function updateCover(ratio) {
var css = getCSS(ui.coverSize, ratio); var css = getCSS(coverSize, ratio);
$image.css(css.image).show(); $image.css(css.image).show();
$reflectionImage.css(css.image); $reflectionImage.css(css.image);
$reflection.css(css.reflection).show(); $reflection.css(css.reflection).show();
@ -271,7 +274,7 @@ oml.ui.infoView = function(identifyData) {
? '/' + data.id + '/cover512.jpg?' + data.modified ? '/' + data.id + '/cover512.jpg?' + data.modified
: data.cover, : data.cover,
ratio = data.coverRatio || oml.config.coverRatio, ratio = data.coverRatio || oml.config.coverRatio,
size = ui.coverSize, size = coverSize,
reflectionSize = Math.round(size / 2); reflectionSize = Math.round(size / 2);
$elements.forEach(function($element) { $elements.forEach(function($element) {
@ -307,7 +310,9 @@ oml.ui.infoView = function(identifyData) {
.hide() .hide()
.bindEvent({ .bindEvent({
singleclick: function() { singleclick: function() {
toggleCoverSize(ratio); if (!identifyData) {
toggleCoverSize(ratio);
}
} }
}) })
.appendTo($cover); .appendTo($cover);
@ -341,101 +346,56 @@ oml.ui.infoView = function(identifyData) {
} else if ($element == $info) { } else if ($element == $info) {
$('<div>') $('<div>')
.css({marginTop: '-2px'}) .css({
.append( marginTop: '-2px',
Ox.EditableContent({ fontSize: '13px',
clickLink: oml.clickLink, fontWeight: 'bold'
editable: isEditable, })
placeholder: formatLight(Ox._('Unknown Title')), .html(
tooltip: isEditable ? oml.getEditTooltip() : '', data.title
value: data.title || '' || '<span class="OxLight">'
}) + Ox._('No Title')
.bindEvent({ + '</span>'
submit: function(data) {
editMetadata('title', data.value);
}
})
.css({
fontSize: '13px',
fontWeight: 'bold'
})
) )
.appendTo($info); .appendTo($info);
if (data.author || isEditable) { if (data.author) {
$('<div>') $('<div>')
.css({ .css({
marginTop: '4px', marginTop: '4px',
fontSize: '13px', fontSize: '13px',
fontWeight: 'bold' fontWeight: 'bold'
}) })
.append( .html(formatValue(data.author, 'author'))
Ox.EditableContent({
clickLink: oml.clickLink,
editable: isEditable,
format: function(value) {
return !identifyData
? formatValue(value.split(', '), 'author')
: value;
},
placeholder: formatLight(Ox._('Unknown Author')),
tooltip: isEditable ? oml.getEditTooltip() : '',
value: data.author ? data.author.join(', ') : ''
})
.css({
fontSize: '13px',
fontWeight: 'bold'
})
.bindEvent({
submit: function(data) {
editMetadata('author', value.split(', '));
}
})
)
.appendTo($info); .appendTo($info);
} }
if (!isEditable) { if (data.place || data.publisher || data.date) {
$('<div>') $('<div>')
.css({ .css({
marginTop: '8px' marginTop: '8px'
}) })
.text( .text(
(data.place || '') (data.place || []).join(' ; ')
+ (data.place && (data.publisher || data.date) ? ' : ' : '') + (data.place && (data.publisher || data.date) ? ' : ' : '')
+ (data.publisher || '') + (data.publisher || '')
+ (data.publisher && data.date ? ', ' : '') + (data.publisher && data.date ? ', ' : '')
+ (data.date || '') + (data.date || '')
) )
.appendTo($info); .appendTo($info);
} else { }
var $div = $('<div>')
.addClass('OxSelectable') if (data.edition || data.language) {
.css({marginTop: '8px'}) $('<div>')
.css({
marginTop: '8px'
})
.text(
(data.edition || '')
+ (data.edition && data.language ? '; ' : '')
+ (data.language || '')
)
.appendTo($info); .appendTo($info);
['edition', 'publisher', 'date'].forEach(function(key, index) {
index && $('<div>').css({float: 'left'}).html(';&nbsp;').appendTo($div);
$('<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);
});
} }
if (data.classification) { if (data.classification) {

View file

@ -42,7 +42,7 @@ txtjs.open = function(url) {
scale; scale;
text = Ox.encodeHTMLEntities(text) text = Ox.encodeHTMLEntities(text)
.replace(/\r\n/g, '\n') .replace(/\r\n/g, '\n')
.replace(/\n/g, '<br>'); .replace(/[\r\n]/g, '<br>');
$text.html(text); $text.html(text);
$scrollText.html(text); $scrollText.html(text);
var textHeight = $text[0].clientHeight, var textHeight = $text[0].clientHeight,