covers/links
This commit is contained in:
parent
75a14fed1e
commit
b6daa19d73
15 changed files with 209 additions and 44 deletions
|
@ -110,8 +110,7 @@
|
||||||
"title": "Extension",
|
"title": "Extension",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"columnWidth": 80,
|
"columnWidth": 80,
|
||||||
"sort": true,
|
"sort": true
|
||||||
"find": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "size",
|
"id": "size",
|
||||||
|
|
|
@ -70,7 +70,7 @@ def autocompleteFolder(request):
|
||||||
folder, name = os.path.split(path)
|
folder, name = os.path.split(path)
|
||||||
if os.path.exists(folder):
|
if os.path.exists(folder):
|
||||||
prefix, folders, files = os.walk(folder).next()
|
prefix, folders, files = os.walk(folder).next()
|
||||||
folders = [os.path.join(prefix, f) for f in folders if not name or f.startswith(name)]
|
folders = [os.path.join(prefix, f) for f in folders if (not name or f.startswith(name)) and not f.startswith('.')]
|
||||||
if prefix == path:
|
if prefix == path:
|
||||||
folders = [path] + folders
|
folders = [path] + folders
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -24,10 +24,10 @@ class Downloads(Thread):
|
||||||
for i in item.models.Item.query.filter(
|
for i in item.models.Item.query.filter(
|
||||||
item.models.Item.transferadded!=None).filter(
|
item.models.Item.transferadded!=None).filter(
|
||||||
item.models.Item.transferprogress<1).order_by(item.models.Item.transferadded):
|
item.models.Item.transferprogress<1).order_by(item.models.Item.transferadded):
|
||||||
logger.debug('DOWNLOAD %s %s', i, i.users)
|
for u in i.users:
|
||||||
for p in i.users:
|
if state.nodes.check_online(u.id):
|
||||||
if state.nodes.check_online(p.id):
|
logger.debug('DOWNLOAD %s %s', i, u)
|
||||||
r = state.nodes.download(p.id, i)
|
r = state.nodes.download(u.id, i)
|
||||||
logger.debug('download ok? %s', r)
|
logger.debug('download ok? %s', r)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -318,7 +318,7 @@ class Item(db.Model):
|
||||||
if cover:
|
if cover:
|
||||||
img = Image.open(StringIO(cover))
|
img = Image.open(StringIO(cover))
|
||||||
self.meta['coverRatio'] = img.size[0]/img.size[1]
|
self.meta['coverRatio'] = img.size[0]/img.size[1]
|
||||||
for p in (':128', ':256'):
|
for p in (':128', ':256', ':512'):
|
||||||
del covers['%s%s' % (self.id, p)]
|
del covers['%s%s' % (self.id, p)]
|
||||||
return cover
|
return cover
|
||||||
|
|
||||||
|
@ -381,6 +381,8 @@ class Item(db.Model):
|
||||||
user = state.user()
|
user = state.user()
|
||||||
if user in self.users:
|
if user in self.users:
|
||||||
self.users.remove(user)
|
self.users.remove(user)
|
||||||
|
for l in self.lists.filter_by(user_id=user.id):
|
||||||
|
l.items.remove(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
if not self.users:
|
if not self.users:
|
||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
|
|
|
@ -2,22 +2,51 @@
|
||||||
# vi:si:et:sw=4:sts=4:ts=4
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
|
|
||||||
import sys
|
import os
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import zipfile
|
import zipfile
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
import re
|
||||||
|
|
||||||
import Image
|
import Image
|
||||||
import stdnum.isbn
|
import stdnum.isbn
|
||||||
|
|
||||||
from utils import normalize_isbn, find_isbns
|
from utils import normalize_isbn, find_isbns
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger('oml.media.epub')
|
||||||
|
|
||||||
def cover(path):
|
def cover(path):
|
||||||
img = Image.new('RGB', (80, 128))
|
logger.debug('cover %s', path)
|
||||||
o = StringIO()
|
z = zipfile.ZipFile(path)
|
||||||
img.save(o, format='jpeg')
|
data = None
|
||||||
data = o.getvalue()
|
for f in z.filelist:
|
||||||
o.close()
|
if 'cover' in f.filename and f.filename.split('.')[-1] in ('jpg', 'jpeg', 'png'):
|
||||||
|
logger.debug('using %s', f.filename)
|
||||||
|
data = z.read(f.filename)
|
||||||
|
break
|
||||||
|
if not data:
|
||||||
|
opf = [f.filename for f in z.filelist if f.filename.endswith('opf')]
|
||||||
|
if opf:
|
||||||
|
info = ET.fromstring(z.read(opf[0]))
|
||||||
|
manifest = info.findall('{http://www.idpf.org/2007/opf}manifest')[0]
|
||||||
|
for e in manifest.getchildren():
|
||||||
|
if 'html' in e.attrib['media-type']:
|
||||||
|
filename = e.attrib['href']
|
||||||
|
filename = os.path.normpath(os.path.join(os.path.dirname(opf[0]), filename))
|
||||||
|
html = z.read(filename)
|
||||||
|
img = re.compile('<img.*?src="(.*?)"').findall(html)
|
||||||
|
if img:
|
||||||
|
img = os.path.normpath(os.path.join(os.path.dirname(filename), img[0]))
|
||||||
|
logger.debug('using %s', img)
|
||||||
|
data = z.read(img)
|
||||||
|
break
|
||||||
|
if not data:
|
||||||
|
img = Image.new('RGB', (80, 128))
|
||||||
|
o = StringIO()
|
||||||
|
img.save(o, format='jpeg')
|
||||||
|
data = o.getvalue()
|
||||||
|
o.close()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def info(epub):
|
def info(epub):
|
||||||
|
|
|
@ -2,18 +2,19 @@
|
||||||
# vi:si:et:sw=4:sts=4:ts=4
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
from utils import find_isbns
|
from utils import find_isbns
|
||||||
from StringIO import StringIO
|
import tempfile
|
||||||
import Image
|
import subprocess
|
||||||
|
|
||||||
def cover(path):
|
def cover(path):
|
||||||
img = Image.new('RGB', (80, 128))
|
image = tempfile.mkstemp('.jpg')[1]
|
||||||
o = StringIO()
|
cmd = ['python2', 'static/txt.js/txt.py', '-i', path, '-o', image]
|
||||||
img.save(o, format='jpeg')
|
p = subprocess.Popen(cmd)
|
||||||
data = o.getvalue()
|
p.wait()
|
||||||
o.close()
|
with open(image, 'rb') as fd:
|
||||||
|
data = fd.read()
|
||||||
|
os.unlink(image)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def info(path):
|
def info(path):
|
||||||
|
|
|
@ -17,6 +17,7 @@ import json
|
||||||
from utils import valid, get_public_ipv6
|
from utils import valid, get_public_ipv6
|
||||||
import nodeapi
|
import nodeapi
|
||||||
import cert
|
import cert
|
||||||
|
from websocket import trigger_event
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger('oml.node.server')
|
logger = logging.getLogger('oml.node.server')
|
||||||
|
@ -109,12 +110,7 @@ class ShareHandler(tornado.web.RequestHandler):
|
||||||
|
|
||||||
|
|
||||||
def publish_node(app):
|
def publish_node(app):
|
||||||
host = get_public_ipv6()
|
update_online()
|
||||||
state.online = directory.put(settings.sk, {
|
|
||||||
'host': host,
|
|
||||||
'port': settings.server['node_port'],
|
|
||||||
'cert': settings.server['cert']
|
|
||||||
})
|
|
||||||
if state.online:
|
if state.online:
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
for u in user.models.User.query.filter_by(queued=True):
|
for u in user.models.User.query.filter_by(queued=True):
|
||||||
|
@ -122,6 +118,32 @@ def publish_node(app):
|
||||||
state.nodes.queue('add', u.id)
|
state.nodes.queue('add', u.id)
|
||||||
state.check_nodes = PeriodicCallback(lambda: check_nodes(app), 120000)
|
state.check_nodes = PeriodicCallback(lambda: check_nodes(app), 120000)
|
||||||
state.check_nodes.start()
|
state.check_nodes.start()
|
||||||
|
state._online = PeriodicCallback(update_online, 60000)
|
||||||
|
state._online.start()
|
||||||
|
|
||||||
|
def update_online():
|
||||||
|
host = get_public_ipv6()
|
||||||
|
if not host:
|
||||||
|
if state.online:
|
||||||
|
state.online = False
|
||||||
|
trigger_event('status', {
|
||||||
|
'id': settings.USER_ID,
|
||||||
|
'online': state.online
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
if host != state.host:
|
||||||
|
state.host = host
|
||||||
|
online = directory.put(settings.sk, {
|
||||||
|
'host': host,
|
||||||
|
'port': settings.server['node_port'],
|
||||||
|
'cert': settings.server['cert']
|
||||||
|
})
|
||||||
|
if online != state.online:
|
||||||
|
state.online = online
|
||||||
|
trigger_event('status', {
|
||||||
|
'id': settings.USER_ID,
|
||||||
|
'online': state.online
|
||||||
|
})
|
||||||
|
|
||||||
def check_nodes(app):
|
def check_nodes(app):
|
||||||
if state.online:
|
if state.online:
|
||||||
|
|
|
@ -2,6 +2,7 @@ websockets = []
|
||||||
nodes = False
|
nodes = False
|
||||||
main = None
|
main = None
|
||||||
online = False
|
online = False
|
||||||
|
host = None
|
||||||
|
|
||||||
activity = {}
|
activity = {}
|
||||||
|
|
||||||
|
|
16
oml/utils.py
16
oml/utils.py
|
@ -45,7 +45,7 @@ def get_by_id(objects, id):
|
||||||
return get_by_key(objects, 'id', id)
|
return get_by_key(objects, 'id', id)
|
||||||
|
|
||||||
def resize_image(data, width=None, size=None):
|
def resize_image(data, width=None, size=None):
|
||||||
source = Image.open(StringIO(data)).convert('RGB')
|
source = Image.open(StringIO(data)) #.convert('RGB')
|
||||||
source_width = source.size[0]
|
source_width = source.size[0]
|
||||||
source_height = source.size[1]
|
source_height = source.size[1]
|
||||||
if size:
|
if size:
|
||||||
|
@ -106,11 +106,15 @@ def valid(key, value, sig):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_public_ipv6():
|
def get_public_ipv6():
|
||||||
host = ('2a01:4f8:120:3201::3', 25519)
|
try:
|
||||||
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
host = ('2a01:4f8:120:3201::3', 25519)
|
||||||
s.connect(host)
|
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||||
ip = s.getsockname()[0]
|
s.settimeout(1)
|
||||||
s.close()
|
s.connect(host)
|
||||||
|
ip = s.getsockname()[0]
|
||||||
|
s.close()
|
||||||
|
except:
|
||||||
|
ip = None
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
def update_dict(root, data):
|
def update_dict(root, data):
|
||||||
|
|
|
@ -285,7 +285,7 @@ oml.URL = (function() {
|
||||||
// pushes a new URL (as string or from state)
|
// pushes a new URL (as string or from state)
|
||||||
that.push = function(stateOrURL, expandURL) {
|
that.push = function(stateOrURL, expandURL) {
|
||||||
var state,
|
var state,
|
||||||
title = oml.getPageTitle(stateOrURL)
|
title = oml.getPageTitle(stateOrURL),
|
||||||
url;
|
url;
|
||||||
oml.replaceURL = expandURL;
|
oml.replaceURL = expandURL;
|
||||||
if (Ox.isObject(stateOrURL)) {
|
if (Ox.isObject(stateOrURL)) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ oml.ui.infoView = function(identifyData) {
|
||||||
|
|
||||||
var ui = oml.user.ui,
|
var ui = oml.user.ui,
|
||||||
|
|
||||||
css = getCSS(ui.coverSize),
|
css = getCSS(ui.coverSize, oml.config.coverRatio),
|
||||||
|
|
||||||
that = Ox.Element()
|
that = Ox.Element()
|
||||||
.addClass('OxTextPage')
|
.addClass('OxTextPage')
|
||||||
|
@ -39,6 +39,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']()
|
||||||
.appendTo(that),
|
.appendTo(that),
|
||||||
|
|
||||||
$data,
|
$data,
|
||||||
|
@ -232,13 +233,6 @@ oml.ui.infoView = function(identifyData) {
|
||||||
$image.animate(css.image, 250);
|
$image.animate(css.image, 250);
|
||||||
$reflectionImage.animate(css.image, 250);
|
$reflectionImage.animate(css.image, 250);
|
||||||
$reflection.animate(css.reflection, 250);
|
$reflection.animate(css.reflection, 250);
|
||||||
/*
|
|
||||||
$reflectionGradient.animate({
|
|
||||||
width: iconSize + 'px',
|
|
||||||
height: iconSize / 2 + 'px'
|
|
||||||
}, 250);
|
|
||||||
*/
|
|
||||||
|
|
||||||
oml.UI.set({coverSize: coverSize});
|
oml.UI.set({coverSize: coverSize});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,9 +285,19 @@ oml.ui.infoView = function(identifyData) {
|
||||||
tooltip: '' // TODO
|
tooltip: '' // TODO
|
||||||
})
|
})
|
||||||
.on({
|
.on({
|
||||||
|
error: function() {
|
||||||
|
if (size == 512) {
|
||||||
|
$info.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
load: function() {
|
load: function() {
|
||||||
ratio = $image[0].width / $image[0].height;
|
ratio = $image[0].width / $image[0].height;
|
||||||
updateCover(ratio);
|
updateCover(ratio);
|
||||||
|
if (size == 512) {
|
||||||
|
$info.css({
|
||||||
|
left: getCSS(512, ratio).info.left
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.attr({src: src})
|
.attr({src: src})
|
||||||
|
|
|
@ -122,6 +122,17 @@ oml.clearFilters = function() {
|
||||||
oml.UI.set({find: find});
|
oml.UI.set({find: find});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
oml.clickLink = function(e) {
|
||||||
|
if (
|
||||||
|
e.target.hostname == document.location.hostname
|
||||||
|
&& !Ox.startsWith(e.target.pathname, '/static')
|
||||||
|
) {
|
||||||
|
oml.URL.push(e.target.pathname, true);
|
||||||
|
} else {
|
||||||
|
oml.openLink(e.target.href);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
oml.doHistory = function(action, items, targets, callback) {
|
oml.doHistory = function(action, items, targets, callback) {
|
||||||
|
@ -894,6 +905,15 @@ oml.hasDialogOrScreen = function() {
|
||||||
|| !!$('.OxScreen').length;
|
|| !!$('.OxScreen').length;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
oml.openLink = function(url) {
|
||||||
|
if (Ox.startsWith(url, 'mailto:')) {
|
||||||
|
window.open(url);
|
||||||
|
} else {
|
||||||
|
//window.open('/url=' + encodeURIComponent(url), '_blank');
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
oml.reloadList = function() {
|
oml.reloadList = function() {
|
||||||
oml.$ui.list.updateElement();
|
oml.$ui.list.updateElement();
|
||||||
};
|
};
|
||||||
|
@ -934,6 +954,7 @@ oml.resizeWindow = function() {
|
||||||
oml.$ui.leftPanel && oml.$ui.leftPanel.size(2, oml.getInfoHeight());
|
oml.$ui.leftPanel && oml.$ui.leftPanel.size(2, oml.getInfoHeight());
|
||||||
oml.resizeListFolders();
|
oml.resizeListFolders();
|
||||||
oml.$ui.rightPanel && oml.$ui.rightPanel.updateElement();
|
oml.$ui.rightPanel && oml.$ui.rightPanel.updateElement();
|
||||||
|
oml.$ui.list && oml.$ui.list.size();
|
||||||
};
|
};
|
||||||
|
|
||||||
oml.updateFilterMenus = function() {
|
oml.updateFilterMenus = function() {
|
||||||
|
|
82
static/txt.js/txt.py
Executable file
82
static/txt.js/txt.py
Executable file
|
@ -0,0 +1,82 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division
|
||||||
|
import os
|
||||||
|
|
||||||
|
import Image
|
||||||
|
from optparse import OptionParser
|
||||||
|
from ox.image import drawText, wrapText
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
|
||||||
|
root_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__)))
|
||||||
|
os.chdir(root_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def render(infile, outfile):
|
||||||
|
|
||||||
|
with open(infile) as f:
|
||||||
|
|
||||||
|
image_size = (768, 1024)
|
||||||
|
margin = 64
|
||||||
|
offset = margin
|
||||||
|
font_file = 'txt.ttf'
|
||||||
|
font_size = 24
|
||||||
|
line_height = 32
|
||||||
|
max_lines = (image_size[1] - 2 * margin) / line_height
|
||||||
|
|
||||||
|
image = Image.new('L', image_size, (255))
|
||||||
|
|
||||||
|
for line in f:
|
||||||
|
|
||||||
|
line = line.decode('utf-8').strip()
|
||||||
|
|
||||||
|
lines = wrapText(
|
||||||
|
line,
|
||||||
|
image_size[0] - 2 * margin,
|
||||||
|
# we don't want the last line that ends with an ellipsis
|
||||||
|
max_lines + 1,
|
||||||
|
'txt.ttf',
|
||||||
|
font_size
|
||||||
|
)
|
||||||
|
|
||||||
|
for line_ in lines:
|
||||||
|
drawText(
|
||||||
|
image,
|
||||||
|
(margin, offset),
|
||||||
|
line_,
|
||||||
|
font_file,
|
||||||
|
font_size,
|
||||||
|
(0)
|
||||||
|
)
|
||||||
|
offset += line_height
|
||||||
|
max_lines -= 1
|
||||||
|
|
||||||
|
if max_lines == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
if max_lines == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
image.save(outfile)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = OptionParser()
|
||||||
|
parser.add_option(
|
||||||
|
'-i', '--infile', dest='infile', help='txt file to be read'
|
||||||
|
)
|
||||||
|
parser.add_option(
|
||||||
|
'-o', '--outfile', dest='outfile', help='jpg file to be written'
|
||||||
|
)
|
||||||
|
(options, args) = parser.parse_args()
|
||||||
|
if None in (options.infile, options.outfile):
|
||||||
|
parser.print_help()
|
||||||
|
else:
|
||||||
|
render(options.infile, options.outfile)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
BIN
static/txt.js/txt.ttf
Normal file
BIN
static/txt.js/txt.ttf
Normal file
Binary file not shown.
Loading…
Reference in a new issue