This commit is contained in:
2025-10-25 13:21:06 +02:00
parent eb57506d39
commit 033ffb21f5
8388 changed files with 484789 additions and 16 deletions

View File

@@ -0,0 +1 @@
# Dummy file to make this directory a package.

View File

@@ -0,0 +1,143 @@
import json
from kodi_six import xbmc, xbmcvfs
from . import utils as utils
class CronSchedule:
expression = ''
name = 'library'
timer_type = 'xbmc'
command = {'method': 'VideoLibrary.Scan', 'params': {'showdialogs': True}}
next_run = 0
on_delay = False # used to defer processing until after player finishes
def executeCommand(self):
jsonCommand = {'jsonrpc': '2.0', 'method': self.command['method'], 'params': self.command['params'], 'id': 44}
utils.log(json.dumps(jsonCommand))
xbmc.executeJSONRPC(json.dumps(jsonCommand))
def cleanLibrarySchedule(self, selectedIndex):
if(selectedIndex == 1):
# once per day
return "* * *"
elif (selectedIndex == 2):
# once per week
return "* * 0"
else:
# once per month
return "1 * *"
class CustomPathFile:
jsonFile = xbmcvfs.translatePath(utils.data_dir() + "custom_paths.json")
paths = None
contentType = 'video' # all by default
def __init__(self, contentType):
self.paths = []
self.contentType = contentType
# try and read in the custom file
self._readFile()
def getSchedules(self, showDialogs=True):
schedules = []
# create schedules from the path information
for aPath in self.paths:
if(self.contentType == aPath['content']):
schedules.append(self._createSchedule(aPath, showDialogs))
return schedules
def addPath(self, path):
path['id'] = self._getNextId()
self.paths.append(path)
# save the file
self._writeFile()
def deletePath(self, aKey):
# find the given key
index = -1
for i in range(0, len(self.paths)):
if(self.paths[i]['id'] == aKey):
index = i
# if found, delete it
if(i != -1):
del self.paths[index]
# save the file
self._writeFile()
def getPaths(self):
result = []
for aPath in self.paths:
# if type matches the one we want
if(self.contentType == 'all' or self.contentType == aPath['content']):
result.append(aPath)
return result
def _getNextId(self):
result = 0
if(len(self.paths) > 0):
# sort ids, get highest one
maxId = sorted(self.paths, reverse=True, key=lambda k: k['id'])
result = maxId[0]['id']
return result + 1
def _writeFile(self):
# sort the ids
self.paths = sorted(self.paths, reverse=True, key=lambda k: k['id'])
# create the custom file
aFile = xbmcvfs.File(self.jsonFile, 'w')
aFile.write(json.dumps(self.paths))
aFile.close()
def _readFile(self):
if(xbmcvfs.exists(self.jsonFile)):
# read in the custom file
aFile = xbmcvfs.File(self.jsonFile)
# load paths in the format {path:path,expression:expression,content:type}
tempPaths = json.loads(aFile.read())
# update values in path
for aPath in tempPaths:
# old files are only video, update
if('content' not in aPath):
aPath['content'] = 'video'
if('id' not in aPath):
aPath['id'] = self._getNextId()
self.paths.append(aPath)
aFile.close()
else:
# write a blank file
self._writeFile()
def _createSchedule(self, aPath, showDialogs):
aSchedule = CronSchedule()
aSchedule.name = aPath['path']
# command depends on content type
if(aPath['content'] == 'video'):
aSchedule.command = {'method': 'VideoLibrary.Scan', 'params': {'directory': aPath['path'], 'showdialogs': showDialogs}}
else:
aSchedule.command = {'method': 'AudioLibrary.Scan', 'params': {'directory': aPath['path'], 'showdialogs': showDialogs}}
aSchedule.expression = aPath['expression']
return aSchedule

View File

@@ -0,0 +1,301 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import re
from time import time, mktime
from datetime import datetime
from dateutil.relativedelta import relativedelta
search_re = re.compile(r'^([^-]+)-([^-/]+)(/(.*))?$')
only_int_re = re.compile(r'^\d+$')
any_int_re = re.compile(r'^\d+')
star_or_int_re = re.compile(r'^(\d+|\*)$')
__all__ = ('croniter',)
class croniter(object):
RANGES = (
(0, 59),
(0, 23),
(1, 31),
(1, 12),
(0, 6),
(0, 59)
)
DAYS = (
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
)
ALPHACONV = (
{ },
{ },
{ },
{ 'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6,
'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12 },
{ 'sun':0, 'mon':1, 'tue':2, 'wed':3, 'thu':4, 'fri':5, 'sat':6 },
{ }
)
LOWMAP = (
{},
{},
{0: 1},
{0: 1},
{7: 0},
{},
)
bad_length = 'Exactly 5 or 6 columns has to be specified for iterator' \
'expression.'
def __init__(self, expr_format, start_time=time()):
if isinstance(start_time, datetime):
start_time = mktime(start_time.timetuple())
self.cur = start_time
self.exprs = expr_format.split()
if len(self.exprs) != 5 and len(self.exprs) != 6:
raise ValueError(self.bad_length)
expanded = []
for i, expr in enumerate(self.exprs):
e_list = expr.split(',')
res = []
while len(e_list) > 0:
e = e_list.pop()
t = re.sub(r'^\*(/.+)$', r'%d-%d\1' % (self.RANGES[i][0],
self.RANGES[i][1]),
str(e))
m = search_re.search(t)
if m:
(low, high, step) = m.group(1), m.group(2), m.group(4) or 1
if not any_int_re.search(low):
low = self.ALPHACONV[i][low.lower()]
if not any_int_re.search(high):
high = self.ALPHACONV[i][high.lower()]
if (not low or not high or int(low) > int(high)
or not only_int_re.search(str(step))):
raise ValueError("[%s] is not acceptable" %expr_format)
for j in range(int(low), int(high)+1):
if j % int(step) == 0:
e_list.append(j)
else:
if not star_or_int_re.search(t):
t = self.ALPHACONV[i][t.lower()]
try:
t = int(t)
except:
pass
if t in self.LOWMAP[i]:
t = self.LOWMAP[i][t]
if t != '*' and (int(t) < self.RANGES[i][0] or
int(t) > self.RANGES[i][1]):
raise ValueError("[%s] is not acceptable, out of range" % expr_format)
res.append(t)
res.sort()
expanded.append(['*'] if (len(res) == 1 and res[0] == '*') else res)
self.expanded = expanded
def get_next(self, ret_type=float):
return self._get_next(ret_type, is_prev=False)
def get_prev(self, ret_type=float):
return self._get_next(ret_type, is_prev=True)
def _get_next(self, ret_type=float, is_prev=False):
expanded = self.expanded[:]
if ret_type not in (float, datetime):
raise TypeError("Invalid ret_type, only 'float' or 'datetime' " \
"is acceptable.")
if expanded[2][0] != '*' and expanded[4][0] != '*':
bak = expanded[4]
expanded[4] = ['*']
t1 = self._calc(self.cur, expanded, is_prev)
expanded[4] = bak
expanded[2] = ['*']
t2 = self._calc(self.cur, expanded, is_prev)
if not is_prev:
result = t1 if t1 < t2 else t2
else:
result = t1 if t1 > t2 else t2
else:
result = self._calc(self.cur, expanded, is_prev)
self.cur = result
if ret_type == datetime:
result = datetime.fromtimestamp(result)
return result
def _calc(self, now, expanded, is_prev):
if is_prev:
nearest_method = self._get_prev_nearest
nearest_diff_method = self._get_prev_nearest_diff
sign = -1
else:
nearest_method = self._get_next_nearest
nearest_diff_method = self._get_next_nearest_diff
sign = 1
offset = len(expanded) == 6 and 1 or 60
dst = now = datetime.fromtimestamp(now + sign * offset)
day, month, year = dst.day, dst.month, dst.year
current_year = now.year
DAYS = self.DAYS
def proc_month(d):
if expanded[3][0] != '*':
diff_month = nearest_diff_method(month, expanded[3], 12)
days = DAYS[month - 1]
if month == 2 and self.is_leap(year) == True:
days += 1
reset_day = days if is_prev else 1
if diff_month != None and diff_month != 0:
if is_prev:
d += relativedelta(months=diff_month)
else:
d += relativedelta(months=diff_month, day=reset_day,
hour=0, minute=0, second=0)
return True, d
return False, d
def proc_day_of_month(d):
if expanded[2][0] != '*':
days = DAYS[month - 1]
if month == 2 and self.is_leap(year) == True:
days += 1
diff_day = nearest_diff_method(d.day, expanded[2], days)
if diff_day != None and diff_day != 0:
if is_prev:
d += relativedelta(days=diff_day)
else:
d += relativedelta(days=diff_day, hour=0, minute=0, second=0)
return True, d
return False, d
def proc_day_of_week(d):
if expanded[4][0] != '*':
diff_day_of_week = nearest_diff_method(d.isoweekday() % 7, expanded[4], 7)
if diff_day_of_week != None and diff_day_of_week != 0:
if is_prev:
d += relativedelta(days=diff_day_of_week)
else:
d += relativedelta(days=diff_day_of_week, hour=0, minute=0, second=0)
return True, d
return False, d
def proc_hour(d):
if expanded[1][0] != '*':
diff_hour = nearest_diff_method(d.hour, expanded[1], 24)
if diff_hour != None and diff_hour != 0:
if is_prev:
d += relativedelta(hours = diff_hour)
else:
d += relativedelta(hours = diff_hour, minute=0, second=0)
return True, d
return False, d
def proc_minute(d):
if expanded[0][0] != '*':
diff_min = nearest_diff_method(d.minute, expanded[0], 60)
if diff_min != None and diff_min != 0:
if is_prev:
d += relativedelta(minutes = diff_min)
else:
d += relativedelta(minutes = diff_min, second=0)
return True, d
return False, d
def proc_second(d):
if len(expanded) == 6:
if expanded[5][0] != '*':
diff_sec = nearest_diff_method(d.second, expanded[5], 60)
if diff_sec != None and diff_sec != 0:
dst += relativedelta(seconds = diff_sec)
return True, d
else:
d += relativedelta(second = 0)
return False, d
if is_prev:
procs = [proc_second,
proc_minute,
proc_hour,
proc_day_of_week,
proc_day_of_month,
proc_month]
else:
procs = [proc_month,
proc_day_of_month,
proc_day_of_week,
proc_hour,
proc_minute,
proc_second]
while abs(year - current_year) <= 1:
next = False
for proc in procs:
(changed, dst) = proc(dst)
if changed:
next = True
break
if next:
continue
return mktime(dst.timetuple())
raise "failed to find prev date"
def _get_next_nearest(self, x, to_check):
small = [item for item in to_check if item < x]
large = [item for item in to_check if item >= x]
large.extend(small)
return large[0]
def _get_prev_nearest(self, x, to_check):
small = [item for item in to_check if item <= x]
large = [item for item in to_check if item > x]
small.reverse()
large.reverse()
small.extend(large)
return small[0]
def _get_next_nearest_diff(self, x, to_check, range_val):
for i, d in enumerate(to_check):
if d >= x:
return d - x
return to_check[0] - x + range_val
def _get_prev_nearest_diff(self, x, to_check, range_val):
candidates = to_check[:]
candidates.reverse()
for d in candidates:
if d <= x:
return d - x
return (candidates[0]) - x - range_val
def is_leap(self, year):
if year % 400 == 0 or (year % 4 == 0 and year % 100 != 0):
return True
else:
return False

View File

@@ -0,0 +1,424 @@
# -*- coding: cp1252 -*-
import time
from datetime import datetime
from kodi_six import xbmc, xbmcgui, xbmcvfs
from future.moves.urllib.request import urlopen, unquote
import json
import resources.lib.utils as utils
from resources.lib.croniter import croniter
from resources.lib.cronclasses import CronSchedule, CustomPathFile
UPGRADE_INT = 1 # to keep track of any upgrade notifications
class AutoUpdater:
last_run = 0
schedules = []
lock = False
monitor = None
def __init__(self):
utils.check_data_dir() # in case this directory does not exist yet
self.readLastRun()
# force and update on startup to create the array
self.createSchedules(True)
def runProgram(self):
self.monitor = UpdateMonitor(update_settings=self.createSchedules, after_scan=self.databaseUpdated)
# a one-time catch for the startup delay
if(utils.getSettingInt("startup_delay") != 0):
count = 0
while count < len(self.schedules):
if(time.time() > self.schedules[count].next_run):
# we missed at least one update, fix this
self.schedules[count].next_run = time.time() + utils.getSettingInt("startup_delay") * 60
count = count + 1
utils.log(str(utils.getSettingInt('startup_delay')))
# display upgrade messages if they exist
if(utils.getSettingInt('upgrade_notes') < UPGRADE_INT):
xbmcgui.Dialog().ok(utils.getString(30000), utils.getString(30030))
utils.setSetting('upgrade_notes', str(UPGRADE_INT))
# program has started, check if we should show a notification
self.showNotify()
while(True):
# don't check unless new minute
if(time.time() > self.last_run + 60):
self.readLastRun()
self.evalSchedules()
# calculate the sleep time (next minute)
now = datetime.now()
if(self.monitor.waitForAbort(60 - now.second)):
break
# clean up monitor on exit
del self.monitor
def evalSchedules(self, manual=False):
if(not self.lock):
now = time.time()
count = 0
player = xbmc.Player()
while count < len(self.schedules):
cronJob = self.schedules[count]
if(cronJob.next_run <= now or manual):
if(not player.isPlaying() or utils.getSetting("run_during_playback") == "true"):
# check if run on idle is checked and screen is idle - disable this on manual run
if(not utils.getSettingBool('run_on_idle') or (utils.getSettingBool('run_on_idle') and (self.monitor.screensaver_running or manual))):
# check for valid network connection - check sources if setting enabled
if(self._networkUp() and (not utils.getSettingBool('check_sources') or (utils.getSettingBool('check_sources') and self._checkSources(cronJob)))):
# check if this scan was delayed due to playback
if(cronJob.on_delay):
# add another minute to the delay
self.schedules[count].next_run = now + 60
self.schedules[count].on_delay = False
utils.log(cronJob.name + " paused due to playback")
elif(not self.scanRunning()):
# run the command for this job
utils.log(cronJob.name)
if(cronJob.timer_type == 'xbmc'):
cronJob.executeCommand()
else:
self.cleanLibrary(cronJob)
# find the next run time
cronJob.next_run = self.calcNextRun(cronJob.expression, now)
self.schedules[count] = cronJob
elif(self.scanRunning()):
self.schedules[count].next_run = now + 60
utils.log("Waiting for other scan to finish")
else:
utils.log("Network down, not running")
else:
utils.log("Skipping scan, only run when idle")
else:
self.schedules[count].on_delay = True
utils.log("Player is running, wait until finished")
count = count + 1
# write last run time
now = time.time()
self.last_run = now - (now % 60)
def createSchedules(self, forceUpdate=False):
utils.log("update timers")
self.lock = True # lock so the eval portion does not run
self.schedules = []
showDialogs = utils.getSettingBool('notify_next_run') # if the user has selected to show dialogs for library operations
if(utils.getSettingBool('clean_libraries')):
# create clean schedule (if needed)
if(utils.getSettingInt("clean_timer") != 0):
if(utils.getSettingInt('library_to_clean') == 0 or utils.getSettingInt('library_to_clean') == 1):
# video clean schedule starts at 12am by default
aSchedule = CronSchedule()
aSchedule.name = utils.getString(30048)
aSchedule.timer_type = utils.__addon_id__
aSchedule.command = {'method': 'VideoLibrary.Clean', 'params': {'showdialogs': showDialogs}}
if(utils.getSettingInt("clean_timer") == 4):
aSchedule.expression = utils.getSetting("clean_video_cron_expression")
else:
aSchedule.expression = "0 0 " + aSchedule.cleanLibrarySchedule(utils.getSettingInt("clean_timer"))
aSchedule.next_run = self.calcNextRun(aSchedule.expression, time.time())
self.schedules.append(aSchedule)
if(utils.getSettingInt('library_to_clean') == 2 or utils.getSettingInt('library_to_clean') == 0):
# music clean schedule starts at 2am by default
aSchedule = CronSchedule()
aSchedule.name = utils.getString(30049)
aSchedule.timer_type = utils.__addon_id__
aSchedule.command = {'method': 'AudioLibrary.Clean', 'params': {'showdialogs': showDialogs}}
if(utils.getSettingInt("clean_timer") == 4):
aSchedule.expression = utils.getSetting("clean_music_cron_expression")
else:
aSchedule.expression = "0 2 " + aSchedule.cleanLibrarySchedule(utils.getSettingInt("clean_timer"))
aSchedule.next_run = self.calcNextRun(aSchedule.expression, time.time())
self.schedules.append(aSchedule)
if(utils.getSettingBool('update_video')):
utils.log("Creating timer for Video Library")
# create the video schedule
aSchedule = CronSchedule()
aSchedule.name = utils.getString(30012)
aSchedule.command = {'method': 'VideoLibrary.Scan', 'params': {'showdialogs': showDialogs}}
aSchedule.expression = self.checkTimer('video')
aSchedule.next_run = self.calcNextRun(aSchedule.expression, self.last_run)
self.schedules.append(aSchedule)
# add custom video paths (separate timers)
customPaths = CustomPathFile('video')
for aJob in customPaths.getSchedules(showDialogs):
utils.log("Creating timer " + aJob.name)
aJob.next_run = self.calcNextRun(aJob.expression, self.last_run)
self.schedules.append(aJob)
if(utils.getSettingBool('update_music')):
utils.log("Creating timer for Music Library")
# create the music schedule
aSchedule = CronSchedule()
aSchedule.name = utils.getString(30013)
aSchedule.command = {'method': 'AudioLibrary.Scan', 'params': {'showdialogs': showDialogs}}
aSchedule.expression = self.checkTimer('music')
aSchedule.next_run = self.calcNextRun(aSchedule.expression, self.last_run)
self.schedules.append(aSchedule)
# add custom music paths (separate timers)
customPaths = CustomPathFile('music')
for aJob in customPaths.getSchedules(showDialogs):
utils.log("Creating timer " + aJob.name)
aJob.next_run = self.calcNextRun(aJob.expression, self.last_run)
self.schedules.append(aJob)
# release the lock
self.lock = False
utils.log("Created " + str(len(self.schedules)) + " schedules", xbmc.LOGDEBUG)
# show any notifications
self.showNotify(not forceUpdate)
def checkTimer(self, settingName):
result = ''
utils.log(utils.getSetting(settingName + "_timer"))
# figure out if using standard or advanced timer
if(utils.getSettingBool(settingName + '_advanced_timer')):
# copy the expression
result = utils.getSetting(settingName + "_cron_expression")
else:
result = '0 */' + str(utils.getSetting(settingName + "_timer")) + ' * * *'
return result
def calcNextRun(self, cronExp, startTime):
nextRun = -1
try:
# create croniter for this expression
cron = croniter(cronExp, startTime)
nextRun = cron.get_next(float)
except ValueError:
# error in syntax
xbmcgui.Dialog().ok(utils.getString(30000), utils.getString(30016) % cronExp)
utils.log('Cron syntax error %s' % cronExp, xbmc.LOGDEBUG)
# rerun with a valid syntax
nextRun = self.calcNextRun('0 */2 * * *', startTime)
return nextRun
def showNotify(self, displayToScreen=True):
# go through and find the next schedule to run
next_run_time = CronSchedule()
for cronJob in self.schedules:
if(cronJob.next_run < next_run_time.next_run or next_run_time.next_run == 0):
next_run_time = cronJob
inWords = self.nextRunCountdown(next_run_time.next_run)
# show the notification (if applicable)
if(next_run_time.next_run > time.time() and utils.getSettingBool('notify_next_run') and displayToScreen):
utils.showNotification(utils.getString(30000), inWords + " - " + next_run_time.name)
return inWords
def nextRunCountdown(self, nextRun):
# compare now with next date
cronDiff = nextRun - time.time()
if cronDiff < 0:
return ""
hours = int((cronDiff / 60) / 60)
minutes = int(round(cronDiff / 60.0 - hours * 60))
# we always have at least one minute
if minutes == 0:
minutes = 1
result = str(hours) + " h " + str(minutes) + " m"
if hours == 0:
result = str(minutes) + " m"
elif hours > 36:
# just show the date instead
result = datetime.fromtimestamp(nextRun).strftime('%m/%d %I:%M%p')
elif hours > 24:
days = int(hours / 24)
hours = hours - days * 24
result = str(days) + " d " + str(hours) + " h " + str(minutes) + " m"
return result
def cleanLibrary(self, cronJob):
# check if we should verify with user first unless we're on 'clean after update'
if(utils.getSettingBool('user_confirm_clean') and utils.getSettingInt('clean_timer') != 0):
# user can decide 'no' here and exit this
runClean = xbmcgui.Dialog().yesno(utils.getString(30000), utils.getString(30052), line2=utils.getString(30053), autoclose=15000)
if(not runClean):
return
# run the clean operation
utils.log("Cleaning Database")
cronJob.executeCommand()
# write last run time, will trigger notifications
self.writeLastRun()
def readLastRun(self):
if(self.last_run == 0):
# read it in from the settings
if(xbmcvfs.exists(xbmcvfs.translatePath(utils.data_dir() + "last_run.txt"))):
runFile = xbmcvfs.File(xbmcvfs.translatePath(utils.data_dir() + "last_run.txt"))
try:
# there may be an issue with this file, we'll get it the next time through
self.last_run = float(runFile.read())
except ValueError:
self.last_run = 0
runFile.close()
else:
self.last_run = 0
def writeLastRun(self):
runFile = xbmcvfs.File(xbmcvfs.translatePath(utils.data_dir() + "last_run.txt"), 'w')
runFile.write(str(self.last_run))
runFile.close()
self.showNotify(True)
def scanRunning(self):
# check if any type of scan is currently running
if(xbmc.getCondVisibility('Library.IsScanningVideo') or xbmc.getCondVisibility('Library.IsScanningMusic')):
return True
else:
return False
def databaseUpdated(self, database):
showDialogs = utils.getSettingBool('notify_next_run') # if the user has selected to show dialogs for library operations
# check if we should clean the library
if(utils.getSettingBool('clean_libraries')):
# check if should update while playing media
if(not xbmc.Player().isPlaying() or utils.getSettingBool("run_during_playback")):
if(utils.getSettingInt("clean_timer") == 0):
# check if we should clean music, or video
aJob = CronSchedule()
aJob.name = utils.getString(30048)
aJob.timer_type = utils.__addon_id__
if((utils.getSettingInt('library_to_clean') == 0 or utils.getSettingInt('library_to_clean') == 1) and database == 'video'):
# create the clean job schedule
aJob.command = {'method': 'VideoLibrary.Clean', 'params': {'showdialogs': showDialogs}}
if((utils.getSettingInt('library_to_clean') == 2 or utils.getSettingInt('library_to_clean') == 0) and database == 'music'):
aJob.command = {'method': 'AudioLibrary.Clean', 'params': {'showdialogs': showDialogs}}
self.cleanLibrary(aJob)
# writeLastRun will trigger notifications
self.writeLastRun()
def _networkUp(self):
try:
urlopen('http://connectivitycheck.gstatic.com/generate_204', timeout=1)
return True
except Exception:
pass
return False
def _checkSources(self, aJob):
result = False
mediaType = 'video'
if(aJob.command['method'] == 'VideoLibrary.Scan' or aJob.command['method'] == 'AudioLibrary.Scan'):
# set the media type
if(aJob.command['method'] != 'VideoLibrary.Scan'):
mediaType = 'music'
if('directory' in aJob.command['params']):
# we have a specific path to check
result = self._sourceExists(aJob.command['params']['directory'])
else:
# check every video path
response = json.loads(xbmc.executeJSONRPC(json.dumps({'jsonrpc': '2.0', 'method': 'Files.GetSources', 'params': {'media': mediaType}, 'id': 44})))
# make sure we got something
if('result' in response):
for source in response['result']['sources']:
if(not self._sourceExists(source['file'])):
# one failure fails the whole thing
return False
# if we make it this far we got them all
result = True
else:
# must be a cleaning, skip this check since Kodi will do it
result = True
return result
def _sourceExists(self, source):
utils.log("checking: " + source)
# check if this is a multipath source
if(source.startswith('multipath://')):
# code adapted from xbmc source MultiPathDirectory.cpp
source = source[12:]
if(source[-1:] == "/"):
source = source[:-1]
splitSource = source.split('/')
if(len(splitSource) > 0):
for aSource in splitSource:
if not xbmcvfs.exists(unquote(aSource)):
# if one source in the multi does not exist, return false
return False
# if we make it here they all exist
return True
else:
return False
else:
return xbmcvfs.exists(source)
class UpdateMonitor(xbmc.Monitor):
update_settings = None
after_scan = None
screensaver_running = False
def __init__(self, *args, **kwargs):
xbmc.Monitor.__init__(self)
self.update_settings = kwargs['update_settings']
self.after_scan = kwargs['after_scan']
def onSettingsChanged(self):
xbmc.sleep(1000) # slight delay for notifications
self.update_settings()
def onScanFinished(self, database):
self.after_scan(database)
def onScreensaverActivated(self):
utils.log("screen saver on", xbmc.LOGDEBUG)
self.screensaver_running = True
def onScreensaverDeactivated(self):
utils.log("screen saver off", xbmc.LOGDEBUG)
self.screensaver_running = False

View File

@@ -0,0 +1,45 @@
from kodi_six import xbmc, xbmcgui, xbmcaddon, xbmcvfs
__addon_id__ = 'service.libraryautoupdate'
__Addon = xbmcaddon.Addon(__addon_id__)
def check_data_dir():
if(not xbmcvfs.exists(xbmcvfs.translatePath(data_dir()))):
xbmcvfs.mkdir(xbmcvfs.translatePath(data_dir()))
def data_dir():
return __Addon.getAddonInfo('profile')
def addon_dir():
return __Addon.getAddonInfo('path')
def log(message, loglevel=xbmc.LOGDEBUG):
xbmc.log(__addon_id__ + "-" + __Addon.getAddonInfo('version') + " : " + message, level=loglevel)
def showNotification(title, message):
xbmcgui.Dialog().notification(getString(30000), message, time=5000, icon=xbmcvfs.translatePath(__Addon.getAddonInfo('path') + "/resources/media/icon.png"), sound=False)
def setSetting(name, value):
__Addon.setSettingString(name, value)
def getSetting(name):
return __Addon.getSetting(name)
def getSettingBool(name):
return bool(__Addon.getSettingBool(name))
def getSettingInt(name):
return __Addon.getSettingInt(name)
def getString(string_id):
return __Addon.getLocalizedString(string_id)