Merge pull request #1058 from croneter/fix-locked-db
Fix database being locked in rare cases
This commit is contained in:
commit
8e72033aef
10 changed files with 265 additions and 221 deletions
97
resources/lib/db.py
Normal file
97
resources/lib/db.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sqlite3
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from . import variables as v, app
|
||||||
|
|
||||||
|
DB_WRITE_ATTEMPTS = 100
|
||||||
|
|
||||||
|
|
||||||
|
class LockedDatabase(Exception):
|
||||||
|
"""
|
||||||
|
Dedicated class to make sure we're not silently catching locked DBs.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Also see https://github.com/mattn/go-sqlite3/issues/274
|
||||||
|
"""
|
||||||
|
@wraps(method)
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
attempts = DB_WRITE_ATTEMPTS
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
return method(self, *args, **kwargs)
|
||||||
|
except sqlite3.OperationalError as err:
|
||||||
|
if 'database is locked' not in err:
|
||||||
|
# Not an error we want to catch, so reraise it
|
||||||
|
raise
|
||||||
|
attempts -= 1
|
||||||
|
if attempts == 0:
|
||||||
|
# Reraise in order to NOT catch nested OperationalErrors
|
||||||
|
raise LockedDatabase('Database is locked')
|
||||||
|
# Need to close the transactions and begin new ones
|
||||||
|
self.kodiconn.commit()
|
||||||
|
if self.artconn:
|
||||||
|
self.artconn.commit()
|
||||||
|
if app.APP.monitor.waitForAbort(0.1):
|
||||||
|
# PKC needs to quit
|
||||||
|
return
|
||||||
|
# Start new transactions
|
||||||
|
self.kodiconn.execute('BEGIN')
|
||||||
|
if self.artconn:
|
||||||
|
self.artconn.execute('BEGIN')
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def _initial_db_connection_setup(conn):
|
||||||
|
"""
|
||||||
|
Set-up DB e.g. for WAL journal mode, if that hasn't already been done
|
||||||
|
before. Also start a transaction
|
||||||
|
"""
|
||||||
|
conn.execute('PRAGMA journal_mode=WAL;')
|
||||||
|
conn.execute('PRAGMA cache_size = -8000;')
|
||||||
|
conn.execute('PRAGMA synchronous=NORMAL;')
|
||||||
|
conn.execute('BEGIN')
|
||||||
|
|
||||||
|
|
||||||
|
def connect(media_type=None):
|
||||||
|
"""
|
||||||
|
Open a connection to the Kodi database.
|
||||||
|
media_type: 'video' (standard if not passed), 'plex', 'music', 'texture'
|
||||||
|
"""
|
||||||
|
if media_type == "plex":
|
||||||
|
db_path = v.DB_PLEX_PATH
|
||||||
|
elif media_type == "music":
|
||||||
|
db_path = v.DB_MUSIC_PATH
|
||||||
|
elif media_type == "texture":
|
||||||
|
db_path = v.DB_TEXTURE_PATH
|
||||||
|
else:
|
||||||
|
db_path = v.DB_VIDEO_PATH
|
||||||
|
conn = sqlite3.connect(db_path, timeout=30.0)
|
||||||
|
attempts = DB_WRITE_ATTEMPTS
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
_initial_db_connection_setup(conn)
|
||||||
|
except sqlite3.OperationalError as err:
|
||||||
|
if 'database is locked' not in err:
|
||||||
|
# Not an error we want to catch, so reraise it
|
||||||
|
raise
|
||||||
|
attempts -= 1
|
||||||
|
if attempts == 0:
|
||||||
|
# Reraise in order to NOT catch nested OperationalErrors
|
||||||
|
raise LockedDatabase('Database is locked')
|
||||||
|
if app.APP.monitor.waitForAbort(0.05):
|
||||||
|
# PKC needs to quit
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return conn
|
|
@ -6,7 +6,7 @@ from ntpath import dirname
|
||||||
|
|
||||||
from ..plex_db import PlexDB, PLEXDB_LOCK
|
from ..plex_db import PlexDB, PLEXDB_LOCK
|
||||||
from ..kodi_db import KodiVideoDB, KODIDB_LOCK
|
from ..kodi_db import KodiVideoDB, KODIDB_LOCK
|
||||||
from .. import utils, timing, app
|
from .. import db, timing, app
|
||||||
|
|
||||||
LOG = getLogger('PLEX.itemtypes.common')
|
LOG = getLogger('PLEX.itemtypes.common')
|
||||||
|
|
||||||
|
@ -57,11 +57,11 @@ class ItemBase(object):
|
||||||
if self.lock:
|
if self.lock:
|
||||||
PLEXDB_LOCK.acquire()
|
PLEXDB_LOCK.acquire()
|
||||||
KODIDB_LOCK.acquire()
|
KODIDB_LOCK.acquire()
|
||||||
self.plexconn = utils.kodi_sql('plex')
|
self.plexconn = db.connect('plex')
|
||||||
self.plexcursor = self.plexconn.cursor()
|
self.plexcursor = self.plexconn.cursor()
|
||||||
self.kodiconn = utils.kodi_sql('video')
|
self.kodiconn = db.connect('video')
|
||||||
self.kodicursor = self.kodiconn.cursor()
|
self.kodicursor = self.kodiconn.cursor()
|
||||||
self.artconn = utils.kodi_sql('texture')
|
self.artconn = db.connect('texture')
|
||||||
self.artcursor = self.artconn.cursor()
|
self.artcursor = self.artconn.cursor()
|
||||||
self.plexdb = PlexDB(plexconn=self.plexconn, lock=False)
|
self.plexdb = PlexDB(plexconn=self.plexconn, lock=False)
|
||||||
self.kodidb = KodiVideoDB(texture_db=True,
|
self.kodidb = KodiVideoDB(texture_db=True,
|
||||||
|
|
|
@ -7,7 +7,7 @@ from .common import ItemBase
|
||||||
from ..plex_api import API
|
from ..plex_api import API
|
||||||
from ..plex_db import PlexDB, PLEXDB_LOCK
|
from ..plex_db import PlexDB, PLEXDB_LOCK
|
||||||
from ..kodi_db import KodiMusicDB, KODIDB_LOCK
|
from ..kodi_db import KodiMusicDB, KODIDB_LOCK
|
||||||
from .. import plex_functions as PF, utils, timing, app, variables as v
|
from .. import plex_functions as PF, db, timing, app, variables as v
|
||||||
|
|
||||||
LOG = getLogger('PLEX.music')
|
LOG = getLogger('PLEX.music')
|
||||||
|
|
||||||
|
@ -20,11 +20,11 @@ class MusicMixin(object):
|
||||||
if self.lock:
|
if self.lock:
|
||||||
PLEXDB_LOCK.acquire()
|
PLEXDB_LOCK.acquire()
|
||||||
KODIDB_LOCK.acquire()
|
KODIDB_LOCK.acquire()
|
||||||
self.plexconn = utils.kodi_sql('plex')
|
self.plexconn = db.connect('plex')
|
||||||
self.plexcursor = self.plexconn.cursor()
|
self.plexcursor = self.plexconn.cursor()
|
||||||
self.kodiconn = utils.kodi_sql('music')
|
self.kodiconn = db.connect('music')
|
||||||
self.kodicursor = self.kodiconn.cursor()
|
self.kodicursor = self.kodiconn.cursor()
|
||||||
self.artconn = utils.kodi_sql('texture')
|
self.artconn = db.connect('texture')
|
||||||
self.artcursor = self.artconn.cursor()
|
self.artcursor = self.artconn.cursor()
|
||||||
self.plexdb = PlexDB(plexconn=self.plexconn, lock=False)
|
self.plexdb = PlexDB(plexconn=self.plexconn, lock=False)
|
||||||
self.kodidb = KodiMusicDB(texture_db=True,
|
self.kodidb = KodiMusicDB(texture_db=True,
|
||||||
|
|
|
@ -8,7 +8,7 @@ from .video import KodiVideoDB
|
||||||
from .music import KodiMusicDB
|
from .music import KodiMusicDB
|
||||||
from .texture import KodiTextureDB
|
from .texture import KodiTextureDB
|
||||||
|
|
||||||
from .. import path_ops, utils, timing, variables as v
|
from .. import path_ops, utils, variables as v
|
||||||
|
|
||||||
LOG = getLogger('PLEX.kodi_db')
|
LOG = getLogger('PLEX.kodi_db')
|
||||||
|
|
||||||
|
@ -56,30 +56,7 @@ def setup_kodi_default_entries():
|
||||||
"""
|
"""
|
||||||
if utils.settings('enableMusic') == 'true':
|
if utils.settings('enableMusic') == 'true':
|
||||||
with KodiMusicDB() as kodidb:
|
with KodiMusicDB() as kodidb:
|
||||||
kodidb.cursor.execute('''
|
kodidb.setup_kodi_default_entries()
|
||||||
INSERT OR REPLACE INTO artist(
|
|
||||||
idArtist,
|
|
||||||
strArtist,
|
|
||||||
strMusicBrainzArtistID)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
''', (1, '[Missing Tag]', 'Artist Tag Missing'))
|
|
||||||
kodidb.cursor.execute('''
|
|
||||||
INSERT OR REPLACE INTO role(
|
|
||||||
idRole,
|
|
||||||
strRole)
|
|
||||||
VALUES (?, ?)
|
|
||||||
''', (1, 'Artist'))
|
|
||||||
if v.KODIVERSION >= 18:
|
|
||||||
kodidb.cursor.execute('DELETE FROM versiontagscan')
|
|
||||||
kodidb.cursor.execute('''
|
|
||||||
INSERT INTO versiontagscan(
|
|
||||||
idVersion,
|
|
||||||
iNeedsScan,
|
|
||||||
lastscanned)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
''', (v.DB_MUSIC_VERSION,
|
|
||||||
0,
|
|
||||||
timing.kodi_now()))
|
|
||||||
|
|
||||||
|
|
||||||
def reset_cached_images():
|
def reset_cached_images():
|
||||||
|
@ -98,10 +75,7 @@ def reset_cached_images():
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
with KodiTextureDB() as kodidb:
|
with KodiTextureDB() as kodidb:
|
||||||
for row in kodidb.cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type=?',
|
kodidb.reset_cached_images()
|
||||||
('table', )):
|
|
||||||
if row[0] != 'version':
|
|
||||||
kodidb.cursor.execute("DELETE FROM %s" % row[0])
|
|
||||||
|
|
||||||
|
|
||||||
def wipe_dbs(music=True):
|
def wipe_dbs(music=True):
|
||||||
|
@ -112,25 +86,13 @@ def wipe_dbs(music=True):
|
||||||
DO NOT use context menu as we need to connect without WAL mode - if Kodi
|
DO NOT use context menu as we need to connect without WAL mode - if Kodi
|
||||||
is still accessing the DB
|
is still accessing the DB
|
||||||
"""
|
"""
|
||||||
from sqlite3 import connect
|
|
||||||
LOG.warn('Wiping Kodi databases!')
|
LOG.warn('Wiping Kodi databases!')
|
||||||
kinds = [v.DB_VIDEO_PATH, v.DB_TEXTURE_PATH]
|
kinds = [KodiVideoDB, KodiTextureDB]
|
||||||
if music:
|
if music:
|
||||||
kinds.append(v.DB_MUSIC_PATH)
|
kinds.insert(1, KodiMusicDB)
|
||||||
for path in kinds:
|
for kind in kinds:
|
||||||
conn = connect(path, timeout=30.0)
|
with kind() as kodidb:
|
||||||
cursor = conn.cursor()
|
kodidb.wipe()
|
||||||
cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table'")
|
|
||||||
tables = cursor.fetchall()
|
|
||||||
tables = [i[0] for i in tables]
|
|
||||||
if 'version' in tables:
|
|
||||||
tables.remove('version')
|
|
||||||
if 'versiontagscan' in tables:
|
|
||||||
tables.remove('versiontagscan')
|
|
||||||
for table in tables:
|
|
||||||
cursor.execute('DELETE FROM %s' % table)
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
setup_kodi_default_entries()
|
setup_kodi_default_entries()
|
||||||
# Delete SQLITE wal files
|
# Delete SQLITE wal files
|
||||||
import xbmc
|
import xbmc
|
||||||
|
@ -140,6 +102,14 @@ def wipe_dbs(music=True):
|
||||||
xbmc.executebuiltin('UpdateLibrary(music)')
|
xbmc.executebuiltin('UpdateLibrary(music)')
|
||||||
|
|
||||||
|
|
||||||
|
def create_kodi_db_indicees():
|
||||||
|
"""
|
||||||
|
Index the "actors" because we got a TON - speed up SELECT and WHEN
|
||||||
|
"""
|
||||||
|
with KodiVideoDB() as kodidb:
|
||||||
|
kodidb.create_kodi_db_indicees()
|
||||||
|
|
||||||
|
|
||||||
KODIDB_FROM_PLEXTYPE = {
|
KODIDB_FROM_PLEXTYPE = {
|
||||||
v.PLEX_TYPE_MOVIE: KodiVideoDB,
|
v.PLEX_TYPE_MOVIE: KodiVideoDB,
|
||||||
v.PLEX_TYPE_SHOW: KodiVideoDB,
|
v.PLEX_TYPE_SHOW: KodiVideoDB,
|
||||||
|
|
|
@ -2,56 +2,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
from .. import utils, path_ops, app
|
from .. import db, path_ops
|
||||||
|
|
||||||
KODIDB_LOCK = Lock()
|
KODIDB_LOCK = Lock()
|
||||||
DB_WRITE_ATTEMPTS = 100
|
# Names of tables we generally leave untouched and e.g. don't wipe
|
||||||
|
UNTOUCHED_TABLES = ('version', 'versiontagscan')
|
||||||
|
|
||||||
class LockedKodiDatabase(Exception):
|
|
||||||
"""
|
|
||||||
Dedicated class to make sure we're not silently catching locked DBs.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Also see https://github.com/mattn/go-sqlite3/issues/274
|
|
||||||
"""
|
|
||||||
@wraps(method)
|
|
||||||
def wrapper(self, *args, **kwargs):
|
|
||||||
attempts = DB_WRITE_ATTEMPTS
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
return method(self, *args, **kwargs)
|
|
||||||
except utils.OperationalError as err:
|
|
||||||
if 'database is locked' not in err:
|
|
||||||
# Not an error we want to catch, so reraise it
|
|
||||||
raise
|
|
||||||
attempts -= 1
|
|
||||||
if attempts == 0:
|
|
||||||
# Reraise in order to NOT catch nested OperationalErrors
|
|
||||||
raise LockedKodiDatabase('Kodi database locked')
|
|
||||||
# Need to close the transactions and begin new ones
|
|
||||||
self.kodiconn.commit()
|
|
||||||
if self.artconn:
|
|
||||||
self.artconn.commit()
|
|
||||||
if app.APP.monitor.waitForAbort(0.1):
|
|
||||||
# PKC needs to quit
|
|
||||||
return
|
|
||||||
# Start new transactions
|
|
||||||
self.kodiconn.execute('BEGIN')
|
|
||||||
if self.artconn:
|
|
||||||
self.artconn.execute('BEGIN')
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
class KodiDBBase(object):
|
class KodiDBBase(object):
|
||||||
|
@ -72,9 +28,9 @@ class KodiDBBase(object):
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
if self.lock:
|
if self.lock:
|
||||||
KODIDB_LOCK.acquire()
|
KODIDB_LOCK.acquire()
|
||||||
self.kodiconn = utils.kodi_sql(self.db_kind)
|
self.kodiconn = db.connect(self.db_kind)
|
||||||
self.cursor = self.kodiconn.cursor()
|
self.cursor = self.kodiconn.cursor()
|
||||||
self.artconn = utils.kodi_sql('texture') if self._texture_db else None
|
self.artconn = db.connect('texture') if self._texture_db else None
|
||||||
self.artcursor = self.artconn.cursor() if self._texture_db else None
|
self.artcursor = self.artconn.cursor() if self._texture_db else None
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -110,7 +66,7 @@ class KodiDBBase(object):
|
||||||
for kodi_art, url in artworks.iteritems():
|
for kodi_art, url in artworks.iteritems():
|
||||||
self.add_art(url, kodi_id, kodi_type, kodi_art)
|
self.add_art(url, kodi_id, kodi_type, kodi_art)
|
||||||
|
|
||||||
@catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_art(self, url, kodi_id, kodi_type, kodi_art):
|
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
|
Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the
|
||||||
|
@ -129,7 +85,7 @@ class KodiDBBase(object):
|
||||||
for kodi_art, url in artworks.iteritems():
|
for kodi_art, url in artworks.iteritems():
|
||||||
self.modify_art(url, kodi_id, kodi_type, kodi_art)
|
self.modify_art(url, kodi_id, kodi_type, kodi_art)
|
||||||
|
|
||||||
@catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def modify_art(self, url, kodi_id, kodi_type, kodi_art):
|
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
|
Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the
|
||||||
|
@ -166,7 +122,7 @@ class KodiDBBase(object):
|
||||||
for row in self.cursor.fetchall():
|
for row in self.cursor.fetchall():
|
||||||
self.delete_cached_artwork(row[0])
|
self.delete_cached_artwork(row[0])
|
||||||
|
|
||||||
@catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def delete_cached_artwork(self, url):
|
def delete_cached_artwork(self, url):
|
||||||
try:
|
try:
|
||||||
self.artcursor.execute("SELECT cachedurl FROM texture WHERE url = ? LIMIT 1",
|
self.artcursor.execute("SELECT cachedurl FROM texture WHERE url = ? LIMIT 1",
|
||||||
|
@ -182,3 +138,16 @@ class KodiDBBase(object):
|
||||||
if path_ops.exists(path):
|
if path_ops.exists(path):
|
||||||
path_ops.rmtree(path, ignore_errors=True)
|
path_ops.rmtree(path, ignore_errors=True)
|
||||||
self.artcursor.execute("DELETE FROM texture WHERE url = ?", (url, ))
|
self.artcursor.execute("DELETE FROM texture WHERE url = ?", (url, ))
|
||||||
|
|
||||||
|
@db.catch_operationalerrors
|
||||||
|
def wipe(self):
|
||||||
|
"""
|
||||||
|
Completely wipes the corresponding Kodi database
|
||||||
|
"""
|
||||||
|
self.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table'")
|
||||||
|
tables = [i[0] for i in self.cursor.fetchall()]
|
||||||
|
for table in UNTOUCHED_TABLES:
|
||||||
|
if table in tables:
|
||||||
|
tables.remove(table)
|
||||||
|
for table in tables:
|
||||||
|
self.cursor.execute('DELETE FROM %s' % table)
|
||||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from . import common
|
from . import common
|
||||||
from .. import variables as v, app
|
from .. import db, variables as v, app, timing
|
||||||
|
|
||||||
LOG = getLogger('PLEX.kodi_db.music')
|
LOG = getLogger('PLEX.kodi_db.music')
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ LOG = getLogger('PLEX.kodi_db.music')
|
||||||
class KodiMusicDB(common.KodiDBBase):
|
class KodiMusicDB(common.KodiDBBase):
|
||||||
db_kind = 'music'
|
db_kind = 'music'
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_path(self, path):
|
def add_path(self, path):
|
||||||
"""
|
"""
|
||||||
Add the path (unicode) to the music DB, if it does not exist already.
|
Add the path (unicode) to the music DB, if it does not exist already.
|
||||||
|
@ -34,7 +34,38 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
(pathid, path, '123'))
|
(pathid, path, '123'))
|
||||||
return pathid
|
return pathid
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
|
def setup_kodi_default_entries(self):
|
||||||
|
"""
|
||||||
|
Makes sure that we retain the Kodi standard databases. E.g. that there
|
||||||
|
is a dummy artist with ID 1
|
||||||
|
"""
|
||||||
|
self.cursor.execute('''
|
||||||
|
INSERT OR REPLACE INTO artist(
|
||||||
|
idArtist,
|
||||||
|
strArtist,
|
||||||
|
strMusicBrainzArtistID)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
''', (1, '[Missing Tag]', 'Artist Tag Missing'))
|
||||||
|
self.cursor.execute('''
|
||||||
|
INSERT OR REPLACE INTO role(
|
||||||
|
idRole,
|
||||||
|
strRole)
|
||||||
|
VALUES (?, ?)
|
||||||
|
''', (1, 'Artist'))
|
||||||
|
if v.KODIVERSION >= 18:
|
||||||
|
self.cursor.execute('DELETE FROM versiontagscan')
|
||||||
|
self.cursor.execute('''
|
||||||
|
INSERT INTO versiontagscan(
|
||||||
|
idVersion,
|
||||||
|
iNeedsScan,
|
||||||
|
lastscanned)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
''', (v.DB_MUSIC_VERSION,
|
||||||
|
0,
|
||||||
|
timing.kodi_now()))
|
||||||
|
|
||||||
|
@db.catch_operationalerrors
|
||||||
def update_path(self, path, kodi_pathid):
|
def update_path(self, path, kodi_pathid):
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
UPDATE path
|
UPDATE path
|
||||||
|
@ -62,7 +93,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
return
|
return
|
||||||
return song_ids[0][0]
|
return song_ids[0][0]
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def delete_song_from_song_artist(self, song_id):
|
def delete_song_from_song_artist(self, song_id):
|
||||||
"""
|
"""
|
||||||
Deletes son from song_artist table and possibly orphaned roles
|
Deletes son from song_artist table and possibly orphaned roles
|
||||||
|
@ -79,7 +110,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
self.cursor.execute('DELETE FROM song_artist WHERE idSong = ?',
|
self.cursor.execute('DELETE FROM song_artist WHERE idSong = ?',
|
||||||
(song_id, ))
|
(song_id, ))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def delete_album_from_discography(self, album_id):
|
def delete_album_from_discography(self, album_id):
|
||||||
"""
|
"""
|
||||||
Removes the album with id album_id from the table discography
|
Removes the album with id album_id from the table discography
|
||||||
|
@ -99,7 +130,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
self.cursor.execute('DELETE FROM discography WHERE idArtist = ? AND strAlbum = ? AND strYear = ?',
|
self.cursor.execute('DELETE FROM discography WHERE idArtist = ? AND strAlbum = ? AND strYear = ?',
|
||||||
(artist[0], name, year))
|
(artist[0], name, year))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def delete_song_from_song_genre(self, song_id):
|
def delete_song_from_song_genre(self, song_id):
|
||||||
"""
|
"""
|
||||||
Deletes the one entry with id song_id from the song_genre table.
|
Deletes the one entry with id song_id from the song_genre table.
|
||||||
|
@ -120,7 +151,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
if not self.cursor.fetchone():
|
if not self.cursor.fetchone():
|
||||||
self.delete_genre(genre[0])
|
self.delete_genre(genre[0])
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def delete_genre(self, genre_id):
|
def delete_genre(self, genre_id):
|
||||||
"""
|
"""
|
||||||
Dedicated method in order to catch OperationalErrors correctly
|
Dedicated method in order to catch OperationalErrors correctly
|
||||||
|
@ -128,7 +159,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
self.cursor.execute('DELETE FROM genre WHERE idGenre = ?',
|
self.cursor.execute('DELETE FROM genre WHERE idGenre = ?',
|
||||||
(genre_id, ))
|
(genre_id, ))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def delete_album_from_album_genre(self, album_id):
|
def delete_album_from_album_genre(self, album_id):
|
||||||
"""
|
"""
|
||||||
Deletes the one entry with id album_id from the album_genre table.
|
Deletes the one entry with id album_id from the album_genre table.
|
||||||
|
@ -153,7 +184,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
self.cursor.execute('SELECT COALESCE(MAX(idAlbum), 0) FROM album')
|
self.cursor.execute('SELECT COALESCE(MAX(idAlbum), 0) FROM album')
|
||||||
return self.cursor.fetchone()[0] + 1
|
return self.cursor.fetchone()[0] + 1
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_album_17(self, *args):
|
def add_album_17(self, *args):
|
||||||
"""
|
"""
|
||||||
strReleaseType: 'album' or 'single'
|
strReleaseType: 'album' or 'single'
|
||||||
|
@ -196,7 +227,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_album_17(self, *args):
|
def update_album_17(self, *args):
|
||||||
if app.SYNC.artwork:
|
if app.SYNC.artwork:
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
|
@ -234,7 +265,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
WHERE idAlbum = ?
|
WHERE idAlbum = ?
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_album(self, *args):
|
def add_album(self, *args):
|
||||||
"""
|
"""
|
||||||
strReleaseType: 'album' or 'single'
|
strReleaseType: 'album' or 'single'
|
||||||
|
@ -277,7 +308,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_album(self, *args):
|
def update_album(self, *args):
|
||||||
if app.SYNC.artwork:
|
if app.SYNC.artwork:
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
|
@ -315,7 +346,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
WHERE idAlbum = ?
|
WHERE idAlbum = ?
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_albumartist(self, artist_id, kodi_id, artistname):
|
def add_albumartist(self, artist_id, kodi_id, artistname):
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
INSERT OR REPLACE INTO album_artist(
|
INSERT OR REPLACE INTO album_artist(
|
||||||
|
@ -325,7 +356,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
VALUES (?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
''', (artist_id, kodi_id, artistname))
|
''', (artist_id, kodi_id, artistname))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_discography(self, artist_id, albumname, year):
|
def add_discography(self, artist_id, albumname, year):
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
INSERT OR REPLACE INTO discography(
|
INSERT OR REPLACE INTO discography(
|
||||||
|
@ -335,7 +366,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
VALUES (?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
''', (artist_id, albumname, year))
|
''', (artist_id, albumname, year))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_music_genres(self, kodiid, genres, mediatype):
|
def add_music_genres(self, kodiid, genres, mediatype):
|
||||||
"""
|
"""
|
||||||
Adds a list of genres (list of unicode) for a certain Kodi item
|
Adds a list of genres (list of unicode) for a certain Kodi item
|
||||||
|
@ -388,7 +419,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
self.cursor.execute('SELECT COALESCE(MAX(idSong),0) FROM song')
|
self.cursor.execute('SELECT COALESCE(MAX(idSong),0) FROM song')
|
||||||
return self.cursor.fetchone()[0] + 1
|
return self.cursor.fetchone()[0] + 1
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_song(self, *args):
|
def add_song(self, *args):
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
INSERT INTO song(
|
INSERT INTO song(
|
||||||
|
@ -413,7 +444,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_song_17(self, *args):
|
def add_song_17(self, *args):
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
INSERT INTO song(
|
INSERT INTO song(
|
||||||
|
@ -438,7 +469,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_song(self, *args):
|
def update_song(self, *args):
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
UPDATE song
|
UPDATE song
|
||||||
|
@ -459,7 +490,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
WHERE idSong = ?
|
WHERE idSong = ?
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def set_playcount(self, *args):
|
def set_playcount(self, *args):
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
UPDATE song
|
UPDATE song
|
||||||
|
@ -468,7 +499,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
WHERE idSong = ?
|
WHERE idSong = ?
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_song_17(self, *args):
|
def update_song_17(self, *args):
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
UPDATE song
|
UPDATE song
|
||||||
|
@ -497,7 +528,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_artist(self, name, musicbrainz):
|
def add_artist(self, name, musicbrainz):
|
||||||
"""
|
"""
|
||||||
Adds a single artist's name to the db
|
Adds a single artist's name to the db
|
||||||
|
@ -534,7 +565,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
(name, artistid,))
|
(name, artistid,))
|
||||||
return artistid
|
return artistid
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_artist(self, *args):
|
def update_artist(self, *args):
|
||||||
if app.SYNC.artwork:
|
if app.SYNC.artwork:
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
|
@ -557,15 +588,15 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
WHERE idArtist = ?
|
WHERE idArtist = ?
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_song(self, kodi_id):
|
def remove_song(self, kodi_id):
|
||||||
self.cursor.execute('DELETE FROM song WHERE idSong = ?', (kodi_id, ))
|
self.cursor.execute('DELETE FROM song WHERE idSong = ?', (kodi_id, ))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_path(self, path_id):
|
def remove_path(self, path_id):
|
||||||
self.cursor.execute('DELETE FROM path WHERE idPath = ?', (path_id, ))
|
self.cursor.execute('DELETE FROM path WHERE idPath = ?', (path_id, ))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_song_artist(self, artist_id, song_id, artist_name):
|
def add_song_artist(self, artist_id, song_id, artist_name):
|
||||||
self.cursor.execute('''
|
self.cursor.execute('''
|
||||||
INSERT OR REPLACE INTO song_artist(
|
INSERT OR REPLACE INTO song_artist(
|
||||||
|
@ -577,7 +608,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
''', (artist_id, song_id, 1, 0, artist_name))
|
''', (artist_id, song_id, 1, 0, artist_name))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_albuminfosong(self, song_id, album_id, track_no, track_title,
|
def add_albuminfosong(self, song_id, album_id, track_no, track_title,
|
||||||
runtime):
|
runtime):
|
||||||
"""
|
"""
|
||||||
|
@ -593,7 +624,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
''', (song_id, album_id, track_no, track_title, runtime))
|
''', (song_id, album_id, track_no, track_title, runtime))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_userrating(self, kodi_id, kodi_type, userrating):
|
def update_userrating(self, kodi_id, kodi_type, userrating):
|
||||||
"""
|
"""
|
||||||
Updates userrating for songs and albums
|
Updates userrating for songs and albums
|
||||||
|
@ -610,7 +641,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
% (kodi_type, column),
|
% (kodi_type, column),
|
||||||
(userrating, identifier, kodi_id))
|
(userrating, identifier, kodi_id))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_albuminfosong(self, kodi_id):
|
def remove_albuminfosong(self, kodi_id):
|
||||||
"""
|
"""
|
||||||
Kodi 17 only
|
Kodi 17 only
|
||||||
|
@ -618,7 +649,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
self.cursor.execute('DELETE FROM albuminfosong WHERE idAlbumInfoSong = ?',
|
self.cursor.execute('DELETE FROM albuminfosong WHERE idAlbumInfoSong = ?',
|
||||||
(kodi_id, ))
|
(kodi_id, ))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_album(self, kodi_id):
|
def remove_album(self, kodi_id):
|
||||||
if v.KODIVERSION < 18:
|
if v.KODIVERSION < 18:
|
||||||
self.cursor.execute('DELETE FROM albuminfosong WHERE idAlbumInfo = ?',
|
self.cursor.execute('DELETE FROM albuminfosong WHERE idAlbumInfo = ?',
|
||||||
|
@ -627,7 +658,7 @@ class KodiMusicDB(common.KodiDBBase):
|
||||||
(kodi_id, ))
|
(kodi_id, ))
|
||||||
self.cursor.execute('DELETE FROM album WHERE idAlbum = ?', (kodi_id, ))
|
self.cursor.execute('DELETE FROM album WHERE idAlbum = ?', (kodi_id, ))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_artist(self, kodi_id):
|
def remove_artist(self, kodi_id):
|
||||||
self.cursor.execute('DELETE FROM album_artist WHERE idArtist = ?',
|
self.cursor.execute('DELETE FROM album_artist WHERE idArtist = ?',
|
||||||
(kodi_id, ))
|
(kodi_id, ))
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
from . import common
|
from . import common
|
||||||
|
from .. import db
|
||||||
|
|
||||||
|
|
||||||
class KodiTextureDB(common.KodiDBBase):
|
class KodiTextureDB(common.KodiDBBase):
|
||||||
|
@ -15,3 +16,11 @@ class KodiTextureDB(common.KodiDBBase):
|
||||||
self.artcursor.execute('SELECT url FROM texture WHERE url = ? LIMIT 1',
|
self.artcursor.execute('SELECT url FROM texture WHERE url = ? LIMIT 1',
|
||||||
(url, ))
|
(url, ))
|
||||||
return self.artcursor.fetchone() is None
|
return self.artcursor.fetchone() is None
|
||||||
|
|
||||||
|
@db.catch_operationalerrors
|
||||||
|
def reset_cached_images(self):
|
||||||
|
for row in self.cursor.execute('SELECT tbl_name '
|
||||||
|
'FROM sqlite_master WHERE type=?',
|
||||||
|
('table', )):
|
||||||
|
if row[0] != 'version':
|
||||||
|
self.cursor.execute("DELETE FROM %s" % row[0])
|
||||||
|
|
|
@ -5,7 +5,7 @@ from logging import getLogger
|
||||||
from sqlite3 import IntegrityError
|
from sqlite3 import IntegrityError
|
||||||
|
|
||||||
from . import common
|
from . import common
|
||||||
from .. import path_ops, timing, variables as v
|
from .. import db, path_ops, timing, variables as v
|
||||||
|
|
||||||
LOG = getLogger('PLEX.kodi_db.video')
|
LOG = getLogger('PLEX.kodi_db.video')
|
||||||
|
|
||||||
|
@ -16,7 +16,19 @@ SHOW_PATH = 'plugin://%s.tvshows/' % v.ADDON_ID
|
||||||
class KodiVideoDB(common.KodiDBBase):
|
class KodiVideoDB(common.KodiDBBase):
|
||||||
db_kind = 'video'
|
db_kind = 'video'
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
|
def create_kodi_db_indicees(self):
|
||||||
|
"""
|
||||||
|
Index the "actors" because we got a TON - speed up SELECT and WHEN
|
||||||
|
"""
|
||||||
|
commands = (
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS ix_actor_2 ON actor (actor_id);',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS ix_files_2 ON files (idFile);',
|
||||||
|
)
|
||||||
|
for cmd in commands:
|
||||||
|
self.cursor.execute(cmd)
|
||||||
|
|
||||||
|
@db.catch_operationalerrors
|
||||||
def setup_path_table(self):
|
def setup_path_table(self):
|
||||||
"""
|
"""
|
||||||
Use with Kodi video DB
|
Use with Kodi video DB
|
||||||
|
@ -66,7 +78,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
1,
|
1,
|
||||||
0))
|
0))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def parent_path_id(self, path):
|
def parent_path_id(self, path):
|
||||||
"""
|
"""
|
||||||
Video DB: Adds all subdirectories to path table while setting a "trail"
|
Video DB: Adds all subdirectories to path table while setting a "trail"
|
||||||
|
@ -90,7 +102,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
self.update_parentpath_id(parent_id, pathid)
|
self.update_parentpath_id(parent_id, pathid)
|
||||||
return pathid
|
return pathid
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_parentpath_id(self, parent_id, pathid):
|
def update_parentpath_id(self, parent_id, pathid):
|
||||||
"""
|
"""
|
||||||
Dedicated method in order to catch OperationalErrors correctly
|
Dedicated method in order to catch OperationalErrors correctly
|
||||||
|
@ -98,7 +110,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
self.cursor.execute('UPDATE path SET idParentPath = ? WHERE idPath = ?',
|
self.cursor.execute('UPDATE path SET idParentPath = ? WHERE idPath = ?',
|
||||||
(parent_id, pathid))
|
(parent_id, pathid))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_path(self, path, date_added=None, id_parent_path=None,
|
def add_path(self, path, date_added=None, id_parent_path=None,
|
||||||
content=None, scraper=None):
|
content=None, scraper=None):
|
||||||
"""
|
"""
|
||||||
|
@ -143,7 +155,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_file(self, filename, path_id, date_added):
|
def add_file(self, filename, path_id, date_added):
|
||||||
"""
|
"""
|
||||||
Adds the filename [unicode] to the table files if not already added
|
Adds the filename [unicode] to the table files if not already added
|
||||||
|
@ -201,7 +213,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_file(self, file_id, remove_orphans=True):
|
def remove_file(self, file_id, remove_orphans=True):
|
||||||
"""
|
"""
|
||||||
Removes the entry for file_id from the files table. Will also delete
|
Removes the entry for file_id from the files table. Will also delete
|
||||||
|
@ -237,7 +249,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
'''
|
'''
|
||||||
self.cursor.execute(query, (path_id, MOVIE_PATH, SHOW_PATH))
|
self.cursor.execute(query, (path_id, MOVIE_PATH, SHOW_PATH))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def _modify_link_and_table(self, kodi_id, kodi_type, entries, link_table,
|
def _modify_link_and_table(self, kodi_id, kodi_type, entries, link_table,
|
||||||
table, key, first_id=None):
|
table, key, first_id=None):
|
||||||
first_id = first_id if first_id is not None else 1
|
first_id = first_id if first_id is not None else 1
|
||||||
|
@ -343,7 +355,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
for kind, people_list in people.iteritems():
|
for kind, people_list in people.iteritems():
|
||||||
self._add_people_kind(kodi_id, kodi_type, kind, people_list)
|
self._add_people_kind(kodi_id, kodi_type, kind, people_list)
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def _add_people_kind(self, kodi_id, kodi_type, kind, people_list):
|
def _add_people_kind(self, kodi_id, kodi_type, kind, people_list):
|
||||||
# Save new people to Kodi DB by iterating over the remaining entries
|
# Save new people to Kodi DB by iterating over the remaining entries
|
||||||
if kind == 'actor':
|
if kind == 'actor':
|
||||||
|
@ -388,7 +400,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
'writer': []}).iteritems():
|
'writer': []}).iteritems():
|
||||||
self._modify_people_kind(kodi_id, kodi_type, kind, people_list)
|
self._modify_people_kind(kodi_id, kodi_type, kind, people_list)
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def _modify_people_kind(self, kodi_id, kodi_type, kind, people_list):
|
def _modify_people_kind(self, kodi_id, kodi_type, kind, people_list):
|
||||||
# Get the people already saved in the DB for this specific item
|
# Get the people already saved in the DB for this specific item
|
||||||
if kind == 'actor':
|
if kind == 'actor':
|
||||||
|
@ -443,7 +455,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
# Save new people to Kodi DB by iterating over the remaining entries
|
# Save new people to Kodi DB by iterating over the remaining entries
|
||||||
self._add_people_kind(kodi_id, kodi_type, kind, people_list)
|
self._add_people_kind(kodi_id, kodi_type, kind, people_list)
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def _new_actor_id(self, name, art_url):
|
def _new_actor_id(self, name, art_url):
|
||||||
# Not yet in actor DB, add person
|
# Not yet in actor DB, add person
|
||||||
self.cursor.execute('SELECT COALESCE(MAX(actor_id), 0) FROM actor')
|
self.cursor.execute('SELECT COALESCE(MAX(actor_id), 0) FROM actor')
|
||||||
|
@ -503,7 +515,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
(kodi_id, kodi_type))
|
(kodi_id, kodi_type))
|
||||||
return dict(self.cursor.fetchall())
|
return dict(self.cursor.fetchall())
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def modify_streams(self, fileid, streamdetails=None, runtime=None):
|
def modify_streams(self, fileid, streamdetails=None, runtime=None):
|
||||||
"""
|
"""
|
||||||
Leave streamdetails and runtime empty to delete all stream entries for
|
Leave streamdetails and runtime empty to delete all stream entries for
|
||||||
|
@ -622,7 +634,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def set_resume(self, file_id, resume_seconds, total_seconds, playcount,
|
def set_resume(self, file_id, resume_seconds, total_seconds, playcount,
|
||||||
dateplayed):
|
dateplayed):
|
||||||
"""
|
"""
|
||||||
|
@ -660,7 +672,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
'',
|
'',
|
||||||
1))
|
1))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def create_tag(self, name):
|
def create_tag(self, name):
|
||||||
"""
|
"""
|
||||||
Will create a new tag if needed and return the tag_id
|
Will create a new tag if needed and return the tag_id
|
||||||
|
@ -676,7 +688,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
(tag_id, name))
|
(tag_id, name))
|
||||||
return tag_id
|
return tag_id
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_tag(self, oldtag, newtag, kodiid, mediatype):
|
def update_tag(self, oldtag, newtag, kodiid, mediatype):
|
||||||
"""
|
"""
|
||||||
Updates the tag_id by replaying oldtag with newtag
|
Updates the tag_id by replaying oldtag with newtag
|
||||||
|
@ -695,7 +707,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
WHERE media_id = ? AND media_type = ? AND tag_id = ?
|
WHERE media_id = ? AND media_type = ? AND tag_id = ?
|
||||||
''', (kodiid, mediatype, oldtag,))
|
''', (kodiid, mediatype, oldtag,))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def create_collection(self, set_name):
|
def create_collection(self, set_name):
|
||||||
"""
|
"""
|
||||||
Returns the collection/set id for set_name [unicode]
|
Returns the collection/set id for set_name [unicode]
|
||||||
|
@ -711,7 +723,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
(setid, set_name))
|
(setid, set_name))
|
||||||
return setid
|
return setid
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def assign_collection(self, setid, movieid):
|
def assign_collection(self, setid, movieid):
|
||||||
"""
|
"""
|
||||||
Assign the movie to one set/collection
|
Assign the movie to one set/collection
|
||||||
|
@ -719,7 +731,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
self.cursor.execute('UPDATE movie SET idSet = ? WHERE idMovie = ?',
|
self.cursor.execute('UPDATE movie SET idSet = ? WHERE idMovie = ?',
|
||||||
(setid, movieid,))
|
(setid, movieid,))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_from_set(self, movieid):
|
def remove_from_set(self, movieid):
|
||||||
"""
|
"""
|
||||||
Remove the movie with movieid [int] from an associated movie set, movie
|
Remove the movie with movieid [int] from an associated movie set, movie
|
||||||
|
@ -739,7 +751,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def delete_possibly_empty_set(self, set_id):
|
def delete_possibly_empty_set(self, set_id):
|
||||||
"""
|
"""
|
||||||
Checks whether there are other movies in the set set_id. If not,
|
Checks whether there are other movies in the set set_id. If not,
|
||||||
|
@ -750,7 +762,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
if self.cursor.fetchone() is None:
|
if self.cursor.fetchone() is None:
|
||||||
self.cursor.execute('DELETE FROM sets WHERE idSet = ?', (set_id,))
|
self.cursor.execute('DELETE FROM sets WHERE idSet = ?', (set_id,))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_season(self, showid, seasonnumber):
|
def add_season(self, showid, seasonnumber):
|
||||||
"""
|
"""
|
||||||
Adds a TV show season to the Kodi video DB or simply returns the ID,
|
Adds a TV show season to the Kodi video DB or simply returns the ID,
|
||||||
|
@ -764,7 +776,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
''', (seasonid, showid, seasonnumber))
|
''', (seasonid, showid, seasonnumber))
|
||||||
return seasonid
|
return seasonid
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_uniqueid(self, *args):
|
def add_uniqueid(self, *args):
|
||||||
"""
|
"""
|
||||||
Feed with:
|
Feed with:
|
||||||
|
@ -799,7 +811,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return self.add_uniqueid_id()
|
return self.add_uniqueid_id()
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_uniqueid(self, *args):
|
def update_uniqueid(self, *args):
|
||||||
"""
|
"""
|
||||||
Pass in media_id, media_type, value, type, uniqueid_id
|
Pass in media_id, media_type, value, type, uniqueid_id
|
||||||
|
@ -810,7 +822,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
WHERE uniqueid_id = ?
|
WHERE uniqueid_id = ?
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_uniqueid(self, kodi_id, kodi_type):
|
def remove_uniqueid(self, kodi_id, kodi_type):
|
||||||
"""
|
"""
|
||||||
Deletes the entry from the uniqueid table for the item
|
Deletes the entry from the uniqueid table for the item
|
||||||
|
@ -833,7 +845,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return self.add_ratingid()
|
return self.add_ratingid()
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_ratings(self, *args):
|
def update_ratings(self, *args):
|
||||||
"""
|
"""
|
||||||
Feed with media_id, media_type, rating_type, rating, votes, rating_id
|
Feed with media_id, media_type, rating_type, rating, votes, rating_id
|
||||||
|
@ -848,7 +860,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
WHERE rating_id = ?
|
WHERE rating_id = ?
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_ratings(self, *args):
|
def add_ratings(self, *args):
|
||||||
"""
|
"""
|
||||||
feed with:
|
feed with:
|
||||||
|
@ -867,7 +879,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_ratings(self, kodi_id, kodi_type):
|
def remove_ratings(self, kodi_id, kodi_type):
|
||||||
"""
|
"""
|
||||||
Removes all ratings from the rating table for the item
|
Removes all ratings from the rating table for the item
|
||||||
|
@ -883,7 +895,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
self.cursor.execute('SELECT COALESCE(MAX(idEpisode), 0) FROM episode')
|
self.cursor.execute('SELECT COALESCE(MAX(idEpisode), 0) FROM episode')
|
||||||
return self.cursor.fetchone()[0] + 1
|
return self.cursor.fetchone()[0] + 1
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_episode(self, *args):
|
def add_episode(self, *args):
|
||||||
self.cursor.execute(
|
self.cursor.execute(
|
||||||
'''
|
'''
|
||||||
|
@ -911,7 +923,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_episode(self, *args):
|
def update_episode(self, *args):
|
||||||
self.cursor.execute(
|
self.cursor.execute(
|
||||||
'''
|
'''
|
||||||
|
@ -936,7 +948,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
WHERE idEpisode = ?
|
WHERE idEpisode = ?
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_show(self, *args):
|
def add_show(self, *args):
|
||||||
self.cursor.execute(
|
self.cursor.execute(
|
||||||
'''
|
'''
|
||||||
|
@ -955,7 +967,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_show(self, *args):
|
def update_show(self, *args):
|
||||||
self.cursor.execute(
|
self.cursor.execute(
|
||||||
'''
|
'''
|
||||||
|
@ -973,21 +985,21 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
WHERE idShow = ?
|
WHERE idShow = ?
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_showlinkpath(self, kodi_id, kodi_pathid):
|
def add_showlinkpath(self, kodi_id, kodi_pathid):
|
||||||
self.cursor.execute('INSERT INTO tvshowlinkpath(idShow, idPath) VALUES (?, ?)',
|
self.cursor.execute('INSERT INTO tvshowlinkpath(idShow, idPath) VALUES (?, ?)',
|
||||||
(kodi_id, kodi_pathid))
|
(kodi_id, kodi_pathid))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_show(self, kodi_id):
|
def remove_show(self, kodi_id):
|
||||||
self.cursor.execute('DELETE FROM tvshow WHERE idShow = ?', (kodi_id,))
|
self.cursor.execute('DELETE FROM tvshow WHERE idShow = ?', (kodi_id,))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_season(self, kodi_id):
|
def remove_season(self, kodi_id):
|
||||||
self.cursor.execute('DELETE FROM seasons WHERE idSeason = ?',
|
self.cursor.execute('DELETE FROM seasons WHERE idSeason = ?',
|
||||||
(kodi_id,))
|
(kodi_id,))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_episode(self, kodi_id):
|
def remove_episode(self, kodi_id):
|
||||||
self.cursor.execute('DELETE FROM episode WHERE idEpisode = ?',
|
self.cursor.execute('DELETE FROM episode WHERE idEpisode = ?',
|
||||||
(kodi_id,))
|
(kodi_id,))
|
||||||
|
@ -996,7 +1008,7 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
self.cursor.execute('SELECT COALESCE(MAX(idMovie), 0) FROM movie')
|
self.cursor.execute('SELECT COALESCE(MAX(idMovie), 0) FROM movie')
|
||||||
return self.cursor.fetchone()[0] + 1
|
return self.cursor.fetchone()[0] + 1
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def add_movie(self, *args):
|
def add_movie(self, *args):
|
||||||
self.cursor.execute(
|
self.cursor.execute(
|
||||||
'''
|
'''
|
||||||
|
@ -1030,11 +1042,11 @@ class KodiVideoDB(common.KodiDBBase):
|
||||||
?, ?, ?, ?)
|
?, ?, ?, ?)
|
||||||
''', (args))
|
''', (args))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def remove_movie(self, kodi_id):
|
def remove_movie(self, kodi_id):
|
||||||
self.cursor.execute('DELETE FROM movie WHERE idMovie = ?', (kodi_id,))
|
self.cursor.execute('DELETE FROM movie WHERE idMovie = ?', (kodi_id,))
|
||||||
|
|
||||||
@common.catch_operationalerrors
|
@db.catch_operationalerrors
|
||||||
def update_userrating(self, kodi_id, kodi_type, userrating):
|
def update_userrating(self, kodi_id, kodi_type, userrating):
|
||||||
"""
|
"""
|
||||||
Updates userrating
|
Updates userrating
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
from .. import utils, variables as v
|
from .. import db, variables as v
|
||||||
|
|
||||||
PLEXDB_LOCK = Lock()
|
PLEXDB_LOCK = Lock()
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class PlexDBBase(object):
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
if self.lock:
|
if self.lock:
|
||||||
PLEXDB_LOCK.acquire()
|
PLEXDB_LOCK.acquire()
|
||||||
self.plexconn = utils.kodi_sql('plex')
|
self.plexconn = db.connect('plex')
|
||||||
self.cursor = self.plexconn.cursor()
|
self.cursor = self.plexconn.cursor()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ Various functions and decorators for PKC
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from sqlite3 import connect, OperationalError
|
from sqlite3 import OperationalError
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from unicodedata import normalize
|
from unicodedata import normalize
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
@ -525,50 +525,6 @@ def delete_temporary_subtitles():
|
||||||
root, file, err)
|
root, file, err)
|
||||||
|
|
||||||
|
|
||||||
def kodi_sql(media_type=None):
|
|
||||||
"""
|
|
||||||
Open a connection to the Kodi database.
|
|
||||||
media_type: 'video' (standard if not passed), 'plex', 'music', 'texture'
|
|
||||||
"""
|
|
||||||
if media_type == "plex":
|
|
||||||
db_path = v.DB_PLEX_PATH
|
|
||||||
elif media_type == "music":
|
|
||||||
db_path = v.DB_MUSIC_PATH
|
|
||||||
elif media_type == "texture":
|
|
||||||
db_path = v.DB_TEXTURE_PATH
|
|
||||||
else:
|
|
||||||
db_path = v.DB_VIDEO_PATH
|
|
||||||
conn = connect(db_path, timeout=30.0)
|
|
||||||
conn.execute('PRAGMA journal_mode=WAL;')
|
|
||||||
conn.execute('PRAGMA cache_size = -8000;')
|
|
||||||
conn.execute('PRAGMA synchronous=NORMAL;')
|
|
||||||
conn.execute('BEGIN')
|
|
||||||
# Use transactions
|
|
||||||
return conn
|
|
||||||
|
|
||||||
|
|
||||||
def create_kodi_db_indicees():
|
|
||||||
"""
|
|
||||||
Index the "actors" because we got a TON - speed up SELECT and WHEN
|
|
||||||
"""
|
|
||||||
conn = kodi_sql('video')
|
|
||||||
cursor = conn.cursor()
|
|
||||||
commands = (
|
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS ix_actor_2 ON actor (actor_id);',
|
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS ix_files_2 ON files (idFile);',
|
|
||||||
)
|
|
||||||
for cmd in commands:
|
|
||||||
cursor.execute(cmd)
|
|
||||||
# Already used in Kodi >=17: CREATE UNIQUE INDEX ix_actor_1 ON actor (name)
|
|
||||||
# try:
|
|
||||||
# cursor.execute('CREATE UNIQUE INDEX ix_pkc_actor_index ON actor (name);')
|
|
||||||
# except OperationalError:
|
|
||||||
# # Index already exists
|
|
||||||
# pass
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
def wipe_synched_playlists():
|
def wipe_synched_playlists():
|
||||||
"""
|
"""
|
||||||
Deletes all synched playlist files on the Kodi side; resets the Plex table
|
Deletes all synched playlist files on the Kodi side; resets the Plex table
|
||||||
|
@ -644,7 +600,7 @@ def init_dbs():
|
||||||
# Ensure that Plex DB is set-up
|
# Ensure that Plex DB is set-up
|
||||||
plex_db.initialize()
|
plex_db.initialize()
|
||||||
# Hack to speed up look-ups for actors (giant table!)
|
# Hack to speed up look-ups for actors (giant table!)
|
||||||
create_kodi_db_indicees()
|
kodi_db.create_kodi_db_indicees()
|
||||||
kodi_db.setup_kodi_default_entries()
|
kodi_db.setup_kodi_default_entries()
|
||||||
with kodi_db.KodiVideoDB() as kodidb:
|
with kodi_db.KodiVideoDB() as kodidb:
|
||||||
# Setup the paths for addon-paths (even when using direct paths)
|
# Setup the paths for addon-paths (even when using direct paths)
|
||||||
|
|
Loading…
Reference in a new issue