Increase database resiliance with sqlite WAL mode
This commit is contained in:
parent
1f35caba54
commit
3d4ba1e165
9 changed files with 103 additions and 73 deletions
|
@ -4,8 +4,8 @@ from __future__ import absolute_import, division, unicode_literals
|
|||
from logging import getLogger
|
||||
from ntpath import dirname
|
||||
|
||||
from ..plex_db import PlexDB
|
||||
from ..kodi_db import KodiVideoDB
|
||||
from ..plex_db import PlexDB, PLEXDB_LOCK
|
||||
from ..kodi_db import KodiVideoDB, KODIDB_LOCK
|
||||
from .. import utils, timing
|
||||
|
||||
LOG = getLogger('PLEX.itemtypes.common')
|
||||
|
@ -38,8 +38,9 @@ class ItemBase(object):
|
|||
Input:
|
||||
kodiType: optional argument; e.g. 'video' or 'music'
|
||||
"""
|
||||
def __init__(self, last_sync, plexdb=None, kodidb=None):
|
||||
def __init__(self, last_sync, plexdb=None, kodidb=None, lock=True):
|
||||
self.last_sync = last_sync
|
||||
self.lock = lock
|
||||
self.plexconn = None
|
||||
self.plexcursor = plexdb.cursor if plexdb else None
|
||||
self.kodiconn = None
|
||||
|
@ -53,13 +54,16 @@ class ItemBase(object):
|
|||
"""
|
||||
Open DB connections and cursors
|
||||
"""
|
||||
if self.lock:
|
||||
PLEXDB_LOCK.acquire()
|
||||
KODIDB_LOCK.acquire()
|
||||
self.plexconn = utils.kodi_sql('plex')
|
||||
self.plexcursor = self.plexconn.cursor()
|
||||
self.kodiconn = utils.kodi_sql('video')
|
||||
self.kodicursor = self.kodiconn.cursor()
|
||||
self.artconn = utils.kodi_sql('texture')
|
||||
self.artcursor = self.artconn.cursor()
|
||||
self.plexdb = PlexDB(self.plexcursor)
|
||||
self.plexdb = PlexDB(cursor=self.plexcursor)
|
||||
self.kodidb = KodiVideoDB(texture_db=True,
|
||||
cursor=self.kodicursor,
|
||||
artcursor=self.artcursor)
|
||||
|
@ -69,16 +73,21 @@ class ItemBase(object):
|
|||
"""
|
||||
Make sure DB changes are committed and connection to DB is closed.
|
||||
"""
|
||||
if exc_type:
|
||||
# re-raise any exception
|
||||
return False
|
||||
self.plexconn.commit()
|
||||
self.artconn.commit()
|
||||
self.kodiconn.commit()
|
||||
self.plexconn.close()
|
||||
self.kodiconn.close()
|
||||
self.artconn.close()
|
||||
return self
|
||||
try:
|
||||
if exc_type:
|
||||
# re-raise any exception
|
||||
return False
|
||||
self.plexconn.commit()
|
||||
self.artconn.commit()
|
||||
self.kodiconn.commit()
|
||||
return self
|
||||
finally:
|
||||
self.plexconn.close()
|
||||
self.kodiconn.close()
|
||||
self.artconn.close()
|
||||
if self.lock:
|
||||
PLEXDB_LOCK.release()
|
||||
KODIDB_LOCK.release()
|
||||
|
||||
def commit(self):
|
||||
self.plexconn.commit()
|
||||
|
|
|
@ -5,8 +5,8 @@ from logging import getLogger
|
|||
|
||||
from .common import ItemBase
|
||||
from ..plex_api import API
|
||||
from ..plex_db import PlexDB
|
||||
from ..kodi_db import KodiMusicDB
|
||||
from ..plex_db import PlexDB, PLEXDB_LOCK
|
||||
from ..kodi_db import KodiMusicDB, KODIDB_LOCK
|
||||
from .. import plex_functions as PF, utils, timing, app, variables as v
|
||||
|
||||
LOG = getLogger('PLEX.music')
|
||||
|
@ -17,6 +17,9 @@ class MusicMixin(object):
|
|||
"""
|
||||
Overwrite to use the Kodi music DB instead of the video DB
|
||||
"""
|
||||
if self.lock:
|
||||
PLEXDB_LOCK.acquire()
|
||||
KODIDB_LOCK.acquire()
|
||||
self.plexconn = utils.kodi_sql('plex')
|
||||
self.plexcursor = self.plexconn.cursor()
|
||||
self.kodiconn = utils.kodi_sql('music')
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
|
||||
from .common import KODIDB_LOCK
|
||||
from .video import KodiVideoDB
|
||||
from .music import KodiMusicDB
|
||||
from .texture import KodiTextureDB
|
||||
|
|
|
@ -1,15 +1,34 @@
|
|||
#!/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
|
||||
from .. import utils, path_ops, app
|
||||
|
||||
KODIDB_LOCK = Lock()
|
||||
|
||||
|
||||
def catch_operationalerrors(method):
|
||||
@wraps(method)
|
||||
def wrapped(*args, **kwargs):
|
||||
attempts = 3
|
||||
while True:
|
||||
try:
|
||||
return method(*args, **kwargs)
|
||||
except utils.OperationalError:
|
||||
app.APP.monitor.waitForAbort(0.01)
|
||||
attempts -= 1
|
||||
if attempts == 0:
|
||||
raise
|
||||
return wrapped
|
||||
|
||||
|
||||
class KodiDBBase(object):
|
||||
"""
|
||||
Kodi database methods used for all types of items
|
||||
"""
|
||||
def __init__(self, texture_db=False, cursor=None, artcursor=None):
|
||||
def __init__(self, texture_db=False, cursor=None, artcursor=None, lock=True):
|
||||
"""
|
||||
Allows direct use with a cursor instead of context mgr
|
||||
"""
|
||||
|
@ -17,8 +36,11 @@ class KodiDBBase(object):
|
|||
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:
|
||||
|
@ -27,14 +49,19 @@ class KodiDBBase(object):
|
|||
return self
|
||||
|
||||
def __exit__(self, e_typ, e_val, trcbak):
|
||||
if e_typ:
|
||||
# re-raise any exception
|
||||
return False
|
||||
self.kodiconn.commit()
|
||||
self.kodiconn.close()
|
||||
if self.artconn:
|
||||
self.artconn.commit()
|
||||
self.artconn.close()
|
||||
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
|
||||
|
@ -53,6 +80,7 @@ class KodiDBBase(object):
|
|||
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
|
||||
|
@ -71,6 +99,7 @@ class KodiDBBase(object):
|
|||
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
|
||||
|
@ -102,10 +131,12 @@ class KodiDBBase(object):
|
|||
''', (url, kodi_id, kodi_type, kodi_art))
|
||||
|
||||
def delete_artwork(self, kodi_id, kodi_type):
|
||||
for row in self.cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?',
|
||||
(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",
|
||||
|
|
|
@ -153,15 +153,7 @@ def process_fanart(plex_id, plex_type, refresh=False):
|
|||
setid,
|
||||
v.KODI_TYPE_SET)
|
||||
done = True
|
||||
except utils.OperationalError:
|
||||
# We were not fast enough when a sync started
|
||||
pass
|
||||
finally:
|
||||
if done is True and not suspends():
|
||||
try:
|
||||
with PlexDB() as plexdb:
|
||||
plexdb.set_fanart_synced(plex_id,
|
||||
plex_type)
|
||||
except utils.OperationalError:
|
||||
# We were not fast enough when a sync started
|
||||
pass
|
||||
with PlexDB() as plexdb:
|
||||
plexdb.set_fanart_synced(plex_id, plex_type)
|
||||
|
|
|
@ -127,27 +127,11 @@ def process_new_item_message(message):
|
|||
LOG.error('Could not download metadata for %s', message['plex_id'])
|
||||
return False, False, False
|
||||
LOG.debug("Processing new/updated PMS item: %s", message['plex_id'])
|
||||
attempts = 3
|
||||
while True:
|
||||
try:
|
||||
with itemtypes.ITEMTYPE_FROM_PLEXTYPE[plex_type](timing.unix_timestamp()) as typus:
|
||||
typus.add_update(xml[0],
|
||||
section_name=xml.get('librarySectionTitle'),
|
||||
section_id=xml.get('librarySectionID'))
|
||||
cache_artwork(message['plex_id'], plex_type)
|
||||
except utils.OperationalError:
|
||||
# Since parallel caching of artwork might invalidade the current
|
||||
# WAL snapshot of the db, sqlite immediatly throws
|
||||
# OperationalError, NOT after waiting for a duraton of timeout
|
||||
# See https://github.com/mattn/go-sqlite3/issues/274#issuecomment-211759641
|
||||
LOG.debug('sqlite OperationalError encountered, trying again')
|
||||
attempts -= 1
|
||||
if attempts == 0:
|
||||
LOG.error('Repeatedly could not process message %s', message)
|
||||
return False, False, False
|
||||
continue
|
||||
else:
|
||||
break
|
||||
with itemtypes.ITEMTYPE_FROM_PLEXTYPE[plex_type](timing.unix_timestamp()) as typus:
|
||||
typus.add_update(xml[0],
|
||||
section_name=xml.get('librarySectionTitle'),
|
||||
section_id=xml.get('librarySectionID'))
|
||||
cache_artwork(message['plex_id'], plex_type)
|
||||
return True, plex_type in v.PLEX_VIDEOTYPES, plex_type in v.PLEX_AUDIOTYPES
|
||||
|
||||
|
||||
|
@ -349,9 +333,9 @@ def process_playing(data):
|
|||
else:
|
||||
mark_played = False
|
||||
LOG.debug('Update playstate for user %s for %s with plex id %s to '
|
||||
'viewCount %s, resume %s, mark_played %s',
|
||||
'viewCount %s, resume %s, mark_played %s for item %s',
|
||||
app.ACCOUNT.plex_username, session['kodi_type'], plex_id,
|
||||
session['viewCount'], resume, mark_played)
|
||||
session['viewCount'], resume, mark_played, PLAYSTATE_SESSIONS[session_key])
|
||||
func = itemtypes.ITEMTYPE_FROM_KODITYPE[session['kodi_type']]
|
||||
with func(None) as fkt:
|
||||
fkt.update_playstate(mark_played,
|
||||
|
|
|
@ -929,7 +929,7 @@ class API(object):
|
|||
artworks[kodi_artwork] = art
|
||||
if not full_artwork:
|
||||
return artworks
|
||||
with PlexDB() as plexdb:
|
||||
with PlexDB(lock=False) as plexdb:
|
||||
db_item = plexdb.item_by_id(self.plex_id(),
|
||||
v.PLEX_TYPE_EPISODE)
|
||||
if db_item:
|
||||
|
@ -938,12 +938,12 @@ class API(object):
|
|||
else:
|
||||
return artworks
|
||||
# Grab artwork from the season
|
||||
with KodiVideoDB() as kodidb:
|
||||
with KodiVideoDB(lock=False) as kodidb:
|
||||
season_art = kodidb.get_art(season_id, v.KODI_TYPE_SEASON)
|
||||
for kodi_art in season_art:
|
||||
artworks['season.%s' % kodi_art] = season_art[kodi_art]
|
||||
# Grab more artwork from the show
|
||||
with KodiVideoDB() as kodidb:
|
||||
with KodiVideoDB(lock=False) as kodidb:
|
||||
show_art = kodidb.get_art(show_id, v.KODI_TYPE_SHOW)
|
||||
for kodi_art in show_art:
|
||||
artworks['tvshow.%s' % kodi_art] = show_art[kodi_art]
|
||||
|
@ -952,10 +952,10 @@ class API(object):
|
|||
if kodi_id:
|
||||
# in Kodi database, potentially with additional e.g. clearart
|
||||
if self.plex_type() in v.PLEX_VIDEOTYPES:
|
||||
with KodiVideoDB() as kodidb:
|
||||
with KodiVideoDB(lock=False) as kodidb:
|
||||
return kodidb.get_art(kodi_id, kodi_type)
|
||||
else:
|
||||
with KodiMusicDB() as kodidb:
|
||||
with KodiMusicDB(lock=False) as kodidb:
|
||||
return kodidb.get_art(kodi_id, kodi_type)
|
||||
|
||||
# Grab artwork from Plex
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
|
||||
from .common import PlexDBBase, initialize, wipe
|
||||
from .common import PlexDBBase, initialize, wipe, PLEXDB_LOCK
|
||||
from .tvshows import TVShows
|
||||
from .movies import Movies
|
||||
from .music import Music
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from threading import Lock
|
||||
|
||||
from .. import utils, variables as v
|
||||
|
||||
PLEXDB_LOCK = Lock()
|
||||
|
||||
SUPPORTED_KODI_TYPES = (
|
||||
v.KODI_TYPE_MOVIE,
|
||||
v.KODI_TYPE_SHOW,
|
||||
|
@ -19,21 +22,28 @@ class PlexDBBase(object):
|
|||
"""
|
||||
Plex database methods used for all types of items
|
||||
"""
|
||||
def __init__(self, cursor=None):
|
||||
def __init__(self, cursor=None, lock=True):
|
||||
# Allows us to use this class with a cursor instead of context mgr
|
||||
self.cursor = cursor
|
||||
self.lock = lock
|
||||
|
||||
def __enter__(self):
|
||||
if self.lock:
|
||||
PLEXDB_LOCK.acquire()
|
||||
self.plexconn = utils.kodi_sql('plex')
|
||||
self.cursor = self.plexconn.cursor()
|
||||
return self
|
||||
|
||||
def __exit__(self, e_typ, e_val, trcbak):
|
||||
if e_typ:
|
||||
# re-raise any exception
|
||||
return False
|
||||
self.plexconn.commit()
|
||||
self.plexconn.close()
|
||||
try:
|
||||
if e_typ:
|
||||
# re-raise any exception
|
||||
return False
|
||||
self.plexconn.commit()
|
||||
finally:
|
||||
self.plexconn.close()
|
||||
if self.lock:
|
||||
PLEXDB_LOCK.release()
|
||||
|
||||
def is_recorded(self, plex_id, plex_type):
|
||||
"""
|
||||
|
|
Loading…
Reference in a new issue