diff --git a/.gitignore b/.gitignore index 28f597f..38eb73b 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,8 @@ Icon # gedit tmp files #***************** *~ + +*.pyc +debug + diff --git a/Contents/Code/Docs/webtools-README_DEVS.odt b/Contents/Code/Docs/webtools-README_DEVS.odt index ee2fb35..0953083 100644 Binary files a/Contents/Code/Docs/webtools-README_DEVS.odt and b/Contents/Code/Docs/webtools-README_DEVS.odt differ diff --git a/Contents/Code/__init__.py b/Contents/Code/__init__.py index a26f917..eb4b69d 100644 --- a/Contents/Code/__init__.py +++ b/Contents/Code/__init__.py @@ -41,12 +41,11 @@ def Start(): DEBUGMODE = os.path.isfile(debugFile) if DEBUGMODE: VERSION = VERSION + ' ****** WARNING Debug mode on *********' - PLUGIN_VERSION = VERSION - print("******** Started %s on %s **********" %(NAME + ' V' + PLUGIN_VERSION, Platform.OS)) - Log.Debug("******* Started %s on %s ***********" %(NAME + ' V' + PLUGIN_VERSION, Platform.OS)) + print("******** Started %s on %s **********" %(NAME + ' V' + VERSION, Platform.OS)) + Log.Debug("******* Started %s on %s ***********" %(NAME + ' V' + VERSION, Platform.OS)) HTTP.CacheTime = 0 DirectoryObject.thumb = R(ICON) - ObjectContainer.title1 = NAME + ' V' + PLUGIN_VERSION + ObjectContainer.title1 = NAME + ' V' + VERSION Plugin.AddViewGroup('List', viewMode='List', mediaType='items') ObjectContainer.view_group = 'List' makeSettings() @@ -97,6 +96,12 @@ def makeSettings(): # Create the pwdset entry if Dict['pwdset'] == None: Dict['pwdset'] = False + # Init the installed dict + if Dict['installed'] == None: + Dict['installed'] = {} + # Init the allBundle Dict + if Dict['PMS-AllBundleInfo'] == None: + Dict['PMS-AllBundleInfo'] = {} return #################################################################################################### diff --git a/Contents/Code/git.py b/Contents/Code/git.py index c25b7c9..2e19576 100644 --- a/Contents/Code/git.py +++ b/Contents/Code/git.py @@ -101,12 +101,9 @@ def removeEmptyFolders(path, removeRoot=True): Log.Debug('Removing empty directory: ' + path) os.rmdir(path) - - # Reset dicts nukeSpecialDicts() - url= req.get_argument('debugURL', 'https://api.github.com/repos/dagalufh/WebTools.bundle/releases/latest') bundleName = Core.storage.join_path(Core.app_support_path, Core.config.bundles_dir_name, NAME + '.bundle') Log.Info('WT install dir is: ' + bundleName) @@ -174,16 +171,17 @@ def removeEmptyFolders(path, removeRoot=True): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish('Upgraded ok') except Exception, e: + Log.Critical('***************************************************************') + Log.Critical('Error when updating WebTools') + Log.Critical('The error was: ' + str(e)) Log.Critical('***************************************************************') Log.Critical('DARN....When we tried to upgrade WT, we had an error :-(') Log.Critical('Only option now might be to do a manual install, like you did the first time') - Log.Critical('The error was: ' + str(e)) Log.Critical('Do NOT FORGET!!!!') Log.Critical('We NEED this log, so please upload to Plex forums') Log.Critical('***************************************************************') return - ''' This function will return a list of bundles, where there is an update avail ''' def getUpdateList(self, req): Log.Debug('Got a call for getUpdateList') @@ -218,7 +216,6 @@ def getUpdateList(self, req): req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish('Fatal error happened in getUpdateList ' + str(e)) - ''' This function will migrate bundles that has been installed without using our UAS into our UAS ''' def migrate(self, req, silent=False): @@ -259,9 +256,6 @@ def getIdentifier(pluginDir): # Main call Log.Debug('Migrate function called') try: - # Init dict, if not already so - if Dict['installed'] == None: - Dict['installed'] = {} # Let's start by getting a list of known installed bundles knownBundles = [] for installedBundles in Dict['installed']: @@ -286,6 +280,11 @@ def getIdentifier(pluginDir): uasListjson = getUASCacheList() bFound = False for git in uasListjson: + ''' + if target == 'com.plexapp.plugins.Plex2csv': + print 'GED KIG HER' + continue + ''' if target == uasListjson[git]['identifier']: Log.Debug('Found %s is part of uas' %(target)) targetGit = {} @@ -302,6 +301,8 @@ def getIdentifier(pluginDir): Log.Debug('Dict stamped with the following install entry: ' + git + ' - ' + str(targetGit)) # Now update the PMS-AllBundleInfo Dict as well Dict['PMS-AllBundleInfo'][git] = targetGit + # Update installed dict as well + Dict['installed'][git] = targetGit # If it existed as unknown as well, we need to remove that Dict['PMS-AllBundleInfo'].pop(uasListjson[git]['identifier'], None) Dict['installed'].pop(uasListjson[git]['identifier'], None) @@ -402,7 +403,15 @@ def updateUASCache(self, req): req.finish('Exception in updateUASCache: ' + errMsg) return req # Grap file from Github - zipfile = Archive.ZipFromURL(self.UAS_URL+ '/archive/master.zip') + try: + zipfile = Archive.ZipFromURL(self.UAS_URL+ '/archive/master.zip') + except Exception, e: + Log.Critical('Could not download UAS Repo from GitHub') + req.clear() + req.set_status(500) + req.set_header('Content-Type', 'application/json; charset=utf-8') + req.finish('Exception in updateUASCache while downloading UAS repo from Github: ' + str(e)) + return req for filename in zipfile: # Walk contents of the zip, and extract as needed data = zipfile[filename] @@ -549,7 +558,11 @@ def removeEmptyFolders(path, removeRoot=True): if len(files) == 0 and removeRoot: Log.Debug('Removing empty directory: ' + path) os.rmdir(path) + try: + # Get the dict with the installed bundles, and init it if it doesn't exists + if not 'installed' in Dict: + Dict['installed'] = {} zipPath = url + '/archive/' + branch + '.zip' try: # Grap file from Github diff --git a/Contents/Code/pms.py b/Contents/Code/pms.py index acb1284..e87ca47 100644 --- a/Contents/Code/pms.py +++ b/Contents/Code/pms.py @@ -9,6 +9,7 @@ import shutil, os import time, json import io +from xml.etree import ElementTree # Undate uasTypesCounters @@ -44,11 +45,40 @@ def updateUASTypesCounters(): #TODO fix updateAllBundleInfo # updateAllBundleInfo def updateAllBundleInfoFromUAS(): + def updateInstallDict(): + ''' + # Debugging stuff + print 'Ged debugging stuff' + Dict['PMS-AllBundleInfo'].pop('https://github.com/ukdtom/plex2csv.bundle', None) + Dict['installed'].clear() + Dict.Save() + #Debug end + ''' + + + + # Start by creating a fast lookup cache for all uas bundles + uasBundles = {} + bundles = Dict['PMS-AllBundleInfo'] + for bundle in bundles: + uasBundles[bundles[bundle]['identifier']] = bundle + # Now walk the installed ones + for installedBundle in Dict['installed']: + if not installedBundle.startswith('https://'): + Log.Info('Checking unknown bundle: ' + installedBundle + ' to see if it is part of UAS now') + if installedBundle in uasBundles: + # Get the installed date of the bundle formerly known as unknown :-) + installedDate = Dict['installed'][installedBundle]['date'] + # Add updated stuff to the dicts + Dict['PMS-AllBundleInfo'][uasBundles[installedBundle]]['date'] = installedDate + Dict['installed'][uasBundles[installedBundle]] = Dict['PMS-AllBundleInfo'][uasBundles[installedBundle]] + # Remove old stuff from the Ditcs + Dict['PMS-AllBundleInfo'].pop(installedBundle, None) + Dict['installed'].pop(installedBundle, None) + Dict.Save() + return + try: - # Init the Dict - if Dict['PMS-AllBundleInfo'] == None: - Dict['PMS-AllBundleInfo'] = {} - Dict.Save() # start by checking if UAS cache has been populated jsonUAS = Core.storage.join_path(Core.app_support_path, Core.config.bundles_dir_name, NAME + '.bundle', 'http', 'uas', 'Resources', 'plugin_details.json') if os.path.exists(jsonUAS): @@ -59,38 +89,29 @@ def updateAllBundleInfoFromUAS(): json_file.close() # Convert to a JSON Object gits = JSON.ObjectFromString(str(response)) - for git in gits: - # Rearrange data - key = git['repo'] - if key == 'https://github.com/ukdtom/test': - # This is out test bundle.....Only add if this is running in devmode - # meaning a file named 'devmode' is present in the root of the bundle dir - fname = Core.storage.join_path(Core.app_support_path, Core.config.bundles_dir_name, 'WebTools.bundle', 'devmode') - print 'GED Look here' - if os.path.isfile(fname): - continue - -#******************* WORK IN PROGRESS HERE **************** - - - - # Check if already present, and if an install date also is there - if key in Dict['PMS-AllBundleInfo']: - jsonPMSAllBundleInfo = Dict['PMS-AllBundleInfo'][key] - if 'date' not in jsonPMSAllBundleInfo: - installDate = "" - else: + try: + for git in gits: + # Rearrange data + key = git['repo'] + # Check if already present, and if an install date also is there + installDate = "" + if key in Dict['PMS-AllBundleInfo']: + jsonPMSAllBundleInfo = Dict['PMS-AllBundleInfo'][key] + if 'date' in jsonPMSAllBundleInfo: installDate = Dict['PMS-AllBundleInfo'][key]['date'] - del git['repo'] - # Add/Update our Dict - Dict['PMS-AllBundleInfo'][key] = git - Dict['PMS-AllBundleInfo'][key]['date'] = installDate + del git['repo'] + # Add/Update our Dict + Dict['PMS-AllBundleInfo'][key] = git + Dict['PMS-AllBundleInfo'][key]['date'] = installDate + except Exception, e: + Log.Critical('Critical error in updateInstallDict while walking the gits: ' + str(e)) Dict.Save() updateUASTypesCounters() + updateInstallDict() else: Log.Debug('UAS was sadly not present') except Exception, e: - Log.Debug('Fatal error happened in updateAllBundleInfoFromUAS: ' + str(e)) + Log.Critical('Fatal error happened in updateAllBundleInfoFromUAS: ' + str(e)) class pms(object): # Defaults used by the rest of the class @@ -168,6 +189,30 @@ def reqprocessPost(self, req): req.set_status(412) req.finish("Unknown function call") + ''' Delete from an XML file ''' + def DelFromXML(self, fileName, attribute, value): + Log.Debug('Need to delete element with an attribute named "%s" with a value of "%s" from file named "%s"' %(attribute, value, fileName)) + with io.open(fileName, 'r') as f: + tree = ElementTree.parse(f) + root = tree.getroot() + mySubtitles = root.findall('.//Subtitle') + for Subtitles in root.findall("Language[Subtitle]"): + for node in Subtitles.findall("Subtitle"): + myValue = node.attrib.get(attribute) + + print 'Ged9', myValue + + if myValue: + if '_' in myValue: + drop, myValue = myValue.split("_") + if myValue == value: + + print 'Ged10', value + + Subtitles.remove(node) + tree.write(fileName, encoding='utf-8', xml_declaration=True) + return + # getParts def getParts(self, req): Log.Debug('Got a call for getParts') @@ -234,15 +279,10 @@ def getAllBundleInfo(self, req): Log.Debug('Got a call for getAllBundleInfo') try: req.clear() - if Dict['PMS-AllBundleInfo'] == None: - Log.Debug('getAllBundleInfo has not been populated yet') - req.set_status(204) - req.finish('getAllBundleInfo has not been populated yet') - else: - Log.Debug('Returning: ' + str(len(Dict['PMS-AllBundleInfo'])) + ' items') - req.set_status(200) - req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish(json.dumps(Dict['PMS-AllBundleInfo'])) + Log.Debug('Returning: ' + str(len(Dict['PMS-AllBundleInfo'])) + ' items') + req.set_status(200) + req.set_header('Content-Type', 'application/json; charset=utf-8') + req.finish(json.dumps(Dict['PMS-AllBundleInfo'])) except Exception, e: Log.Debug('Fatal error happened in getAllBundleInfo: ' + str(e)) req.clear() @@ -393,60 +433,48 @@ def delSub(self, req): req.finish('Hmmm....This is invalid, and most likely due to trying to delete an embedded sub :-)') else: if filePath.startswith('media://'): - ''' - Here we look at an agent provided subtitle, so this part of the function - has been crippled on purpose - ''' + # Path to symblink filePath = filePath.replace('media:/', os.path.join(Core.app_support_path, 'Media', 'localhost')) # Subtitle name agent, sub = filePath.split('_') tmp, agent = agent.split('com.') # Agent used agent = 'com.' + agent - filePath2 = filePath.replace('Contents', 'Subtitle Contributions') + filePath2 = filePath.replace('Contents', os.path.join('Contents', 'Subtitle Contributions')) filePath2, language = filePath2.split('Subtitles') language = language[1:3] filePath3 = os.path.join(filePath2[:-1], agent, language, sub) - - ''' This is removed from the code, due to the fact, that Plex will re-download right after the deletion - subtitlesXMLPath, tmp = filePath.split('Contents') agentXMLPath = os.path.join(subtitlesXMLPath, 'Contents', 'Subtitle Contributions', agent + '.xml') subtitlesXMLPath = os.path.join(subtitlesXMLPath, 'Contents', 'Subtitles.xml') self.DelFromXML(agentXMLPath, 'media', sub) self.DelFromXML(subtitlesXMLPath, 'media', sub) - agentXML = XML.ElementFromURL('"' + agentXMLPath + '"') - #Let's refresh the media - url = 'http://127.0.0.1:32400/library/metadata/' + params['param2'] + '/refresh&force=1' - refresh = HTTP.Request(url, immediate=False) - # Nuke the actual file try: # Delete the actual file os.remove(filePath) - print 'Removing: ' + filePath - + # Delete the symb link os.remove(filePath3) - print 'Removing: ' + filePath3 - # Refresh the subtitles in Plex - self.getSubTitles(params) - except: - return 500 - ''' - + #TODO: Refresh is sadly not working for me, so could use some help here :-( + #Let's refresh the media + url = 'http://127.0.0.1:32400/library/metadata/' + key + '/refresh?force=1' + HTTP.Request(url, cacheTime=0, immediate=True, method="PUT") + except Exception, e: + Log.Critical('Exception while deleting an agent based sub: ' + str(e)) + req.clear() + req.set_status(404) + req.set_header('Content-Type', 'application/json; charset=utf-8') + req.finish('Exception while deleting an agent based sub: ' + str(e)) retValues = {} retValues['FilePath']=filePath3 retValues['SymbLink']=filePath - Log.Debug('Agent subtitle returning %s' %(retValues)) req.clear() req.set_status(200) req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish(json.dumps(retValues)) - return req elif filePath.startswith('file://'): # We got a sidecar here, so killing time.....YES - filePath = filePath.replace('file://', '') try: # Delete the actual file @@ -458,13 +486,13 @@ def delSub(self, req): req.set_status(200) req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish(json.dumps(retVal)) - except: + except Exception, e: # Could not find req. subtitle - Log.Debug('Fatal error happened in delSub, when deleting %s' %(filePath)) + Log.Debug('Fatal error happened in delSub, when deleting %s : %s' %(filePath, str(e))) req.clear() req.set_status(404) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in delSub, when deleting %s' %(filePath)) + req.finish('Fatal error happened in delSub, when deleting %s : %s' %(filePath, str(e))) else: # Could not find req. subtitle Log.Debug('Fatal error happened in delSub, subtitle not found') @@ -472,12 +500,12 @@ def delSub(self, req): req.set_status(404) req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish('Could not find req. subtitle') - except: - Log.Debug('Fatal error happened in delSub') + except Exception, e: + Log.Debug('Fatal error happened in delSub: ' + str(e)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in delSub') + req.finish('Fatal error happened in delSub: ' + str(e)) ''' TVShow ''' def TVshow(self, req): diff --git a/http/changelog.txt b/http/changelog.txt index 2343ced..3804735 100644 --- a/http/changelog.txt +++ b/http/changelog.txt @@ -1,3 +1,37 @@ +V2.2-DEV: +Internal Note: + Here we collect stuff that needs to go into the real released changelog, aka acumulated, and only since 2.1 changes here ;-) +BACKEND: + Fix: + #115 UAS: Migration fails, if plugin directory contains hidden folder + #116 UAS: Make UAS work, even if a git is dir levels too low + #117 UAS and uninstall unknown bundle + #121 UAS: Cleanup old files/folders when updating + #122 UAS: bundle migration + #138 UAS: getAllBundleInfo is reset when UASRepo is updated + New: + #129 UAS: Alllow 3.Party devs to clear their stuff + #135 WT: Tell users, if wrong install path is used + #137 WT: Add new decorator for auth + #126 UAS: Allow WebTools to autoupdate + +FRONTEND: + Fix: + TODO + New: + #126 UAS: Allow WebTools to autoupdate +#### + +V2.2-DEV-2016-02-25: +UAS: + The return of the Uninstall-button (Issue #132) +WebTools: + Implemented update of main plugin. This however currently causes an error in the logs. + +V2.2-DEV-2016-02-19: +UAS: + Changed focus after showing channel. This should not bring up the keyboard on mobile devices anymore. + V2.2-DEV-2016-02-02: UAS: Reverting Issue #119 because of reported issue with locked interface.