diff --git a/resources/lib/kodi_db/common.py b/resources/lib/kodi_db/common.py index dc9984c1..dc9dafea 100644 --- a/resources/lib/kodi_db/common.py +++ b/resources/lib/kodi_db/common.py @@ -23,7 +23,8 @@ def catch_operationalerrors(method): app.APP.monitor.waitForAbort(0.05) attempts -= 1 if attempts == 0: - raise + # Reraise in order to NOT catch nested OperationalErrors + raise RuntimeError('Kodi database locked') return wrapped diff --git a/resources/lib/kodi_db/music.py b/resources/lib/kodi_db/music.py index f4f8b288..793b7a25 100644 --- a/resources/lib/kodi_db/music.py +++ b/resources/lib/kodi_db/music.py @@ -12,6 +12,7 @@ LOG = getLogger('PLEX.kodi_db.music') class KodiMusicDB(common.KodiDBBase): db_kind = 'music' + @common.catch_operationalerrors def add_path(self, path): """ Add the path (unicode) to the music DB, if it does not exist already. @@ -33,6 +34,7 @@ class KodiMusicDB(common.KodiDBBase): (pathid, path, '123')) return pathid + @common.catch_operationalerrors def update_path(self, path, kodi_pathid): self.cursor.execute(''' UPDATE path @@ -60,6 +62,7 @@ class KodiMusicDB(common.KodiDBBase): return return song_ids[0][0] + @common.catch_operationalerrors def delete_song_from_song_artist(self, song_id): """ Deletes son from song_artist table and possibly orphaned roles @@ -76,6 +79,7 @@ class KodiMusicDB(common.KodiDBBase): self.cursor.execute('DELETE FROM song_artist WHERE idSong = ?', (song_id, )) + @common.catch_operationalerrors def delete_album_from_discography(self, album_id): """ Removes the album with id album_id from the table discography @@ -95,6 +99,7 @@ class KodiMusicDB(common.KodiDBBase): self.cursor.execute('DELETE FROM discography WHERE idArtist = ? AND strAlbum = ? AND strYear = ?', (artist[0], name, year)) + @common.catch_operationalerrors def delete_song_from_song_genre(self, song_id): """ Deletes the one entry with id song_id from the song_genre table. @@ -113,9 +118,17 @@ class KodiMusicDB(common.KodiDBBase): self.cursor.execute('SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1', (genre[0], )) if not self.cursor.fetchone(): - self.cursor.execute('DELETE FROM genre WHERE idGenre = ?', - (genre[0], )) + self.delete_genre(genre[0]) + @common.catch_operationalerrors + def delete_genre(self, genre_id): + """ + Dedicated method in order to catch OperationalErrors correctly + """ + self.cursor.execute('DELETE FROM genre WHERE idGenre = ?', + (genre_id, )) + + @common.catch_operationalerrors def delete_album_from_album_genre(self, album_id): """ Deletes the one entry with id album_id from the album_genre table. @@ -134,13 +147,13 @@ class KodiMusicDB(common.KodiDBBase): self.cursor.execute('SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1', (genre[0], )) if not self.cursor.fetchone(): - self.cursor.execute('DELETE FROM genre WHERE idGenre = ?', - (genre[0], )) + self.delete_genre(genre[0]) def new_album_id(self): self.cursor.execute('SELECT COALESCE(MAX(idAlbum), 0) FROM album') return self.cursor.fetchone()[0] + 1 + @common.catch_operationalerrors def add_album_17(self, *args): """ strReleaseType: 'album' or 'single' @@ -183,6 +196,7 @@ class KodiMusicDB(common.KodiDBBase): VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (args)) + @common.catch_operationalerrors def update_album_17(self, *args): if app.SYNC.artwork: self.cursor.execute(''' @@ -220,6 +234,7 @@ class KodiMusicDB(common.KodiDBBase): WHERE idAlbum = ? ''', (args)) + @common.catch_operationalerrors def add_album(self, *args): """ strReleaseType: 'album' or 'single' @@ -262,6 +277,7 @@ class KodiMusicDB(common.KodiDBBase): VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (args)) + @common.catch_operationalerrors def update_album(self, *args): if app.SYNC.artwork: self.cursor.execute(''' @@ -299,6 +315,7 @@ class KodiMusicDB(common.KodiDBBase): WHERE idAlbum = ? ''', (args)) + @common.catch_operationalerrors def add_albumartist(self, artist_id, kodi_id, artistname): self.cursor.execute(''' INSERT OR REPLACE INTO album_artist( @@ -308,6 +325,7 @@ class KodiMusicDB(common.KodiDBBase): VALUES (?, ?, ?) ''', (artist_id, kodi_id, artistname)) + @common.catch_operationalerrors def add_discography(self, artist_id, albumname, year): self.cursor.execute(''' INSERT OR REPLACE INTO discography( @@ -317,6 +335,7 @@ class KodiMusicDB(common.KodiDBBase): VALUES (?, ?, ?) ''', (artist_id, albumname, year)) + @common.catch_operationalerrors def add_music_genres(self, kodiid, genres, mediatype): """ Adds a list of genres (list of unicode) for a certain Kodi item @@ -369,6 +388,7 @@ class KodiMusicDB(common.KodiDBBase): self.cursor.execute('SELECT COALESCE(MAX(idSong),0) FROM song') return self.cursor.fetchone()[0] + 1 + @common.catch_operationalerrors def add_song(self, *args): self.cursor.execute(''' INSERT INTO song( @@ -393,6 +413,7 @@ class KodiMusicDB(common.KodiDBBase): VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (args)) + @common.catch_operationalerrors def add_song_17(self, *args): self.cursor.execute(''' INSERT INTO song( @@ -417,6 +438,7 @@ class KodiMusicDB(common.KodiDBBase): VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (args)) + @common.catch_operationalerrors def update_song(self, *args): self.cursor.execute(''' UPDATE song @@ -437,6 +459,7 @@ class KodiMusicDB(common.KodiDBBase): WHERE idSong = ? ''', (args)) + @common.catch_operationalerrors def update_song_17(self, *args): self.cursor.execute(''' UPDATE song @@ -465,6 +488,7 @@ class KodiMusicDB(common.KodiDBBase): except TypeError: pass + @common.catch_operationalerrors def add_artist(self, name, musicbrainz): """ Adds a single artist's name to the db @@ -501,6 +525,7 @@ class KodiMusicDB(common.KodiDBBase): (name, artistid,)) return artistid + @common.catch_operationalerrors def update_artist(self, *args): if app.SYNC.artwork: self.cursor.execute(''' @@ -523,12 +548,15 @@ class KodiMusicDB(common.KodiDBBase): WHERE idArtist = ? ''', (args)) + @common.catch_operationalerrors def remove_song(self, kodi_id): self.cursor.execute('DELETE FROM song WHERE idSong = ?', (kodi_id, )) + @common.catch_operationalerrors def remove_path(self, path_id): self.cursor.execute('DELETE FROM path WHERE idPath = ?', (path_id, )) + @common.catch_operationalerrors def add_song_artist(self, artist_id, song_id, artist_name): self.cursor.execute(''' INSERT OR REPLACE INTO song_artist( @@ -540,6 +568,7 @@ class KodiMusicDB(common.KodiDBBase): VALUES (?, ?, ?, ?, ?) ''', (artist_id, song_id, 1, 0, artist_name)) + @common.catch_operationalerrors def add_albuminfosong(self, song_id, album_id, track_no, track_title, runtime): """ @@ -555,6 +584,7 @@ class KodiMusicDB(common.KodiDBBase): VALUES (?, ?, ?, ?, ?) ''', (song_id, album_id, track_no, track_title, runtime)) + @common.catch_operationalerrors def update_userrating(self, kodi_id, kodi_type, userrating): """ Updates userrating for songs and albums @@ -571,6 +601,7 @@ class KodiMusicDB(common.KodiDBBase): % (kodi_type, column), (userrating, identifier, kodi_id)) + @common.catch_operationalerrors def remove_albuminfosong(self, kodi_id): """ Kodi 17 only @@ -578,6 +609,7 @@ class KodiMusicDB(common.KodiDBBase): self.cursor.execute('DELETE FROM albuminfosong WHERE idAlbumInfoSong = ?', (kodi_id, )) + @common.catch_operationalerrors def remove_album(self, kodi_id): if v.KODIVERSION < 18: self.cursor.execute('DELETE FROM albuminfosong WHERE idAlbumInfo = ?', @@ -586,6 +618,7 @@ class KodiMusicDB(common.KodiDBBase): (kodi_id, )) self.cursor.execute('DELETE FROM album WHERE idAlbum = ?', (kodi_id, )) + @common.catch_operationalerrors def remove_artist(self, kodi_id): self.cursor.execute('DELETE FROM album_artist WHERE idArtist = ?', (kodi_id, )) diff --git a/resources/lib/kodi_db/video.py b/resources/lib/kodi_db/video.py index d10a3a69..6954e19e 100644 --- a/resources/lib/kodi_db/video.py +++ b/resources/lib/kodi_db/video.py @@ -13,6 +13,7 @@ LOG = getLogger('PLEX.kodi_db.video') class KodiVideoDB(common.KodiDBBase): db_kind = 'video' + @common.catch_operationalerrors def setup_path_table(self): """ Use with Kodi video DB @@ -62,6 +63,7 @@ class KodiVideoDB(common.KodiDBBase): 1, 0)) + @common.catch_operationalerrors def parent_path_id(self, path): """ Video DB: Adds all subdirectories to path table while setting a "trail" @@ -82,10 +84,18 @@ class KodiVideoDB(common.KodiDBBase): if parentpath != path: # In case we end up having media in the filesystem root, C:\ parent_id = self.parent_path_id(parentpath) - self.cursor.execute('UPDATE path SET idParentPath = ? WHERE idPath = ?', - (parent_id, pathid)) + self.update_parentpath_id(parent_id, pathid) return pathid + @common.catch_operationalerrors + def update_parentpath_id(self, parent_id, pathid): + """ + Dedicated method in order to catch OperationalErrors correctly + """ + self.cursor.execute('UPDATE path SET idParentPath = ? WHERE idPath = ?', + (parent_id, pathid)) + + @common.catch_operationalerrors def add_path(self, path, date_added=None, id_parent_path=None, content=None, scraper=None): """ @@ -130,6 +140,7 @@ class KodiVideoDB(common.KodiDBBase): except TypeError: pass + @common.catch_operationalerrors def add_file(self, filename, path_id, date_added): """ Adds the filename [unicode] to the table files if not already added @@ -187,6 +198,7 @@ class KodiVideoDB(common.KodiDBBase): except TypeError: pass + @common.catch_operationalerrors def remove_file(self, file_id, remove_orphans=True, plex_type=None): """ Removes the entry for file_id from the files table. Will also delete @@ -234,6 +246,7 @@ class KodiVideoDB(common.KodiDBBase): self.cursor.execute('DELETE FROM path WHERE idPath = ?', (path_id,)) + @common.catch_operationalerrors def _modify_link_and_table(self, kodi_id, kodi_type, entries, link_table, table, key, first_id=None): first_id = first_id if first_id is not None else 1 @@ -339,6 +352,7 @@ class KodiVideoDB(common.KodiDBBase): for kind, people_list in people.iteritems(): self._add_people_kind(kodi_id, kodi_type, kind, people_list) + @common.catch_operationalerrors def _add_people_kind(self, kodi_id, kodi_type, kind, people_list): # Save new people to Kodi DB by iterating over the remaining entries if kind == 'actor': @@ -377,6 +391,7 @@ class KodiVideoDB(common.KodiDBBase): 'writer': []}).iteritems(): self._modify_people_kind(kodi_id, kodi_type, kind, people_list) + @common.catch_operationalerrors def _modify_people_kind(self, kodi_id, kodi_type, kind, people_list): # Get the people already saved in the DB for this specific item if kind == 'actor': @@ -431,6 +446,7 @@ class KodiVideoDB(common.KodiDBBase): # Save new people to Kodi DB by iterating over the remaining entries self._add_people_kind(kodi_id, kodi_type, kind, people_list) + @common.catch_operationalerrors def _new_actor_id(self, name, art_url): # Not yet in actor DB, add person self.cursor.execute('SELECT COALESCE(MAX(actor_id), 0) FROM actor') @@ -475,6 +491,7 @@ class KodiVideoDB(common.KodiDBBase): (kodi_id, kodi_type)) return dict(self.cursor.fetchall()) + @common.catch_operationalerrors def modify_streams(self, fileid, streamdetails=None, runtime=None): """ Leave streamdetails and runtime empty to delete all stream entries for @@ -487,7 +504,7 @@ class KodiVideoDB(common.KodiDBBase): return for videotrack in streamdetails['video']: self.cursor.execute(''' - INSERT INTO streamdetails( + INSERT OR REPLACE INTO streamdetails( idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth, iVideoHeight, iVideoDuration ,strStereoMode) VALUES (?, ?, ?, ?, ?, ?, ?, ?) @@ -497,7 +514,7 @@ class KodiVideoDB(common.KodiDBBase): videotrack['video3DFormat'])) for audiotrack in streamdetails['audio']: self.cursor.execute(''' - INSERT INTO streamdetails( + INSERT OR REPLACE INTO streamdetails( idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) VALUES (?, ?, ?, ?, ?) @@ -506,7 +523,7 @@ class KodiVideoDB(common.KodiDBBase): audiotrack['language'])) for subtitletrack in streamdetails['subtitle']: self.cursor.execute(''' - INSERT INTO streamdetails(idFile, iStreamType, + INSERT OR REPLACE INTO streamdetails(idFile, iStreamType, strSubtitleLanguage) VALUES (?, ?, ?) ''', (fileid, 2, subtitletrack)) @@ -650,6 +667,7 @@ class KodiVideoDB(common.KodiDBBase): '', 1)) + @common.catch_operationalerrors def create_tag(self, name): """ Will create a new tag if needed and return the tag_id @@ -665,6 +683,7 @@ class KodiVideoDB(common.KodiDBBase): (tag_id, name)) return tag_id + @common.catch_operationalerrors def update_tag(self, oldtag, newtag, kodiid, mediatype): """ Updates the tag_id by replaying oldtag with newtag @@ -683,6 +702,7 @@ class KodiVideoDB(common.KodiDBBase): WHERE media_id = ? AND media_type = ? AND tag_id = ? ''', (kodiid, mediatype, oldtag,)) + @common.catch_operationalerrors def create_collection(self, set_name): """ Returns the collection/set id for set_name [unicode] @@ -698,6 +718,7 @@ class KodiVideoDB(common.KodiDBBase): (setid, set_name)) return setid + @common.catch_operationalerrors def assign_collection(self, setid, movieid): """ Assign the movie to one set/collection @@ -705,6 +726,7 @@ class KodiVideoDB(common.KodiDBBase): self.cursor.execute('UPDATE movie SET idSet = ? WHERE idMovie = ?', (setid, movieid,)) + @common.catch_operationalerrors def remove_from_set(self, movieid): """ Remove the movie with movieid [int] from an associated movie set, movie @@ -724,6 +746,7 @@ class KodiVideoDB(common.KodiDBBase): except TypeError: pass + @common.catch_operationalerrors def delete_possibly_empty_set(self, set_id): """ Checks whether there are other movies in the set set_id. If not, @@ -734,6 +757,7 @@ class KodiVideoDB(common.KodiDBBase): if self.cursor.fetchone() is None: self.cursor.execute('DELETE FROM sets WHERE idSet = ?', (set_id,)) + @common.catch_operationalerrors def add_season(self, showid, seasonnumber): """ Adds a TV show season to the Kodi video DB or simply returns the ID, @@ -747,6 +771,7 @@ class KodiVideoDB(common.KodiDBBase): ''', (seasonid, showid, seasonnumber)) return seasonid + @common.catch_operationalerrors def add_uniqueid(self, *args): """ Feed with: @@ -781,6 +806,7 @@ class KodiVideoDB(common.KodiDBBase): except TypeError: return self.add_uniqueid_id() + @common.catch_operationalerrors def update_uniqueid(self, *args): """ Pass in media_id, media_type, value, type, uniqueid_id @@ -791,6 +817,7 @@ class KodiVideoDB(common.KodiDBBase): WHERE uniqueid_id = ? ''', (args)) + @common.catch_operationalerrors def remove_uniqueid(self, kodi_id, kodi_type): """ Deletes the entry from the uniqueid table for the item @@ -813,6 +840,7 @@ class KodiVideoDB(common.KodiDBBase): except TypeError: return self.add_ratingid() + @common.catch_operationalerrors def update_ratings(self, *args): """ Feed with media_id, media_type, rating_type, rating, votes, rating_id @@ -827,6 +855,7 @@ class KodiVideoDB(common.KodiDBBase): WHERE rating_id = ? ''', (args)) + @common.catch_operationalerrors def add_ratings(self, *args): """ feed with: @@ -845,6 +874,7 @@ class KodiVideoDB(common.KodiDBBase): VALUES (?, ?, ?, ?, ?, ?) ''', (args)) + @common.catch_operationalerrors def remove_ratings(self, kodi_id, kodi_type): """ Removes all ratings from the rating table for the item @@ -860,6 +890,7 @@ class KodiVideoDB(common.KodiDBBase): self.cursor.execute('SELECT COALESCE(MAX(idEpisode), 0) FROM episode') return self.cursor.fetchone()[0] + 1 + @common.catch_operationalerrors def add_episode(self, *args): self.cursor.execute( ''' @@ -887,6 +918,7 @@ class KodiVideoDB(common.KodiDBBase): (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (args)) + @common.catch_operationalerrors def update_episode(self, *args): self.cursor.execute( ''' @@ -911,6 +943,7 @@ class KodiVideoDB(common.KodiDBBase): WHERE idEpisode = ? ''', (args)) + @common.catch_operationalerrors def add_show(self, *args): self.cursor.execute( ''' @@ -929,6 +962,7 @@ class KodiVideoDB(common.KodiDBBase): VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (args)) + @common.catch_operationalerrors def update_show(self, *args): self.cursor.execute( ''' @@ -946,17 +980,21 @@ class KodiVideoDB(common.KodiDBBase): WHERE idShow = ? ''', (args)) + @common.catch_operationalerrors def add_showlinkpath(self, kodi_id, kodi_pathid): self.cursor.execute('INSERT INTO tvshowlinkpath(idShow, idPath) VALUES (?, ?)', (kodi_id, kodi_pathid)) + @common.catch_operationalerrors def remove_show(self, kodi_id): self.cursor.execute('DELETE FROM tvshow WHERE idShow = ?', (kodi_id,)) + @common.catch_operationalerrors def remove_season(self, kodi_id): self.cursor.execute('DELETE FROM seasons WHERE idSeason = ?', (kodi_id,)) + @common.catch_operationalerrors def remove_episode(self, kodi_id): self.cursor.execute('DELETE FROM episode WHERE idEpisode = ?', (kodi_id,)) @@ -965,6 +1003,7 @@ class KodiVideoDB(common.KodiDBBase): self.cursor.execute('SELECT COALESCE(MAX(idMovie), 0) FROM movie') return self.cursor.fetchone()[0] + 1 + @common.catch_operationalerrors def add_movie(self, *args): self.cursor.execute( ''' @@ -998,9 +1037,11 @@ class KodiVideoDB(common.KodiDBBase): ?, ?, ?, ?) ''', (args)) + @common.catch_operationalerrors def remove_movie(self, kodi_id): self.cursor.execute('DELETE FROM movie WHERE idMovie = ?', (kodi_id,)) + @common.catch_operationalerrors def update_userrating(self, kodi_id, kodi_type, userrating): """ Updates userrating