"""
svn repo installer plugin
Nuka1195
"""
# main imports
import sys
import os
import xbmc
import xbmcgui
import xbmcplugin
import urllib
import re
from xml.sax.saxutils import unescape
#from pprint import pprint
from xbmcplugin_lib import *
# Script constants
__date__ = '15-10-2009'
log("Module: %s Dated: %s loaded!" % (__name__, __date__))
class Parser:
""" Parser Class: grabs all tag versions and urls """
# regexpressions
revision_regex = re.compile( '
.+?Revision ([0-9]*): ([^<]*)
' )
asset_regex = re.compile( '([^"]*)' )
def __init__( self, htmlSource ):
log("%s __init__!" % (self.__class__))
# set our initial status
self.dict = { "status": "fail", "revision": 0, "assets": [], "url": "" }
# fetch revision number
self._fetch_revision( htmlSource )
# if we were successful, fetch assets
if ( self.dict[ "revision" ] != 0 ):
self._fetch_assets( htmlSource )
def _fetch_revision( self, htmlSource ):
try:
# parse revision and current dir level
revision, url = self.revision_regex.findall( htmlSource )[ 0 ]
# we succeeded :), set our info
self.dict[ "url" ] = url
self.dict[ "revision" ] = int( revision )
except:
pass
def _fetch_assets( self, htmlSource ):
try:
assets = self.asset_regex.findall( htmlSource )
if ( len( assets ) ):
for asset in assets:
if ( asset[ 0 ] != "../" ):
self.dict[ "assets" ] += [ unescape( asset[ 0 ] ) ]
self.dict[ "status" ] = "ok"
except:
pass
class Main:
# base path
BASE_CACHE_PATH = os.path.join( xbmc.translatePath( "special://profile/" ), "Thumbnails", "Pictures" )
# INSTALLED_ITEMS_FILENAME = os.path.join( os.getcwd(), "installed_items.dat" )
INSTALLED_ITEMS_FILENAME = os.path.join( HOME_DIR, "installed_items.dat" )
def __init__( self ):
log( "%s init!" % self.__class__ )
ok = False
# parse sys.argv for our current url
self._parse_argv()
# if this is first run list all the repos
if ( sys.argv[ 2 ] == "" ):
ok = self._get_repos()
else:
# get the repository info
repo_info = get_repo_info( self.args.repo )
if repo_info:
self.REPO_URL, self.REPO_ROOT, self.REPO_STRUCTURES = repo_info
# if category is root, set our repo root
if ( self.args.category == "root" ):
self.args.category = self.REPO_ROOT
# get XBMC revision
self.XBMC_REVISION = get_xbmc_revision()
# get the list
ok = self._show_categories()
# send notification we're finished, successfully or unsuccessfully
xbmcplugin.endOfDirectory( handle=int( sys.argv[ 1 ] ), succeeded=ok )
def _clear_log( self, repo ):
# base_path = os.path.join( xbmc.translatePath( "special://profile/" ), "plugin_data", "programs", os.path.basename( os.getcwd() ) )
base_path = os.path.join( xbmc.translatePath( "special://profile/" ), "plugin_data", "programs", os.path.basename( HOME_DIR ) )
for page in range( 3 ):
path = os.path.join( base_path, "%s%d.txt" % ( repo, page, ) )
# remove log file
if ( os.path.isfile( path ) ):
os.remove( path )
def _parse_argv( self ):
# if first run set title to blank
if ( sys.argv[ 2 ] == "" ):
self.args = Info( title="" )
else:
# call _Info() with our formatted argv to create the self.args object
exec "self.args = Info(%s)" % ( urllib.unquote_plus( sys.argv[ 2 ][ 1 : ].replace( "&", ", " ) ), )
def _get_repos( self ):
try:
# we fetch the log here only at start of plugin
#import xbmcplugin_logviewer
# add the check for updates item to the media list
url = "%s?category='updates'" % ( sys.argv[ 0 ], )
# set the default icon
icon = "DefaultFolder.png"
# set thumbnail
# thumbnail = os.path.join( os.getcwd(), "resources", "media", "update_checker.png" )
thumbnail = os.path.join( HOME_DIR, "resources", "media", "update_checker.png" )
# create our listitem, fixing title
listitem = xbmcgui.ListItem( xbmc.getLocalizedString( 30500 ), iconImage=icon, thumbnailImage=thumbnail )
# set the title
listitem.setInfo( type="Video", infoLabels={ "Title": xbmc.getLocalizedString( 30500 ) } )
cm = [ ( xbmc.getLocalizedString( 30610 ), "XBMC.RunPlugin(%s?showreadme=True&repo=None&readme=None)" % ( sys.argv[ 0 ], ), ) ]
listitem.addContextMenuItems( cm, replaceItems=True )
# add our item
ok = xbmcplugin.addDirectoryItem( handle=int( sys.argv[ 1 ] ), url=url, listitem=listitem, isFolder=True )
# now add all the repos
repos = load_repos()
for repo in repos:
cm = []
# create the url
url = "%s?category='root'&repo=%s&title=%s" % ( sys.argv[ 0 ], repr( urllib.quote_plus( repo ) ), repr( urllib.quote_plus( repo ) ), )
# set thumbnail
# thumbnail = os.path.join( os.getcwd(), "resources", "media", "svn_repo.png" )
thumbnail = os.path.join( HOME_DIR, "resources", "media", "svn_repo.png" )
# create our listitem, fixing title
listitem = xbmcgui.ListItem( repo, iconImage=icon, thumbnailImage=thumbnail )
# set the title
listitem.setInfo( type="Video", infoLabels={ "Title": repo } )
# grab the log for this repo
if ( "(tagged)" not in repo ):
#parser = xbmcplugin_logviewer.ChangelogParser( repo, parse=False )
#parser.fetch_changelog()
# clear logs on first run
self._clear_log( repo )
# add view log context menu item
cm += [ ( xbmc.getLocalizedString( 30600 ), "XBMC.RunPlugin(%s?showlog=True&repo=%s&category=None&revision=None&parse=True)" % ( sys.argv[ 0 ], urllib.quote_plus( repr( repo ) ), ), ) ]
# add view readme context menu item
cm += [ ( xbmc.getLocalizedString( 30610 ), "XBMC.RunPlugin(%s?showreadme=True&repo=None&readme=None)" % ( sys.argv[ 0 ], ), ) ]
# add context menu items
listitem.addContextMenuItems( cm, replaceItems=True )
# add the item to the media list
ok = xbmcplugin.addDirectoryItem( handle=int( sys.argv[ 1 ] ), url=url, listitem=listitem, isFolder=True )
# if user cancels, call raise to exit loop
if ( not ok ): raise
except:
# user cancelled dialog or an error occurred
logError()
ok = False
if ( ok ):
xbmcplugin.addSortMethod( handle=int( sys.argv[ 1 ] ), sortMethod=xbmcplugin.SORT_METHOD_LABEL )
return ok
def _show_categories( self ):
ok = False
# fetch the html source
items = self._get_items()
# if successful
if ( items and items[ "status" ] == "ok" ):
# if there are assets, we have categories
ok = self._fill_list( items[ "url" ], items[ "revision" ], items[ "assets" ] )
return ok
def _fill_list( self, repo_url, revision, assets ):
log("_fill_list()")
try:
ok = False
# enumerate through the list of categories and add the item to the media list
all_addons = []
for item in assets:
info={}
cm = []
isFolder = True
for name, noffset, install, ioffset, voffset in self.REPO_STRUCTURES:
try:
if ( repo_url.split( "/" )[ int( noffset ) ].lower() == name.lower() ):
isFolder = False
break
except:
pass
if ( isFolder ):
heading = "category"
thumbnail = ""
label2 = ""
version = ""
path = ""
else:
heading = "download_url"
thumbnail = "%s%s/%sdefault.tbn" % ( self.REPO_URL, repo_url.replace( " ", "%20" ), item.replace( " ", "%20" ), )
info['thumb'] = thumbnail
svn_url = "%s%s/%s" % ( self.REPO_URL, repo_url.replace( " ", "%20" ), item.replace( " ", "%20" ), )
svn_tagInfo, installed_version, label2, path = self._check_compatible( svn_url, self.REPO_URL, install, int( ioffset ), int( voffset ) )
if svn_tagInfo:
info.update(svn_tagInfo)
info['svn_ver'] = svn_tagInfo.get('version')
info['svn_url'] = svn_url
info['version'] = installed_version
info['filepath'] = path
if not info.get('title'):
info['title'] = urllib.unquote_plus( item[ : -1 ] )
info['install'] = install
info['ioffset'] = ioffset
info['voffset'] = voffset
info['repo'] = self.args.repo
info['category'] = parseCategory(path)
version = info.get('svn_ver')
if not version:
version = "?"
version = " (v%s)" % version
# discover if a readme exists, save url
readme = check_readme( svn_url )
info['readme'] = readme
if heading == "category":
url = '%s?%s="%s/%s"&repo=%s&install="%s"&ioffset=%s&voffset=%s&title=%s' % \
( sys.argv[ 0 ], heading, urllib.quote_plus( repo_url ), urllib.quote_plus( item ), repr( urllib.quote_plus( self.args.repo ) ), \
install, ioffset, voffset, repr( urllib.quote_plus( self.args.repo ) ), )
elif heading == "download_url":
# dont add install url to info dict if incompatible
if xbmc.getLocalizedString( 30015 ) not in label2:
dl = "%s/%s" % ( urllib.quote_plus( repo_url ), urllib.quote_plus( item ) )
info['download_url'] = "download_url=%s&repo=%s&install=%s&ioffset=%s&voffset=%s" % \
( repr( dl ), repr( urllib.quote_plus( self.args.repo ) ), repr(install), ioffset, voffset )
url_args = "show_info=%s" % urllib.quote_plus( repr(info['filepath']) )
url = '%s?%s' % ( sys.argv[ 0 ], url_args, )
# add uninstall item
if ( not isFolder and not "SVN%20Repo%20Installer" in item and os.path.isfile( path ) ):
cm += [ ( xbmc.getLocalizedString( 30022 ), "XBMC.RunPlugin(%s?delete=%s&title=%s&delete_from_list=True)" % ( sys.argv[ 0 ], urllib.quote_plus( repr( os.path.dirname( path ) ) ), repr( urllib.quote_plus( item[ : -1 ] ) ), ), ) ]
# set the default icon
if isFolder:
icon = "DefaultFolder.png"
else:
icon = "DefaultFile.png"
# create our listitem, fixing title
label1 = "%s%s" % ( urllib.unquote_plus( item[ : -1 ] ), version, )
listitem = xbmcgui.ListItem( label1, label2=label2, iconImage=icon, thumbnailImage=thumbnail )
# set the title
listitem.setInfo( type="Video", infoLabels={ "Title": label1, "Genre": label2 } )
if ( not isFolder ):
cm += [ ( xbmc.getLocalizedString( 30600 ), "XBMC.RunPlugin(%s?showlog=True&repo=%s&category=%s&revision=None&parse=True)" % ( sys.argv[ 0 ], urllib.quote_plus( repr( self.args.repo ) ), urllib.quote_plus( repr( item[ : -1 ].replace( "%20", " " ) ) ), ), ) ]
# add context menu items
if ( readme ):
cm += [ ( xbmc.getLocalizedString( 30610 ), "XBMC.RunPlugin(%s?showreadme=True&repo=None&readme=%s)" % ( sys.argv[ 0 ], urllib.quote_plus( repr( readme ) ), ), ) ]
listitem.addContextMenuItems( cm, replaceItems=True )
all_addons.append(info)
# add the item to the media list
ok = xbmcplugin.addDirectoryItem( handle=int( sys.argv[ 1 ] ), url=url, listitem=listitem, isFolder=isFolder, totalItems=len( assets ) )
# if user cancels, call raise to exit loop
if ( not ok ): raise
# save info to file
# pprint (all_addons)
saveFileObj(self.INSTALLED_ITEMS_FILENAME, all_addons)
except:
# user cancelled dialog or an error occurred
logError()
ok = False
if ( ok ):
# set our plugin category
xbmcplugin.setPluginCategory( handle=int( sys.argv[ 1 ] ), category=self.args.title )
# sort by genre so all update status' are grouped
xbmcplugin.addSortMethod( handle=int( sys.argv[ 1 ] ), sortMethod=xbmcplugin.SORT_METHOD_GENRE )
return ok
def _check_compatible( self, url, repo_url, install, ioffset, voffset ):
log("> _check_compatible() url=%s" % (url, ) )
# get svn info
# 1st try description.xml
doc = readURL( os.path.join(url, "description.xml") )
svn_tagInfo = parseDescriptionXML( doc )
if not svn_tagInfo:
# 2nd try defult.py
doc = readURL( os.path.join(url, "default.py") )
svn_tagInfo = parseAllDocTags( doc )
if not svn_tagInfo:
log("unable to find any svn tags")
ok = False # no description.xml or default.py - prevent installation
else:
# parse source for revision and version
svn_version = svn_tagInfo.get('version','')
svn_xbmc_rev = svn_tagInfo.get('XBMC_Revision',0)
# compatible - 0 == unknown, so allow it
ok = bool((not self.XBMC_REVISION) or (not svn_xbmc_rev) or (self.XBMC_REVISION >= svn_xbmc_rev))
# create path
items = url.replace( repo_url, "" ).split( "/" )
# base path
drive = xbmc.translatePath( "/".join( [ "special://home", install ] ) )
if ( voffset != 0 ):
items[ voffset - 1 ] = "%s - %s" % ( items[ voffset - 1 ].replace( "%20", " " ), items[ voffset ], )
del items[ voffset ]
path = os.path.join( drive, os.path.sep.join( items[ ioffset : ] ).replace( "%20", " " ) )
# discover installed version
version = ""
isInstalled = os.path.isdir(path)
log("isInstalled %s" % isInstalled)
if isInstalled:
# 1st use description.xml
doc = readFile(os.path.join(path, "description.xml"))
version = parseXMLTag(doc, "version")
if not version:
# parse from .py
doc = readFile(os.path.join(path, "default.py"))
version = parseDocTag( doc, "version" )
if ( not ok ):
verState = xbmc.getLocalizedString( 30015 ) # Incompatible
elif not isInstalled:
verState = xbmc.getLocalizedString( 30021 ) # install
else:
# determine if svn update; always NEW if installed ver unknown
if (not version and svn_version) or (svn_version and svn_version > version):
verState = xbmc.getLocalizedString( 30014 ) # New
else:
verState = xbmc.getLocalizedString( 30011 ) # OK
# make label2 according to state
label2 = makeLabel2( verState )
log("< _check_compatible() installed_ver=%s label2=%s path=%s" % (version, label2, path))
return svn_tagInfo, version, label2, path
def _get_items( self ):
try:
# open url
url = self.REPO_URL + self.args.category
htmlSource = readURL( url )
# parse source and return a dictionary
return Parser( htmlSource ).dict
except:
# oops print error message
print "ERROR: %s::%s (%d) - %s" % ( self.__class__.__name__, sys.exc_info()[ 2 ].tb_frame.f_code.co_name, sys.exc_info()[ 2 ].tb_lineno, sys.exc_info()[ 1 ], )
return {}