This commit is contained in:
angelblue05 2016-11-05 02:17:02 -05:00
commit c211a26a35
11 changed files with 509 additions and 589 deletions

View file

@ -23,6 +23,7 @@ import entrypoint
import loghandler import loghandler
from utils import window, dialog, language as lang from utils import window, dialog, language as lang
from ga_client import GoogleAnalytics from ga_client import GoogleAnalytics
import database
################################################################################################# #################################################################################################
@ -80,7 +81,7 @@ class Main(object):
import utils import utils
modes = { modes = {
'reset': utils.reset, 'reset': database.db_reset,
'resetauth': entrypoint.resetAuth, 'resetauth': entrypoint.resetAuth,
'play': entrypoint.doPlayback, 'play': entrypoint.doPlayback,
'passwords': utils.passwordsXML, 'passwords': utils.passwordsXML,

View file

@ -13,7 +13,9 @@ import xbmcvfs
import requests import requests
import image_cache_thread import image_cache_thread
from utils import window, settings, dialog, language as lang, kodiSQL, JSONRPC from utils import window, settings, dialog, language as lang, JSONRPC
from database import DatabaseConn
from contextlib import closing
################################################################################################## ##################################################################################################
@ -164,47 +166,49 @@ class Artwork(object):
def _cache_all_video_entries(self, pdialog): def _cache_all_video_entries(self, pdialog):
conn = kodiSQL('video') with DatabaseConn('video') as conn:
cursor = conn.cursor() with closing(conn.cursor()) as cursor_video:
cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
result = cursor.fetchall()
total = len(result)
log.info("Image cache sync about to process %s images", total)
cursor.close()
count = 0 cursor_video.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
for url in result: result = cursor_video.fetchall()
total = len(result)
log.info("Image cache sync about to process %s images", total)
cursor_video.close()
if pdialog.iscanceled(): count = 0
break for url in result:
percentage = int((float(count) / float(total))*100) if pdialog.iscanceled():
message = "%s of %s (%s)" % (count, total, len(self.image_cache_threads)) break
pdialog.update(percentage, "%s %s" % (lang(33045), message))
self.cache_texture(url[0]) percentage = int((float(count) / float(total))*100)
count += 1 message = "%s of %s (%s)" % (count, total, len(self.image_cache_threads))
pdialog.update(percentage, "%s %s" % (lang(33045), message))
self.cache_texture(url[0])
count += 1
def _cache_all_music_entries(self, pdialog): def _cache_all_music_entries(self, pdialog):
conn = kodiSQL('music') with DatabaseConn('music') as conn:
cursor = conn.cursor() with closing(conn.cursor()) as cursor_music:
cursor.execute("SELECT url FROM art")
result = cursor.fetchall()
total = len(result)
log.info("Image cache sync about to process %s images", total)
cursor.close()
count = 0 cursor_music.execute("SELECT url FROM art")
for url in result: result = cursor_music.fetchall()
total = len(result)
if pdialog.iscanceled(): log.info("Image cache sync about to process %s images", total)
break
percentage = int((float(count) / float(total))*100) count = 0
message = "%s of %s" % (count, total) for url in result:
pdialog.update(percentage, "%s %s" % (lang(33045), message))
self.cache_texture(url[0]) if pdialog.iscanceled():
count += 1 break
percentage = int((float(count) / float(total))*100)
message = "%s of %s" % (count, total)
pdialog.update(percentage, "%s %s" % (lang(33045), message))
self.cache_texture(url[0])
count += 1
@classmethod @classmethod
def delete_cache(cls): def delete_cache(cls):
@ -226,16 +230,14 @@ class Artwork(object):
log.debug("deleted: %s", filename) log.debug("deleted: %s", filename)
# remove all existing data from texture DB # remove all existing data from texture DB
conn = kodiSQL('texture') with DatabaseConn('texture') as conn:
cursor = conn.cursor() with closing(conn.cursor()) as cursor_texture:
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') cursor_texture.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall() rows = cursor_texture.fetchall()
for row in rows: for row in rows:
table_name = row[0] table_name = row[0]
if table_name != "version": if table_name != "version":
cursor.execute("DELETE FROM " + table_name) cursor_texture.execute("DELETE FROM " + table_name)
conn.commit()
cursor.close()
def _add_worker_image_thread(self, url): def _add_worker_image_thread(self, url):
@ -429,32 +431,28 @@ class Artwork(object):
@classmethod @classmethod
def delete_cached_artwork(cls, url): def delete_cached_artwork(cls, url):
# Only necessary to remove and apply a new backdrop or poster # Only necessary to remove and apply a new backdrop or poster
conn = kodiSQL('texture') with DatabaseConn('texture') as conn:
cursor = conn.cursor() with closing(conn.cursor()) as cursor_texture:
try:
cursor_texture.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
cached_url = cursor_texture.fetchone()[0]
try: except TypeError:
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,)) log.info("Could not find cached url")
cached_url = cursor.fetchone()[0]
except TypeError: except OperationalError:
log.info("Could not find cached url") log.info("Database is locked. Skip deletion process.")
except OperationalError: else: # Delete thumbnail as well as the entry
log.info("Database is locked. Skip deletion process.") thumbnails = xbmc.translatePath("special://thumbnails/%s" % cached_url).decode('utf-8')
log.info("Deleting cached thumbnail: %s", thumbnails)
xbmcvfs.delete(thumbnails)
else: # Delete thumbnail as well as the entry try:
thumbnails = xbmc.translatePath("special://thumbnails/%s" % cached_url).decode('utf-8') cursor_texture.execute("DELETE FROM texture WHERE url = ?", (url,))
log.info("Deleting cached thumbnail: %s", thumbnails) except OperationalError:
xbmcvfs.delete(thumbnails) log.debug("Issue deleting url from cache. Skipping.")
try:
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
conn.commit()
except OperationalError:
log.debug("Issue deleting url from cache. Skipping.")
finally:
cursor.close()
def get_people_artwork(self, people): def get_people_artwork(self, people):
# append imageurl if existing # append imageurl if existing

View file

@ -11,8 +11,10 @@ import api
import read_embyserver as embyserver import read_embyserver as embyserver
import embydb_functions as embydb import embydb_functions as embydb
import musicutils as musicutils import musicutils as musicutils
from utils import settings, dialog, language as lang, kodiSQL from utils import settings, dialog, language as lang
from dialogs import context from dialogs import context
from database import DatabaseConn
from contextlib import closing
################################################################################################# #################################################################################################
@ -87,15 +89,14 @@ class ContextMenu(object):
if not item_id and kodi_id and item_type: if not item_id and kodi_id and item_type:
conn = kodiSQL('emby') with DatabaseConn('emby') as conn:
cursor = conn.cursor() with closing(conn.cursor()) as cursor:
emby_db = embydb.Embydb_Functions(cursor) emby_db = embydb.Embydb_Functions(cursor)
item = emby_db.getItem_byKodiId(kodi_id, item_type) item = emby_db.getItem_byKodiId(kodi_id, item_type)
cursor.close() try:
try: item_id = item[0]
item_id = item[0] except TypeError:
except TypeError: pass
pass
return item_id return item_id
@ -164,31 +165,28 @@ class ContextMenu(object):
def _rate_song(self): def _rate_song(self):
conn = kodiSQL('music') with DatabaseConn('music') as conn:
cursor = conn.cursor() with closing(conn.cursor()) as cursor_music:
query = "SELECT rating FROM song WHERE idSong = ?" query = "SELECT rating FROM song WHERE idSong = ?"
cursor.execute(query, (self.kodi_id,)) cursor_music.execute(query, (self.kodi_id,))
try: try:
value = cursor.fetchone()[0] value = cursor_music.fetchone()[0]
current_value = int(round(float(value), 0)) current_value = int(round(float(value), 0))
except TypeError: except TypeError:
pass pass
else: else:
new_value = dialog("numeric", 0, lang(30411), str(current_value)) new_value = dialog("numeric", 0, lang(30411), str(current_value))
if new_value > -1: if new_value > -1:
new_value = int(new_value) new_value = int(new_value)
if new_value > 5: if new_value > 5:
new_value = 5 new_value = 5
if settings('enableUpdateSongRating') == "true": if settings('enableUpdateSongRating') == "true":
musicutils.updateRatingToFile(new_value, self.api.get_file_path()) musicutils.updateRatingToFile(new_value, self.api.get_file_path())
query = "UPDATE song SET rating = ? WHERE idSong = ?" query = "UPDATE song SET rating = ? WHERE idSong = ?"
cursor.execute(query, (new_value, self.kodi_id,)) cursor_music.execute(query, (new_value, self.kodi_id,))
conn.commit()
finally:
cursor.close()
def _delete_item(self): def _delete_item(self):

View file

@ -4,10 +4,17 @@
import logging import logging
import sqlite3 import sqlite3
from contextlib import closing
import sys
import traceback
import xbmc import xbmc
import xbmcaddon
import xbmcgui
import xbmcplugin
import xbmcvfs
from utils import window, should_stop from utils import window, should_stop, settings, language, deletePlaylists, deleteNodes
################################################################################################# #################################################################################################
@ -79,22 +86,26 @@ def kodi_commit():
class DatabaseConn(object): class DatabaseConn(object):
# To be called as context manager - i.e. with DatabaseConn() as conn: #dostuff # To be called as context manager - i.e. with DatabaseConn() as conn: #dostuff
def __init__(self, database_file="video", commit_mode="", timeout=20): def __init__(self, database_file="video", commit_on_close=True, timeout=120):
""" """
database_file can be custom: emby, texture, music, video, :memory: or path to the file database_file can be custom: emby, texture, music, video, :memory: or path to the file
commit_mode set to None to autocommit (isolation_level). See python documentation. commit_mode set to None to autocommit (isolation_level). See python documentation.
""" """
self.db_file = database_file self.db_file = database_file
self.commit_mode = commit_mode self.commit_on_close = commit_on_close
self.timeout = timeout self.timeout = timeout
def __enter__(self): def __enter__(self):
# Open the connection # Open the connection
self.path = self._SQL(self.db_file) self.path = self._SQL(self.db_file)
log.info("opening database: %s", self.path) log.info("opening: %s", self.path)
self.conn = sqlite3.connect(self.path, #traceback.print_stack()
isolation_level=self.commit_mode,
timeout=self.timeout) if settings('dblock') == "true":
self.conn = sqlite3.connect(self.path, isolation_level=None, timeout=self.timeout)
else:
self.conn = sqlite3.connect(self.path, timeout=self.timeout)
return self.conn return self.conn
def _SQL(self, media_type): def _SQL(self, media_type):
@ -114,17 +125,102 @@ class DatabaseConn(object):
if exc_type is not None: if exc_type is not None:
# Errors were raised in the with statement # Errors were raised in the with statement
log.error("Type: %s Value: %s", exc_type, exc_val) log.error("Type: %s Value: %s", exc_type, exc_val)
if "database is locked" in exc_val:
self.conn.rollback()
else:
raise
elif self.commit_mode is not None and changes: if self.commit_on_close == True and changes:
log.info("number of rows updated: %s", changes) log.info("number of rows updated: %s", changes)
if self.db_file == "video" and kodi_commit(): if self.db_file == "video":
self.conn.commit() kodi_commit()
else: self.conn.commit()
self.conn.commit() log.info("commit: %s", self.path)
log.info("close: %s", self.path) log.info("closing: %s", self.path)
self.conn.close() self.conn.close()
def db_reset():
dialog = xbmcgui.Dialog()
if not dialog.yesno(language(29999), language(33074)):
return
# first stop any db sync
window('emby_online', value="reset")
window('emby_shouldStop', value="true")
count = 10
while window('emby_dbScan') == "true":
log.info("Sync is running, will retry: %s..." % count)
count -= 1
if count == 0:
dialog.ok(language(29999), language(33085))
return
xbmc.sleep(1000)
# Clean up the playlists
deletePlaylists()
# Clean up the video nodes
deleteNodes()
# Wipe the kodi databases
log.warn("Resetting the Kodi video database.")
with DatabaseConn('video') as conn:
with closing(conn.cursor()) as cursor:
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
if settings('enableMusic') == "true":
log.warn("Resetting the Kodi music database.")
with DatabaseConn('music') as conn:
with closing(conn.cursor()) as cursor:
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
# Wipe the emby database
log.warn("Resetting the Emby database.")
with DatabaseConn('emby') as conn:
with closing(conn.cursor()) as cursor:
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
cursor.execute('DROP table IF EXISTS emby')
cursor.execute('DROP table IF EXISTS view')
cursor.execute("DROP table IF EXISTS version")
# Offer to wipe cached thumbnails
if dialog.yesno(language(29999), language(33086)):
log.warn("Resetting all cached artwork")
# Remove all existing textures first
import artwork
artwork.Artwork().delete_cache()
# reset the install run flag
settings('SyncInstallRunDone', value="false")
# Remove emby info
resp = dialog.yesno(language(29999), language(33087))
if resp:
import connectmanager
# Delete the settings
addon = xbmcaddon.Addon()
addondir = xbmc.translatePath(
"special://profile/addon_data/plugin.video.emby/").decode('utf-8')
dataPath = "%ssettings.xml" % addondir
xbmcvfs.delete(dataPath)
connectmanager.ConnectManager().clear_data()
dialog.ok(heading=language(29999), line1=language(33088))
xbmc.executebuiltin('RestartApp')
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xbmcgui.ListItem())

View file

@ -28,6 +28,8 @@ import playbackutils as pbutils
import playutils import playutils
import api import api
from utils import window, settings, dialog, language as lang from utils import window, settings, dialog, language as lang
from database import DatabaseConn
from contextlib import closing
################################################################################################# #################################################################################################
@ -178,12 +180,10 @@ def emby_backup():
shutil.copy(src=xbmc.translatePath("special://database/emby.db").decode('utf-8'), shutil.copy(src=xbmc.translatePath("special://database/emby.db").decode('utf-8'),
dst=database) dst=database)
# Videos database # Videos database
shutil.copy(src=utils.getKodiVideoDBPath(), shutil.copy(src=DatabaseConn()._SQL('video'), dst=database)
dst=database)
# Music database # Music database
if settings('enableMusic') == "true": if settings('enableMusic') == "true":
shutil.copy(src=utils.getKodiMusicDBPath(), shutil.copy(src=DatabaseConn()._SQL('music'), dst=database)
dst=database)
dialog(type_="ok", dialog(type_="ok",
heading="{emby}", heading="{emby}",
@ -234,11 +234,10 @@ def deleteItem():
log.info("Unknown type, unable to proceed.") log.info("Unknown type, unable to proceed.")
return return
embyconn = utils.kodiSQL('emby') with DatabaseConn('emby') as conn:
embycursor = embyconn.cursor() with closing(conn.cursor()) as cursor:
emby_db = embydb.Embydb_Functions(embycursor) emby_db = embydb.Embydb_Functions(cursor)
item = emby_db.getItem_byKodiId(dbId, itemType) item = emby_db.getItem_byKodiId(dbId, itemType)
embycursor.close()
try: try:
itemId = item[0] itemId = item[0]
@ -422,11 +421,10 @@ def getThemeMedia():
return return
# Get every user view Id # Get every user view Id
embyconn = utils.kodiSQL('emby') with DatabaseConn('emby') as conn:
embycursor = embyconn.cursor() with closing(conn.cursor()) as cursor:
emby_db = embydb.Embydb_Functions(embycursor) emby_db = embydb.Embydb_Functions(cursor)
viewids = emby_db.getViews() viewids = emby_db.getViews()
embycursor.close()
# Get Ids with Theme Videos # Get Ids with Theme Videos
itemIds = {} itemIds = {}

View file

@ -6,7 +6,9 @@ import logging
import read_embyserver as embyserver import read_embyserver as embyserver
from objects import Movies, MusicVideos, TVShows, Music from objects import Movies, MusicVideos, TVShows, Music
from utils import settings, kodiSQL from utils import settings
from database import DatabaseConn
from contextlib import closing
################################################################################################# #################################################################################################
@ -58,46 +60,40 @@ class Items(object):
if pdialog: if pdialog:
pdialog.update(heading="Processing %s: %s items" % (process, total)) pdialog.update(heading="Processing %s: %s items" % (process, total))
for itemtype in items: # this is going to open a music connection even if it is not needed but
# I feel that is better than trying to sort out the login yourself
with DatabaseConn('music') as conn:
with closing(conn.cursor()) as cursor_music:
# Safety check for itemtype in items:
if not itemtypes.get(itemtype):
# We don't process this type of item
continue
itemlist = items[itemtype] # Safety check
if not itemlist: if not itemtypes.get(itemtype):
# The list to process is empty # We don't process this type of item
continue continue
musicconn = None itemlist = items[itemtype]
if not itemlist:
# The list to process is empty
continue
if itemtype in ('MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio'): if itemtype in ('MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio'):
if self.music_enabled: if self.music_enabled:
musicconn = kodiSQL('music') items_process = itemtypes[itemtype](embycursor, cursor_music, pdialog) # see note above
musiccursor = musicconn.cursor() else:
items_process = itemtypes[itemtype](embycursor, musiccursor, pdialog) # Music is not enabled, do not proceed with itemtype
else: continue
# Music is not enabled, do not proceed with itemtype else:
continue update_videolibrary = True
else: items_process = itemtypes[itemtype](embycursor, kodicursor, pdialog)
update_videolibrary = True
items_process = itemtypes[itemtype](embycursor, kodicursor, pdialog)
if process == "added":
items_process.add_all(itemtype, itemlist)
elif process == "remove":
items_process.remove_all(itemtype, itemlist)
else:
process_items = self.emby.getFullItems(itemlist)
items_process.process_all(itemtype, process, process_items, total)
if process == "added":
items_process.add_all(itemtype, itemlist)
elif process == "remove":
items_process.remove_all(itemtype, itemlist)
else:
process_items = self.emby.getFullItems(itemlist)
items_process.process_all(itemtype, process, process_items, total)
if musicconn is not None:
# close connection for special types
log.info("updating music database")
musicconn.commit()
musiccursor.close()
return (True, update_videolibrary) return (True, update_videolibrary)

View file

@ -11,8 +11,10 @@ import xbmcgui
import downloadutils import downloadutils
import embydb_functions as embydb import embydb_functions as embydb
import playbackutils as pbutils import playbackutils as pbutils
from utils import window, settings, kodiSQL from utils import window, settings
from ga_client import log_error from ga_client import log_error
from database import DatabaseConn
from contextlib import closing
################################################################################################# #################################################################################################
@ -165,11 +167,10 @@ class KodiMonitor(xbmc.Monitor):
item_id = None item_id = None
conn = kodiSQL('emby') with DatabaseConn('emby') as conn:
cursor = conn.cursor() with closing(conn.cursor()) as cursor:
emby_db = embydb.Embydb_Functions(cursor) emby_db = embydb.Embydb_Functions(cursor)
db_item = emby_db.getItem_byKodiId(kodi_id, item_type) db_item = emby_db.getItem_byKodiId(kodi_id, item_type)
cursor.close()
try: try:
item_id = db_item[0] item_id = db_item[0]

View file

@ -12,7 +12,6 @@ import xbmcgui
import xbmcvfs import xbmcvfs
import api import api
import database
import utils import utils
import clientinfo import clientinfo
import downloadutils import downloadutils
@ -25,6 +24,8 @@ import videonodes
from objects import Movies, MusicVideos, TVShows, Music from objects import Movies, MusicVideos, TVShows, Music
from utils import window, settings, language as lang, should_stop from utils import window, settings, language as lang, should_stop
from ga_client import GoogleAnalytics from ga_client import GoogleAnalytics
from database import DatabaseConn
from contextlib import closing
################################################################################################## ##################################################################################################
@ -56,7 +57,6 @@ class LibrarySync(threading.Thread):
self.monitor = xbmc.Monitor() self.monitor = xbmc.Monitor()
self.clientInfo = clientinfo.ClientInfo() self.clientInfo = clientinfo.ClientInfo()
self.database = database.DatabaseConn
self.doUtils = downloadutils.DownloadUtils().downloadUrl self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.user = userclient.UserClient() self.user = userclient.UserClient()
self.emby = embyserver.Read_EmbyServer() self.emby = embyserver.Read_EmbyServer()
@ -234,123 +234,110 @@ class LibrarySync(threading.Thread):
# Add sources # Add sources
utils.sourcesXML() utils.sourcesXML()
embyconn = utils.kodiSQL('emby') # use emby and video DBs
embycursor = embyconn.cursor() with DatabaseConn('emby') as conn_emby, DatabaseConn('video') as conn_video:
# content sync: movies, tvshows, musicvideos, music with closing(conn_emby.cursor()) as cursor_emby, closing(conn_video.cursor()) as cursor_video:
kodiconn = utils.kodiSQL('video') # content sync: movies, tvshows, musicvideos, music
kodicursor = kodiconn.cursor()
if manualrun: if manualrun:
message = "Manual sync" message = "Manual sync"
elif repair: elif repair:
message = "Repair sync" message = "Repair sync"
repair_list = [] repair_list = []
choices = ['all', 'movies', 'musicvideos', 'tvshows'] choices = ['all', 'movies', 'musicvideos', 'tvshows']
if music_enabled: if music_enabled:
choices.append('music') choices.append('music')
if self.kodi_version > 15: if self.kodi_version > 15:
# Jarvis or higher # Jarvis or higher
types = xbmcgui.Dialog().multiselect(lang(33094), choices) types = xbmcgui.Dialog().multiselect(lang(33094), choices)
if types is None: if types is None:
pass pass
elif 0 in types: # all elif 0 in types: # all
choices.pop(0) choices.pop(0)
repair_list.extend(choices) repair_list.extend(choices)
else:
for index in types:
repair_list.append(choices[index])
else:
resp = xbmcgui.Dialog().select(lang(33094), choices)
if resp == 0: # all
choices.pop(resp)
repair_list.extend(choices)
else:
repair_list.append(choices[resp])
log.info("Repair queued for: %s", repair_list)
else: else:
for index in types: message = "Initial sync"
repair_list.append(choices[index]) window('emby_initialScan', value="true")
else:
resp = xbmcgui.Dialog().select(lang(33094), choices)
if resp == 0: # all
choices.pop(resp)
repair_list.extend(choices)
else:
repair_list.append(choices[resp])
log.info("Repair queued for: %s", repair_list) pDialog = self.progressDialog("%s" % message)
else: starttotal = datetime.now()
message = "Initial sync"
window('emby_initialScan', value="true")
pDialog = self.progressDialog("%s" % message) # Set views
starttotal = datetime.now() self.maintainViews(cursor_emby, cursor_video)
# Set views # Sync video library
self.maintainViews(embycursor, kodicursor) process = {
embyconn.commit()
# Sync video library 'movies': self.movies,
process = { 'musicvideos': self.musicvideos,
'tvshows': self.tvshows
}
for itemtype in process:
'movies': self.movies, if repair and itemtype not in repair_list:
'musicvideos': self.musicvideos, continue
'tvshows': self.tvshows
}
for itemtype in process:
if repair and itemtype not in repair_list: startTime = datetime.now()
continue completed = process[itemtype](cursor_emby, cursor_video, pDialog)
if not completed:
xbmc.executebuiltin('InhibitIdleShutdown(false)')
utils.setScreensaver(value=screensaver)
window('emby_dbScan', clear=True)
if pDialog:
pDialog.close()
startTime = datetime.now() return False
completed = process[itemtype](embycursor, kodicursor, pDialog) else:
if not completed: elapsedTime = datetime.now() - startTime
xbmc.executebuiltin('InhibitIdleShutdown(false)') log.info("SyncDatabase (finished %s in: %s)"
utils.setScreensaver(value=screensaver) % (itemtype, str(elapsedTime).split('.')[0]))
window('emby_dbScan', clear=True)
if pDialog:
pDialog.close()
embycursor.close()
kodicursor.close()
return False
else:
self.dbCommit(kodiconn)
embyconn.commit()
elapsedTime = datetime.now() - startTime
log.info("SyncDatabase (finished %s in: %s)"
% (itemtype, str(elapsedTime).split('.')[0]))
else:
# Close the Kodi cursor
kodicursor.close()
# sync music # sync music
# use emby and music
if music_enabled: if music_enabled:
if repair and 'music' not in repair_list: if repair and 'music' not in repair_list:
pass pass
else: else:
musicconn = utils.kodiSQL('music') with DatabaseConn('emby') as conn_emby, DatabaseConn('music') as conn_music:
musiccursor = musicconn.cursor() with closing(conn_emby.cursor()) as cursor_emby, closing(conn_music.cursor()) as cursor_music:
startTime = datetime.now()
completed = self.music(cursor_emby, cursor_music, pDialog)
if not completed:
xbmc.executebuiltin('InhibitIdleShutdown(false)')
utils.setScreensaver(value=screensaver)
window('emby_dbScan', clear=True)
if pDialog:
pDialog.close()
startTime = datetime.now() return False
completed = self.music(embycursor, musiccursor, pDialog) else:
if not completed: elapsedTime = datetime.now() - startTime
xbmc.executebuiltin('InhibitIdleShutdown(false)') log.info("SyncDatabase (finished music in: %s)"
utils.setScreensaver(value=screensaver) % (str(elapsedTime).split('.')[0]))
window('emby_dbScan', clear=True)
if pDialog:
pDialog.close()
embycursor.close()
musiccursor.close()
return False
else:
musicconn.commit()
embyconn.commit()
elapsedTime = datetime.now() - startTime
log.info("SyncDatabase (finished music in: %s)"
% (str(elapsedTime).split('.')[0]))
musiccursor.close()
if pDialog: if pDialog:
pDialog.close() pDialog.close()
emby_db = embydb.Embydb_Functions(embycursor) with DatabaseConn('emby') as conn_emby:
current_version = emby_db.get_version(self.clientInfo.get_version()) with closing(conn_emby.cursor()) as cursor_emby:
emby_db = embydb.Embydb_Functions(cursor_emby)
current_version = emby_db.get_version(self.clientInfo.get_version())
window('emby_version', current_version) window('emby_version', current_version)
embyconn.commit()
embycursor.close()
settings('SyncInstallRunDone', value="true") settings('SyncInstallRunDone', value="true")
@ -375,19 +362,11 @@ class LibrarySync(threading.Thread):
def refreshViews(self): def refreshViews(self):
embyconn = utils.kodiSQL('emby') with DatabaseConn('emby') as conn_emby, DatabaseConn('video') as conn_video:
embycursor = embyconn.cursor() with closing(conn_emby.cursor()) as cursor_emby, closing(conn_video.cursor()) as cursor_video:
kodiconn = utils.kodiSQL('video') # Compare views, assign correct tags to items
kodicursor = kodiconn.cursor() self.maintainViews(cursor_emby, cursor_video)
# Compare views, assign correct tags to items
self.maintainViews(embycursor, kodicursor)
self.dbCommit(kodiconn)
kodicursor.close()
embyconn.commit()
embycursor.close()
def maintainViews(self, embycursor, kodicursor): def maintainViews(self, embycursor, kodicursor):
@ -736,96 +715,89 @@ class LibrarySync(threading.Thread):
def incrementalSync(self): def incrementalSync(self):
embyconn = utils.kodiSQL('emby') with DatabaseConn('emby') as conn_emby, DatabaseConn('video') as conn_video:
embycursor = embyconn.cursor() with closing(conn_emby.cursor()) as cursor_emby, closing(conn_video.cursor()) as cursor_video:
kodiconn = utils.kodiSQL('video')
kodicursor = kodiconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
pDialog = None
update_embydb = False
if self.refresh_views: emby_db = embydb.Embydb_Functions(cursor_emby)
# Received userconfig update pDialog = None
self.refresh_views = False update_embydb = False
self.maintainViews(embycursor, kodicursor)
embycursor.commit()
self.forceLibraryUpdate = True
update_embydb = True
incSyncIndicator = int(settings('incSyncIndicator') or 10) if self.refresh_views:
totalUpdates = len(self.addedItems) + len(self.updateItems) + len(self.userdataItems) + len(self.removeItems) # Received userconfig update
self.refresh_views = False
self.maintainViews(cursor_emby, cursor_video)
self.forceLibraryUpdate = True
update_embydb = True
if incSyncIndicator != -1 and totalUpdates > incSyncIndicator: incSyncIndicator = int(settings('incSyncIndicator') or 10)
# Only present dialog if we are going to process items totalUpdates = len(self.addedItems) + len(self.updateItems) + len(self.userdataItems) + len(self.removeItems)
pDialog = self.progressDialog('Incremental sync')
log.info("incSyncIndicator=" + str(incSyncIndicator) + " totalUpdates=" + str(totalUpdates))
process = { if incSyncIndicator != -1 and totalUpdates > incSyncIndicator:
# Only present dialog if we are going to process items
pDialog = self.progressDialog('Incremental sync')
log.info("incSyncIndicator=" + str(incSyncIndicator) + " totalUpdates=" + str(totalUpdates))
'added': self.addedItems, process = {
'update': self.updateItems,
'userdata': self.userdataItems,
'remove': self.removeItems
}
for process_type in ['added', 'update', 'userdata', 'remove']:
if process[process_type] and window('emby_kodiScan') != "true": 'added': self.addedItems,
'update': self.updateItems,
'userdata': self.userdataItems,
'remove': self.removeItems
}
for process_type in ['added', 'update', 'userdata', 'remove']:
listItems = list(process[process_type]) if process[process_type] and window('emby_kodiScan') != "true":
del process[process_type][:] # Reset class list
items_process = itemtypes.Items(embycursor, kodicursor) listItems = list(process[process_type])
update = False del process[process_type][:] # Reset class list
# Prepare items according to process process_type items_process = itemtypes.Items(cursor_emby, cursor_video)
if process_type == "added": update = False
items = self.emby.sortby_mediatype(listItems)
elif process_type in ("userdata", "remove"): # Prepare items according to process process_type
items = emby_db.sortby_mediaType(listItems, unsorted=False) if process_type == "added":
items = self.emby.sortby_mediatype(listItems)
else: elif process_type in ("userdata", "remove"):
items = emby_db.sortby_mediaType(listItems) items = emby_db.sortby_mediaType(listItems, unsorted=False)
if items.get('Unsorted'):
sorted_items = self.emby.sortby_mediatype(items['Unsorted']) else:
doupdate = items_process.itemsbyId(sorted_items, "added", pDialog) items = emby_db.sortby_mediaType(listItems)
if items.get('Unsorted'):
sorted_items = self.emby.sortby_mediatype(items['Unsorted'])
doupdate = items_process.itemsbyId(sorted_items, "added", pDialog)
if doupdate:
embyupdate, kodiupdate_video = doupdate
if embyupdate:
update_embydb = True
if kodiupdate_video:
self.forceLibraryUpdate = True
del items['Unsorted']
doupdate = items_process.itemsbyId(items, process_type, pDialog)
if doupdate: if doupdate:
embyupdate, kodiupdate_video = doupdate embyupdate, kodiupdate_video = doupdate
if embyupdate: if embyupdate:
update_embydb = True update_embydb = True
if kodiupdate_video: if kodiupdate_video:
self.forceLibraryUpdate = True self.forceLibraryUpdate = True
del items['Unsorted']
doupdate = items_process.itemsbyId(items, process_type, pDialog) if update_embydb:
if doupdate: update_embydb = False
embyupdate, kodiupdate_video = doupdate log.info("Updating emby database.")
if embyupdate: self.saveLastSync()
update_embydb = True
if kodiupdate_video:
self.forceLibraryUpdate = True
if update_embydb: if self.forceLibraryUpdate:
update_embydb = False # Force update the Kodi library
log.info("Updating emby database.") self.forceLibraryUpdate = False
embyconn.commit()
self.saveLastSync()
if self.forceLibraryUpdate: log.info("Updating video library.")
# Force update the Kodi library window('emby_kodiScan', value="true")
self.forceLibraryUpdate = False xbmc.executebuiltin('UpdateLibrary(video)')
self.dbCommit(kodiconn)
log.info("Updating video library.")
window('emby_kodiScan', value="true")
xbmc.executebuiltin('UpdateLibrary(video)')
if pDialog: if pDialog:
pDialog.close() pDialog.close()
kodicursor.close()
embycursor.close()
def compareDBVersion(self, current, minimum): def compareDBVersion(self, current, minimum):
# It returns True is database is up to date. False otherwise. # It returns True is database is up to date. False otherwise.
@ -848,18 +820,18 @@ class LibrarySync(threading.Thread):
def _verify_emby_database(self): def _verify_emby_database(self):
# Create the tables for the emby database # Create the tables for the emby database
with self.database('emby') as conn: with DatabaseConn('emby') as conn:
cursor = conn.cursor() with closing(conn.cursor()) as cursor:
# emby, view, version # emby, view, version
cursor.execute( cursor.execute(
"""CREATE TABLE IF NOT EXISTS emby( """CREATE TABLE IF NOT EXISTS emby(
emby_id TEXT UNIQUE, media_folder TEXT, emby_type TEXT, media_type TEXT, emby_id TEXT UNIQUE, media_folder TEXT, emby_type TEXT, media_type TEXT,
kodi_id INTEGER, kodi_fileid INTEGER, kodi_pathid INTEGER, parent_id INTEGER, kodi_id INTEGER, kodi_fileid INTEGER, kodi_pathid INTEGER, parent_id INTEGER,
checksum INTEGER)""") checksum INTEGER)""")
cursor.execute( cursor.execute(
"""CREATE TABLE IF NOT EXISTS view( """CREATE TABLE IF NOT EXISTS view(
view_id TEXT UNIQUE, view_name TEXT, media_type TEXT, kodi_tagid INTEGER)""") view_id TEXT UNIQUE, view_name TEXT, media_type TEXT, kodi_tagid INTEGER)""")
cursor.execute("CREATE TABLE IF NOT EXISTS version(idVersion TEXT)") cursor.execute("CREATE TABLE IF NOT EXISTS version(idVersion TEXT)")
def run(self): def run(self):
@ -906,18 +878,18 @@ class LibrarySync(threading.Thread):
if (window('emby_dbCheck') != "true" and settings('SyncInstallRunDone') == "true"): if (window('emby_dbCheck') != "true" and settings('SyncInstallRunDone') == "true"):
# Verify the validity of the database # Verify the validity of the database
log.info("Doing DB Version Check")
with DatabaseConn('emby') as conn:
with closing(conn.cursor()) as cursor:
emby_db = embydb.Embydb_Functions(cursor)
currentVersion = emby_db.get_version()
###$ Begin migration $###
if not currentVersion:
currentVersion = emby_db.get_version(settings('dbCreatedWithVersion') or self.clientInfo.get_version())
log.info("Migration of database version completed")
###$ End migration $###
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
currentVersion = emby_db.get_version()
###$ Begin migration $###
if not currentVersion:
currentVersion = emby_db.get_version(settings('dbCreatedWithVersion') or self.clientInfo.get_version())
embyconn.commit()
log.info("Migration of database version completed")
###$ End migration $###
embycursor.close()
window('emby_version', value=currentVersion) window('emby_version', value=currentVersion)
minVersion = window('emby_minDBVersion') minVersion = window('emby_minDBVersion')
@ -932,7 +904,7 @@ class LibrarySync(threading.Thread):
log.warn("Database version is out of date! USER IGNORED!") log.warn("Database version is out of date! USER IGNORED!")
dialog.ok(lang(29999), lang(33023)) dialog.ok(lang(29999), lang(33023))
else: else:
utils.reset() database.db_reset()
break break
@ -941,7 +913,7 @@ class LibrarySync(threading.Thread):
if not startupComplete: if not startupComplete:
# Verify the video database can be found # Verify the video database can be found
videoDb = utils.getKodiVideoDBPath() videoDb = DatabaseConn()._SQL('video')
if not xbmcvfs.exists(videoDb): if not xbmcvfs.exists(videoDb):
# Database does not exists # Database does not exists
log.error( log.error(

View file

@ -11,7 +11,9 @@ import playutils
import playbackutils import playbackutils
import embydb_functions as embydb import embydb_functions as embydb
import read_embyserver as embyserver import read_embyserver as embyserver
from utils import window, kodiSQL, JSONRPC from utils import window, JSONRPC
from database import DatabaseConn
from contextlib import closing
################################################################################################# #################################################################################################
@ -29,77 +31,76 @@ class Playlist(object):
def play_all(self, item_ids, start_at): def play_all(self, item_ids, start_at):
conn = kodiSQL('emby') with DatabaseConn('emby') as conn:
cursor = conn.cursor() with closing(conn.cursor()) as cursor:
emby_db = embydb.Embydb_Functions(cursor) emby_db = embydb.Embydb_Functions(cursor)
player = xbmc.Player() player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear() playlist.clear()
log.info("---*** PLAY ALL ***---") log.info("---*** PLAY ALL ***---")
log.info("Items: %s and start at: %s", item_ids, start_at) log.info("Items: %s and start at: %s", item_ids, start_at)
started = False started = False
window('emby_customplaylist', value="true") window('emby_customplaylist', value="true")
if start_at: if start_at:
# Seek to the starting position # Seek to the starting position
window('emby_customplaylist.seektime', str(start_at)) window('emby_customplaylist.seektime', str(start_at))
for item_id in item_ids: for item_id in item_ids:
log.info("Adding %s to playlist", item_id) log.info("Adding %s to playlist", item_id)
item = emby_db.getItem_byId(item_id) item = emby_db.getItem_byId(item_id)
try: try:
db_id = item[0] db_id = item[0]
media_type = item[4] media_type = item[4]
except TypeError: except TypeError:
# Item is not found in our database, add item manually # Item is not found in our database, add item manually
log.info("Item was not found in the database, manually adding item") log.info("Item was not found in the database, manually adding item")
item = self.emby.getItem(item_id) item = self.emby.getItem(item_id)
self.add_to_xbmc_playlist(playlist, item) self.add_to_xbmc_playlist(playlist, item)
else: # Add to playlist else: # Add to playlist
self.add_to_playlist(db_id, media_type) self.add_to_playlist(db_id, media_type)
if not started: if not started:
started = True started = True
player.play(playlist) player.play(playlist)
self.verify_playlist() self.verify_playlist()
cursor.close()
def modify_playlist(self, item_ids): def modify_playlist(self, item_ids):
conn = kodiSQL('emby') with DatabaseConn('emby') as conn:
cursor = conn.cursor() with closing(conn.cursor()) as cursor:
emby_db = embydb.Embydb_Functions(cursor) emby_db = embydb.Embydb_Functions(cursor)
log.info("---*** ADD TO PLAYLIST ***---") log.info("---*** ADD TO PLAYLIST ***---")
log.info("Items: %s", item_ids) log.info("Items: %s", item_ids)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
for item_id in item_ids: for item_id in item_ids:
log.info("Adding %s to playlist", item_id) log.info("Adding %s to playlist", item_id)
item = emby_db.getItem_byId(item_id) item = emby_db.getItem_byId(item_id)
try: try:
db_id = item[0] db_id = item[0]
media_type = item[4] media_type = item[4]
except TypeError: except TypeError:
# Item is not found in our database, add item manually # Item is not found in our database, add item manually
item = self.emby.getItem(item_id) item = self.emby.getItem(item_id)
self.add_to_xbmc_playlist(playlist, item) self.add_to_xbmc_playlist(playlist, item)
else: # Add to playlist else: # Add to playlist
self.add_to_playlist(db_id, media_type) self.add_to_playlist(db_id, media_type)
self.verify_playlist()
self.verify_playlist()
cursor.close()
return playlist return playlist
@classmethod @classmethod

View file

@ -8,7 +8,9 @@ import hashlib
import xbmc import xbmc
import downloadutils import downloadutils
from utils import window, settings, kodiSQL from utils import window, settings
from database import DatabaseConn
from contextlib import closing
################################################################################################# #################################################################################################
@ -102,24 +104,23 @@ class Read_EmbyServer():
viewId = view['Id'] viewId = view['Id']
# Compare to view table in emby database # Compare to view table in emby database
emby = kodiSQL('emby') with DatabaseConn('emby') as conn:
cursor_emby = emby.cursor() with closing(conn.cursor()) as cursor:
query = ' '.join(( query = ' '.join((
"SELECT view_name, media_type", "SELECT view_name, media_type",
"FROM view", "FROM view",
"WHERE view_id = ?" "WHERE view_id = ?"
)) ))
cursor_emby.execute(query, (viewId,)) cursor.execute(query, (viewId,))
result = cursor_emby.fetchone() result = cursor.fetchone()
try: try:
viewName = result[0] viewName = result[0]
mediatype = result[1] mediatype = result[1]
except TypeError: except TypeError:
viewName = None viewName = None
mediatype = None mediatype = None
cursor_emby.close()
return [viewName, viewId, mediatype] return [viewName, viewId, mediatype]

View file

@ -2,7 +2,6 @@
################################################################################################# #################################################################################################
import inspect import inspect
import json import json
import logging import logging
@ -15,6 +14,7 @@ import unicodedata
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
from datetime import datetime from datetime import datetime
import xbmc import xbmc
import xbmcaddon import xbmcaddon
import xbmcgui import xbmcgui
@ -120,55 +120,6 @@ def should_stop():
else: # Keep going else: # Keep going
return False return False
def kodiSQL(media_type="video"):
if media_type == "emby":
dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8')
elif media_type == "texture":
dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
elif media_type == "music":
dbPath = getKodiMusicDBPath()
else:
dbPath = getKodiVideoDBPath()
if settings('dblock') == "true":
connection = sqlite3.connect(dbPath, isolation_level=None, timeout=20)
else:
connection = sqlite3.connect(dbPath, timeout=20)
return connection
def getKodiVideoDBPath():
dbVersion = {
"13": 78, # Gotham
"14": 90, # Helix
"15": 93, # Isengard
"16": 99, # Jarvis
"17": 107 # Krypton
}
dbPath = xbmc.translatePath(
"special://database/MyVideos%s.db"
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8')
return dbPath
def getKodiMusicDBPath():
dbVersion = {
"13": 46, # Gotham
"14": 48, # Helix
"15": 52, # Isengard
"16": 56, # Jarvis
"17": 60 # Krypton
}
dbPath = xbmc.translatePath(
"special://database/MyMusic%s.db"
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8')
return dbPath
################################################################################################# #################################################################################################
# Utility methods # Utility methods
@ -280,99 +231,6 @@ def profiling(sortby="cumulative"):
################################################################################################# #################################################################################################
# Addon utilities # Addon utilities
def reset():
dialog = xbmcgui.Dialog()
if not dialog.yesno(language(29999), language(33074)):
return
# first stop any db sync
window('emby_online', value="reset")
window('emby_shouldStop', value="true")
count = 10
while window('emby_dbScan') == "true":
log.info("Sync is running, will retry: %s..." % count)
count -= 1
if count == 0:
dialog.ok(language(29999), language(33085))
return
xbmc.sleep(1000)
# Clean up the playlists
deletePlaylists()
# Clean up the video nodes
deleteNodes()
# Wipe the kodi databases
log.warn("Resetting the Kodi video database.")
connection = kodiSQL('video')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
connection.commit()
cursor.close()
if settings('enableMusic') == "true":
log.warn("Resetting the Kodi music database.")
connection = kodiSQL('music')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
connection.commit()
cursor.close()
# Wipe the emby database
log.warn("Resetting the Emby database.")
connection = kodiSQL('emby')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
cursor.execute('DROP table IF EXISTS emby')
cursor.execute('DROP table IF EXISTS view')
cursor.execute("DROP table IF EXISTS version")
connection.commit()
cursor.close()
# Offer to wipe cached thumbnails
if dialog.yesno(language(29999), language(33086)):
log.warn("Resetting all cached artwork")
# Remove all existing textures first
import artwork
artwork.Artwork().delete_cache()
# reset the install run flag
settings('SyncInstallRunDone', value="false")
# Remove emby info
resp = dialog.yesno(language(29999), language(33087))
if resp:
import connectmanager
# Delete the settings
addon = xbmcaddon.Addon()
addondir = xbmc.translatePath(
"special://profile/addon_data/plugin.video.emby/").decode('utf-8')
dataPath = "%ssettings.xml" % addondir
xbmcvfs.delete(dataPath)
connectmanager.ConnectManager().clear_data()
dialog.ok(heading=language(29999), line1=language(33088))
xbmc.executebuiltin('RestartApp')
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xbmcgui.ListItem())
def sourcesXML(): def sourcesXML():
# To make Master lock compatible # To make Master lock compatible
path = xbmc.translatePath("special://profile/").decode('utf-8') path = xbmc.translatePath("special://profile/").decode('utf-8')