2014-05-04 17:26:43 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2016-01-10 07:26:46 +00:00
|
|
|
import os
|
2014-08-12 08:16:57 +00:00
|
|
|
import sqlite3
|
2019-01-18 13:36:39 +00:00
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
2014-05-19 21:39:52 +00:00
|
|
|
|
2014-08-12 08:16:57 +00:00
|
|
|
import tornado.concurrent
|
|
|
|
import tornado.gen
|
2014-05-19 21:39:52 +00:00
|
|
|
import tornado.ioloop
|
|
|
|
import tornado.web
|
2018-08-26 14:03:48 +00:00
|
|
|
from tornado.concurrent import run_on_executor
|
2019-01-18 13:36:39 +00:00
|
|
|
import ox
|
2018-08-26 14:03:48 +00:00
|
|
|
|
2019-01-18 13:36:39 +00:00
|
|
|
from settings import static_path
|
2016-02-18 13:53:49 +00:00
|
|
|
from utils import resize_image, is_svg
|
2014-08-09 16:14:14 +00:00
|
|
|
import db
|
2014-05-04 17:26:43 +00:00
|
|
|
|
2016-01-10 07:26:46 +00:00
|
|
|
|
2014-05-21 00:02:21 +00:00
|
|
|
import logging
|
2015-11-29 14:56:38 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2014-05-21 00:02:21 +00:00
|
|
|
|
2018-08-26 14:03:48 +00:00
|
|
|
MAX_WORKERS = 4
|
|
|
|
|
|
|
|
|
2014-08-12 08:16:57 +00:00
|
|
|
|
2014-05-21 00:02:21 +00:00
|
|
|
class Icons(dict):
|
2014-05-04 17:26:43 +00:00
|
|
|
def __init__(self, db):
|
|
|
|
self._db = db
|
2014-05-17 09:24:17 +00:00
|
|
|
self.create()
|
2014-05-04 17:26:43 +00:00
|
|
|
|
|
|
|
def connect(self):
|
2016-01-29 16:51:13 +00:00
|
|
|
conn = sqlite3.connect(self._db, timeout=90)
|
2014-05-17 09:24:17 +00:00
|
|
|
return conn
|
2014-05-04 17:26:43 +00:00
|
|
|
|
|
|
|
def create(self):
|
2014-05-17 09:24:17 +00:00
|
|
|
conn = self.connect()
|
|
|
|
c = conn.cursor()
|
2014-09-02 22:32:44 +00:00
|
|
|
c.execute('CREATE TABLE IF NOT EXISTS icon (id varchar(64) unique, data blob)')
|
|
|
|
c.execute('CREATE TABLE IF NOT EXISTS setting (key varchar(256) unique, value text)')
|
2014-05-04 17:26:43 +00:00
|
|
|
if int(self.get_setting(c, 'version', 0)) < 1:
|
|
|
|
self.set_setting(c, 'version', 1)
|
|
|
|
|
|
|
|
def get_setting(self, c, key, default=None):
|
2014-09-02 22:32:44 +00:00
|
|
|
c.execute('SELECT value FROM setting WHERE key = ?', (key, ))
|
2014-05-04 17:26:43 +00:00
|
|
|
for row in c:
|
|
|
|
return row[0]
|
|
|
|
return default
|
|
|
|
|
|
|
|
def set_setting(self, c, key, value):
|
2014-09-02 22:32:44 +00:00
|
|
|
c.execute('INSERT OR REPLACE INTO setting values (?, ?)', (key, str(value)))
|
2014-05-04 17:26:43 +00:00
|
|
|
|
2016-01-10 07:26:46 +00:00
|
|
|
def default_cover(self):
|
|
|
|
with open(os.path.join(static_path, 'png', 'cover.png'), 'rb') as f:
|
|
|
|
data = f.read()
|
2014-05-04 17:26:43 +00:00
|
|
|
return data
|
|
|
|
|
|
|
|
def __getitem__(self, id, default=None):
|
2014-09-02 22:32:44 +00:00
|
|
|
sql = 'SELECT data FROM icon WHERE id=?'
|
2014-05-17 09:24:17 +00:00
|
|
|
conn = self.connect()
|
|
|
|
c = conn.cursor()
|
2014-05-04 17:26:43 +00:00
|
|
|
c.execute(sql, (id, ))
|
|
|
|
data = default
|
|
|
|
for row in c:
|
|
|
|
data = row[0]
|
|
|
|
break
|
|
|
|
c.close()
|
2014-05-17 09:24:17 +00:00
|
|
|
conn.close()
|
2014-05-04 17:26:43 +00:00
|
|
|
return data
|
|
|
|
|
|
|
|
def __setitem__(self, id, data):
|
2014-09-02 22:32:44 +00:00
|
|
|
sql = 'INSERT OR REPLACE INTO icon values (?, ?)'
|
2016-01-23 12:17:33 +00:00
|
|
|
try:
|
|
|
|
conn = self.connect()
|
|
|
|
c = conn.cursor()
|
|
|
|
data = sqlite3.Binary(data)
|
|
|
|
c.execute(sql, (id, data))
|
|
|
|
conn.commit()
|
|
|
|
c.close()
|
|
|
|
conn.close()
|
|
|
|
except:
|
2019-01-22 13:43:12 +00:00
|
|
|
logger.debug('failed to insert icon %s (%s)', id, self._db, exc_info=True)
|
2014-05-04 17:26:43 +00:00
|
|
|
|
|
|
|
def __delitem__(self, id):
|
2014-09-02 22:32:44 +00:00
|
|
|
sql = 'DELETE FROM icon WHERE id = ?'
|
2016-01-23 12:17:33 +00:00
|
|
|
try:
|
|
|
|
conn = self.connect()
|
|
|
|
c = conn.cursor()
|
|
|
|
c.execute(sql, (id, ))
|
|
|
|
conn.commit()
|
|
|
|
c.close()
|
|
|
|
conn.close()
|
|
|
|
except:
|
2019-01-22 13:43:12 +00:00
|
|
|
logger.debug('failed to delete icon %s (%s)', id, self._db)
|
2014-05-04 17:26:43 +00:00
|
|
|
|
2016-01-16 05:17:52 +00:00
|
|
|
def clear(self, prefix):
|
2016-01-23 12:17:33 +00:00
|
|
|
try:
|
|
|
|
conn = self.connect()
|
|
|
|
c = conn.cursor()
|
|
|
|
sql = 'DELETE FROM icon WHERE id = ?'
|
|
|
|
for size in (64, 128, 256, 512, 1024):
|
|
|
|
id = '%s%s' % (prefix, size)
|
|
|
|
c.execute(sql, (id, ))
|
|
|
|
conn.commit()
|
|
|
|
c.close()
|
|
|
|
conn.close()
|
|
|
|
except:
|
2019-01-24 12:49:01 +00:00
|
|
|
logger.debug('failed to clear icon %s', prefix)
|
2016-01-23 12:17:33 +00:00
|
|
|
|
|
|
|
def vacuum(self, ids):
|
|
|
|
conn = self.connect()
|
|
|
|
c = conn.cursor()
|
|
|
|
sql = 'SELECT id from icon'
|
|
|
|
c.execute(sql)
|
|
|
|
icons = [row[0] for row in c]
|
|
|
|
sql = 'DELETE FROM icon WHERE id = ?'
|
|
|
|
for i in icons:
|
|
|
|
id = i.split(':')[1]
|
|
|
|
if id not in ids:
|
|
|
|
c.execute(sql, (id, ))
|
|
|
|
conn.commit()
|
|
|
|
sql = 'VACUUM'
|
|
|
|
c.execute(sql)
|
|
|
|
conn.commit()
|
|
|
|
c.close()
|
|
|
|
conn.close()
|
2016-01-16 05:17:52 +00:00
|
|
|
|
2019-01-18 13:36:39 +00:00
|
|
|
def get_icons_db_path():
|
|
|
|
import settings
|
|
|
|
import shutil
|
|
|
|
|
2019-01-22 19:02:11 +00:00
|
|
|
library = os.path.expanduser(settings.preferences['libraryPath'])
|
|
|
|
metadata = os.path.join(library, 'Metadata')
|
2019-01-18 13:36:39 +00:00
|
|
|
icons_db_path = os.path.join(metadata, 'icons.db')
|
2019-01-22 19:02:11 +00:00
|
|
|
if os.path.exists(os.path.dirname(library)):
|
|
|
|
ox.makedirs(metadata)
|
|
|
|
old_icons_db_path = os.path.join(settings.data_path, 'icons.db')
|
|
|
|
if not os.path.exists(icons_db_path) and os.path.exists(old_icons_db_path):
|
|
|
|
shutil.move(old_icons_db_path, icons_db_path)
|
2019-01-18 13:36:39 +00:00
|
|
|
return icons_db_path
|
2014-05-19 21:39:52 +00:00
|
|
|
|
2016-01-21 07:05:49 +00:00
|
|
|
def get_icon_sync(id, type_, size):
|
2014-05-26 10:11:13 +00:00
|
|
|
if size:
|
|
|
|
skey = '%s:%s:%s' % (type_, id, size)
|
|
|
|
data = icons[skey]
|
|
|
|
if data:
|
2016-01-21 07:05:49 +00:00
|
|
|
return bytes(data)
|
2014-05-26 10:11:13 +00:00
|
|
|
key = '%s:%s' % (type_, id)
|
2014-05-27 09:59:23 +00:00
|
|
|
data = icons[key]
|
2016-02-18 13:53:49 +00:00
|
|
|
if is_svg(data):
|
|
|
|
return bytes(data)
|
2014-05-27 09:59:23 +00:00
|
|
|
if not data:
|
2014-05-26 10:11:13 +00:00
|
|
|
type_ = 'preview' if type_ == 'cover' else 'cover'
|
2014-05-27 09:59:23 +00:00
|
|
|
key = '%s:%s' % (type_, id)
|
2014-05-26 10:11:13 +00:00
|
|
|
if size:
|
|
|
|
skey = '%s:%s:%s' % (type_, id, size)
|
|
|
|
if size:
|
|
|
|
data = icons[skey]
|
|
|
|
if data:
|
|
|
|
size = None
|
|
|
|
if not data:
|
|
|
|
data = icons[key]
|
|
|
|
if not data:
|
2016-01-10 07:26:46 +00:00
|
|
|
skey = '%s:%s:%s' % ('default', 'cover', size)
|
|
|
|
if size:
|
|
|
|
data = icons[skey]
|
|
|
|
if data:
|
|
|
|
size = None
|
|
|
|
if not data:
|
|
|
|
data = icons.default_cover()
|
|
|
|
if size:
|
2016-02-18 13:53:49 +00:00
|
|
|
try:
|
|
|
|
data = resize_image(data, size=size)
|
|
|
|
except:
|
|
|
|
logger.debug('failed to resize default cover %s %s %s', id, size, skey)
|
|
|
|
data = None
|
|
|
|
if data:
|
|
|
|
icons[skey] = data
|
2014-05-26 10:11:13 +00:00
|
|
|
size = None
|
|
|
|
if size:
|
2016-02-18 13:53:49 +00:00
|
|
|
try:
|
|
|
|
data = resize_image(data, size=size)
|
|
|
|
except:
|
|
|
|
logger.debug('failed to resize %s %s %s', id, size, skey)
|
|
|
|
data = None
|
|
|
|
if data:
|
|
|
|
icons[skey] = data
|
|
|
|
if data:
|
|
|
|
data = bytes(data)
|
|
|
|
else:
|
|
|
|
data = ''
|
2016-01-21 07:05:49 +00:00
|
|
|
return data
|
|
|
|
|
2016-01-10 07:26:46 +00:00
|
|
|
def clear_default_cover_cache():
|
2016-01-16 05:17:52 +00:00
|
|
|
icons.clear('default:cover:')
|
2016-01-10 07:26:46 +00:00
|
|
|
|
2014-05-19 21:39:52 +00:00
|
|
|
|
2014-05-21 00:02:21 +00:00
|
|
|
class IconHandler(tornado.web.RequestHandler):
|
2018-08-26 14:03:48 +00:00
|
|
|
executor = ThreadPoolExecutor(max_workers=MAX_WORKERS)
|
2014-05-19 21:39:52 +00:00
|
|
|
|
2014-08-09 17:06:06 +00:00
|
|
|
def initialize(self):
|
|
|
|
pass
|
2014-05-19 21:39:52 +00:00
|
|
|
|
2018-08-26 14:03:48 +00:00
|
|
|
@run_on_executor
|
|
|
|
def get_icon(self, id, type_, size):
|
|
|
|
return get_icon_sync(id, type_, size)
|
|
|
|
|
2014-05-19 21:39:52 +00:00
|
|
|
@tornado.gen.coroutine
|
2014-05-21 00:02:21 +00:00
|
|
|
def get(self, id, type_, size=None):
|
|
|
|
|
|
|
|
size = int(size) if size else None
|
|
|
|
|
|
|
|
if type_ not in ('cover', 'preview'):
|
2014-05-25 20:32:00 +00:00
|
|
|
self.set_status(404)
|
2014-05-21 00:02:21 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
self.set_header('Content-Type', 'image/jpeg')
|
|
|
|
|
2018-08-26 14:03:48 +00:00
|
|
|
response = yield self.get_icon(id, type_, size)
|
2014-05-21 00:02:21 +00:00
|
|
|
if not response:
|
2014-05-25 20:32:00 +00:00
|
|
|
self.set_status(404)
|
2014-05-21 00:02:21 +00:00
|
|
|
return
|
|
|
|
if self._finished:
|
|
|
|
return
|
|
|
|
self.write(response)
|
2019-01-18 13:36:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
icons = Icons(get_icons_db_path())
|