Fix encoding of file and path operations

This commit is contained in:
Croneter 2018-06-23 18:25:18 +02:00
parent 074c439e99
commit 1234f61fc0
19 changed files with 405 additions and 290 deletions

View file

@ -3,15 +3,13 @@
###############################################################################
from logging import getLogger
from Queue import Queue, Empty
from shutil import rmtree
from urllib import quote_plus, unquote
from threading import Thread
from os import makedirs
import requests
import xbmc
from xbmcvfs import exists
from . import path_ops
from . import utils
from . import state
@ -202,10 +200,9 @@ class Artwork():
if utils.dialog('yesno', "Image Texture Cache", utils.lang(39251)):
LOG.info("Resetting all cache data first")
# Remove all existing textures first
path = utils.try_decode(
xbmc.translatePath("special://thumbnails/"))
if utils.exists_dir(path):
rmtree(path, ignore_errors=True)
path = path_ops.translate_path('special://thumbnails/')
if path_ops.exists(path):
path_ops.rmtree(path, ignore_errors=True)
self.restore_cache_directories()
# remove all existing data from texture DB
@ -321,10 +318,11 @@ class Artwork():
pass
else:
# Delete thumbnail as well as the entry
path = xbmc.translatePath("special://thumbnails/%s" % cachedurl)
path = path_ops.translate_path("special://thumbnails/%s"
% cachedurl)
LOG.debug("Deleting cached thumbnail: %s", path)
if exists(path):
rmtree(utils.try_decode(path), ignore_errors=True)
if path_ops.exists(path):
path_ops.rmtree(path, ignore_errors=True)
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
connection.commit()
finally:
@ -337,8 +335,8 @@ class Artwork():
"a", "b", "c", "d", "e", "f",
"Video", "plex")
for path in paths:
makedirs(utils.try_decode(
xbmc.translatePath("special://thumbnails/%s" % path)))
new_path = path_ops.translate_path("special://thumbnails/%s" % path)
path_ops.makedirs(utils.encode_path(new_path))
class ArtworkSyncMessage(object):

View file

@ -1,16 +1,15 @@
# -*- coding: utf-8 -*-
###############################################################################
from logging import getLogger
from os.path import join
import xbmcgui
from xbmcaddon import Addon
from . import utils
from . import path_ops
from . import variables as v
###############################################################################
LOG = getLogger('PLEX.context')
ADDON = Addon('plugin.video.plexkodiconnect')
ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10
@ -65,10 +64,11 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
self.close()
def _add_editcontrol(self, x, y, height, width, password=None):
media = join(ADDON.getAddonInfo('path'),
'resources', 'skins', 'default', 'media')
media = path_ops.path.join(
v.ADDON_PATH, 'resources', 'skins', 'default', 'media')
filename = utils.try_encode(path_ops.path.join(media, 'white.png'))
control = xbmcgui.ControlImage(0, 0, 0, 0,
filename=join(media, "white.png"),
filename=filename,
aspectRatio=0,
colorDiffuse="ff111111")
control.setPosition(x, y)

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
###############################################################################
from logging import getLogger
from xbmcaddon import Addon
import xbmc
import xbmcgui
@ -103,7 +102,7 @@ class ContextMenu(object):
options.append(OPTIONS['Addon'])
context_menu = context.ContextMenu(
"script-plex-context.xml",
Addon('plugin.video.plexkodiconnect').getAddonInfo('path'),
utils.try_encode(v.ADDON_PATH),
"default",
"1080i")
context_menu.set_options(options)

View file

@ -5,16 +5,14 @@
#
###############################################################################
from logging import getLogger
from shutil import copyfile
from os import walk, makedirs
from os.path import basename, join
from sys import argv
from urllib import urlencode
import xbmcplugin
from xbmc import sleep, executebuiltin, translatePath
from xbmc import sleep, executebuiltin
from xbmcgui import ListItem
from . import utils
from . import path_ops
from .downloadutils import DownloadUtils as DU
from .plex_api import API
from . import plex_functions as PF
@ -441,7 +439,7 @@ def get_video_files(plex_id, params):
item = PF.GetPlexMetadata(plex_id)
try:
path = item[0][0][0].attrib['file']
path = utils.try_decode(item[0][0][0].attrib['file'])
except (TypeError, IndexError, AttributeError, KeyError):
LOG.error('Could not get file path for item %s', plex_id)
return xbmcplugin.endOfDirectory(HANDLE)
@ -453,18 +451,19 @@ def get_video_files(plex_id, params):
elif '\\' in path:
path = path.replace('\\', '\\\\')
# Directory only, get rid of filename
path = path.replace(basename(path), '')
if utils.exists_dir(path):
for root, dirs, files in walk(path):
path = path.replace(path_ops.path.basename(path), '')
if path_ops.exists(path):
for root, dirs, files in path_ops.walk(path):
for directory in dirs:
item_path = utils.try_encode(join(root, directory))
item_path = utils.try_encode(path_ops.path.join(root,
directory))
listitem = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=item_path,
listitem=listitem,
isFolder=True)
for file in files:
item_path = utils.try_encode(join(root, file))
item_path = utils.try_encode(path_ops.path.join(root, file))
listitem = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=file,
@ -492,11 +491,11 @@ def extra_fanart(plex_id, plex_path):
# We need to store the images locally for this to work
# because of the caching system in xbmc
fanart_dir = utils.try_decode(translatePath(
"special://thumbnails/plex/%s/" % plex_id))
if not utils.exists_dir(fanart_dir):
fanart_dir = path_ops.translate_path("special://thumbnails/plex/%s/"
% plex_id)
if not path_ops.exists(fanart_dir):
# Download the images to the cache directory
makedirs(fanart_dir)
path_ops.makedirs(fanart_dir)
xml = PF.GetPlexMetadata(plex_id)
if xml is None:
LOG.error('Could not download metadata for %s', plex_id)
@ -506,20 +505,23 @@ def extra_fanart(plex_id, plex_path):
backdrops = api.artwork()['Backdrop']
for count, backdrop in enumerate(backdrops):
# Same ordering as in artwork
art_file = utils.try_encode(join(fanart_dir,
"fanart%.3d.jpg" % count))
art_file = utils.try_encode(path_ops.path.join(
fanart_dir, "fanart%.3d.jpg" % count))
listitem = ListItem("%.3d" % count, path=art_file)
xbmcplugin.addDirectoryItem(
handle=HANDLE,
url=art_file,
listitem=listitem)
copyfile(backdrop, utils.try_decode(art_file))
path_ops.copyfile(backdrop, utils.try_decode(art_file))
else:
LOG.info("Found cached backdrop.")
# Use existing cached images
for root, _, files in walk(fanart_dir):
fanart_dir = utils.try_decode(fanart_dir)
for root, _, files in path_ops.walk(fanart_dir):
root = utils.decode_path(root)
for file in files:
art_file = utils.try_encode(join(root, file))
file = utils.decode_path(file)
art_file = utils.try_encode(path_ops.path.join(root, file))
listitem = ListItem(file, path=art_file)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=art_file,

View file

@ -7,6 +7,7 @@ import xml.etree.ElementTree as etree
from xbmc import executebuiltin, translatePath
from . import utils
from . import path_ops
from . import migration
from .downloadutils import DownloadUtils as DU
from . import videonodes
@ -25,6 +26,9 @@ LOG = getLogger('PLEX.initialsetup')
###############################################################################
if not path_ops.exists(v.EXTERNAL_SUBTITLE_TEMP_PATH):
path_ops.makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
WINDOW_PROPERTIES = (
"plex_online", "plex_serverStatus", "plex_shouldStop", "plex_dbScan",

View file

@ -1025,23 +1025,11 @@ class LibrarySync(Thread):
do with "process_" methods
"""
if message['type'] == 'playing':
try:
self.process_playing(message['PlaySessionStateNotification'])
except KeyError:
LOG.error('Received invalid PMS message for playstate: %s',
message)
self.process_playing(message['PlaySessionStateNotification'])
elif message['type'] == 'timeline':
try:
self.process_timeline(message['TimelineEntry'])
except (KeyError, ValueError):
LOG.error('Received invalid PMS message for timeline: %s',
message)
self.process_timeline(message['TimelineEntry'])
elif message['type'] == 'activity':
try:
self.process_activity(message['ActivityNotification'])
except KeyError:
LOG.error('Received invalid PMS message for activity: %s',
message)
self.process_activity(message['ActivityNotification'])
def multi_delete(self, liste, delete_list):
"""
@ -1196,7 +1184,7 @@ class LibrarySync(Thread):
continue
playlists.process_websocket(plex_id=str(item['itemID']),
updated_at=str(item['updatedAt']),
state=status)
status=status)
elif status == 9:
# Immediately and always process deletions (as the PMS will
# send additional message with other codes)

View file

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from re import compile as re_compile
from xml.etree.ElementTree import ParseError
from . import utils
@ -9,8 +8,6 @@ from . import variables as v
###############################################################################
LOG = getLogger('PLEX.music')
REGEX_MUSICPATH = re_compile(r'''^\^(.+)\$$''')
###############################################################################

192
resources/lib/path_ops.py Normal file
View file

@ -0,0 +1,192 @@
# -*- coding: utf-8 -*-
# File and Path operations
#
# Kodi xbmc*.*() functions usually take utf-8 encoded commands, thus try_encode
# works.
# Unfortunatly, working with filenames and paths seems to require an encoding in
# the OS' getfilesystemencoding - it will NOT always work with unicode paths.
# However, sys.getfilesystemencoding might return None.
# Feed unicode to all the functions below and you're fine.
import shutil
import os
from os import path # allows to use path_ops.path.join, for example
from distutils import dir_util
import xbmc
import xbmcvfs
from .watchdog.utils import unicode_paths
# Kodi seems to encode in utf-8 in ALL cases (unlike e.g. the OS filesystem)
KODI_ENCODING = 'utf-8'
def encode_path(path):
"""
Filenames and paths are not necessarily utf-8 encoded. Use this function
instead of try_encode/trydecode if working with filenames and paths!
(os.walk only feeds on encoded paths. sys.getfilesystemencoding returns None
for Raspberry Pi)
"""
return unicode_paths.encode(path)
def decode_path(path):
"""
Filenames and paths are not necessarily utf-8 encoded. Use this function
instead of try_encode/trydecode if working with filenames and paths!
(os.walk only feeds on encoded paths. sys.getfilesystemencoding returns None
for Raspberry Pi)
"""
return unicode_paths.decode(path)
def translate_path(path):
"""
Returns the XBMC translated path [unicode]
e.g. Converts 'special://masterprofile/script_data'
-> '/home/user/XBMC/UserData/script_data' on Linux.
"""
translated = xbmc.translatePath(path.encode(KODI_ENCODING, 'strict'))
return translated.decode(KODI_ENCODING, 'strict')
def exists(path):
"""Returns True if the path [unicode] exists"""
return xbmcvfs.exists(path.encode(KODI_ENCODING, 'strict'))
def rmtree(path, *args, **kwargs):
"""Recursively delete a directory tree.
If ignore_errors is set, errors are ignored; otherwise, if onerror
is set, it is called to handle the error with arguments (func,
path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
path is the argument to that function that caused it to fail; and
exc_info is a tuple returned by sys.exc_info(). If ignore_errors
is false and onerror is None, an exception is raised.
"""
return shutil.rmtree(encode_path(path), *args, **kwargs)
def copyfile(src, dst):
"""Copy data from src to dst"""
return shutil.copyfile(encode_path(src), encode_path(dst))
def makedirs(path, *args, **kwargs):
"""makedirs(path [, mode=0777])
Super-mkdir; create a leaf directory and all intermediate ones. Works like
mkdir, except that any intermediate path segment (not just the rightmost)
will be created if it does not exist. This is recursive.
"""
return os.makedirs(encode_path(path), *args, **kwargs)
def remove(path):
"""
Remove (delete) the file path. If path is a directory, OSError is raised;
see rmdir() below to remove a directory. This is identical to the unlink()
function documented below. On Windows, attempting to remove a file that is
in use causes an exception to be raised; on Unix, the directory entry is
removed but the storage allocated to the file is not made available until
the original file is no longer in use.
"""
return os.remove(encode_path(path))
def walk(top, topdown=True, onerror=None, followlinks=False):
"""
Directory tree generator.
For each directory in the directory tree rooted at top (including top
itself, but excluding '.' and '..'), yields a 3-tuple
dirpath, dirnames, filenames
dirpath is a string, the path to the directory. dirnames is a list of
the names of the subdirectories in dirpath (excluding '.' and '..').
filenames is a list of the names of the non-directory files in dirpath.
Note that the names in the lists are just names, with no path components.
To get a full path (which begins with top) to a file or directory in
dirpath, do os.path.join(dirpath, name).
If optional arg 'topdown' is true or not specified, the triple for a
directory is generated before the triples for any of its subdirectories
(directories are generated top down). If topdown is false, the triple
for a directory is generated after the triples for all of its
subdirectories (directories are generated bottom up).
When topdown is true, the caller can modify the dirnames list in-place
(e.g., via del or slice assignment), and walk will only recurse into the
subdirectories whose names remain in dirnames; this can be used to prune the
search, or to impose a specific order of visiting. Modifying dirnames when
topdown is false is ineffective, since the directories in dirnames have
already been generated by the time dirnames itself is generated. No matter
the value of topdown, the list of subdirectories is retrieved before the
tuples for the directory and its subdirectories are generated.
By default errors from the os.listdir() call are ignored. If
optional arg 'onerror' is specified, it should be a function; it
will be called with one argument, an os.error instance. It can
report the error to continue with the walk, or raise the exception
to abort the walk. Note that the filename is available as the
filename attribute of the exception object.
By default, os.walk does not follow symbolic links to subdirectories on
systems that support them. In order to get this functionality, set the
optional argument 'followlinks' to true.
Caution: if you pass a relative pathname for top, don't change the
current working directory between resumptions of walk. walk never
changes the current directory, and assumes that the client doesn't
either.
Example:
import os
from os.path import join, getsize
for root, dirs, files in os.walk('python/Lib/email'):
print root, "consumes",
print sum([getsize(join(root, name)) for name in files]),
print "bytes in", len(files), "non-directory files"
if 'CVS' in dirs:
dirs.remove('CVS') # don't visit CVS directories
"""
# Get all the results from os.walk and store them in a list
walker = list(os.walk(encode_path(top),
topdown,
onerror,
followlinks))
for top, dirs, nondirs in walker:
yield (decode_path(top),
[decode_path(x) for x in dirs],
[decode_path(x) for x in nondirs])
def copy_tree(src, dst, *args, **kwargs):
"""
Copy an entire directory tree 'src' to a new location 'dst'.
Both 'src' and 'dst' must be directory names. If 'src' is not a
directory, raise DistutilsFileError. If 'dst' does not exist, it is
created with 'mkpath()'. The end result of the copy is that every
file in 'src' is copied to 'dst', and directories under 'src' are
recursively copied to 'dst'. Return the list of files that were
copied or might have been copied, using their output name. The
return value is unaffected by 'update' or 'dry_run': it is simply
the list of all files under 'src', with the names changed to be
under 'dst'.
'preserve_mode' and 'preserve_times' are the same as for
'copy_file'; note that they only apply to regular files, not to
directories. If 'preserve_symlinks' is true, symlinks will be
copied as symlinks (on platforms that support them!); otherwise
(the default), the destination of the symlink will be copied.
'update' and 'verbose' are the same as for 'copy_file'.
"""
src = encode_path(src)
dst = encode_path(dst)
return dir_util.copy_tree(src, dst, *args, **kwargs)

View file

@ -3,7 +3,6 @@ Used to kick off Kodi playback
"""
from logging import getLogger
from threading import Thread
from os.path import join
from xbmc import Player, sleep
from .plex_api import API
@ -25,8 +24,6 @@ from . import state
LOG = getLogger('PLEX.playback')
# Do we need to return ultimately with a setResolvedUrl?
RESOLVE = True
# We're "failing" playback with a video of 0 length
NULL_VIDEO = join(v.ADDON_FOLDER, 'addons', v.ADDON_ID, 'empty_video.mp4')
###############################################################################
@ -273,7 +270,7 @@ def _ensure_resolve(abort=False):
state.PKC_CAUSED_STOP_DONE = False
if not abort:
result = pickler.Playback_Successful()
result.listitem = PKCListItem(path=NULL_VIDEO)
result.listitem = PKCListItem(path=v.NULL_VIDEO)
pickler.pickle_me(result)
else:
# Shows PKC error message

View file

@ -3,10 +3,8 @@
Collection of functions associated with Kodi and Plex playlists and playqueues
"""
from logging import getLogger
import os
import urllib
from urlparse import parse_qsl, urlsplit
from re import compile as re_compile
from .plex_api import API
from . import plex_functions as PF
@ -14,6 +12,7 @@ from . import plexdb_functions as plexdb
from . import kodidb_functions as kodidb
from .downloadutils import DownloadUtils as DU
from . import utils
from . import path_ops
from . import json_rpc as js
from . import variables as v
@ -21,7 +20,6 @@ from . import variables as v
LOG = getLogger('PLEX.playlist_func')
REGEX = re_compile(r'''metadata%2F(\d+)''')
###############################################################################
@ -42,9 +40,9 @@ class PlaylistObjectBaseclase(object):
def __repr__(self):
"""
Print the playlist, e.g. to log. Returns utf-8 encoded string
Print the playlist, e.g. to log. Returns unicode
"""
answ = u'{\'%s\': {\'id\': %s, ' % (self.__class__.__name__, self.id)
answ = '{\'%s\': {\'id\': %s, ' % (self.__class__.__name__, self.id)
# For some reason, can't use dir directly
for key in self.__dict__:
if key in ('id', 'kodi_pl'):
@ -58,7 +56,7 @@ class PlaylistObjectBaseclase(object):
else:
# e.g. int
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
return utils.try_encode(answ + '}}')
return answ + '}}'
class Playlist_Object(PlaylistObjectBaseclase):
@ -82,7 +80,9 @@ class Playlist_Object(PlaylistObjectBaseclase):
@kodi_path.setter
def kodi_path(self, path):
file = os.path.basename(path)
if not isinstance(path, unicode):
raise RuntimeError('Path is %s, not unicode!' % type(path))
file = path_ops.path.basename(path)
try:
self.kodi_filename, self.kodi_extension = file.split('.', 1)
except ValueError:
@ -220,9 +220,9 @@ class Playlist_Item(object):
def __repr__(self):
"""
Print the playlist item, e.g. to log. Returns utf-8 encoded string
Print the playlist item, e.g. to log. Returns unicode
"""
answ = (u'{\'%s\': {\'id\': \'%s\', \'plex_id\': \'%s\', '
answ = ('{\'%s\': {\'id\': \'%s\', \'plex_id\': \'%s\', '
% (self.__class__.__name__, self.id, self.plex_id))
for key in self.__dict__:
if key in ('id', 'plex_id', 'xml'):
@ -240,7 +240,7 @@ class Playlist_Item(object):
answ += '\'xml\': None}}'
else:
answ += '\'xml\': \'%s\'}}' % self.xml.tag
return utils.try_encode(answ)
return answ
def plex_stream_index(self, kodi_stream_index, stream_type):
"""
@ -865,7 +865,8 @@ def get_plextype_from_xml(xml):
returns None if unsuccessful
"""
try:
plex_id = REGEX.findall(xml.attrib['playQueueSourceURI'])[0]
plex_id = utils.REGEX_PLEX_ID_FROM_URL.findall(
xml.attrib['playQueueSourceURI'])[0]
except IndexError:
LOG.error('Could not get plex_id from xml: %s', xml.attrib)
return

View file

@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
from logging import getLogger
import os
import sys
from xbmcvfs import exists
from .watchdog.events import FileSystemEventHandler
from .watchdog.observers import Observer
@ -11,6 +8,7 @@ from .plex_api import API
from . import kodidb_functions as kodidb
from . import plexdb_functions as plexdb
from . import utils
from . import path_ops
from . import variables as v
from . import state
@ -32,12 +30,6 @@ EVENT_TYPE_DELETED = 'deleted'
EVENT_TYPE_CREATED = 'created'
EVENT_TYPE_MODIFIED = 'modified'
# m3u files do not have encoding specified
if v.PLATFORM == 'Windows':
ENCODING = 'mbcs'
else:
ENCODING = sys.getdefaultencoding()
def create_plex_playlist(playlist):
"""
@ -99,20 +91,21 @@ def create_kodi_playlist(plex_id=None, updated_at=None):
playlist.plex_updatedat = updated_at
LOG.debug('Creating new Kodi playlist from Plex playlist: %s', playlist)
name = utils.valid_filename(playlist.plex_name)
path = os.path.join(v.PLAYLIST_PATH, playlist.type, '%s.m3u' % name)
while exists(path) or playlist_object_from_db(path=path):
path = path_ops.path.join(v.PLAYLIST_PATH, playlist.type, '%s.m3u' % name)
while path_ops.exists(path) or playlist_object_from_db(path=path):
# In case the Plex playlist names are not unique
occurance = utils.REGEX_FILE_NUMBERING.search(path)
if not occurance:
path = os.path.join(v.PLAYLIST_PATH,
playlist.type,
'%s_01.m3u' % name[:min(len(name), 248)])
path = path_ops.path.join(v.PLAYLIST_PATH,
playlist.type,
'%s_01.m3u' % name[:min(len(name), 248)])
else:
occurance = int(occurance.group(1)) + 1
path = os.path.join(v.PLAYLIST_PATH,
playlist.type,
'%s_%02d.m3u' % (name[:min(len(name), 248)],
occurance))
path = path_ops.path.join(v.PLAYLIST_PATH,
playlist.type,
'%s_%02d.m3u' % (name[:min(len(name),
248)],
occurance))
LOG.debug('Kodi playlist path: %s', path)
playlist.kodi_path = path
# Derive filename close to Plex playlist name
@ -130,7 +123,7 @@ def delete_kodi_playlist(playlist):
Returns None or raises PL.PlaylistError
"""
try:
os.remove(playlist.kodi_path)
path_ops.remove(playlist.kodi_path)
except (OSError, IOError) as err:
LOG.error('Could not delete Kodi playlist file %s. Error:\n %s: %s',
playlist, err.errno, err.strerror)
@ -180,10 +173,10 @@ def m3u_to_plex_ids(playlist):
Adapter to process *.m3u playlist files. Encoding is not uniform!
"""
plex_ids = list()
with open(utils.encode_path(playlist.kodi_path), 'rb') as f:
with open(path_ops.encode_path(playlist.kodi_path), 'rb') as f:
text = f.read()
try:
text = text.decode(ENCODING)
text = text.decode(v.M3U_ENCODING)
except UnicodeDecodeError:
LOG.warning('Fallback to ISO-8859-1 decoding for %s', playlist)
text = text.decode('ISO-8859-1')
@ -210,15 +203,15 @@ def _write_playlist_to_file(playlist, xml):
Feed with playlist [Playlist_Object]. Will write the playlist to a m3u file
Returns None or raises PL.PlaylistError
"""
text = u'#EXTCPlayListM3U::M3U\n'
text = '#EXTCPlayListM3U::M3U\n'
for element in xml:
api = API(element)
text += (u'#EXTINF:%s,%s\n%s\n'
text += ('#EXTINF:%s,%s\n%s\n'
% (api.runtime(), api.title(), api.path()))
text += '\n'
text = text.encode(ENCODING, 'ignore')
text = text.encode(v.M3U_ENCODING, 'strict')
try:
with open(utils.encode_path(playlist.kodi_path), 'wb') as f:
with open(path_ops.encode_path(playlist.kodi_path), 'wb') as f:
f.write(text)
except (OSError, IOError) as err:
LOG.error('Could not write Kodi playlist file: %s', playlist)
@ -267,15 +260,15 @@ def _kodi_playlist_identical(xml_element):
pass
def process_websocket(plex_id, updated_at, state):
def process_websocket(plex_id, updated_at, status):
"""
Hit by librarysync to process websocket messages concerning playlists
"""
create = False
playlist = playlist_object_from_db(plex_id=plex_id)
with state.LOCK_PLAYLISTS:
playlist = playlist_object_from_db(plex_id=plex_id)
try:
if playlist and state == 9:
if playlist and status == 9:
LOG.debug('Plex deletion of playlist detected: %s', playlist)
delete_kodi_playlist(playlist)
elif playlist and playlist.plex_updatedat == updated_at:
@ -285,7 +278,7 @@ def process_websocket(plex_id, updated_at, state):
LOG.debug('Change of Plex playlist detected: %s', playlist)
delete_kodi_playlist(playlist)
create = True
elif not playlist and not state == 9:
elif not playlist and not status == 9:
LOG.debug('Creation of new Plex playlist detected: %s',
plex_id)
create = True
@ -333,7 +326,7 @@ def _full_sync():
elif playlist.plex_updatedat != api.updated_at():
LOG.debug('Detected changed Plex playlist %s: %s',
api.plex_id(), api.title())
if exists(playlist.kodi_path):
if path_ops.exists(playlist.kodi_path):
delete_kodi_playlist(playlist)
else:
update_plex_table(playlist, delete=True)
@ -361,17 +354,19 @@ def _full_sync():
if state.ENABLE_MUSIC:
master_paths.append(v.PLAYLIST_PATH_MUSIC)
for master_path in master_paths:
for root, _, files in os.walk(utils.encode_path(master_path)):
root = utils.decode_path(root)
for root, _, files in path_ops.walk(master_path):
for file in files:
file = utils.decode_path(file)
try:
extension = file.rsplit('.', 1)[1]
except IndexError:
continue
if extension not in SUPPORTED_FILETYPES:
continue
path = os.path.join(root, file)
LOG.debug('root: %s', root)
LOG.debug('type: %s', type(root))
LOG.debug('file: %s', file)
LOG.debug('type: %s', type(file))
path = path_ops.path.join(root, file)
kodi_hash = utils.generate_file_md5(path)
playlist = playlist_object_from_db(kodi_hash=kodi_hash)
playlist_2 = playlist_object_from_db(path=path)

View file

@ -3,7 +3,6 @@ Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
"""
from logging import getLogger
from threading import Thread
from re import compile as re_compile
import xbmc
from . import utils
@ -18,7 +17,6 @@ from . import state
LOG = getLogger('PLEX.playqueue')
PLUGIN = 'plugin://%s' % v.ADDON_ID
REGEX = re_compile(r'''plex_id=(\d+)''')
# Our PKC playqueues (3 instances of Playqueue_Object())
PLAYQUEUES = []
@ -133,7 +131,7 @@ class PlayqueueMonitor(Thread):
old_item.kodi_type == new_item['type'])
else:
try:
plex_id = REGEX.findall(new_item['file'])[0]
plex_id = utils.REGEX_PLEX_ID.findall(new_item['file'])[0]
except IndexError:
LOG.debug('Comparing paths directly as a fallback')
identical = old_item.file == new_item['file']

View file

@ -30,15 +30,15 @@ http://stackoverflow.com/questions/111945/is-there-any-way-to-do-http-put-in-pyt
(and others...)
"""
from logging import getLogger
from re import compile as re_compile, sub
from re import sub
from urllib import urlencode, unquote
import os
from xbmcgui import ListItem
from xbmcvfs import exists
from .downloadutils import DownloadUtils as DU
from . import clientinfo
from . import utils
from . import path_ops
from . import plex_functions as PF
from . import plexdb_functions as plexdb
from . import kodidb_functions as kodidb
@ -48,12 +48,6 @@ from . import state
###############################################################################
LOG = getLogger('PLEX.plex_api')
REGEX_IMDB = re_compile(r'''/(tt\d+)''')
REGEX_TVDB = re_compile(r'''thetvdb:\/\/(.+?)\?''')
if not utils.exists_dir(v.EXTERNAL_SUBTITLE_TEMP_PATH):
os.makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
###############################################################################
@ -438,10 +432,10 @@ class API(object):
return None
if providername == 'imdb':
regex = REGEX_IMDB
regex = utils.REGEX_IMDB
elif providername == 'tvdb':
# originally e.g. com.plexapp.agents.thetvdb://276564?lang=en
regex = REGEX_TVDB
regex = utils.REGEX_TVDB
else:
return None
@ -1246,7 +1240,7 @@ class API(object):
# Get additional info (filename / languages)
filename = None
if 'file' in entry[0].attrib:
filename = os.path.basename(entry[0].attrib['file'])
filename = path_ops.path.basename(entry[0].attrib['file'])
# Languages of audio streams
languages = []
for stream in entry[0]:
@ -1401,7 +1395,7 @@ class API(object):
Returns the path to the downloaded subtitle or None
"""
path = os.path.join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
path = path_ops.path.join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
response = DU().downloadUrl(url, return_response=True)
try:
response.status_code
@ -1410,7 +1404,7 @@ class API(object):
return
else:
LOG.debug('Writing temp subtitle to %s', path)
with open(utils.encode_path(path), 'wb') as filer:
with open(path_ops.encode_path(path), 'wb') as filer:
filer.write(response.content)
return path
@ -1603,17 +1597,18 @@ class API(object):
check = exists(utils.try_encode(path))
else:
# directories
if "\\" in path:
if not path.endswith('\\'):
checkpath = utils.try_encode(path)
if b"\\" in checkpath:
if not checkpath.endswith('\\'):
# Add the missing backslash
check = utils.exists_dir(path + "\\")
check = utils.exists_dir(checkpath + "\\")
else:
check = utils.exists_dir(path)
check = utils.exists_dir(checkpath)
else:
if not path.endswith('/'):
check = utils.exists_dir(path + "/")
if not checkpath.endswith('/'):
check = utils.exists_dir(checkpath + "/")
else:
check = utils.exists_dir(path)
check = utils.exists_dir(checkpath)
if not check:
if force_check is False:

View file

@ -3,7 +3,6 @@ from logging import getLogger
from urllib import urlencode, quote_plus
from ast import literal_eval
from urlparse import urlparse, parse_qsl
from re import compile as re_compile
from copy import deepcopy
from time import time
from threading import Thread
@ -18,8 +17,6 @@ from . import variables as v
LOG = getLogger('PLEX.plex_functions')
CONTAINERSIZE = int(utils.settings('limitindex'))
REGEX_PLEX_KEY = re_compile(r'''/(.+)/(\d+)$''')
REGEX_PLEX_DIRECT = re_compile(r'''\.plex\.direct:\d+$''')
# For discovery of PMS in the local LAN
PLEX_GDM_IP = '239.0.0.250' # multicast to PMS
@ -47,7 +44,7 @@ def GetPlexKeyNumber(plexKey):
Returns ('','') if nothing is found
"""
try:
result = REGEX_PLEX_KEY.findall(plexKey)[0]
result = utils.REGEX_END_DIGITS.findall(plexKey)[0]
except IndexError:
result = ('', '')
return result
@ -411,7 +408,7 @@ def _pms_list_from_plex_tv(token):
def _poke_pms(pms, queue):
data = pms['connections'][0].attrib
url = data['uri']
if data['local'] == '1' and REGEX_PLEX_DIRECT.findall(url):
if data['local'] == '1' and utils.REGEX_PLEX_DIRECT.findall(url):
# In case DNS resolve of plex.direct does not work, append a new
# connection that will directly access the local IP (e.g. internet down)
conn = deepcopy(pms['connections'][0])

View file

@ -55,6 +55,7 @@ class Service():
utils.settings('useDirectPaths') == '1')
LOG.info("Number of sync threads: %s",
utils.settings('syncThreadNumber'))
LOG.info('Playlist m3u encoding: %s', v.M3U_ENCODING)
LOG.info("Full sys.argv received: %s", sys.argv)
self.monitor = xbmc.Monitor()
# Load/Reset PKC entirely - important for user/Kodi profile switch

View file

@ -3,14 +3,14 @@
from logging import getLogger
from threading import Thread
from xbmc import sleep, executebuiltin, translatePath
import xbmcaddon
from xbmcvfs import exists
from xbmc import sleep, executebuiltin
from .downloadutils import DownloadUtils as DU
from . import utils
from . import path_ops
from . import plex_tv
from . import plex_functions as PF
from . import variables as v
from . import state
###############################################################################
@ -44,7 +44,6 @@ class UserClient(Thread):
self.ssl = None
self.sslcert = None
self.addon = xbmcaddon.Addon()
self.do_utils = None
Thread.__init__(self)
@ -197,11 +196,8 @@ class UserClient(Thread):
'Addon.Openutils.settings(plugin.video.plexkodiconnect)')
return False
# Get /profile/addon_data
addondir = translatePath(self.addon.getAddonInfo('profile'))
# If there's no settings.xml
if not exists("%ssettings.xml" % addondir):
if not path_ops.exists("%ssettings.xml" % v.ADDON_PROFILE):
LOG.error("Error, no settings.xml found.")
self.auth = False
return False

View file

@ -4,11 +4,6 @@ Various functions and decorators for PKC
"""
###############################################################################
from logging import getLogger
import xbmc
import xbmcaddon
import xbmcgui
from xbmcvfs import exists, delete
import os
from cProfile import Profile
from pstats import Stats
from sqlite3 import connect, OperationalError
@ -18,13 +13,14 @@ from time import localtime, strftime
from unicodedata import normalize
import xml.etree.ElementTree as etree
from functools import wraps, partial
from shutil import rmtree
from urllib import quote_plus
import hashlib
import re
import unicodedata
import xbmc
import xbmcaddon
import xbmcgui
from .watchdog.utils import unicode_paths
from . import path_ops
from . import variables as v
from . import state
@ -36,9 +32,19 @@ WINDOW = xbmcgui.Window(10000)
ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
EPOCH = datetime.utcfromtimestamp(0)
# Grab Plex id from '...plex_id=XXXX....'
REGEX_PLEX_ID = re.compile(r'''plex_id=(\d+)''')
# Return the numbers at the end of an url like '.../.../XXXX'
REGEX_END_DIGITS = re.compile(r'''/(.+)/(\d+)$''')
REGEX_PLEX_DIRECT = re.compile(r'''\.plex\.direct:\d+$''')
REGEX_FILE_NUMBERING = re.compile(r'''_(\d+)\.\w+$''')
# Plex API
REGEX_IMDB = re.compile(r'''/(tt\d+)''')
REGEX_TVDB = re.compile(r'''thetvdb:\/\/(.+?)\?''')
# Plex music
REGEX_MUSICPATH = re.compile(r'''^\^(.+)\$$''')
# Grab Plex id from an URL-encoded string
REGEX_PLEX_ID_FROM_URL = re.compile(r'''metadata%2F(\d+)''')
###############################################################################
# Main methods
@ -106,30 +112,6 @@ def settings(setting, value=None):
return try_decode(addon.getSetting(setting))
def exists_dir(path):
"""
Safe way to check whether the directory path exists already (broken in Kodi
<17)
Feed with encoded string or unicode
"""
if v.KODIVERSION >= 17:
answ = exists(try_encode(path))
else:
dummyfile = os.path.join(try_decode(path), 'dummyfile.txt')
try:
with open(encode_path(dummyfile), 'w') as filer:
filer.write('text')
except IOError:
# folder does not exist yet
answ = 0
else:
# Folder exists. Delete file again.
delete(try_encode(dummyfile))
answ = 1
return answ
def lang(stringid):
"""
Central string retrieval from strings.po
@ -248,26 +230,6 @@ def kodi_time_to_millis(time):
return ret
def encode_path(path):
"""
Filenames and paths are not necessarily utf-8 encoded. Use this function
instead of try_encode/trydecode if working with filenames and paths!
(os.walk only feeds on encoded paths. sys.getfilesystemencoding returns None
for Raspberry Pi)
"""
return unicode_paths.encode(path)
def decode_path(path):
"""
Filenames and paths are not necessarily utf-8 encoded. Use this function
instead of try_encode/trydecode if working with filenames and paths!
(os.walk only feeds on encoded paths. sys.getfilesystemencoding returns None
for Raspberry Pi)
"""
return unicode_paths.decode(path)
def try_encode(input_str, encoding='utf-8'):
"""
Will try to encode input_str (in unicode) to encoding. This possibly
@ -333,10 +295,6 @@ def valid_filename(text):
else:
# Linux
text = re.sub(r'/', '', text)
if not os.path.supports_unicode_filenames:
text = unicodedata.normalize('NFKD', text)
text = text.encode('ascii', 'ignore')
text = text.decode('ascii')
# Ensure that filename length is at most 255 chars (including 3 chars for
# filename extension and 1 dot to separate the extension)
text = text[:min(len(text), 251)]
@ -485,15 +443,15 @@ def wipe_database():
# Delete all synced playlists
for path in playlist_paths:
try:
os.remove(path)
path_ops.remove(path)
except (OSError, IOError):
pass
LOG.info("Resetting all cached artwork.")
# Remove all existing textures first
path = xbmc.translatePath("special://thumbnails/")
if exists(path):
rmtree(try_decode(path), ignore_errors=True)
path = path_ops.translate_path("special://thumbnails/")
if path_ops.exists(path):
path_ops.rmtree(path, ignore_errors=True)
# remove all existing data from texture DB
connection = kodi_sql('texture')
cursor = connection.cursor()
@ -547,10 +505,8 @@ def reset(ask_user=True):
heading='{plex} %s ' % lang(30132),
line1=lang(39603)):
# Delete the settings
addon = xbmcaddon.Addon()
addondir = try_decode(xbmc.translatePath(addon.getAddonInfo('profile')))
LOG.info("Deleting: settings.xml")
os.remove("%ssettings.xml" % addondir)
path_ops.remove("%ssettings.xml" % v.ADDON_PROFILE)
reboot_kodi()
@ -612,29 +568,6 @@ def compare_version(current, minimum):
return curr_patch >= min_patch
def normalize_nodes(text):
"""
For video nodes
"""
text = text.replace(":", "")
text = text.replace("/", "-")
text = text.replace("\\", "-")
text = text.replace("<", "")
text = text.replace(">", "")
text = text.replace("*", "")
text = text.replace("?", "")
text = text.replace('|', "")
text = text.replace('(', "")
text = text.replace(')', "")
text = text.strip()
# Remove dots from the last character as windows can not have directories
# with dots at the end
text = text.rstrip('.')
text = try_encode(normalize('NFKD', unicode(text, 'utf-8')))
return text
def normalize_string(text):
"""
For theme media, do not modify unless modified in TV Tunes
@ -656,6 +589,28 @@ def normalize_string(text):
return text
def normalize_nodes(text):
"""
For video nodes. Returns unicode
"""
text = text.replace(":", "")
text = text.replace("/", "-")
text = text.replace("\\", "-")
text = text.replace("<", "")
text = text.replace(">", "")
text = text.replace("*", "")
text = text.replace("?", "")
text = text.replace('|', "")
text = text.replace('(', "")
text = text.replace(')', "")
text = text.strip()
# Remove dots from the last character as windows can not have directories
# with dots at the end
text = text.rstrip('.')
text = normalize('NFKD', unicode(text, 'utf-8'))
return text
def indent(elem, level=0):
"""
Prettifies xml trees. Pass the etree root in
@ -702,9 +657,9 @@ class XmlKodiSetting(object):
top_element=None):
self.filename = filename
if path is None:
self.path = os.path.join(v.KODI_PROFILE, filename)
self.path = path_ops.path.join(v.KODI_PROFILE, filename)
else:
self.path = os.path.join(path, filename)
self.path = path_ops.path.join(path, filename)
self.force_create = force_create
self.top_element = top_element
self.tree = None
@ -869,7 +824,7 @@ def passwords_xml():
"""
To add network credentials to Kodi's password xml
"""
path = try_decode(xbmc.translatePath("special://userdata/"))
path = path_ops.translate_path('special://userdata/')
xmlpath = "%spasswords.xml" % path
try:
xmlparse = etree.parse(xmlpath)
@ -990,7 +945,7 @@ def playlist_xsp(mediatype, tagname, viewid, viewtype="", delete=False):
"""
Feed with tagname as unicode
"""
path = try_decode(xbmc.translatePath("special://profile/playlists/video/"))
path = path_ops.translate_path("special://profile/playlists/video/")
if viewtype == "mixed":
plname = "%s - %s" % (tagname, mediatype)
xsppath = "%sPlex %s - %s.xsp" % (path, viewid, mediatype)
@ -999,15 +954,15 @@ def playlist_xsp(mediatype, tagname, viewid, viewtype="", delete=False):
xsppath = "%sPlex %s.xsp" % (path, viewid)
# Create the playlist directory
if not exists(try_encode(path)):
if not path_ops.exists(path):
LOG.info("Creating directory: %s", path)
os.makedirs(path)
path_ops.makedirs(path)
# Only add the playlist if it doesn't already exists
if exists(try_encode(xsppath)):
if path_ops.exists(xsppath):
LOG.info('Path %s does exist', xsppath)
if delete:
os.remove(xsppath)
path_ops.remove(xsppath)
LOG.info("Successfully removed playlist: %s.", tagname)
return
@ -1019,7 +974,7 @@ def playlist_xsp(mediatype, tagname, viewid, viewtype="", delete=False):
'show': 'tvshows'
}
LOG.info("Writing playlist file to: %s", xsppath)
with open(encode_path(xsppath), 'wb') as filer:
with open(path_ops.encode_path(xsppath), 'wb') as filer:
filer.write(try_encode(
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
'<smartplaylist type="%s">\n\t'
@ -1037,22 +992,22 @@ def delete_playlists():
"""
Clean up the playlists
"""
path = try_decode(xbmc.translatePath("special://profile/playlists/video/"))
for root, _, files in os.walk(path):
path = path_ops.translate_path('special://profile/playlists/video/')
for root, _, files in path_ops.walk(path):
for file in files:
if file.startswith('Plex'):
os.remove(os.path.join(root, file))
path_ops.remove(path_ops.path.join(root, file))
def delete_nodes():
"""
Clean up video nodes
"""
path = try_decode(xbmc.translatePath("special://profile/library/video/"))
for root, dirs, _ in os.walk(path):
path = path_ops.translate_path("special://profile/library/video/")
for root, dirs, _ in path_ops.walk(path):
for directory in dirs:
if directory.startswith('Plex-'):
rmtree(os.path.join(root, directory))
path_ops.rmtree(path_ops.path.join(root, directory))
break
@ -1065,7 +1020,7 @@ def generate_file_md5(path):
"""
m = hashlib.md5()
m.update(path.encode('utf-8'))
with open(encode_path(path), 'rb') as f:
with open(path_ops.encode_path(path), 'rb') as f:
while True:
piece = f.read(32768)
if not piece:

View file

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import os
import sys
import xbmc
from xbmcaddon import Addon
@ -34,7 +35,9 @@ _ADDON = Addon()
ADDON_NAME = 'PlexKodiConnect'
ADDON_ID = 'plugin.video.plexkodiconnect'
ADDON_VERSION = _ADDON.getAddonInfo('version')
ADDON_PATH = try_decode(_ADDON.getAddonInfo('path'))
ADDON_FOLDER = try_decode(xbmc.translatePath('special://home'))
ADDON_PROFILE = try_decode(_ADDON.getAddonInfo('profile'))
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
@ -81,10 +84,6 @@ MIN_DB_VERSION = '2.0.27'
# Database paths
_DB_VIDEO_VERSION = {
13: 78, # Gotham
14: 90, # Helix
15: 93, # Isengard
16: 99, # Jarvis
17: 107, # Krypton
18: 109 # Leia
}
@ -92,10 +91,6 @@ DB_VIDEO_PATH = try_decode(xbmc.translatePath(
"special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION]))
_DB_MUSIC_VERSION = {
13: 46, # Gotham
14: 48, # Helix
15: 52, # Isengard
16: 56, # Jarvis
17: 60, # Krypton
18: 70 # Leia
}
@ -103,10 +98,6 @@ DB_MUSIC_PATH = try_decode(xbmc.translatePath(
"special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION]))
_DB_TEXTURE_VERSION = {
13: 13, # Gotham
14: 13, # Helix
15: 13, # Isengard
16: 13, # Jarvis
17: 13, # Krypton
18: 13 # Leia
}
@ -122,6 +113,12 @@ EXTERNAL_SUBTITLE_TEMP_PATH = try_decode(xbmc.translatePath(
# Multiply Plex time by this factor to receive Kodi time
PLEX_TO_KODI_TIMEFACTOR = 1.0 / 1000.0
# We're "failing" playback with a video of 0 length
NULL_VIDEO = os.path.join(ADDON_FOLDER,
'addons',
ADDON_ID,
'empty_video.mp4')
# Playlist stuff
PLAYLIST_PATH = os.path.join(KODI_PROFILE, 'playlists')
PLAYLIST_PATH_MIXED = os.path.join(PLAYLIST_PATH, 'mixed')
@ -512,3 +509,14 @@ PLEX_STREAM_TYPE_FROM_STREAM_TYPE = {
'audio': '2',
'subtitle': '3'
}
# Encoding to be used for our m3u playlist files
# m3u files do not have encoding specified by definition, unfortunately.
if PLATFORM == 'Windows':
M3U_ENCODING = 'mbcs'
else:
M3U_ENCODING = sys.getfilesystemencoding()
if (not M3U_ENCODING or
M3U_ENCODING == 'ascii' or
M3U_ENCODING == 'ANSI_X3.4-1968'):
M3U_ENCODING = 'utf-8'

View file

@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
###############################################################################
from logging import getLogger
from distutils import dir_util
import xml.etree.ElementTree as etree
from os import makedirs
import xbmc
from xbmcvfs import exists
from . import utils
from . import path_ops
from . import variables as v
from . import state
@ -16,7 +14,6 @@ from . import state
LOG = getLogger('PLEX.videonodes')
###############################################################################
# Paths are strings, NOT unicode!
class VideoNodes(object):
@ -66,33 +63,30 @@ class VideoNodes(object):
dirname = viewid
# Returns strings
path = utils.try_decode(xbmc.translatePath(
"special://profile/library/video/"))
nodepath = utils.try_decode(xbmc.translatePath(
"special://profile/library/video/Plex-%s/" % dirname))
path = path_ops.translate_path('special://profile/library/video/')
nodepath = path_ops.translate_path(
'special://profile/library/video/Plex-%s/' % dirname)
if delete:
if utils.exists_dir(nodepath):
from shutil import rmtree
rmtree(nodepath)
if path_ops.exists(nodepath):
path_ops.rmtree(nodepath)
LOG.info("Sucessfully removed videonode: %s." % tagname)
return
# Verify the video directory
if not utils.exists_dir(path):
dir_util.copy_tree(
src=utils.try_decode(
xbmc.translatePath("special://xbmc/system/library/video")),
dst=utils.try_decode(
xbmc.translatePath("special://profile/library/video")),
if not path_ops.exists(path):
path_ops.copy_tree(
src=path_ops.translate_path(
'special://xbmc/system/library/video'),
dst=path_ops.translate_path('special://profile/library/video'),
preserve_mode=0) # do not copy permission bits!
# Create the node directory
if mediatype != "photos":
if not utils.exists_dir(nodepath):
if not path_ops.exists(nodepath):
# folder does not exist yet
LOG.debug('Creating folder %s' % nodepath)
makedirs(nodepath)
path_ops.makedirs(nodepath)
# Create index entry
nodeXML = "%sindex.xml" % nodepath
@ -298,7 +292,7 @@ class VideoNodes(object):
# kodi picture sources somehow
continue
if exists(utils.try_encode(nodeXML)):
if path_ops.exists(nodeXML):
# Don't recreate xml if already exists
continue
@ -403,13 +397,12 @@ class VideoNodes(object):
utils.indent(root)
except:
pass
etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
etree.ElementTree(root).write(path_ops.encode_path(nodeXML),
encoding="UTF-8")
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
tagname = utils.try_encode(tagname)
cleantagname = utils.try_decode(utils.normalize_nodes(tagname))
nodepath = utils.try_decode(xbmc.translatePath(
"special://profile/library/video/"))
cleantagname = utils.normalize_nodes(tagname)
nodepath = path_ops.translate_path('special://profile/library/video/')
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
path = "library://video/plex_%s.xml" % cleantagname
if v.KODIVERSION >= 17:
@ -419,13 +412,12 @@ class VideoNodes(object):
windowpath = "ActivateWindow(Video,%s,return)" % path
# Create the video node directory
if not utils.exists_dir(nodepath):
if not path_ops.exists(nodepath):
# We need to copy over the default items
dir_util.copy_tree(
src=utils.try_decode(
xbmc.translatePath("special://xbmc/system/library/video")),
dst=utils.try_decode(
xbmc.translatePath("special://profile/library/video")),
path_ops.copy_tree(
src=path_ops.translate_path(
'special://xbmc/system/library/video'),
dst=path_ops.translate_path('special://profile/library/video'),
preserve_mode=0) # do not copy permission bits!
labels = {
@ -440,7 +432,7 @@ class VideoNodes(object):
utils.window('%s.content' % embynode, value=path)
utils.window('%s.type' % embynode, value=itemtype)
if exists(utils.try_encode(nodeXML)):
if path_ops.exists(nodeXML):
# Don't recreate xml if already exists
return