From 11b00f4b4744848f17c49063340522f1eb29ac02 Mon Sep 17 00:00:00 2001 From: dagalufh Date: Sun, 22 May 2016 17:39:41 +0200 Subject: [PATCH] V2.3 DEV: Fix: #165 LV: Fixed issue with spaces in filenames #166 UAS: Removed highlight of All after selecting category #172 WT: Fixed issue with Factory reset #176 UAS: Critical error in updateInstallDict #178 WT: Fixed issue with intruder detection New: #170 WT: Added a user guide from Trumpy81. URL is /manual/WebTools-User-Manual.pdf #171 PMS: Added Search to the backend #175 PMS: Autodownload Repo if json is missing --- Contents/Code/Docs/webtools-README_DEVS.odt | Bin 94963 -> 95389 bytes Contents/Code/__init__.py | 9 +- Contents/Code/findMedia.py | 16 +- Contents/Code/git.py | 119 +++++++------ Contents/Code/logs.py | 28 ++-- Contents/Code/modules/plex2csv_moviefields.py | 134 ++++++++++++--- Contents/Code/plex2csv.py | 64 ++++++- Contents/Code/plextvhelper.py | 28 +--- Contents/Code/pms.py | 156 ++++++++++-------- Contents/Code/settings.py | 6 +- Contents/Code/webSrv.py | 102 ++++++------ Contents/Code/wt.py | 20 ++- http/changelog.txt | 28 +++- http/credits.txt | 3 + http/custom_themes/trumpy81-Aussie.css | 10 +- http/custom_themes/trumpy81-ItsBlue.css | 10 +- http/custom_themes/trumpy81-ItsGreen.css | 10 +- http/custom_themes/trumpy81-ItsPink.css | 10 +- http/custom_themes/trumpy81-ItsRed.css | 24 ++- http/custom_themes/trumpy81-Plex.css | 16 +- http/custom_themes/trumpy81-Teal.css | 16 +- http/index.html | 17 +- http/jscript/functions.js | 26 +-- http/manual/WebTools-User-Manual.odt | Bin 0 -> 411305 bytes http/manual/WebTools-User-Manual.pdf | Bin 0 -> 428434 bytes http/modules/install/jscript/install.js | 2 +- http/modules/logviewer/jscript/logviewer.js | 16 +- 27 files changed, 536 insertions(+), 334 deletions(-) create mode 100644 http/manual/WebTools-User-Manual.odt create mode 100644 http/manual/WebTools-User-Manual.pdf diff --git a/Contents/Code/Docs/webtools-README_DEVS.odt b/Contents/Code/Docs/webtools-README_DEVS.odt index 7244cac0ac11c6bad841e105653ba98b65e6bb00..6845f468a86c0adebf3719f81c809515ada864a0 100644 GIT binary patch delta 42755 zcmagF1ymeO&^C$&cY?cHu;35~?ry=|-8BsE?oNWcOK^90cXua{Api2d?|1IK=Y0RU zJEyj*s;g`2sp{$8o!PzJht4{NMpBT5gu(;^g9QT%ugi}^l0pMD5>q@CG2G~2VDE&J z!eR@K9~Y1eL`#YYs<^M5>x3ERF(cdXwxf52P-xvrJic{m4@+j`LmN`t7`Qp4BD*P6 zKRf>|{F`IWiGch6cf1uDr=~D+tWJop`)a5jGC5RA3R!*H-0pOpBc;Mc(1l^ww-fs*b=ZcRb8Qg zt+=0_*@RwQAyyFVwyw$!a)QaruV$nOQ1^auaijn1czaXr>0O61AJ7)-zUuOD{dD|t z?{E($B`q&RfcV!ygx56Fpm{dJU=zfzgM zR5iNXSKLgDRQK3Xt+U*FJRL9d(3de@6L{9L+djCsV6;7dS+9FOD5#~)%}&_HcC#eVr9N+ zW=p@KcerR%URJ%T-};x>VZ*rX8|3Nzcb$r7%W<1J+idl7Jk%4Hy3(9A;RgS~QcVGY zs$EPKdn(+@nlb?8nPjpp`Wc5f%0?aMD2_O%H$e~BRKNK7z5M(S_f_O(aMMj-jH*s| zsUe5euD0~)M-HpHXnN0hxuzYnCfzD3Qu14ywnnC$iuJcyf%EBu?W4`6hc|f6#y9d` zXsJp$W&=2JCHHcv`klAZ0x!4n(y^mEN4{WPtX$6Kp@NNYYYg^rFH{kcN14BcfFExa#-W{c$JH$@9VG$(SqVhKdVJtPsbP;#2%6fPWSw zkPrO?vw@$DM%eg6`AK47*q3tehf{QqcyBB)Z+>^g&Pl3w#qrQqa~QkfcCEz}3P|+G zu88mzW1y|#virx&^*jGUKgdV%rzZ`NV3}Ol3h-~nKF-qK`M=i>c8DcEjKFiw$UBX4VjRTZ;gO znQCnH9>(_RZpw0mdV_-`=t!vALb~Dw%PhH%W#v_kRC5uq)5#?LIBkq4ki?Ce zI7;g`b&T%$aQ`W9@P_g1OE+g)8>uJIL7xu9`=-fF#U}igT zp~EVH6gkH*fHEE0x2_!q!|XyGo*IiC)SX9UU}1fv>Jw7sAwASJa7%4 z{6;+%EI^D(;@?CRE_nHzroKUw+(W!8b6=?ac>Ae$qlvlmLo7X>Kl_JFPN%&qtj`s} z)9$EiP804e=v_3=&qz{hY!)iORZ0g^hn&LRHNJ5VaYP~`q$(<%@wZswPv{e&(p>##l$f0TEi01V%zqW)`7ImO6iHfBs3;WaGzRZ7sE!PX#vN-P!mm1p2gyuZ{wJ)7% zdL+1aM}u-q!GFbU8FJgq65ne6Ji^_fKb>0r-g zoX>U5g}5^&b28$VEQyH#R*(`vZ8waaAL=0qy%0DqVrwck^9iJgNL>Wt?lFEB)te3M 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 `?FTiATGbsipmD$xL^IY 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

}ind}L`bn9muepj*T zok>8!)#0(97em(dw5e~w%_;xG!=wFer01<$Rmje;h0Y_{&#mm#+$GXEQFjke;?>@D zH79`#Hr$j|lhukhFK(6kXn+6#A9PG#EVl$|f`9me}2r9&Dc z@>7$tQ|GI*`^{&Q?Ueeh68@S2Za!q1b#8v(sgW76{P;*?ADf2~543&2GyvNS8{55l zs*0h!pfmG+n^qP4Fr?Q}=Hd5^hi*d@AKX}w>w(g&0aw4w=d@+xl*6)m2Vu7BO0MfX zdRZoc>X!i2&c?pO2!@?9Ma}tvwDl&kQzv&TT;*bdyCjLx-%k+jk|>no%ge%ENm;G{%t8M#i{x>ef`i||C{b)$fwgy zC-Z}>6QPSZ-J#zz5_W!02{v0#47^|T=~}E_Foy^T$Q)Z9gpM$P1%rm~q8-OSa#)A1 z#W%`+8+@7&B$kWCQg@`lb{us zdYq68_&t@kf~T9My;DYA*_gdV zqu${`x3}rb4OI7JUCOW6EiI*0; z6H7#75;@aX^K3E5DVc~N>Q~OS+nu?lB8e^z#jPjk4v90>J-A-?rVAAPd$;QyiSpLM z;K_+!<;ZV*0)Hzkn04*JyVgH1kOO&NIgUT4?Az#$vFj##DC$R?X&-Ft=gcnJ>y>$3 zVG=TG#z&1{d*;L)uwHzIspssVW5=x|4&7&C-wYn;OST@*87+oR-)K?pqpFZwyfZ3; zrP}W%%c1j$`jA?t(d$aDuID|NWM{Fx>czJYzS%HKUKCK$Rl6-y9*(`%Lc3^DMt)?~ z$o|Yi?9MgCXNP9gsv|@^Lg$Z*(|p~>(`1Gi7DgiyI9^HW=@felR9(F!Q{zyjK z9Rs^qHZcMl#la=?c?p`b@~q!g|4t!mU3&4Pe3^#BRrCXahd)@#5^iX%D0F-)&vZ|z z;|&qHFHJd9^uxMP%mCXp4T`VURC2G`z>9r0zDeGr6?E)eM_0-^$CMV05K}nLB_5rR z`5bnrq4Rx5;!94RPMh(_UY@3ymMN_%?S=o@dXlfeZB9QpeF-ZdPO7 zX5f(!$w2-?^<*nUT4vCwLZEwRJ#^{F!*<87HpV+(@X8jh?N7jbYqFGaK+P9Iev zF;KKu9>U)qJJ?m2NTTD72a3v@zJ}JccU#j=v9&QIL8uoF-LD zj_R2gF;X!{Er2R!-zh^qj7w-Vw#y1i^tIeAV<%>iw4_$o4$CBi?}?nQsjmzgp{32r zCp&GPEPTc5TOIQlTD5)sBM>=1dT?-{BKBl6{NtBTtnbVDaK}0U)IIRK%V~dXzE%ia zfI4Dzh!V45wY+} zlzuj76;OVPmx(&~)Ehar_IfA3W45CFei_g@N0ec2HGg#SdX@gPD@$^#1=2}bZptnH ziWMDXM^zKdOO8`=||K z<&sL)@nyEP%4^4wo^dTN(Zd!QK0eVZ!-ID{j=-;?2J!|jqc@XR>f*#Q7GfidC;Lu3 zF0X9W$)25UjjzodED&`Y)XJPvd#7-2ygDhr`9rcs@DBG-MpuI&|0UE(o2e}6Xx1OWIwB0%#2gn$;H z1ppENaC#qm->6N5oCg4azxpK$NO|K3VZZX1r0YTos(uv!)KfYfAyUY{So@rd!S$Du z3LyYMe);~V^`~5Sf-pfZC#A?=4j>&V22K!8c_}GLl%lY(q@=L2`fhPET6l&3AtEU z{@2ofQ}vG~05JcX05TR*j9nr8f18SSg)kA|7ZS9VnEC#1s{gHm{5JuRrv$n{g#K1P zcDc-+>R&WXLd_!imH(Ld-(00o{Ur_jTbd&80^#^aW%NHxK353+KTN8W4Oa*k@&7I$ zp(FUblu*CKkn+e4LjHFV{JWYi Vx9wj|J;mD%!V2rRzbp^ne*k{K>2Lr5 diff --git a/Contents/Code/__init__.py b/Contents/Code/__init__.py index eb4b69d..5651733 100644 --- a/Contents/Code/__init__.py +++ b/Contents/Code/__init__.py @@ -15,7 +15,7 @@ NAME = 'WebTools' ICON = 'WebTools.png' -VERSION = '2.2' +VERSION = '2.3 DEV' AUTHTOKEN = '' SECRETKEY = '' DEBUGMODE = False @@ -28,8 +28,9 @@ from random import randint import uuid #Used for secrectKey - import datetime +import time + #********** Initialize ********* def Start(): @@ -41,8 +42,8 @@ def Start(): DEBUGMODE = os.path.isfile(debugFile) if DEBUGMODE: VERSION = VERSION + ' ****** WARNING Debug mode on *********' - print("******** Started %s on %s **********" %(NAME + ' V' + VERSION, Platform.OS)) - Log.Debug("******* Started %s on %s ***********" %(NAME + ' V' + VERSION, Platform.OS)) + print("******** Started %s on %s at %s **********" %(NAME + ' V' + VERSION, Platform.OS, time.strftime("%Y-%m-%d %H:%M"))) + Log.Debug("******* Started %s on %s at %s ***********" %(NAME + ' V' + VERSION, Platform.OS, time.strftime("%Y-%m-%d %H:%M"))) HTTP.CacheTime = 0 DirectoryObject.thumb = R(ICON) ObjectContainer.title1 = NAME + ' V' + VERSION diff --git a/Contents/Code/findMedia.py b/Contents/Code/findMedia.py index 98f9874..b6b9011 100644 --- a/Contents/Code/findMedia.py +++ b/Contents/Code/findMedia.py @@ -11,7 +11,7 @@ import urllib import unicodedata import json -import time +import time, sys # Consts used here AmountOfMediasInDatabase = 0 # Int of amount of medias in a database section @@ -152,10 +152,10 @@ def setSetting(self, req): req.clear() req.set_status(200) except Exception, e: - Log.Debug('Fatal error in setSetting: ' + str(e)) + Log.Debug('Fatal error in setSetting: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) - req.finish("Unknown error happened in findMedia-setSetting: " + str(e)) + req.finish("Unknown error happened in findMedia-setSetting: " + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) # Reset settings to default @@ -272,7 +272,7 @@ def scanMedias(sectionNumber, sectionLocations, sectionType, req): except ValueError: Log.Info('Aborted in ScanMedias') except Exception, e: - Log.Critical('Exception happend in scanMedias: ' + str(e)) + Log.Critical('Exception happend in scanMedias: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) statusMsg = 'Idle' # Scan the file system @@ -320,7 +320,7 @@ def getFiles(filePath): runningState = 99 Log.Info('Aborted in getFiles') except Exception, e: - Log.Critical('Exception happend in getFiles: ' + str(e)) + Log.Critical('Exception happend in getFiles: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) runningState = 99 def scanShowDB(sectionNumber=0): @@ -397,7 +397,7 @@ def scanShowDB(sectionNumber=0): runningState = 99 Log.Info('Aborted in ScanShowDB') except Exception, e: - Log.Debug('Fatal error in scanShowDB: ' + str(e)) + Log.Debug('Fatal error in scanShowDB: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) runningState = 99 # End scanShowDB @@ -440,7 +440,7 @@ def scanMovieDb(sectionNumber=0): break return except Exception, e: - Log.Debug('Fatal error in scanMovieDb: ' + str(e)) + Log.Debug('Fatal error in scanMovieDb: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) runningState = 99 # End scanMovieDb @@ -472,7 +472,7 @@ def scanMovieDb(sectionNumber=0): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish('Scanning already in progress') except Exception, ex: - Log.Debug('Fatal error happened in scanSection: ' + str(ex)) + Log.Debug('Fatal error happened in scanSection: ' + str(ex) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') diff --git a/Contents/Code/git.py b/Contents/Code/git.py index d351731..e3aec82 100644 --- a/Contents/Code/git.py +++ b/Contents/Code/git.py @@ -9,22 +9,36 @@ import datetime # Used for a timestamp in the dict import json -import io, os, shutil +import io, os, shutil, sys import plistlib import pms import tempfile class git(object): - # Defaults used by the rest of the class + init_already = False # Make sure part of init only run once + + # Init of the class def __init__(self): - Log.Debug('******* Starting git *******') self.url = '' self.PLUGIN_DIR = Core.storage.join_path(Core.app_support_path, Core.config.bundles_dir_name) self.UAS_URL = 'https://github.com/ukdtom/UAS2Res' self.IGNORE_BUNDLE = ['WebTools.bundle', 'SiteConfigurations.bundle', 'Services.bundle'] self.OFFICIAL_APP_STORE = 'https://nine.plugins.plexapp.com' - Log.Debug("Plugin directory is: %s" %(self.PLUGIN_DIR)) + # Only init this part once during the lifetime of this + if not git.init_already: + git.init_already = True + Log.Debug('******* Starting git *******') + Log.Debug("Plugin directory is: %s" %(self.PLUGIN_DIR)) + # See a few times, that the json file was missing, so here we check, and if not then force a download + try: + jsonFileName = Core.storage.join_path(self.PLUGIN_DIR, NAME + '.bundle', 'http', 'uas', 'Resources', 'plugin_details.json') + if not os.path.isfile(jsonFileName): + Log.Critical('UAS dir was missing the json, so doing a forced download here') + self.updateUASCache(None, cliForce = True) + except Exception, e: + Log.Critical('Exception happend when trying to force download from UASRes: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) + ''' Grap the tornado req, and process it for GET request''' def reqprocess(self, req): function = req.get_argument('function', 'missing') @@ -143,7 +157,7 @@ def removeEmptyFolders(path, removeRoot=True): Core.storage.save(path, data) except Exception, e: bError = True - Log.Critical('Exception happend in downloadBundle2tmp: ' + str(e)) + Log.Critical('Exception happend in downloadBundle2tmp: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) else: # We got a directory here Log.Debug(filename.split('/')[-2]) @@ -155,7 +169,7 @@ def removeEmptyFolders(path, removeRoot=True): Core.storage.ensure_dirs(path) except Exception, e: bError = True - Log.Critical('Exception happend in downloadBundle2tmp: ' + str(e)) + Log.Critical('Exception happend in downloadBundle2tmp: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) # Now we need to nuke files that should no longer be there! for root, dirs, files in os.walk(bundleName): for fname in files: @@ -174,7 +188,7 @@ def removeEmptyFolders(path, removeRoot=True): except Exception, e: Log.Critical('***************************************************************') Log.Critical('Error when updating WebTools') - Log.Critical('The error was: ' + str(e)) + Log.Critical('The error was: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) 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') @@ -212,7 +226,7 @@ def getUpdateList(self, req): req.clear() req.set_status(204) except Exception, e: - Log.Debug('Fatal error happened in getUpdateList: ' + str(e)) + Log.Critical('Fatal error happened in getUpdateList: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') @@ -238,7 +252,7 @@ def getUASCacheList(): results[title] = git return results except Exception, e: - Log.Debug('Exception in Migrate/getUASCacheList : ' + str(e)) + Log.Critical('Exception in Migrate/getUASCacheList : ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) return '' # Grap indentifier from plist file and timestamp @@ -343,11 +357,11 @@ def getIdentifier(pluginDir): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish(json.dumps(migratedBundles)) except Exception, e: - Log.Critical('Fatal error happened in migrate: ' + str(e)) + Log.Critical('Fatal error happened in migrate: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in migrate: ' + str(e)) + req.finish('Fatal error happened in migrate: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) return req ''' This will return a list of UAS bundle types from the UAS Cache ''' @@ -359,7 +373,7 @@ def uasTypes(self, req): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish(json.dumps(Dict['uasTypes'])) except Exception, e: - Log.Critical('Exception in uasTypes: ' + str(e)) + Log.Critical('Exception in uasTypes: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') @@ -367,9 +381,12 @@ def uasTypes(self, req): return req ''' This will update the UAS Cache directory from GitHub ''' - def updateUASCache(self, req): + def updateUASCache(self, req, cliForce= False): Log.Debug('Starting to update the UAS Cache') - debugForce = ('false' != req.get_argument('debugForce', 'false')) + if not cliForce: + Force = ('false' != req.get_argument('Force', 'false')) + else: + Force = True # Main call try: # Start by getting the time stamp for the last update @@ -383,7 +400,7 @@ def updateUASCache(self, req): # Now get the last update time from the UAS repository on GitHub masterUpdate = datetime.datetime.strptime(self.getLastUpdateTime(req, True, self.UAS_URL), '%Y-%m-%d %H:%M:%S') # Do we need to update the cache, and add 2 min. tolerance here? - if ((masterUpdate - lastUpdateUAS) > datetime.timedelta(seconds = 120) or debugForce): + if ((masterUpdate - lastUpdateUAS) > datetime.timedelta(seconds = 120) or Force): # We need to update UAS Cache # Target Directory targetDir = Core.storage.join_path(self.PLUGIN_DIR, NAME + '.bundle', 'http', 'uas') @@ -391,7 +408,7 @@ def updateUASCache(self, req): try: Core.storage.ensure_dirs(targetDir) except Exception, e: - errMsg = str(e) + errMsg = str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno) if 'Errno 13' in errMsg: errMsg = errMsg + '\n\nLooks like permissions are not correct, cuz we where denied access\n' errMsg = errMsg + 'to create a needed directory.\n\n' @@ -399,22 +416,26 @@ def updateUASCache(self, req): errMsg = errMsg + 'sudo chown plex:plex ./WebTools.bundle -R\n' errMsg = errMsg + 'And if on Synology, the command is:\n' errMsg = errMsg + 'sudo chown plex:users ./WebTools.bundle -R\n' - Log.Critical('Exception in updateUASCache ' + str(e)) - req.clear() - req.set_status(500) - req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Exception in updateUASCache: ' + errMsg) - return req + Log.Critical('Exception in updateUASCache ' + errMsg) + if not cliForce: + req.clear() + req.set_status(500) + req.set_header('Content-Type', 'application/json; charset=utf-8') + req.finish('Exception in updateUASCache: ' + errMsg) + return req + else: + return # Grap file from Github 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 + Log.Critical('Could not download UAS Repo from GitHub' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) + if not cliForce: + 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)+ ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) + return req for filename in zipfile: # Walk contents of the zip, and extract as needed data = zipfile[filename] @@ -426,7 +447,7 @@ def updateUASCache(self, req): Core.storage.save(path, data) except Exception, e: bError = True - Log.Critical("Unexpected Error " + str(e)) + Log.Critical("Unexpected Error " + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) else: # We got a directory here Log.Debug(filename.split('/')[-2]) @@ -438,7 +459,7 @@ def updateUASCache(self, req): Core.storage.ensure_dirs(path) except Exception, e: bError = True - Log.Critical("Unexpected Error " + str(e)) + Log.Critical("Unexpected Error " + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) # Update the AllBundleInfo as well pms.updateAllBundleInfoFromUAS() pms.updateUASTypesCounters() @@ -446,17 +467,19 @@ def updateUASCache(self, req): Log.Debug('UAS Cache already up to date') # Set timestamp in the Dict Dict['UAS'] = datetime.datetime.now() - req.clear() - req.set_status(200) - req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('UASCache is up to date') + if not cliForce: + req.clear() + req.set_status(200) + req.set_header('Content-Type', 'application/json; charset=utf-8') + req.finish('UASCache is up to date') except Exception, e: - Log.Critical('Exception in updateUASCache ' + str(e)) - req.clear() - req.set_status(500) - req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Exception in updateUASCache ' + str(e)) - return req + Log.Critical('Exception in updateUASCache ' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) + if not cliForce: + req.clear() + req.set_status(500) + req.set_header('Content-Type', 'application/json; charset=utf-8') + req.finish('Exception in updateUASCache ' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) + return req ''' list will return a list of all installed gits from GitHub''' def list(self, req): @@ -581,7 +604,7 @@ def removeEmptyFolders(path, removeRoot=True): # Grap file from Github zipfile = Archive.ZipFromURL(zipPath) except Exception, e: - Log.Critical('Exception in downloadBundle2tmp while downloading from GitHub: ' + str(e)) + Log.Critical('Exception in downloadBundle2tmp while downloading from GitHub: ' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) return False # Create base directory Core.storage.ensure_dirs(Core.storage.join_path(self.PLUGIN_DIR, bundleName)) @@ -610,7 +633,7 @@ def removeEmptyFolders(path, removeRoot=True): Log.Debug('Install is an upgrade') break except Exception, e: - Log.Critical('Exception in downloadBundle2tmp while walking the downloaded file to find the plist: ' + str(e)) + Log.Critical('Exception in downloadBundle2tmp while walking the downloaded file to find the plist: ' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) return False if bUpgrade: # Since this is an upgrade, we need to check, if the dev wants us to delete the Cache directory @@ -661,7 +684,7 @@ def removeEmptyFolders(path, removeRoot=True): Core.storage.save(path, data) except Exception, e: bError = True - Log.Critical('Exception happend in downloadBundle2tmp: ' + str(e)) + Log.Critical('Exception happend in downloadBundle2tmp: ' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) else: if cutStr not in filename: continue @@ -676,7 +699,7 @@ def removeEmptyFolders(path, removeRoot=True): Core.storage.ensure_dirs(path) except Exception, e: bError = True - Log.Critical('Exception happend in downloadBundle2tmp: ' + str(e)) + Log.Critical('Exception happend in downloadBundle2tmp: ' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) if not bError and bUpgrade: # Copy files that should be kept between upgrades ("keepFiles") @@ -724,7 +747,7 @@ def removeEmptyFolders(path, removeRoot=True): shutil.move(extractDir, bundleName) except Exception, e: bError = True - Log.Critical('Unable to update plugin: ' + str(e)) + Log.Critical('Unable to update plugin: ' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) # Delete temporary directory try: @@ -753,7 +776,7 @@ def removeEmptyFolders(path, removeRoot=True): pass return True except Exception, e: - Log.Critical('Exception in downloadBundle2tmp: ' + str(e)) + Log.Critical('Exception in downloadBundle2tmp: ' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) return False # Starting install main @@ -824,11 +847,11 @@ def getLastUpdateTime(self, req, UAS=False, url=''): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish(str(response)) except Exception, e: - Log.Critical('Fatal error happened in getLastUpdateTime for :' + url + ' was: ' + str(e)) + Log.Critical('Fatal error happened in getLastUpdateTime for :' + url + ' was: ' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in getLastUpdateTime for :' + url + ' was: ' + str(e)) + req.finish('Fatal error happened in getLastUpdateTime for :' + url + ' was: ' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) ''' Get list of avail bundles in the UAS ''' def getListofBundles(self, req): diff --git a/Contents/Code/logs.py b/Contents/Code/logs.py index 820966c..6ebbc4b 100644 --- a/Contents/Code/logs.py +++ b/Contents/Code/logs.py @@ -31,11 +31,11 @@ def __init__(self): if not os.direxists(self.LOGDIR): self.LOGDIR = os.path.join(Core.app_support_path, 'Logs') except Exception, e: - Log.Debug('Fatal error happened in Logs list: ' + str(e)) + Log.Debug('Fatal error happened in Logs list: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in Logs list: ' + str(e)) + req.finish('Fatal error happened in Logs list: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) Log.Debug('Log Root dir is: ' + self.LOGDIR) ''' Grap the tornado req for a Get, and process it ''' @@ -84,11 +84,11 @@ def entry(self, req): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish('Entry logged') except Exception, e: - Log.Debug('Fatal error happened in Logs entry: ' + str(e)) + Log.Debug('Fatal error happened in Logs entry: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in Logs entry: ' + str(e)) + req.finish('Fatal error happened in Logs entry: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) ''' This metode will return a list of logfiles. accepts a filter parameter ''' def list(self, req): @@ -113,11 +113,11 @@ def list(self, req): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish(json.dumps(sorted(retFiles))) except Exception, e: - Log.Debug('Fatal error happened in Logs list: ' + str(e)) + Log.Debug('Fatal error happened in Logs list: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in Logs list: ' + str(e)) + req.finish('Fatal error happened in Logs list: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) ''' This will return contents of the logfile as an array. Req. a parameter named fileName ''' def show(self, req): @@ -146,11 +146,11 @@ def show(self, req): req.finish(json.dumps(retFile)) return req except Exception, e: - Log.Debug('Fatal error happened in Logs show: ' + str(e)) + Log.Debug('Fatal error happened in Logs show: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in Logs show: ' + str(e)) + req.finish('Fatal error happened in Logs show: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) ''' This will download a zipfile with the complete log directory. if parameter fileName is specified, only that file will be downloaded, and not zipped''' def download(self, req): @@ -186,11 +186,11 @@ def download(self, req): os.remove(zipFileName) return req except Exception, e: - Log.Debug('Fatal error happened in Logs download: ' + str(e)) + Log.Debug('Fatal error happened in Logs download: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in Logs download: ' + str(e)) + req.finish('Fatal error happened in Logs download: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) else: try: if 'com.plexapp' in fileName: @@ -211,15 +211,15 @@ def download(self, req): req.finish() return req except Exception, e: - Log.Debug('Fatal error happened in Logs download: ' + str(e)) + Log.Debug('Fatal error happened in Logs download: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in Logs download: ' + str(e)) + req.finish('Fatal error happened in Logs download: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) except Exception, e: - Log.Debug('Fatal error happened in Logs download: ' + str(e)) + Log.Debug('Fatal error happened in Logs download: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in Logs download: ' + str(e)) + req.finish('Fatal error happened in Logs download: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) diff --git a/Contents/Code/modules/plex2csv_moviefields.py b/Contents/Code/modules/plex2csv_moviefields.py index c78551a..57a0c5d 100644 --- a/Contents/Code/modules/plex2csv_moviefields.py +++ b/Contents/Code/modules/plex2csv_moviefields.py @@ -111,31 +111,113 @@ "Subtitle Stream Selected" : {"field" : "Media/Part/Stream[@streamType=3]/@selected", "ReqLevel" : 2, "id" : 97} } +fieldsbyID = { + 1 : "Media ID", + 2 : "Title", + 3 : "Sort title", + 4 : "Studio", + 5 : "Content Rating", + 6 : "Year", + 7 : "Rating", + 8 : "Summary", + 9 : "Genres", + 10 : "View Count", + 11 : "Last Viewed at", + 12 : "Tagline", + 13 : "Release Date", + 14 : "Writers", + 15 : "Country", + 16 : "Duration", + 17 : "Directors", + 18 : "Roles", + 19 : "IMDB Id", + 20 : "Labels", + 21 : "Locked Fields", + 22 : "Extras", + 23 : "Collections", + 24 : "Original Title", + 25 : "Added", + 26 : "Updated", + 27 : "Audio Languages", + 28 : "Audio Title", + 29 : "Subtitle Languages", + 30 : "Subtitle Title", + 31 : "Subtitle Codec", + 32 : "Accessible", + 33 : "Exists", + 34 : "Video Resolution", + 35 : "Bitrate", + 36 : "Width", + 37 : "Height", + 38 : "Aspect Ratio", + 39 : "Audio Channels", + 40 : "Audio Codec", + 41 : "Video Codec", + 42 : "Container", + 43 : "Video FrameRate", + 44 : "Part File", + 45 : "Part Size", + 46 : "Part Indexed", + 47 : "Part Duration", + 48 : "Part Container", + 49 : "Part Optimized for Streaming", + 50 : "Video Stream Title", + 51 : "Video Stream Default", + 52 : "Video Stream Index", + 53 : "Video Stream Pixel Format", + 54 : "Video Stream Profile", + 55 : "Video Stream Ref Frames", + 56 : "Video Stream Scan Type", + 57 : "Video Stream Stream Identifier", + 58 : "Video Stream Width", + 59 : "Video Stream Pixel Aspect Ratio", + 60 : "Video Stream Height", + 61 : "Video Stream Has Scaling Matrix", + 62 : "Video Stream Frame Rate Mode", + 63 : "Video Stream Frame Rate", + 64 : "Video Stream Codec", + 65 : "Video Stream Codec ID", + 66 : "Video Stream Chroma Sub Sampling", + 67 : "Video Stream Cabac", + 68 : "Video Stream Anamorphic", + 69 : "Video Stream Language Code", + 70 : "Video Stream Language", + 71 : "Video Stream Bitrate", + 72 : "Video Stream Bit Depth", + 73 : "Video Stream Duration", + 74 : "Video Stream Level", + 75 : "Audio Stream Selected", + 76 : "Audio Stream Default", + 77 : "Audio Stream Codec", + 78 : "Audio Stream Index", + 79 : "Audio Stream Channels", + 80 : "Audio Stream Bitrate", + 81 : "Audio Stream Language", + 82 : "Audio Stream Language Code", + 83 : "Audio Stream Audio Channel Layout", + 84 : "Audio Stream Bit Depth", + 85 : "Audio Stream Bitrate Mode", + 86 : "Audio Stream Codec ID", + 87 : "Audio Stream Duration", + 88 : "Audio Stream Profile", + 89 : "Audio Stream Sampling Rate", + 90 : "Subtitle Stream Codec", + 91 : "Subtitle Stream Index", + 92 : "Subtitle Stream Language", + 93 : "Subtitle Stream Language Code", + 94 : "Subtitle Stream Codec ID", + 95 : "Subtitle Stream Format", + 96 : "Subtitle Stream Title", + 97 : "Subtitle Stream Selected" +} + levels = { - "Level_1" : ['Media ID', 'Title', 'Sort title', 'Studio', 'Content Rating', - 'Year', 'Rating', 'Summary', 'Genres'], - "Level_2" : ['View Count', 'Last Viewed at', 'Tagline', 'Release Date', - 'Writers', 'Country', 'Duration', 'Directors', 'Roles', 'IMDB Id'], - "Level_3" : ['Labels', 'Locked Fields', 'Extras', 'Collections', 'Original Title', - 'Added', 'Updated', 'Audio Languages', 'Audio Title', 'Subtitle Languages', - 'Subtitle Title', 'Subtitle Codec', 'Accessible', 'Exists'], - "Level_4" : ['Video Resolution', 'Bitrate', 'Width', 'Height', 'Aspect Ratio', - 'Audio Channels', 'Audio Codec', 'Video Codec', 'Container', 'Video FrameRate'], - "Level_5" : ['Part File', 'Part Size', 'Part Indexed', 'Part Duration', 'Part Container', - 'Part Optimized for Streaming'], - "Level_6" : ['Video Stream Title', 'Video Stream Default', 'Video Stream Index','Video Stream Pixel Format', - 'Video Stream Profile', 'Video Stream Ref Frames', 'Video Stream Scan Type', - 'Video Stream Stream Identifier', 'Video Stream Width', 'Video Stream Pixel Aspect Ratio', - 'Video Stream Height', 'Video Stream Has Scaling Matrix', 'Video Stream Frame Rate Mode', - 'Video Stream Frame Rate', 'Video Stream Codec', 'Video Stream Codec ID', - 'Video Stream Chroma Sub Sampling', 'Video Stream Cabac', 'Video Stream Anamorphic', - 'Video Stream Language Code', 'Video Stream Language', 'Video Stream Bitrate', - 'Video Stream Bit Depth', 'Video Stream Duration', 'Video Stream Level', - 'Audio Stream Selected', 'Audio Stream Default', 'Audio Stream Codec', - 'Audio Stream Index', 'Audio Stream Channels', 'Audio Stream Bitrate', 'Audio Stream Language', - 'Audio Stream Language Code', 'Audio Stream Audio Channel Layout', 'Audio Stream Bit Depth', - 'Audio Stream Bitrate Mode', 'Audio Stream Codec ID', 'Audio Stream Duration', - 'Audio Stream Profile', 'Audio Stream Sampling Rate', 'Subtitle Stream Codec', - 'Subtitle Stream Index', 'Subtitle Stream Language', 'Subtitle Stream Language Code', - 'Subtitle Stream Codec ID', 'Subtitle Stream Format', 'Subtitle Stream Title', 'Subtitle Stream Selected'] + "Level_1" : [1, 2, 3, 4, 5, 6, 7, 8, 9], + "Level_2" : [10, 11, 12, 13, 14, 15, 16, 17, 18, 19], + "Level_3" : [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33], + "Level_4" : [34, 35, 36, 37, 38, 39, 40, 41, 42, 43], + "Level_5" : [44, 45, 46, 47, 48, 49], + "Level_6" : [50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, + 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97] } diff --git a/Contents/Code/plex2csv.py b/Contents/Code/plex2csv.py index 486cdc1..4ef37d0 100644 --- a/Contents/Code/plex2csv.py +++ b/Contents/Code/plex2csv.py @@ -10,6 +10,7 @@ ###################################################################################################################### import plex2csv_moviefields +import json class plex2csv(object): # Defaults used by the rest of the class @@ -24,22 +25,79 @@ def reqprocess(self, req): req.set_status(412) req.finish("Missing function parameter") elif function == 'getFields': - # Call scanSection + # Call getFields return self.getFields(req) + elif function == 'getFieldListbyIdx': + # Call getFieldListbyIdx + return self.getFieldListbyIdx(req) + elif function == 'getDefaultLevels': + # Call getDefaultLevels + return self.getDefaultLevels(req) else: req.clear() req.set_status(412) req.finish("Unknown function call") + ''' Returns a jason with the build-in levels + Param needed is type=[movie,show,audio,picture] + ''' + def getDefaultLevels(self, req): + def getMovieDefLevels(req): + myResult = [] + fields = json.dumps(plex2csv_moviefields.movieDefaultLevels, sort_keys=True) + print 'Ged1', fields + print 'Ged2' + for key, value in fields: + print 'Ged2', key + myResult.append(key) + req.clear() + req.set_status(200) + req.finish(json.dumps(myResult)) + + # Main code + type = req.get_argument('type', 'missing') + if type == 'missing': + req.clear() + req.set_status(412) + req.finish("Missing type parameter") + if type=='movie': + getMovieDefLevels(req) + + ''' Returns an array of possible fields for a section type. + Param needed is type=[movie,show,audio,picture] + ''' + def getFieldListbyIdx(self, req): + def getMovieListbyIdx(req): + req.clear() + req.set_status(200) + req.finish(json.dumps(plex2csv_moviefields.fieldsbyID)) + + # Main code + type = req.get_argument('type', 'missing') + if type == 'missing': + req.clear() + req.set_status(412) + req.finish("Missing type parameter") + if type=='movie': + getMovieListbyIdx(req) + ''' This will return a list of fields avail Param needed is type=[movie,show,audio,picture] ''' def getFields(self, req): - print 'Ged her' + def getFullMovieFieldsList(req): + req.clear() + req.set_status(200) + req.finish(json.dumps(plex2csv_moviefields.fields)) + + # Main code type = req.get_argument('type', 'missing') if type == 'missing': req.clear() req.set_status(412) req.finish("Missing type parameter") - return + if type=='movie': + getFullMovieFieldsList(req) + + diff --git a/Contents/Code/plextvhelper.py b/Contents/Code/plextvhelper.py index de846fa..1ef6d44 100644 --- a/Contents/Code/plextvhelper.py +++ b/Contents/Code/plextvhelper.py @@ -6,6 +6,7 @@ # NAME variable must be defined in the calling unit, and is the name of the application # ###################################################################################################################### +import sys class plexTV(object): # Defaults used by the rest of the class @@ -27,26 +28,6 @@ def __init__(self): # Login to Plex.tv def login(self, user, pwd): Log.Info('Start to auth towards plex.tv') - - ''' - user = req.get_argument('user', '') - if user == '': - Log.Error('Missing username') - req.clear() - req.set_status(412) - req.finish("Missing username") - return req - pwd = req.get_argument('pwd', '') - if pwd == '': - Log.Error('Missing password') - req.clear() - req.set_status(412) - req.finish("Missing password") - return req - ''' - - - # Got what we needed, so let's logon authString = String.Base64Encode('%s:%s' % (user, pwd)) self.myHeader['Authorization'] = 'Basic ' + authString try: @@ -54,11 +35,8 @@ def login(self, user, pwd): Log.Info('Authenticated towards plex.tv with success') return token except Ex.HTTPError, e: - Log.Critical('Login error: ' + str(e)) - req.clear() - req.set_status(e.code) - req.finish(e) - return (req, '') + Log.Critical('Login error: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) + return None ''' Is user the owner of the server? user identified by token diff --git a/Contents/Code/pms.py b/Contents/Code/pms.py index 05cb92a..38fc759 100644 --- a/Contents/Code/pms.py +++ b/Contents/Code/pms.py @@ -8,10 +8,9 @@ ###################################################################################################################### import shutil, os import time, json -import io +import io, sys from xml.etree import ElementTree - # Undate uasTypesCounters def updateUASTypesCounters(): try: @@ -38,46 +37,38 @@ def updateUASTypesCounters(): counter[bundleType] = {'installed': 1, 'total' : 1} Dict['uasTypes'] = counter Dict.Save() - except Exception, e: - print 'Fatal error happened in updateUASTypesCounters: ' + str(e) - Log.Debug('Fatal error happened in updateUASTypesCounters: ' + str(e)) + except Exception, e: + Log.Debug('Fatal error happened in updateUASTypesCounters: ' + str(e) + ' on line {}'.format(sys.exc_info()[-1].tb_lineno)) #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 - ''' - - - + def updateInstallDict(): # 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 :-) - installedBranch = Dict['installed'][installedBundle]['branch'] - installedDate = Dict['installed'][installedBundle]['date'] - # Add updated stuff to the dicts - Dict['PMS-AllBundleInfo'][uasBundles[installedBundle]]['branch'] = installedBranch - 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() + try: + installed = Dict['installed'].copy() + for installedBundle in 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 :-) + installedBranch = Dict['installed'][installedBundle]['branch'] + installedDate = Dict['installed'][installedBundle]['date'] + # Add updated stuff to the dicts + Dict['PMS-AllBundleInfo'][uasBundles[installedBundle]]['branch'] = installedBranch + Dict['PMS-AllBundleInfo'][uasBundles[installedBundle]]['date'] = installedDate + Dict['installed'][uasBundles[installedBundle]] = Dict['PMS-AllBundleInfo'][uasBundles[installedBundle]] + # Remove old stuff from the Dict + Dict['PMS-AllBundleInfo'].pop(installedBundle, None) + Dict['installed'].pop(installedBundle, None) + Dict.Save() + except Exception, e: + Log.Critical('Critical error in updateInstallDict while walking the gits: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) return try: @@ -102,11 +93,6 @@ def updateInstallDict(): jsonPMSAllBundleInfo = Dict['PMS-AllBundleInfo'][key] if 'branch' in jsonPMSAllBundleInfo: installBranch = Dict['PMS-AllBundleInfo'][key]['branch'] - - - - Log.Debug('Ged1: ' + installBranch) - if 'date' in jsonPMSAllBundleInfo: installDate = Dict['PMS-AllBundleInfo'][key]['date'] del git['repo'] @@ -115,14 +101,14 @@ def updateInstallDict(): Dict['PMS-AllBundleInfo'][key]['branch'] = installBranch Dict['PMS-AllBundleInfo'][key]['date'] = installDate except Exception, e: - Log.Critical('Critical error in updateInstallDict while walking the gits: ' + str(e)) + Log.Critical('Critical error in updateAllBundleInfoFromUAS1 while walking the gits: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) Dict.Save() updateUASTypesCounters() updateInstallDict() else: Log.Debug('UAS was sadly not present') except Exception, e: - Log.Critical('Fatal error happened in updateAllBundleInfoFromUAS: ' + str(e)) + Log.Critical('Fatal error happened in updateAllBundleInfoFromUAS: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) class pms(object): # Defaults used by the rest of the class @@ -157,6 +143,8 @@ def reqprocess(self, req): return self.getSectionLetterList(req) elif function == 'getSectionByLetter': return self.getSectionByLetter(req) + elif function == 'search': + return self.search(req) else: req.clear() req.set_status(412) @@ -204,6 +192,48 @@ def reqprocessPost(self, req): req.set_status(412) req.finish("Unknown function call") + ''' Search for a title ''' + def search(self, req): + Log.Info('Search called') + try: + title = req.get_argument('title', '_WT_missing_') + if title == '_WT_missing_': + req.clear() + req.set_status(412) + req.finish("Missing title parameter") + else: + url = 'http://127.0.0.1:32400/search?query=' + String.Quote(title) + result = {} + # Fetch search result from PMS + foundMedias = XML.ElementFromURL(url) + # Grap all movies from the result + for media in foundMedias.xpath('//Video'): + value = {} + value['title'] = media.get('title') + value['type'] = media.get('type') + value['section'] = media.get('librarySectionID') + key = media.get('ratingKey') + result[key] = value + # Grap results for TV-Shows + for media in foundMedias.xpath('//Directory'): + value = {} + value['title'] = media.get('title') + value['type'] = media.get('type') + value['section'] = media.get('librarySectionID') + key = media.get('ratingKey') + result[key] = value + Log.Info('Search returned: %s' %(result)) + req.clear() + req.set_status(200) + req.set_header('Content-Type', 'application/json; charset=utf-8') + req.finish(json.dumps(result)) + except Exception, e: + Log.Debug('Fatal error happened in search: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) + req.clear() + req.set_status(500) + req.set_header('Content-Type', 'application/json; charset=utf-8') + req.finish('Fatal error happened in search: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) + ''' 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)) @@ -214,16 +244,10 @@ def DelFromXML(self, fileName, attribute, value): 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 @@ -252,11 +276,11 @@ def getParts(self, req): self.set_status(e.code) self.finish(e) except Exception, e: - Log.Debug('Fatal error happened in getParts: ' + str(e)) + Log.Debug('Fatal error happened in getParts: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in getParts: ' + str(e)) + req.finish('Fatal error happened in getParts: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) # uploadFile def uploadFile(self, req): @@ -283,11 +307,11 @@ def uploadFile(self, req): req.set_status(200) req.finish("Upload ok") except Exception, e: - Log.Debug('Fatal error happened in uploadFile: ' + str(e)) + Log.Debug('Fatal error happened in uploadFile: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in uploadFile: ' + str(e)) + req.finish('Fatal error happened in uploadFile: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) # getAllBundleInfo def getAllBundleInfo(self, req): @@ -299,11 +323,11 @@ def getAllBundleInfo(self, req): 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)) + Log.Debug('Fatal error happened in getAllBundleInfo: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in getAllBundleInfo' + str(e)) + req.finish('Fatal error happened in getAllBundleInfo' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) # Delete Bundle def delBundle(self, req): @@ -324,11 +348,11 @@ def removeBundle(bundleName, bundleIdentifier, url): Log.Debug('Bundle directory name digested as: %s' %(bundleInstallDir)) shutil.rmtree(bundleInstallDir) except Exception, e: - Log.Critical("Unable to remove the bundle directory: " + str(e)) + Log.Critical("Unable to remove the bundle directory: " + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened when trying to remove the bundle directory: ' + str(e)) + req.finish('Fatal error happened when trying to remove the bundle directory: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) try: shutil.rmtree(bundleDataDir) except: @@ -376,11 +400,11 @@ def removeBundle(bundleName, bundleIdentifier, url): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish('Fatal error happened when trying to restart the system.bundle') except Exception, e: - Log.Debug('Fatal error happened in removeBundle: ' + str(e)) + Log.Debug('Fatal error happened in removeBundle: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in removeBundle' + str(e)) + req.finish('Fatal error happened in removeBundle' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) # Main function try: @@ -475,11 +499,11 @@ def delSub(self, req): 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)) + Log.Critical('Exception while deleting an agent based sub: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) 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)) + req.finish('Exception while deleting an agent based sub: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) retValues = {} retValues['FilePath']=filePath3 retValues['SymbLink']=filePath @@ -503,7 +527,7 @@ def delSub(self, req): req.finish(json.dumps(retVal)) except Exception, e: # Could not find req. subtitle - Log.Debug('Fatal error happened in delSub, when deleting %s : %s' %(filePath, str(e))) + Log.Debug('Fatal error happened in delSub, when deleting ' + filePath + ' : ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(404) req.set_header('Content-Type', 'application/json; charset=utf-8') @@ -516,11 +540,11 @@ def delSub(self, req): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish('Could not find req. subtitle') except Exception, e: - Log.Debug('Fatal error happened in delSub: ' + str(e)) + Log.Debug('Fatal error happened in delSub: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in delSub: ' + str(e)) + req.finish('Fatal error happened in delSub: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) ''' TVShow ''' def TVshow(self, req): @@ -816,11 +840,11 @@ def getSectionLetterList(self, req): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish(json.dumps(resultJson, sort_keys=True)) except Exception, e: - Log.Debug('Fatal error happened in getSectionLetterList ' + str(e)) + Log.Debug('Fatal error happened in getSectionLetterList ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in getSectionLetterList: ' + str(e)) + req.finish('Fatal error happened in getSectionLetterList: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) ''' get getSectionByLetter ''' def getSectionByLetter(self,req): @@ -873,17 +897,17 @@ def getSectionByLetter(self,req): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish(json.dumps(Section)) except Exception, e: - Log.Debug('Fatal error happened in getSectionByLetter: ' + str(e)) + Log.Debug('Fatal error happened in getSectionByLetter: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in getSectionByLetter: ' + str(e)) + req.finish('Fatal error happened in getSectionByLetter: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) except Exception, e: - Log.Debug('Fatal error happened in getSectionByLetter: ' + str(e)) + Log.Debug('Fatal error happened in getSectionByLetter: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in getSectionByLetter: ' + str(e)) + req.finish('Fatal error happened in getSectionByLetter: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) ''' get section ''' def getSection(self,req): diff --git a/Contents/Code/settings.py b/Contents/Code/settings.py index a30bfe2..cae94e9 100644 --- a/Contents/Code/settings.py +++ b/Contents/Code/settings.py @@ -5,7 +5,7 @@ # ###################################################################################################################### -import json +import json, sys class settings(object): @@ -80,6 +80,7 @@ def setPwd(self, req): req.finish("Old Password did not match") return req except Ex.HTTPError, e: + Log.Critical('Error in setPwd: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(e.code) req.finish(e) @@ -106,6 +107,7 @@ def putSetting(self, req): req.finish("Setting saved") return req except Ex.HTTPError, e: + Log.Critical('Error in putSetting: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(e.code) req.finish(e) @@ -133,6 +135,7 @@ def getSetting(self, req): req.finish(json.dumps('Setting not found')) return req except Ex.HTTPError, e: + Log.Critical('Error in getSetting: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(e.code) req.finish(e) @@ -155,6 +158,7 @@ def getSettings(self, req): req.finish(json.dumps(mySetting)) return req except Ex.HTTPError, e: + Log.Critical('Error in getSettings: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(e.code) req.finish(e) diff --git a/Contents/Code/webSrv.py b/Contents/Code/webSrv.py index 135fed9..f108234 100644 --- a/Contents/Code/webSrv.py +++ b/Contents/Code/webSrv.py @@ -30,7 +30,7 @@ from wt import wt -import os +import os, sys # Below used to find path of this file from inspect import getsourcefile @@ -152,54 +152,62 @@ def post(self): self.allow() Log.Info('All is good, we are authenticated') self.redirect('/') - # Let's start by checking if the server is online - if plexTV().auth2myPlex(): - token = '' - try: - # Authenticate - retVal = plexTV().isServerOwner(plexTV().login(user, pwd)) - self.clear() - if retVal == 0: - # All is good + else: + # Let's start by checking if the server is online + if plexTV().auth2myPlex(): + token = '' + try: + # Authenticate + login_token = plexTV().login(user, pwd) + if login_token == None: + Log.ERROR('Bad credentials detected, denying access') + self.clear() + self.set_status(401) + self.finish('Authentication error') + return self + retVal = plexTV().isServerOwner(login_token) + self.clear() + if retVal == 0: + # All is good + self.allow() + Log.Info('All is good, we are authenticated') + self.redirect('/') + elif retVal == 1: + # Server not found + Log.Info('Server not found on plex.tv') + self.set_status(404) + elif retVal == 2: + # Not the owner + Log.Info('USer is not the server owner') + self.set_status(403) + else: + # Unknown error + Log.Critical('Unknown error, when authenticating') + self.set_status(403) + except Ex.HTTPError, e: + Log.Critical('Exception in Login: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) + self.clear() + self.set_status(e.code) + self.finish(e) + return self + else: + Log.Info('Server is not online according to plex.tv') + # Server is offline + if Dict['password'] == '': + Log.Info('First local login, so we need to set the local password') + Dict['password'] = pwd + Dict['pwdset'] = True + Dict.Save self.allow() - Log.Info('All is good, we are authenticated') self.redirect('/') - elif retVal == 1: - # Server not found - Log.Info('Server not found on plex.tv') - self.set_status(404) - elif retVal == 2: - # Not the owner - Log.Info('USer is not the server owner') - self.set_status(403) - else: - # Unknown error - Log.Critical('Unknown error, when authenticating') - self.set_status(403) - except Ex.HTTPError, e: - Log.Critical('Exception in Login: ' + str(e)) - self.clear() - self.set_status(e.code) - self.finish(e) - return self - else: - Log.Info('Server is not online according to plex.tv') - # Server is offline - if Dict['password'] == '': - Log.Info('First local login, so we need to set the local password') - Dict['password'] = pwd - Dict['pwdset'] = True - Dict.Save - self.allow() - self.redirect('/') - elif Dict['password'] == pwd: - self.allow() - Log.Info('Local password accepted') - self.redirect('/') - elif Dict['password'] != pwd: - Log.Critical('Either local login failed, or PMS lost connection to plex.tv') - self.clear() - self.set_status(401) + elif Dict['password'] == pwd: + self.allow() + Log.Info('Local password accepted') + self.redirect('/') + elif Dict['password'] != pwd: + Log.Critical('Either local login failed, or PMS lost connection to plex.tv') + self.clear() + self.set_status(401) def allow(self): self.set_secure_cookie(NAME, Hash.MD5(Dict['SharedSecret']+Dict['password']), expires_days = None) diff --git a/Contents/Code/wt.py b/Contents/Code/wt.py index c96615b..7b90183 100644 --- a/Contents/Code/wt.py +++ b/Contents/Code/wt.py @@ -10,7 +10,7 @@ import glob import json -import shutil +import shutil, sys class wt(object): @@ -46,10 +46,15 @@ def reqprocessPost(self, req): # Reset WT to factory settings def reset(self, req): try: + Log.Info('Factory Reset called') cachePath = Core.storage.join_path(Core.app_support_path, 'Plug-in Support', 'Caches', 'com.plexapp.plugins.WebTools') dataPath = Core.storage.join_path(Core.app_support_path, 'Plug-in Support', 'Data', 'com.plexapp.plugins.WebTools') shutil.rmtree(cachePath) - shutil.rmtree(dataPath) + try: +# shutil.rmtree(dataPath) + Dict.Reset() + except: + Log.Critical('Fatal error in clearing dict during reset') # Restart system bundle HTTP.Request('http://127.0.0.1:32400/:/plugins/com.plexapp.plugins.WebTools/restart', cacheTime=0, immediate=True) req.clear() @@ -57,11 +62,11 @@ def reset(self, req): req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish('WebTools has been reset') except Exception, e: - Log.Debug('Fatal error happened in wt.reset: ' + str(e)) + Log.Debug('Fatal error happened in wt.reset: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in wt.reset: ' + str(e)) + req.finish('Fatal error happened in wt.reset: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) # Get a list of all css files in http/custom_themes def getCSS(self,req): @@ -74,18 +79,19 @@ def getCSS(self,req): req.set_status(204) else: for n,item in enumerate(myList): - myList[n] = item.replace(targetDir + '/','') + myList[n] = item.replace(targetDir,'') + myList[n] = myList[n][1:] Log.Debug('Returning %s' %(myList)) req.clear() req.set_status(200) req.set_header('Content-Type', 'application/json; charset=utf-8') req.finish(json.dumps(myList)) except Exception, e: - Log.Debug('Fatal error happened in getCSS: ' + str(e)) + Log.Debug('Fatal error happened in getCSS: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) req.clear() req.set_status(500) req.set_header('Content-Type', 'application/json; charset=utf-8') - req.finish('Fatal error happened in getCSS: ' + str(e)) + req.finish('Fatal error happened in getCSS: ' + str(e) + 'on line {}'.format(sys.exc_info()[-1].tb_lineno)) diff --git a/http/changelog.txt b/http/changelog.txt index 5228753..f242062 100644 --- a/http/changelog.txt +++ b/http/changelog.txt @@ -1,6 +1,21 @@ -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 ;-) +V2.3 DEV: + Fix: + #165 LV: Fixed issue with spaces in filenames + #166 UAS: Removed highlight of All after selecting category + #172 WT: Fixed issue with Factory reset + #176 UAS: Critical error in updateInstallDict + #178 WT: Fixed issue with intruder detection + + New: + #170 WT: Added a user guide from Trumpy81. URL is /manual/WebTools-User-Manual.pdf + #171 PMS: Added Search to the backend + #175 PMS: Autodownload Repo if json is missing + + +FRONTEND: + +V2.2 Release: + BACKEND: Fix: #115 UAS: Migration fails, if plugin directory contains hidden folder @@ -22,7 +37,7 @@ FRONTEND: #131 UAS: After changin category, focus is no longer set on inputbox #139 UAS: Unknown bundles without url won't be able to be uninstalled or re-installed. #140 UAS: Migrate now always goes back to showing ALL available bundles - #### Subtitlemgmt: Added more logging for uploads. And fixed error reported by Dane22 about uploads. (2016-03-04) + ## Subtitlemgmt: Added more logging for uploads. And fixed bug reported by Dane22 about uploads. #145 Subtitlemgmt: Fixed display of undefined when deleting sidecars. #159 LogViewer: Fix for incorrect highlights when searching for "Warn" #161 UAS: Partial fix, now retains correct coloring. @@ -32,12 +47,11 @@ FRONTEND: #142 LogViewer: Search for keywords, highlighting of the same. Jump to top. #133 Subtitlemgmt: Added GUI for uploading subtitles #112 Subtitlemgmt: Allow delete/view for agent subtitles aswell. - #### Subtitlemgmt: Now uses the new Language Module + ### Subtitlemgmt: Now uses the new Language Module #93 WT: Now has support for custom themes! First out is a draft of trumpy81 that needs to be finetuned by trumpy81 #160 WT: Implemented Factory Reset functionality. Available through the Options menu. #149 Subtitlemgmt: Implemented letters instead of straight up numbers for paging. - #### WT: Removed LogFiles from top menu, redirecting users to LogViewer instead. - #### WT: Themes are now applied on loginscreen aswell. + ### WT: Removed LogFiles from top menu, redirecting users to LogViewer instead. #### diff --git a/http/credits.txt b/http/credits.txt index 16b706d..38017de 100644 --- a/http/credits.txt +++ b/http/credits.txt @@ -2,6 +2,9 @@ Main Developers: Dane22 (Python, Backend) Dagalufh (JS/HTML, Frontend) +Custom theme's by: +trumpy81 + Beta Testers: OttoKerner sa2000 trumpy81 Xandi92 diff --git a/http/custom_themes/trumpy81-Aussie.css b/http/custom_themes/trumpy81-Aussie.css index f6e19b1..e731f08 100644 --- a/http/custom_themes/trumpy81-Aussie.css +++ b/http/custom_themes/trumpy81-Aussie.css @@ -1,10 +1,4 @@ -.color-primary-0 { color: #0033CC } /* Main Primary color */ -.color-primary-1 { color: #18150F } -.color-primary-2 { color: #666563 } -.color-primary-3 { color: #975C00 } -.color-primary-4 { color: #3F2700 } - .panel-default>.panel-heading {background-color: #f6894b; color: #68452c;} .navbar {margin-bottom: 0px;} @@ -33,6 +27,7 @@ th {background-color: #c28a44; color: #68452c;} .subtitle {padding: 0px;} .btn-default:hover {background-color: #f6894b; color: #68452c;} +.btn-default:focus {background-color: #f6894b; color: #68452c;} .btn-active {background-color: #f6894b; color: #68452c;} .modal-header {background-color: #f6894b; color: #68452c;} body {background-color: #f2e1d8; padding-top:70px; padding-bottom:70px;} @@ -41,6 +36,9 @@ body {background-color: #f2e1d8; padding-top:70px; padding-bottom:70px;} .table {margin-bottom:0px;} .smallfont {font-size: 10pt;} +select {background-color: #f2e1d8; color: #000000;} +input {background-color: #f2e1d8; color: #000000;} + .pagination>.active>span {background-color: #f6894b;color: #68452c; cursor: pointer;} .pagination>.active>span:hover {background-color: #68452c; color: #f6894b;cursor: pointer;} .pagination>li>span:hover {background-color: #f6894b; color: #68452c;cursor: pointer;} diff --git a/http/custom_themes/trumpy81-ItsBlue.css b/http/custom_themes/trumpy81-ItsBlue.css index 49149fd..1c3771b 100644 --- a/http/custom_themes/trumpy81-ItsBlue.css +++ b/http/custom_themes/trumpy81-ItsBlue.css @@ -1,10 +1,4 @@ -.color-primary-0 { color: #0033CC } /* Main Primary color */ -.color-primary-1 { color: #18150F } -.color-primary-2 { color: #666563 } -.color-primary-3 { color: #975C00 } -.color-primary-4 { color: #3F2700 } - .panel-default>.panel-heading {background-color: #002db3; color: #00ccff;} .navbar {margin-bottom: 0px;} @@ -33,6 +27,7 @@ th {background-color: #0033CC; color: #66CCFF;} .subtitle {padding: 0px;} .btn-default:hover {background-color: #66CCFF; color: #0033CC;} +.btn-default:focus {background-color: #FFFFFF; color: #000000;} .btn-active {background-color: #0033CC; color: #66CCFF;} .modal-header {background-color: #0033CC; color: #66CCFF;} body {background-color: #002699; padding-top:70px; padding-bottom:70px;} @@ -41,6 +36,9 @@ body {background-color: #002699; padding-top:70px; padding-bottom:70px;} .table {margin-bottom:0px;} .smallfont {font-size: 10pt;} +select {background-color: #e6f7ff; color: #000000;} +input {background-color: #e6f7ff; color: #000000;} + .pagination {margin: 0px;} .pagination>li>span {height: 31px;} .pagination>.active>span {background-color: #0033CC;color: #000099; cursor: pointer;} diff --git a/http/custom_themes/trumpy81-ItsGreen.css b/http/custom_themes/trumpy81-ItsGreen.css index 266a1eb..88df122 100644 --- a/http/custom_themes/trumpy81-ItsGreen.css +++ b/http/custom_themes/trumpy81-ItsGreen.css @@ -1,10 +1,4 @@ -.color-primary-0 { color: #009933 } /* Main Primary color */ -.color-primary-1 { color: #00FF00 } -.color-primary-2 { color: #66FF66 } -.color-primary-3 { color: #006600 } -.color-primary-4 { color: #009900 } - .panel-default>.panel-heading {background-color: #66FF66; color: #006600;} .navbar {margin-bottom: 0px;} @@ -33,6 +27,7 @@ th {background-color: #006600; color: #00FF00;} .subtitle {padding: 0px;} .btn-default:hover {background-color: #006600; color: #00FF00;} +.btn-default:focus {background-color: #FFFFFF; color: #000000;} .btn-active {background-color: #006600; color: #00FF00;} .modal-header {background-color: #006600; color: #00FF00;} body {background-color: #005500; padding-top:70px; padding-bottom:70px;} @@ -41,6 +36,9 @@ body {background-color: #005500; padding-top:70px; padding-bottom:70px;} .table {margin-bottom:0px;} .smallfont {font-size: 10pt;} +select {background-color: #CCFFCC; color: #000000;} +input {background-color: #CCFFCC; color: #000000;} + .pagination {margin: 0px;} .pagination>li>span {height: 31px;} .pagination>.active>span {background-color: #006600;color: #00FF00; cursor: pointer;} diff --git a/http/custom_themes/trumpy81-ItsPink.css b/http/custom_themes/trumpy81-ItsPink.css index e8e1347..c81dfd7 100644 --- a/http/custom_themes/trumpy81-ItsPink.css +++ b/http/custom_themes/trumpy81-ItsPink.css @@ -1,10 +1,4 @@ -.color-primary-0 { color: #ff4dd2 } /* Main Primary color */ -.color-primary-1 { color: #18150F } -.color-primary-2 { color: #666563 } -.color-primary-3 { color: #975C00 } -.color-primary-4 { color: #3F2700 } - .panel-default>.panel-heading {background-color: #ff4dd2; color: #800060;} .navbar {margin-bottom: 0px;} @@ -33,6 +27,7 @@ th {background-color: #ff4dd2; color: #800060;} .subtitle {padding: 0px;} .btn-default:hover {background-color: #ff4dd2; color: #cc0099;} +.btn-default:focus {background-color: #FFFFFF; color: #000000;} .btn-active {background-color: #ff4dd2; color: #800060;} .modal-header {background-color: #ff4dd2; color: #800060;} body {background-color: #ff80df; padding-top:70px; padding-bottom:70px;} @@ -41,6 +36,9 @@ body {background-color: #ff80df; padding-top:70px; padding-bottom:70px;} .table {margin-bottom:0px;} .smallfont {font-size: 10pt;} +select {background-color: #ffccf2; color: #000000;} +input {background-color: #ffccf2; color: #000000;} + .pagination {margin: 0px;} .pagination>li>span {height: 31px;} .pagination>.active>span {background-color: #ff4dd2;color: #ff80df; cursor: pointer;} diff --git a/http/custom_themes/trumpy81-ItsRed.css b/http/custom_themes/trumpy81-ItsRed.css index 01fb0eb..4f5da44 100644 --- a/http/custom_themes/trumpy81-ItsRed.css +++ b/http/custom_themes/trumpy81-ItsRed.css @@ -1,21 +1,15 @@ -.color-primary-0 { color: #FE9B00 } /* Main Primary color */ -.color-primary-1 { color: #18150F } -.color-primary-2 { color: #666563 } -.color-primary-3 { color: #975C00 } -.color-primary-4 { color: #3F2700 } - -.panel-default>.panel-heading {background-color: #ff0000; color: #330000;} +.panel-default>.panel-heading {background-color: #ff0000; color: #FFFFFF;} .navbar {margin-bottom: 0px;} -.navbar-default {background-color: #FF0000; color: #330000; border: 0px solid #e6e6e6;} +.navbar-default {background-color: #FF0000; color: #FFFFFF; border: 0px solid #e6e6e6;} -.navbar-default .navbar-nav>li>a {color: #330000;} +.navbar-default .navbar-nav>li>a {color: #FFFFFF;} .navbar-default .navbar-nav>li>a:hover{background-color: #ff6666; color: #d9d9d9;} .navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover {background-color: #ff6666; color: #d9d9d9;} -.navbar-default .navbar-brand {color: #330000;} +.navbar-default .navbar-brand {color: #FFFFFF;} .navbar-default .navbar-brand:hover {color: #d9d9d9; cursor: pointer;} .navbar-default .navbar-nav>.active>a {background-color: #555555; color: #022B53;} @@ -32,9 +26,10 @@ th {background-color: #FF0000; color: #d9d9d9;} .subtitle {padding: 0px;} -.btn-default:hover {background-color: #ff6666; color: #d9d9d9;} -.btn-active {background-color: #ff6666; color: #d9d9d9;} -.modal-header {background-color: #ff6666; color: #d9d9d9;} +.btn-default:hover {background-color: #ff0000; color: #d9d9d9;} +.btn-default:focus {background-color: #FFFFFF; color: #000000;} +.btn-active {background-color: #FF0000; color: #FFFFFF;} +.modal-header {background-color: #ff0000; color: #FFFFFF;} body {background-color: #ff6666; padding-top:70px; padding-bottom:70px;} .customlink {cursor: pointer;} @@ -42,6 +37,9 @@ body {background-color: #ff6666; padding-top:70px; padding-bottom:70px;} .table {margin-bottom:0px;} .smallfont {font-size: 10pt;} +select {background-color: #ffe6e6; color: #000000;} +input {background-color: #ffe6e6; color: #000000;} + .pagination {margin: 0px;} .pagination>li>span {height: 31px;} .pagination>.active>span {background-color: #ff6666;color: #d9d9d94; cursor: pointer;} diff --git a/http/custom_themes/trumpy81-Plex.css b/http/custom_themes/trumpy81-Plex.css index 2e9cee1..b35e785 100644 --- a/http/custom_themes/trumpy81-Plex.css +++ b/http/custom_themes/trumpy81-Plex.css @@ -1,10 +1,4 @@ -.color-primary-0 { color: #fe9b00 } /* Main Primary color */ -.color-primary-1 { color: #18150F } -.color-primary-2 { color: #666563 } -.color-primary-3 { color: #975C00 } -.color-primary-4 { color: #3F2700 } - .panel-default>.panel-heading {background-color: #262626; color: #fe9b00;} .navbar {margin-bottom: 0px;} @@ -32,15 +26,19 @@ th {background-color: #fe9b00; color: #262626;} .subtitle {padding: 0px;} -.btn-default:hover {background-color: #fe9b00; color: #FFFFFF;} -.btn-active {background-color: #fe9b00; color: #262626;} -.modal-header {background-color: #fe9b00; color: #262626;} +.btn-default:hover {background-color: #262626; color: #cc7e00;} +.btn-default:focus {background-color: #ffffff; color: #262626;} +.btn-active {background-color: #262626; color: #fe9b00;} +.modal-header {background-color: #262626; color: #fe9b00;} body {background-color: #1f1f1f; padding-top:70px; padding-bottom:70px;} .customlink {cursor: pointer;} .table {margin-bottom:0px;} .smallfont {font-size: 10pt;} +select {background-color: #b3b3b3; color: #000000;} +input {background-color: #b3b3b3; color: #000000;} + .pagination {margin: 0px;} .pagination>li>span {height: 31px;} .pagination>.active>span {background-color: #fe9b00;color: #262626; cursor: pointer;} diff --git a/http/custom_themes/trumpy81-Teal.css b/http/custom_themes/trumpy81-Teal.css index 7a8b2e8..f3b628a 100644 --- a/http/custom_themes/trumpy81-Teal.css +++ b/http/custom_themes/trumpy81-Teal.css @@ -1,11 +1,5 @@ -.color-primary-0 { color: #0082e6 } /* Main Primary color */ -.color-primary-1 { color: #18150F } -.color-primary-2 { color: #666563 } -.color-primary-3 { color: #975C00 } -.color-primary-4 { color: #3F2700 } - -.panel-default>.panel-heading {background-color: #0082e6; color: #003a66;} +.panel-default>.panel-heading {background-color: #0082e6; color: #ffff99;} .navbar {margin-bottom: 0px;} @@ -19,7 +13,7 @@ .navbar-default .navbar-brand:hover {color: #ffff99; cursor: pointer;} .navbar-default .navbar-nav>.active>a {background-color: #0082e6; color: #ffff99;} -.navbar-default .navbar-nav>.active>a:hover {background-color: #0A457F; color: #FFFFFF;} +.navbar-default .navbar-nav>.active>a:hover {background-color: #0082e6; color: #FFFFFF;} .panel-body {background-color: #FFFFFF; padding: 8px;} @@ -33,14 +27,18 @@ th {background-color: #00477e; color: #FFFFFF;} .subtitle {padding: 0px;} .btn-default:hover {background-color: #0082e6; color: #ffff99;} +.btn-default:focus {background-color: #FFFFFF; color: #000000;} .btn-active {background-color: #0082e6; color: #ffff99;} -.modal-header {background-color: #0082e6; color: #003a66;} +.modal-header {background-color: #0082e6; color: #ffff99;} body {background-color: #0082e6; padding-top:70px; padding-bottom:70px;} .customlink {cursor: pointer;} .table {margin-bottom:0px;} .smallfont {font-size: 10pt;} +select {background-color: #cce9ff; color: #000000;} +input {background-color: #cce9ff; color: #000000;} + .pagination {margin: 0px;} .pagination>li>span {height: 31px;} .pagination>.active>span {background-color: #0082e6;color: #003a66; cursor: pointer;} diff --git a/http/index.html b/http/index.html index b5063bb..9324fda 100755 --- a/http/index.html +++ b/http/index.html @@ -32,19 +32,32 @@ - Webtools - + WebTools +