""" Programs/Plugin to check for SVN Repo updates of your installed scripts and Plugins This version as part of "SVN Repo installer" Programs/Plugin Written by BigBellyBilly Contact me at BigBellyBilly at gmail dot com - Bugs reports and suggestions welcome. """ import sys, os import os.path import xbmc, xbmcgui, xbmcplugin import urllib import re from string import find, capwords #from pprint import pprint from xbmcplugin_lib import * # Script constants __plugin__ = sys.modules["__main__"].__plugin__ __date__ = '15-10-2009' log("Module: %s Dated: %s loaded!" % (__name__, __date__)) # check if build is special:// aware - set roots paths accordingly XBMC_HOME = 'special://home' if not os.path.isdir(xbmc.translatePath(XBMC_HOME)): # if fails to convert to Q:, old builds XBMC_HOME = 'Q:' log("XBMC_HOME=%s" % XBMC_HOME) class Main: URL_BASE_SVN_SCRIPTING = "http://xbmc-scripting.googlecode.com/svn" INSTALLED_ITEMS_FILENAME = os.path.join( HOME_DIR, "installed_items.dat" ) UPDATE_ALL_FILENAME = os.path.join( HOME_DIR, "update_all.dat" ) def __init__( self ): log( "%s init!" % self.__class__ ) ok = False # set our plugin category xbmcplugin.setPluginCategory( handle=int( sys.argv[ 1 ] ), category=xbmc.getLocalizedString( 30500 ) ) # load settings self.showNoSVN = bool(xbmcplugin.getSetting( "show_no_svn" ) == "true") self.showNoVer = bool(xbmcplugin.getSetting( "show_no_ver" ) == "true") self.XBMC_REVISION = get_xbmc_revision() self.SVN_URL_LIST = load_repos() #['plugin://programs/SVN Repo Installer/', '-1', '?download_url="%2Ftrunk%2Fplugins%2Fmusic/iTunes%2F"&repo=\'xbmc-addons\'&install=""&ioffset=2&voffset=0'] # create all XBMC script/plugin paths paths = ("plugins/programs","plugins/video","plugins/music","plugins/pictures","plugins/weather","scripts") self.XBMC_PATHS = [] for p in paths: self.XBMC_PATHS.append( xbmc.translatePath( "/".join( [XBMC_HOME, p] ) ) ) self.dialogProgress = xbmcgui.DialogProgress() self.dialogProgress.create(__plugin__) self.findInstalled() if self.INSTALLED: self.checkUpdates() self.dialogProgress.close() # pprint (self.INSTALLED) if self.INSTALLED: ok = self.showUpdates() if ok: saveFileObj(self.INSTALLED_ITEMS_FILENAME, self.INSTALLED) else: xbmcgui.Dialog().ok(__plugin__, "No installed Addons found!") log("endOfDirectory() ok=%s" % ok) xbmcplugin.endOfDirectory( int( sys.argv[ 1 ] ), ok, cacheToDisc=False) ##################################################################################################### def findInstalled(self): log("> findInstalled()") self.INSTALLED = [] ignoreList = (".","..",".backups",".svn") self.dialogProgress.update(0, xbmc.getLocalizedString( 30009 )) # Looking for installed addons TOTAL_PATHS = len(self.XBMC_PATHS) for count, p in enumerate(self.XBMC_PATHS): if not os.path.isdir(p): continue files = os.listdir(p) for f in files: # ignore some if f in ignoreList: continue percent = int( count * 100.0 / TOTAL_PATHS ) self.dialogProgress.update(percent) # extract version try: filepath = os.path.join( p, f ) log("Find Installed from filepath=" + filepath) # try to read info from description.xml doc = readFile( os.path.join(filepath, "description.xml") ) docTags = parseDescriptionXML( doc ) if not docTags: doc = readFile( os.path.join(filepath, "default.py") ) docTags = parseAllDocTags( doc ) if docTags and (docTags.get("version") or self.showNoVer): thumb = os.path.join(filepath, "default.tbn") if not os.path.isfile( thumb ): thumb = "DefaultFile.png" cat = parseCategory(filepath) # if no title, fallback to use installation dir. if not docTags.get("title"): docTags["title"] = capwords(f) tags = {"filepath": filepath, "thumb": thumb, "category": cat } tags.update(docTags) self.INSTALLED.append( tags ) except: logError() log("< findInstalled() installed count=%d" % len(self.INSTALLED)) ##################################################################################################### def checkUpdates(self): log("> checkUpdates()") actionMsg = xbmc.getLocalizedString( 30010 ) self.dialogProgress.update(0, actionMsg) TOTAL_PATHS = len(self.INSTALLED) quit = False # enumarate throu each installed Addon, checking svn for update for count, info in enumerate(self.INSTALLED): log("%d) checking installed=%s" % (count, info)) # unknown installed ver is OK, missing doc tag isn't if info.get('version',None) == None: log("ignored, missing a version tag") continue # find installed category from filepath installedCategory = info['category'] for repo in self.SVN_URL_LIST: try: REPO_URL, REPO_ROOT, REPO_STRUCTURES = get_repo_info( repo ) repo_name, repo_noffset, repo_install, repo_ioffset, repo_voffset = REPO_STRUCTURES[0] except: continue # repo info missing base_url = REPO_URL + REPO_ROOT # xbmc-scripting has no 'scripts' in svn path, so remove it from installedCategory if base_url.startswith(self.URL_BASE_SVN_SCRIPTING): installedCategory = installedCategory.replace('scripts/','') percent = int( count * 100.0 / TOTAL_PATHS ) self.dialogProgress.update(percent, actionMsg,"%s: %s" % (repo, installedCategory)) if self.dialogProgress.iscanceled(): quit = True break # try to read info from description.xml svn_ver = "" url = "/".join( [base_url, installedCategory, "description.xml"] ) doc = readURL( url.replace(' ','%20' ) ) if doc == None: # error quit = True elif doc: desc_info = parseDescriptionXML( doc ) if desc_info: svn_ver = desc_info.get("version") svn_xbmc_rev = desc_info.get("XBMC_Revision", 0) # if no info, try to read info from default.py if not quit and not svn_ver: url = "/".join( [base_url, installedCategory, "default.py"] ) doc = readURL( url.replace(' ','%20' ) ) if doc == None: # error quit = True break elif doc: # check remote file __version__ tag svn_ver = parseDocTag(doc, "version") try: svn_xbmc_rev = int(parseDocTag( doc, "XBMC_Revision" )) # min XBMC revision req. except: svn_xbmc_rev = 0 self.INSTALLED[count]['date'] = parseDocTag( doc, "date" ) # store additional info if not quit and svn_ver: self.INSTALLED[count]['svn_ver'] = svn_ver self.INSTALLED[count]['XBMC_Revision'] = svn_xbmc_rev self.INSTALLED[count]['svn_url'] = url.replace('/default.py','').replace('/description.xml','') self.INSTALLED[count]['readme'] = check_readme( "/".join( [base_url, installedCategory] ) ) self.INSTALLED[count]['repo'] = repo self.INSTALLED[count]['install'] = repo_install self.INSTALLED[count]['ioffset'] = repo_ioffset self.INSTALLED[count]['voffset'] = repo_voffset break # found in svn, move to next installed if quit: break log("< checkUpdates() updated count=%d" % len(self.INSTALLED)) ##################################################################################################### def showUpdates(self): log("> showUpdates()") ok = False # create update_all file that contains all listitem details deleteFile(self.UPDATE_ALL_FILENAME) updateAllItems = [] # create display list sz = len(self.INSTALLED) log("showing INSTALLED count=%s" % sz) for info in self.INSTALLED: try: log("info: %s" % info) svn_url = info.get('svn_url','') svn_ver = info.get('svn_ver','') # ignore those not in SVN as per settings if (not svn_url or not svn_ver) and not self.showNoSVN: log("ignored, as not in svn, showNoSVN: off") continue # get addon details path = "" filepath = info.get('filepath', '') ver = info.get('version', '') svn_xbmc_rev = info.get('XBMC_Revision',0) readme = info.get('readme','') category = info.get('category','') repo = info.get('repo','SVN ?') labelColour = "FFFFFFFF" # add ContextMenu: Delete (unless already deleted status) if "SVN Repo Installer" not in category: cm = self._contextMenuItem( 30022, { "delete": filepath, "title": category } ) # 'Delete' else: cm = [] # make update state according to found information if ".backups" in filepath: verState = xbmc.getLocalizedString( 30018 ) # Deleted labelColour = "66FFFFFF" elif not svn_url: verState = xbmc.getLocalizedString( 30012 ) # not in SVN elif not svn_ver: verState = xbmc.getLocalizedString( 30013 ) # unknown version elif ver >= svn_ver: verState = xbmc.getLocalizedString( 30011 ) # OK url_args = "show_info=%s" % urllib.quote_plus( repr(filepath) ) path = '%s?%s' % ( sys.argv[ 0 ], url_args, ) elif (svn_xbmc_rev and self.XBMC_REVISION and self.XBMC_REVISION >= svn_xbmc_rev) or \ (not svn_xbmc_rev or not self.XBMC_REVISION): # Compatible, NEW AVAILABLE - setup callback url for plugin SVN Repo Installer # assume compatible if no svn xbmc_revision found. verState = "v%s (%s)" % ( svn_ver, xbmc.getLocalizedString( 30014 ) ) # eg. !New! v1.1 trunk_url = re.search('(/(?:trunk|branch|tag).*?)$', svn_url, re.IGNORECASE).group(1) #['plugin://programs/SVN Repo Installer/', '-1', '?download_url="%2Ftrunk%2Fplugins%2Fmusic/iTunes%2F"&repo=\'xbmc-addons\'&install=""&ioffset=2&voffset=0'] info['download_url'] = "download_url=%s&repo=%s&install=%s&ioffset=%s&voffset=%s" % \ (repr(urllib.quote_plus(trunk_url + "/")), repr(urllib.quote_plus(repo)), repr(info["install"]), info["ioffset"], info["voffset"],) url_args = "show_info=%s" % urllib.quote_plus( repr(filepath) ) # exclude self update from "update all" if "SVN Repo Installer" not in category: updateAllItems.append("?" + info['download_url']) path = '%s?%s' % ( sys.argv[ 0 ], url_args, ) else: verState = "v%s (%s)" % ( svn_ver, xbmc.getLocalizedString( 30015 ), ) # eg. Incompatible if not path: path = os.path.join(filepath, 'default.tbn') if not svn_ver: svn_ver = '?' if not ver: ver = '?' # Addon status text as label2 text = "[COLOR=%s][%s] %s (v%s)[/COLOR]" % (labelColour, repo, category, ver) label2 = makeLabel2( verState ) # determine default icon according to addon type icon = "" if find(filepath,'scripts') != -1: icon = "DefaultScriptBig.png" elif find(filepath,'programs') != -1: icon = "DefaultProgramBig.png" elif find(filepath,'music') != -1: icon = "defaultAudioBig.png" elif find(filepath,'pictures') != -1: icon = "defaultPictureBig.png" elif find(filepath,'video') != -1: icon = "defaultVideoBig.png" # check skin for image, else fallback DefaultFile if not icon or not xbmc.skinHasImage(icon): icon = "DefaultFile.png" # assign thumb, in order from: local, svn, icon thumb = info.get('thumb','') if not thumb: thumb = icon li=xbmcgui.ListItem( text, label2, icon, thumb) li.setInfo( type="Video", infoLabels={ "Title": text, "Genre": label2 } ) # add ContextMenu: Changelog cm += self._contextMenuItem( 30600, { "showlog": True, "repo": repo, "category": category.split( "/" )[ -1 ], "revision": None, "parse": True } ) # view readme # add ContextMenu: Readme if readme: cm += self._contextMenuItem( 30610, { "showreadme": True, "repo": None, "readme": readme } ) li.addContextMenuItems( cm, replaceItems=True ) ok = xbmcplugin.addDirectoryItem( handle=int( sys.argv[ 1 ] ), url=path, listitem=li, isFolder=False, totalItems=sz ) if ( not ok ): break except: # user cancelled dialog or an error occurred logError() print info # if New Updates; add Update All item log("Updated Count=%d" % len(updateAllItems)) if updateAllItems: icon = "DefaultFile.png" text = xbmc.getLocalizedString( 30019 ) li=xbmcgui.ListItem( text, "", icon, icon) li.setInfo( type="Video", infoLabels={ "Title": text, "Genre": "" } ) path = '%s?download_url=update_all' % ( sys.argv[ 0 ], ) xbmcplugin.addDirectoryItem( handle=int( sys.argv[ 1 ] ), url=path, listitem=li, isFolder=False, totalItems=sz ) # save update_all dict to file saveFileObj(self.UPDATE_ALL_FILENAME, updateAllItems) # add list sort methods xbmcplugin.addSortMethod( handle=int( sys.argv[ 1 ] ), sortMethod=xbmcplugin.SORT_METHOD_GENRE ) # xbmcplugin.setContent( handle=int( sys.argv[ 1 ] ), content="files") ok = True log("< showUpdates() ok=%s" % ok) return ok ##################################################################################################### def _contextMenuItem(self, stringId, args={}, runType="RunPlugin"): """ create a tuple suitable for adding to a list of Context Menu items """ if isinstance(stringId, int): title = xbmc.getLocalizedString(stringId) else: title = stringId if args: argsStr = "" for key, value in args.items(): argsStr += "&%s=%s" % ( key, urllib.quote_plus( repr(value) ) ) runStr = ("XBMC.%s(%s?%s)" % ( runType, sys.argv[ 0 ], argsStr )).replace('?&','?') else: runStr = "XBMC.%s(%s)" % ( runType, sys.argv[ 0 ] ) return [ (title, runStr), ] def show_info( self ): log("show_info()") if ( __name__ == "__main__" ): try: Main() except: handleException("Main()")