PlexKodiConnect/resources/lib/kodi_db/common.py
2019-01-17 19:30:55 +01:00

166 lines
5.9 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from threading import Lock
from functools import wraps
from .. import utils, path_ops, app
KODIDB_LOCK = Lock()
DB_WRITE_ATTEMPTS = 100
def catch_operationalerrors(method):
"""
sqlite.OperationalError is raised immediately if another DB connection
is open, reading something that we're trying to change
So let's catch it and try again
"""
@wraps(method)
def wrapped(*args, **kwargs):
attempts = DB_WRITE_ATTEMPTS
while True:
try:
return method(*args, **kwargs)
except utils.OperationalError as err:
if 'database is locked' not in err:
raise
if app.APP.monitor.waitForAbort(0.1):
# PKC needs to quit
return
attempts -= 1
if attempts == 0:
# Reraise in order to NOT catch nested OperationalErrors
raise RuntimeError('Kodi database locked')
return wrapped
class KodiDBBase(object):
"""
Kodi database methods used for all types of items
"""
def __init__(self, texture_db=False, cursor=None, artcursor=None, lock=True):
"""
Allows direct use with a cursor instead of context mgr
"""
self._texture_db = texture_db
self.cursor = cursor
self.artconn = None
self.artcursor = artcursor
self.lock = lock
def __enter__(self):
if self.lock:
KODIDB_LOCK.acquire()
self.kodiconn = utils.kodi_sql(self.db_kind)
self.cursor = self.kodiconn.cursor()
if self._texture_db:
self.artconn = utils.kodi_sql('texture')
self.artcursor = self.artconn.cursor()
return self
def __exit__(self, e_typ, e_val, trcbak):
try:
if e_typ:
# re-raise any exception
return False
self.kodiconn.commit()
if self.artconn:
self.artconn.commit()
finally:
self.kodiconn.close()
if self.artconn:
self.artconn.close()
if self.lock:
KODIDB_LOCK.release()
def art_urls(self, kodi_id, kodi_type):
return (x[0] for x in
self.cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?',
(kodi_id, kodi_type)))
def artwork_generator(self, kodi_type, limit, offset):
query = 'SELECT url FROM art WHERE type == ? LIMIT ? OFFSET ?'
return (x[0] for x in
self.cursor.execute(query, (kodi_type, limit, offset)))
def add_artwork(self, artworks, kodi_id, kodi_type):
"""
Pass in an artworks dict (see PlexAPI) to set an items artwork.
"""
for kodi_art, url in artworks.iteritems():
self.add_art(url, kodi_id, kodi_type, kodi_art)
@catch_operationalerrors
def add_art(self, url, kodi_id, kodi_type, kodi_art):
"""
Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the
Kodi art table for item kodi_id/kodi_type. Will also cache everything
except actor portraits.
"""
self.cursor.execute('''
INSERT INTO art(media_id, media_type, type, url)
VALUES (?, ?, ?, ?)
''', (kodi_id, kodi_type, kodi_art, url))
def modify_artwork(self, artworks, kodi_id, kodi_type):
"""
Pass in an artworks dict (see PlexAPI) to set an items artwork.
"""
for kodi_art, url in artworks.iteritems():
self.modify_art(url, kodi_id, kodi_type, kodi_art)
@catch_operationalerrors
def modify_art(self, url, kodi_id, kodi_type, kodi_art):
"""
Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the
Kodi art table for item kodi_id/kodi_type. Will also cache everything
except actor portraits.
"""
self.cursor.execute('''
SELECT url FROM art
WHERE media_id = ? AND media_type = ? AND type = ?
LIMIT 1
''', (kodi_id, kodi_type, kodi_art,))
try:
# Update the artwork
old_url = self.cursor.fetchone()[0]
except TypeError:
# Add the artwork
self.cursor.execute('''
INSERT INTO art(media_id, media_type, type, url)
VALUES (?, ?, ?, ?)
''', (kodi_id, kodi_type, kodi_art, url))
else:
if url == old_url:
# Only cache artwork if it changed
return
self.delete_cached_artwork(old_url)
self.cursor.execute('''
UPDATE art SET url = ?
WHERE media_id = ? AND media_type = ? AND type = ?
''', (url, kodi_id, kodi_type, kodi_art))
def delete_artwork(self, kodi_id, kodi_type):
self.cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?',
(kodi_id, kodi_type, ))
for row in self.cursor.fetchall():
self.delete_cached_artwork(row[0])
@catch_operationalerrors
def delete_cached_artwork(self, url):
try:
self.artcursor.execute("SELECT cachedurl FROM texture WHERE url = ? LIMIT 1",
(url, ))
cachedurl = self.artcursor.fetchone()[0]
except TypeError:
# Could not find cached url
pass
else:
# Delete thumbnail as well as the entry
path = path_ops.translate_path("special://thumbnails/%s"
% cachedurl)
if path_ops.exists(path):
path_ops.rmtree(path, ignore_errors=True)
self.artcursor.execute("DELETE FROM texture WHERE url = ?", (url, ))