Avoid xbmcvfs entirely; use encoded paths

- Fixes #286
This commit is contained in:
tomkat83 2017-05-03 20:30:33 +02:00
parent 60759ba625
commit b2cd6e1156
9 changed files with 181 additions and 246 deletions

View file

@ -39,11 +39,11 @@ import xml.etree.ElementTree as etree
from re import compile as re_compile, sub from re import compile as re_compile, sub
from json import dumps from json import dumps
from urllib import urlencode, quote_plus, unquote from urllib import urlencode, quote_plus, unquote
from os import path as os_path from os.path import basename, join, exists
from os import makedirs
import xbmcgui import xbmcgui
from xbmc import sleep, executebuiltin from xbmc import sleep, executebuiltin
from xbmcvfs import exists, mkdirs
import clientinfo as client import clientinfo as client
from downloadutils import DownloadUtils from downloadutils import DownloadUtils
@ -2113,11 +2113,14 @@ class API():
if fanarttvimage not in data: if fanarttvimage not in data:
continue continue
for entry in data[fanarttvimage]: for entry in data[fanarttvimage]:
if fanartcount < maxfanarts: if entry.get("url") is None:
if exists(entry.get("url")): continue
allartworks['Backdrop'].append( if fanartcount > maxfanarts:
entry.get("url", "").replace(' ', '%20')) break
fanartcount += 1 if exists(tryEncode(entry['url'])):
allartworks['Backdrop'].append(
entry['url'].replace(' ', '%20'))
fanartcount += 1
return allartworks return allartworks
def getSetArtwork(self, parentInfo=False): def getSetArtwork(self, parentInfo=False):
@ -2184,7 +2187,7 @@ class API():
# Get additional info (filename / languages) # Get additional info (filename / languages)
filename = None filename = None
if 'file' in entry[0].attrib: if 'file' in entry[0].attrib:
filename = os_path.basename(entry[0].attrib['file']) filename = basename(entry[0].attrib['file'])
# Languages of audio streams # Languages of audio streams
languages = [] languages = []
for stream in entry[0]: for stream in entry[0]:
@ -2339,8 +2342,8 @@ class API():
Returns the path to the downloaded subtitle or None Returns the path to the downloaded subtitle or None
""" """
if not exists(v.EXTERNAL_SUBTITLE_TEMP_PATH): if not exists(v.EXTERNAL_SUBTITLE_TEMP_PATH):
mkdirs(v.EXTERNAL_SUBTITLE_TEMP_PATH) makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
path = os_path.join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename) path = join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
r = DownloadUtils().downloadUrl(url, return_response=True) r = DownloadUtils().downloadUrl(url, return_response=True)
try: try:
r.status_code r.status_code

View file

@ -4,16 +4,16 @@
import logging import logging
from json import dumps, loads from json import dumps, loads
import requests import requests
from os import path as os_path from os.path import exists
from shutil import rmtree
from urllib import quote_plus, unquote from urllib import quote_plus, unquote
from threading import Thread from threading import Thread
from Queue import Queue, Empty from Queue import Queue, Empty
from xbmc import executeJSONRPC, sleep, translatePath from xbmc import executeJSONRPC, sleep, translatePath
from xbmcvfs import listdir, delete
from utils import window, settings, language as lang, kodiSQL, tryEncode, \ from utils import window, settings, language as lang, kodiSQL, tryEncode, \
tryDecode, IfExists, ThreadMethods, ThreadMethodsAdditionalStop, dialog ThreadMethods, ThreadMethodsAdditionalStop, dialog
# Disable annoying requests warnings # Disable annoying requests warnings
import requests.packages.urllib3 import requests.packages.urllib3
@ -228,30 +228,21 @@ class Artwork():
if dialog('yesno', "Image Texture Cache", lang(39251)): if dialog('yesno', "Image Texture Cache", lang(39251)):
log.info("Resetting all cache data first") log.info("Resetting all cache data first")
# Remove all existing textures first # Remove all existing textures first
path = tryDecode(translatePath("special://thumbnails/")) path = translatePath("special://thumbnails/")
if IfExists(path): if exists(path):
allDirs, allFiles = listdir(path) rmtree(path, ignore_errors=True)
for dir in allDirs:
allDirs, allFiles = listdir(path+dir)
for file in allFiles:
if os_path.supports_unicode_filenames:
delete(os_path.join(
path + tryDecode(dir),
tryDecode(file)))
else:
delete(os_path.join(
tryEncode(path) + dir,
file))
# remove all existing data from texture DB # remove all existing data from texture DB
connection = kodiSQL('texture') connection = kodiSQL('texture')
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') query = 'SELECT tbl_name FROM sqlite_master WHERE type=?'
cursor.execute(query, ('table', ))
rows = cursor.fetchall() rows = cursor.fetchall()
for row in rows: for row in rows:
tableName = row[0] tableName = row[0]
if tableName != "version": if tableName != "version":
cursor.execute("DELETE FROM " + tableName) query = "DELETE FROM ?"
cursor.execute(query, (tableName,))
connection.commit() connection.commit()
connection.close() connection.close()
@ -259,7 +250,8 @@ class Artwork():
connection = kodiSQL('video') connection = kodiSQL('video')
cursor = connection.cursor() cursor = connection.cursor()
# dont include actors # dont include actors
cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") query = "SELECT url FROM art WHERE media_type != ?"
cursor.execute(query, ('actor', ))
result = cursor.fetchall() result = cursor.fetchall()
total = len(result) total = len(result)
log.info("Image cache sync about to process %s video images" % total) log.info("Image cache sync about to process %s video images" % total)
@ -286,7 +278,6 @@ class Artwork():
def addArtwork(self, artwork, kodiId, mediaType, cursor): def addArtwork(self, artwork, kodiId, mediaType, cursor):
# Kodi conversion table # Kodi conversion table
kodiart = { kodiart = {
'Primary': ["thumb", "poster"], 'Primary': ["thumb", "poster"],
'Banner': "banner", 'Banner': "banner",
'Logo': "clearlogo", 'Logo': "clearlogo",
@ -307,7 +298,6 @@ class Artwork():
backdropsNumber = len(backdrops) backdropsNumber = len(backdrops)
query = ' '.join(( query = ' '.join((
"SELECT url", "SELECT url",
"FROM art", "FROM art",
"WHERE media_id = ?", "WHERE media_id = ?",
@ -320,7 +310,6 @@ class Artwork():
if len(rows) > backdropsNumber: if len(rows) > backdropsNumber:
# More backdrops in database. Delete extra fanart. # More backdrops in database. Delete extra fanart.
query = ' '.join(( query = ' '.join((
"DELETE FROM art", "DELETE FROM art",
"WHERE media_id = ?", "WHERE media_id = ?",
"AND media_type = ?", "AND media_type = ?",
@ -339,7 +328,7 @@ class Artwork():
cursor=cursor) cursor=cursor)
if backdropsNumber > 1: if backdropsNumber > 1:
try: # Will only fail on the first try, str to int. try: # Will only fail on the first try, str to int.
index += 1 index += 1
except TypeError: except TypeError:
index = 1 index = 1
@ -438,14 +427,10 @@ class Artwork():
log.info("Could not find cached url.") log.info("Could not find cached url.")
else: else:
# Delete thumbnail as well as the entry # Delete thumbnail as well as the entry
thumbnails = tryDecode( path = translatePath("special://thumbnails/%s" % cachedurl)
translatePath("special://thumbnails/%s" % cachedurl)) log.debug("Deleting cached thumbnail: %s" % path)
log.debug("Deleting cached thumbnail: %s" % thumbnails) if exists(path):
try: rmtree(path, ignore_errors=True)
delete(thumbnails)
except Exception as e:
log.error('Could not delete cached artwork %s. Error: %s'
% (thumbnails, e))
cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
connection.commit() connection.commit()
finally: finally:

View file

@ -1,7 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
import logging import logging
from os import path as os_path from shutil import copyfile
from os import walk, makedirs
from os.path import basename, join, exists
from sys import argv from sys import argv
from urllib import urlencode from urllib import urlencode
@ -9,8 +11,8 @@ import xbmcplugin
from xbmc import sleep, executebuiltin, translatePath from xbmc import sleep, executebuiltin, translatePath
from xbmcgui import ListItem from xbmcgui import ListItem
from utils import window, settings, language as lang, dialog, tryDecode,\ from utils import window, settings, language as lang, dialog, tryEncode, \
tryEncode, CatchExceptions, JSONRPC CatchExceptions, JSONRPC
import downloadutils import downloadutils
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \ from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
@ -486,6 +488,7 @@ def getVideoFiles(plexId, params):
except: except:
log.error('Could not get file path for item %s' % plexId) log.error('Could not get file path for item %s' % plexId)
return xbmcplugin.endOfDirectory(HANDLE) return xbmcplugin.endOfDirectory(HANDLE)
path = tryEncode(path)
# Assign network protocol # Assign network protocol
if path.startswith('\\\\'): if path.startswith('\\\\'):
path = path.replace('\\\\', 'smb://') path = path.replace('\\\\', 'smb://')
@ -493,28 +496,25 @@ def getVideoFiles(plexId, params):
# Plex returns Windows paths as e.g. 'c:\slfkjelf\slfje\file.mkv' # Plex returns Windows paths as e.g. 'c:\slfkjelf\slfje\file.mkv'
elif '\\' in path: elif '\\' in path:
path = path.replace('\\', '\\\\') path = path.replace('\\', '\\\\')
# Directory only, get rid of filename (!! exists() needs / or \ at end) # Directory only, get rid of filename
path = path.replace(os_path.basename(path), '') path = path.replace(basename(path), '')
# Only proceed if we can access this folder if exists(path):
import xbmcvfs for root, dirs, files in walk(path):
if xbmcvfs.exists(path): for directory in dirs:
# Careful, returns encoded strings! item_path = join(root, directory)
dirs, files = xbmcvfs.listdir(path) li = ListItem(item_path, path=item_path)
for file in files: xbmcplugin.addDirectoryItem(handle=HANDLE,
file = path + tryDecode(file) url=item_path,
li = ListItem(file, path=file) listitem=li,
xbmcplugin.addDirectoryItem(handle=HANDLE, isFolder=True)
url=tryEncode(file), for file in files:
listitem=li) item_path = join(root, file)
for dir in dirs: li = ListItem(item_path, path=item_path)
dir = path + tryDecode(dir) xbmcplugin.addDirectoryItem(handle=HANDLE,
li = ListItem(dir, path=dir) url=file,
xbmcplugin.addDirectoryItem(handle=HANDLE, listitem=li)
url=tryEncode(dir),
listitem=li,
isFolder=True)
else: else:
log.warn('Kodi cannot access folder %s' % path) log.error('Kodi cannot access folder %s' % path)
xbmcplugin.endOfDirectory(HANDLE) xbmcplugin.endOfDirectory(HANDLE)
@ -525,7 +525,6 @@ def getExtraFanArt(plexid, plexPath):
will be called by skinhelper script to get the extrafanart will be called by skinhelper script to get the extrafanart
for tvshows we get the plexid just from the path for tvshows we get the plexid just from the path
""" """
import xbmcvfs
log.debug('Called with plexid: %s, plexPath: %s' % (plexid, plexPath)) log.debug('Called with plexid: %s, plexPath: %s' % (plexid, plexPath))
if not plexid: if not plexid:
if "plugin.video.plexkodiconnect" in plexPath: if "plugin.video.plexkodiconnect" in plexPath:
@ -536,11 +535,10 @@ def getExtraFanArt(plexid, plexPath):
# We need to store the images locally for this to work # We need to store the images locally for this to work
# because of the caching system in xbmc # because of the caching system in xbmc
fanartDir = tryDecode(translatePath( fanartDir = translatePath("special://thumbnails/plex/%s/" % plexid)
"special://thumbnails/plex/%s/" % plexid)) if not exists(fanartDir):
if not xbmcvfs.exists(fanartDir):
# Download the images to the cache directory # Download the images to the cache directory
xbmcvfs.mkdirs(tryEncode(fanartDir)) makedirs(fanartDir)
xml = GetPlexMetadata(plexid) xml = GetPlexMetadata(plexid)
if xml is None: if xml is None:
log.error('Could not download metadata for %s' % plexid) log.error('Could not download metadata for %s' % plexid)
@ -550,29 +548,23 @@ def getExtraFanArt(plexid, plexPath):
backdrops = api.getAllArtwork()['Backdrop'] backdrops = api.getAllArtwork()['Backdrop']
for count, backdrop in enumerate(backdrops): for count, backdrop in enumerate(backdrops):
# Same ordering as in artwork # Same ordering as in artwork
if os_path.supports_unicode_filenames: fanartFile = join(fanartDir, "fanart%.3d.jpg" % count)
fanartFile = os_path.join(fanartDir,
"fanart%.3d.jpg" % count)
else:
fanartFile = os_path.join(
tryEncode(fanartDir),
tryEncode("fanart%.3d.jpg" % count))
li = ListItem("%.3d" % count, path=fanartFile) li = ListItem("%.3d" % count, path=fanartFile)
xbmcplugin.addDirectoryItem( xbmcplugin.addDirectoryItem(
handle=HANDLE, handle=HANDLE,
url=fanartFile, url=fanartFile,
listitem=li) listitem=li)
xbmcvfs.copy(backdrop, fanartFile) copyfile(backdrop, fanartFile)
else: else:
log.info("Found cached backdrop.") log.info("Found cached backdrop.")
# Use existing cached images # Use existing cached images
dirs, files = xbmcvfs.listdir(fanartDir) for root, dirs, files in walk(fanartDir):
for file in files: for file in files:
fanartFile = os_path.join(fanartDir, tryDecode(file)) fanartFile = join(root, file)
li = ListItem(file, path=fanartFile) li = ListItem(file, path=fanartFile)
xbmcplugin.addDirectoryItem(handle=HANDLE, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=fanartFile, url=fanartFile,
listitem=li) listitem=li)
xbmcplugin.endOfDirectory(HANDLE) xbmcplugin.endOfDirectory(HANDLE)

View file

@ -1,15 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
import logging import logging
from threading import Thread from threading import Thread
import Queue import Queue
from random import shuffle from random import shuffle
from os.path import exists
import xbmc import xbmc
import xbmcgui import xbmcgui
import xbmcvfs
from utils import window, settings, getUnixTimestamp, sourcesXML,\ from utils import window, settings, getUnixTimestamp, sourcesXML,\
ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\ ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\
@ -1505,7 +1503,7 @@ class LibrarySync(Thread):
# Also runs when first installed # Also runs when first installed
# Verify the video database can be found # Verify the video database can be found
videoDb = v.DB_VIDEO_PATH videoDb = v.DB_VIDEO_PATH
if not xbmcvfs.exists(videoDb): if not exists(videoDb):
# Database does not exists # Database does not exists
log.error("The current Kodi version is incompatible " log.error("The current Kodi version is incompatible "
"to know which Kodi versions are supported.") "to know which Kodi versions are supported.")

View file

@ -3,14 +3,15 @@
############################################################################### ###############################################################################
import logging import logging
import threading import threading
from os.path import exists
import xbmc import xbmc
import xbmcgui import xbmcgui
import xbmcaddon import xbmcaddon
import xbmcvfs
from utils import window, settings, language as lang, ThreadMethods, \ from utils import window, settings, language as lang, ThreadMethods, \
tryDecode, ThreadMethodsAdditionalSuspend ThreadMethodsAdditionalSuspend
import downloadutils import downloadutils
import PlexAPI import PlexAPI
@ -209,9 +210,8 @@ class UserClient(threading.Thread):
return False return False
# Get /profile/addon_data # Get /profile/addon_data
addondir = tryDecode(xbmc.translatePath( addondir = xbmc.translatePath(self.addon.getAddonInfo('profile'))
self.addon.getAddonInfo('profile'))) hasSettings = exists("%ssettings.xml" % addondir)
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
# If there's no settings.xml # If there's no settings.xml
if not hasSettings: if not hasSettings:

View file

@ -13,12 +13,13 @@ from unicodedata import normalize
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
from functools import wraps from functools import wraps
from calendar import timegm from calendar import timegm
from os import path as os_path from os.path import exists, join
from os import remove, makedirs, walk
from shutil import rmtree
import xbmc import xbmc
import xbmcaddon import xbmcaddon
import xbmcgui import xbmcgui
import xbmcvfs
from variables import DB_VIDEO_PATH, DB_MUSIC_PATH, DB_TEXTURE_PATH, \ from variables import DB_VIDEO_PATH, DB_MUSIC_PATH, DB_TEXTURE_PATH, \
DB_PLEX_PATH DB_PLEX_PATH
@ -198,27 +199,6 @@ def DateToKodi(stamp):
return localdate return localdate
def IfExists(path):
"""
Kodi's xbmcvfs.exists is broken - it caches the results for directories.
path: path to a directory (with a slash at the end)
Returns True if path exists, else false
"""
dummyfile = tryEncode(os_path.join(path, 'dummyfile.txt'))
try:
etree.ElementTree(etree.Element('test')).write(dummyfile)
except:
# folder does not exist yet
answer = False
else:
# Folder exists. Delete file again.
xbmcvfs.delete(dummyfile)
answer = True
return answer
def IntFromStr(string): def IntFromStr(string):
""" """
Returns an int from string or the int 0 if something happened Returns an int from string or the int 0 if something happened
@ -362,24 +342,14 @@ def reset():
line1=language(39602)): line1=language(39602)):
log.info("Resetting all cached artwork.") log.info("Resetting all cached artwork.")
# Remove all existing textures first # Remove all existing textures first
path = tryDecode(xbmc.translatePath("special://thumbnails/")) path = xbmc.translatePath("special://thumbnails/")
if xbmcvfs.exists(path): if exists(path):
allDirs, allFiles = xbmcvfs.listdir(path) rmtree(path, ignore_errors=True)
for dir in allDirs:
allDirs, allFiles = xbmcvfs.listdir(path+dir)
for file in allFiles:
if os_path.supports_unicode_filenames:
xbmcvfs.delete(os_path.join(
path + tryDecode(dir),
tryDecode(file)))
else:
xbmcvfs.delete(os_path.join(
tryEncode(path) + dir,
file))
# remove all existing data from texture DB # remove all existing data from texture DB
connection = kodiSQL('texture') connection = kodiSQL('texture')
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') query = 'SELECT tbl_name FROM sqlite_master WHERE type=?'
cursor.execute(query, ("table", ))
rows = cursor.fetchall() rows = cursor.fetchall()
for row in rows: for row in rows:
tableName = row[0] tableName = row[0]
@ -398,10 +368,10 @@ def reset():
line1=language(39603)): line1=language(39603)):
# Delete the settings # Delete the settings
addon = xbmcaddon.Addon() addon = xbmcaddon.Addon()
addondir = tryDecode(xbmc.translatePath(addon.getAddonInfo('profile'))) addondir = xbmc.translatePath(addon.getAddonInfo('profile'))
dataPath = "%ssettings.xml" % addondir dataPath = "%ssettings.xml" % addondir
log.info("Deleting: settings.xml") log.info("Deleting: settings.xml")
xbmcvfs.delete(tryEncode(dataPath)) remove(dataPath)
# Kodi will now restart to apply the changes. # Kodi will now restart to apply the changes.
dialog('ok', dialog('ok',
@ -664,12 +634,13 @@ def sourcesXML():
def passwordsXML(): def passwordsXML():
# To add network credentials # To add network credentials
path = tryDecode(xbmc.translatePath("special://userdata/")) path = xbmc.translatePath("special://userdata/")
xmlpath = "%spasswords.xml" % path xmlpath = "%spasswords.xml" % path
try: try:
xmlparse = etree.parse(xmlpath) xmlparse = etree.parse(xmlpath)
except: # Document is blank or missing except:
# Document is blank or missing
root = etree.Element('passwords') root = etree.Element('passwords')
skipFind = True skipFind = True
else: else:
@ -753,8 +724,6 @@ def passwordsXML():
etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server
topath = "smb://%s:%s@%s/" % (user, password, server) topath = "smb://%s:%s@%s/" % (user, password, server)
etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath
# Force Kodi to see the credentials without restarting
xbmcvfs.exists(topath)
# Add credentials # Add credentials
settings('networkCreds', value="%s" % server) settings('networkCreds', value="%s" % server)
@ -762,7 +731,8 @@ def passwordsXML():
# Prettify and write to file # Prettify and write to file
try: try:
indent(root) indent(root)
except: pass except:
pass
etree.ElementTree(root).write(xmlpath) etree.ElementTree(root).write(xmlpath)
# dialog.notification( # dialog.notification(
@ -776,7 +746,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
""" """
Feed with tagname as unicode Feed with tagname as unicode
""" """
path = tryDecode(xbmc.translatePath("special://profile/playlists/video/")) path = xbmc.translatePath("special://profile/playlists/video/")
if viewtype == "mixed": if viewtype == "mixed":
plname = "%s - %s" % (tagname, mediatype) plname = "%s - %s" % (tagname, mediatype)
xsppath = "%sPlex %s - %s.xsp" % (path, viewid, mediatype) xsppath = "%sPlex %s - %s.xsp" % (path, viewid, mediatype)
@ -785,20 +755,20 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
xsppath = "%sPlex %s.xsp" % (path, viewid) xsppath = "%sPlex %s.xsp" % (path, viewid)
# Create the playlist directory # Create the playlist directory
if not xbmcvfs.exists(tryEncode(path)): if not exists(path):
log.info("Creating directory: %s" % path) log.info("Creating directory: %s" % path)
xbmcvfs.mkdirs(tryEncode(path)) makedirs(path)
# Only add the playlist if it doesn't already exists # Only add the playlist if it doesn't already exists
if xbmcvfs.exists(tryEncode(xsppath)): if exists(xsppath):
log.info('Path %s does exist' % xsppath) log.info('Path %s does exist' % xsppath)
if delete: if delete:
xbmcvfs.delete(tryEncode(xsppath)) remove(xsppath)
log.info("Successfully removed playlist: %s." % tagname) log.info("Successfully removed playlist: %s." % tagname)
return return
# Using write process since there's no guarantee the xml declaration works with etree # Using write process since there's no guarantee the xml declaration works
# with etree
itemtypes = { itemtypes = {
'homevideos': 'movies', 'homevideos': 'movies',
'movie': 'movies', 'movie': 'movies',
@ -806,51 +776,39 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
} }
log.info("Writing playlist file to: %s" % xsppath) log.info("Writing playlist file to: %s" % xsppath)
try: try:
f = xbmcvfs.File(tryEncode(xsppath), 'wb') with open(xsppath, 'wb'):
except: tryEncode(
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
'<smartplaylist type="%s">\n\t'
'<name>Plex %s</name>\n\t'
'<match>all</match>\n\t'
'<rule field="tag" operator="is">\n\t\t'
'<value>%s</value>\n\t'
'</rule>\n'
'</smartplaylist>\n'
% (itemtypes.get(mediatype, mediatype), plname, tagname))
except Exception as e:
log.error("Failed to create playlist: %s" % xsppath) log.error("Failed to create playlist: %s" % xsppath)
log.error(e)
return return
else:
f.write(tryEncode(
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
'<smartplaylist type="%s">\n\t'
'<name>Plex %s</name>\n\t'
'<match>all</match>\n\t'
'<rule field="tag" operator="is">\n\t\t'
'<value>%s</value>\n\t'
'</rule>\n'
'</smartplaylist>\n'
% (itemtypes.get(mediatype, mediatype), plname, tagname)))
f.close()
log.info("Successfully added playlist: %s" % tagname) log.info("Successfully added playlist: %s" % tagname)
def deletePlaylists(): def deletePlaylists():
# Clean up the playlists # Clean up the playlists
path = tryDecode(xbmc.translatePath("special://profile/playlists/video/")) path = xbmc.translatePath("special://profile/playlists/video/")
dirs, files = xbmcvfs.listdir(tryEncode(path)) for root, _, files in walk(path):
for file in files: for file in files:
if tryDecode(file).startswith('Plex'): if file.startswith('Plex'):
xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file)))) remove(join(root, file))
def deleteNodes(): def deleteNodes():
# Clean up video nodes # Clean up video nodes
import shutil path = xbmc.translatePath("special://profile/library/video/")
path = tryDecode(xbmc.translatePath("special://profile/library/video/")) for root, dirs, _ in walk(path):
dirs, files = xbmcvfs.listdir(tryEncode(path)) for directory in dirs:
for dir in dirs: if directory.startswith('Plex-'):
if tryDecode(dir).startswith('Plex'): rmtree(join(root, directory))
try: break
shutil.rmtree("%s%s" % (path, tryDecode(dir)))
except:
log.error("Failed to delete directory: %s" % tryDecode(dir))
for file in files:
if tryDecode(file).startswith('plex'):
try:
xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file))))
except:
log.error("Failed to file: %s" % tryDecode(file))
############################################################################### ###############################################################################

View file

@ -2,6 +2,8 @@
import xbmc import xbmc
from xbmcaddon import Addon from xbmcaddon import Addon
# Paths are in string, not unicode!
def tryDecode(string, encoding='utf-8'): def tryDecode(string, encoding='utf-8'):
""" """
@ -27,7 +29,7 @@ ADDON_VERSION = _ADDON.getAddonInfo('version')
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1) KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion') KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion')
KODI_PROFILE = tryDecode(xbmc.translatePath("special://profile")) KODI_PROFILE = xbmc.translatePath("special://profile")
if xbmc.getCondVisibility('system.platform.osx'): if xbmc.getCondVisibility('system.platform.osx'):
PLATFORM = "MacOSX" PLATFORM = "MacOSX"
@ -68,8 +70,8 @@ _DB_VIDEO_VERSION = {
17: 107, # Krypton 17: 107, # Krypton
18: 107 # Leia 18: 107 # Leia
} }
DB_VIDEO_PATH = tryDecode(xbmc.translatePath( DB_VIDEO_PATH = xbmc.translatePath(
"special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION])) "special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION])
_DB_MUSIC_VERSION = { _DB_MUSIC_VERSION = {
13: 46, # Gotham 13: 46, # Gotham
@ -79,8 +81,8 @@ _DB_MUSIC_VERSION = {
17: 60, # Krypton 17: 60, # Krypton
18: 60 # Leia 18: 60 # Leia
} }
DB_MUSIC_PATH = tryDecode(xbmc.translatePath( DB_MUSIC_PATH = xbmc.translatePath(
"special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION])) "special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION])
_DB_TEXTURE_VERSION = { _DB_TEXTURE_VERSION = {
13: 13, # Gotham 13: 13, # Gotham
@ -90,13 +92,13 @@ _DB_TEXTURE_VERSION = {
17: 13, # Krypton 17: 13, # Krypton
18: 13 # Leia 18: 13 # Leia
} }
DB_TEXTURE_PATH = tryDecode(xbmc.translatePath( DB_TEXTURE_PATH = xbmc.translatePath(
"special://database/Textures%s.db" % _DB_TEXTURE_VERSION[KODIVERSION])) "special://database/Textures%s.db" % _DB_TEXTURE_VERSION[KODIVERSION])
DB_PLEX_PATH = tryDecode(xbmc.translatePath("special://database/plex.db")) DB_PLEX_PATH = xbmc.translatePath("special://database/plex.db")
EXTERNAL_SUBTITLE_TEMP_PATH = tryDecode(xbmc.translatePath( EXTERNAL_SUBTITLE_TEMP_PATH = xbmc.translatePath(
"special://profile/addon_data/%s/temp/" % ADDON_ID)) "special://profile/addon_data/%s/temp/" % ADDON_ID)
# Multiply Plex time by this factor to receive Kodi time # Multiply Plex time by this factor to receive Kodi time

View file

@ -1,16 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
import logging import logging
import shutil from shutil import copytree
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
import xbmc import xbmc
import xbmcvfs from os import remove, makedirs, listdir
from os.path import exists, isfile, join
from utils import window, settings, language as lang, IfExists, tryDecode, \ from utils import window, settings, language as lang, tryEncode, indent, \
tryEncode, indent, normalize_nodes normalize_nodes
import variables as v import variables as v
############################################################################### ###############################################################################
@ -18,6 +17,7 @@ import variables as v
log = logging.getLogger("PLEX."+__name__) log = logging.getLogger("PLEX."+__name__)
############################################################################### ###############################################################################
# Paths are strings, NOT unicode!
class VideoNodes(object): class VideoNodes(object):
@ -61,36 +61,30 @@ class VideoNodes(object):
else: else:
dirname = viewid dirname = viewid
path = tryDecode(xbmc.translatePath( # Returns strings
"special://profile/library/video/")) path = xbmc.translatePath("special://profile/library/video/")
nodepath = tryDecode(xbmc.translatePath( nodepath = xbmc.translatePath(
"special://profile/library/video/Plex-%s/" % dirname)) "special://profile/library/video/Plex-%s/" % dirname)
if delete: if delete:
dirs, files = xbmcvfs.listdir(tryEncode(nodepath)) files = [f for f in listdir(nodepath) if isfile(join(nodepath, f))]
for file in files: for file in files:
xbmcvfs.delete(tryEncode( remove(nodepath + file)
(nodepath + tryDecode(file))))
log.info("Sucessfully removed videonode: %s." % tagname) log.info("Sucessfully removed videonode: %s." % tagname)
return return
# Verify the video directory # Verify the video directory
# KODI BUG if exists(path) is False:
# Kodi caches the result of exists for directories copytree(
# so try creating a file src=xbmc.translatePath("special://xbmc/system/library/video"),
if IfExists(path) is False: dst=xbmc.translatePath("special://profile/library/video"))
shutil.copytree(
src=tryDecode(xbmc.translatePath(
"special://xbmc/system/library/video")),
dst=tryDecode(xbmc.translatePath(
"special://profile/library/video")))
# Create the node directory # Create the node directory
if mediatype != "photos": if mediatype != "photos":
if IfExists(nodepath) is False: if exists(nodepath) is False:
# folder does not exist yet # folder does not exist yet
log.debug('Creating folder %s' % nodepath) log.debug('Creating folder %s' % nodepath)
xbmcvfs.mkdirs(tryEncode(nodepath)) makedirs(nodepath)
# Create index entry # Create index entry
nodeXML = "%sindex.xml" % nodepath nodeXML = "%sindex.xml" % nodepath
@ -103,23 +97,29 @@ class VideoNodes(object):
if mediatype == "photos": if mediatype == "photos":
path = "plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s&id=%s" % (viewid, viewid) path = "plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s&id=%s" % (viewid, viewid)
window('Plex.nodes.%s.index' % indexnumber, value=path) window('Plex.nodes.%s.index' % indexnumber, value=path)
# Root # Root
if not mediatype == "photos": if not mediatype == "photos":
if viewtype == "mixed": if viewtype == "mixed":
specialtag = "%s-%s" % (tagname, mediatype) specialtag = "%s-%s" % (tagname, mediatype)
root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0) root = self.commonRoot(order=0,
label=specialtag,
tagname=tagname,
roottype=0)
else: else:
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0) root = self.commonRoot(order=0,
label=tagname,
tagname=tagname,
roottype=0)
try: try:
indent(root) indent(root)
except: pass except:
pass
etree.ElementTree(root).write(nodeXML) etree.ElementTree(root).write(nodeXML)
nodetypes = { nodetypes = {
'1': "all", '1': "all",
'2': "recent", '2': "recent",
'3': "recentepisodes", '3': "recentepisodes",
@ -255,7 +255,7 @@ class VideoNodes(object):
path = 'plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s/folder' % viewid path = 'plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s/folder' % viewid
else: else:
path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype) path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype)
if mediatype == "photos": if mediatype == "photos":
windowpath = "ActivateWindow(Pictures,%s,return)" % path windowpath = "ActivateWindow(Pictures,%s,return)" % path
else: else:
@ -264,7 +264,7 @@ class VideoNodes(object):
windowpath = "ActivateWindow(Videos,%s,return)" % path windowpath = "ActivateWindow(Videos,%s,return)" % path
else: else:
windowpath = "ActivateWindow(Video,%s,return)" % path windowpath = "ActivateWindow(Video,%s,return)" % path
if nodetype == "all": if nodetype == "all":
if viewtype == "mixed": if viewtype == "mixed":
@ -288,8 +288,8 @@ class VideoNodes(object):
# to be created. # to be created.
# To do: add our photos nodes to kodi picture sources somehow # To do: add our photos nodes to kodi picture sources somehow
continue continue
if xbmcvfs.exists(tryEncode(nodeXML)): if exists(nodeXML):
# Don't recreate xml if already exists # Don't recreate xml if already exists
continue continue
@ -370,15 +370,14 @@ class VideoNodes(object):
try: try:
indent(root) indent(root)
except: pass except:
pass
etree.ElementTree(root).write(nodeXML) etree.ElementTree(root).write(nodeXML)
def singleNode(self, indexnumber, tagname, mediatype, itemtype): def singleNode(self, indexnumber, tagname, mediatype, itemtype):
tagname = tryEncode(tagname) tagname = tryEncode(tagname)
cleantagname = normalize_nodes(tagname) cleantagname = normalize_nodes(tagname)
nodepath = tryDecode(xbmc.translatePath( nodepath = xbmc.translatePath("special://profile/library/video/")
"special://profile/library/video/"))
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname) nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
path = "library://video/plex_%s.xml" % cleantagname path = "library://video/plex_%s.xml" % cleantagname
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
@ -388,17 +387,13 @@ class VideoNodes(object):
windowpath = "ActivateWindow(Video,%s,return)" % path windowpath = "ActivateWindow(Video,%s,return)" % path
# Create the video node directory # Create the video node directory
if not xbmcvfs.exists(nodepath): if not exists(nodepath):
# We need to copy over the default items # We need to copy over the default items
shutil.copytree( copytree(
src=tryDecode(xbmc.translatePath( src=xbmc.translatePath("special://xbmc/system/library/video"),
"special://xbmc/system/library/video")), dst=xbmc.translatePath("special://profile/library/video"))
dst=tryDecode(xbmc.translatePath(
"special://profile/library/video")))
xbmcvfs.exists(path)
labels = { labels = {
'Favorite movies': 30180, 'Favorite movies': 30180,
'Favorite tvshows': 30181, 'Favorite tvshows': 30181,
'channels': 30173 'channels': 30173
@ -410,12 +405,15 @@ class VideoNodes(object):
window('%s.content' % embynode, value=path) window('%s.content' % embynode, value=path)
window('%s.type' % embynode, value=itemtype) window('%s.type' % embynode, value=itemtype)
if xbmcvfs.exists(nodeXML): if exists(nodeXML):
# Don't recreate xml if already exists # Don't recreate xml if already exists
return return
if itemtype == "channels": if itemtype == "channels":
root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2) root = self.commonRoot(order=1,
label=label,
tagname=tagname,
roottype=2)
etree.SubElement(root, 'path').text = "plugin://plugin.video.plexkodiconnect/?id=0&mode=channels" etree.SubElement(root, 'path').text = "plugin://plugin.video.plexkodiconnect/?id=0&mode=channels"
else: else:
root = self.commonRoot(order=1, label=label, tagname=tagname) root = self.commonRoot(order=1, label=label, tagname=tagname)
@ -425,7 +423,8 @@ class VideoNodes(object):
try: try:
indent(root) indent(root)
except: pass except:
pass
etree.ElementTree(root).write(nodeXML) etree.ElementTree(root).write(nodeXML)
def clearProperties(self): def clearProperties(self):
@ -433,7 +432,6 @@ class VideoNodes(object):
log.info("Clearing nodes properties.") log.info("Clearing nodes properties.")
plexprops = window('Plex.nodes.total') plexprops = window('Plex.nodes.total')
propnames = [ propnames = [
"index","path","title","content", "index","path","title","content",
"inprogress.content","inprogress.title", "inprogress.content","inprogress.title",
"inprogress.content","inprogress.path", "inprogress.content","inprogress.path",

View file

@ -164,12 +164,11 @@ class Service():
counter = 0 counter = 0
while not monitor.abortRequested(): while not monitor.abortRequested():
if tryDecode(window('plex_kodiProfile')) != kodiProfile: if window('plex_kodiProfile') != kodiProfile:
# Profile change happened, terminate this thread and others # Profile change happened, terminate this thread and others
log.warn("Kodi profile was: %s and changed to: %s. " log.warn("Kodi profile was: %s and changed to: %s. "
"Terminating old PlexKodiConnect thread." "Terminating old PlexKodiConnect thread."
% (kodiProfile, % (kodiProfile, window('plex_kodiProfile')))
tryDecode(window('plex_kodiProfile'))))
break break
# Before proceeding, need to make sure: # Before proceeding, need to make sure: