diff --git a/lib/python_libtorrent/linux_x86_64/1.1.1/libtorrent.so b/lib/python_libtorrent/linux_x86_64/1.1.1/libtorrent.so new file mode 100644 index 00000000..bbfecbad Binary files /dev/null and b/lib/python_libtorrent/linux_x86_64/1.1.1/libtorrent.so differ diff --git a/lib/schedule.py b/lib/schedule.py new file mode 100644 index 00000000..67fb19d4 --- /dev/null +++ b/lib/schedule.py @@ -0,0 +1,617 @@ +""" +Python job scheduling for humans. + +github.com/dbader/schedule + +An in-process scheduler for periodic jobs that uses the builder pattern +for configuration. Schedule lets you run Python functions (or any other +callable) periodically at pre-determined intervals using a simple, +human-friendly syntax. + +Inspired by Addam Wiggins' article "Rethinking Cron" [1] and the +"clockwork" Ruby module [2][3]. + +Features: + - A simple to use API for scheduling jobs. + - Very lightweight and no external dependencies. + - Excellent test coverage. + - Tested on Python 2.7, 3.5 and 3.6 + +Usage: + >>> import schedule + >>> import time + + >>> def job(message='stuff'): + >>> print("I'm working on:", message) + + >>> schedule.every(10).minutes.do(job) + >>> schedule.every(5).to(10).days.do(job) + >>> schedule.every().hour.do(job, message='things') + >>> schedule.every().day.at("10:30").do(job) + + >>> while True: + >>> schedule.run_pending() + >>> time.sleep(1) + +[1] https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ +[2] https://github.com/Rykian/clockwork +[3] https://adam.herokuapp.com/past/2010/6/30/replace_cron_with_clockwork/ +""" +try: + from collections.abc import Hashable +except ImportError: + from collections import Hashable +import datetime +import functools +import logging +import random +import re +import time + +logger = logging.getLogger('schedule') + + +class ScheduleError(Exception): + """Base schedule exception""" + pass + + +class ScheduleValueError(ScheduleError): + """Base schedule value error""" + pass + + +class IntervalError(ScheduleValueError): + """An improper interval was used""" + pass + + +class CancelJob(object): + """ + Can be returned from a job to unschedule itself. + """ + pass + + +class Scheduler(object): + """ + Objects instantiated by the :class:`Scheduler ` are + factories to create jobs, keep record of scheduled jobs and + handle their execution. + """ + def __init__(self): + self.jobs = [] + + def run_pending(self): + """ + Run all jobs that are scheduled to run. + + Please note that it is *intended behavior that run_pending() + does not run missed jobs*. For example, if you've registered a job + that should run every minute and you only call run_pending() + in one hour increments then your job won't be run 60 times in + between but only once. + """ + runnable_jobs = (job for job in self.jobs if job.should_run) + for job in sorted(runnable_jobs): + self._run_job(job) + + def run_all(self, delay_seconds=0): + """ + Run all jobs regardless if they are scheduled to run or not. + + A delay of `delay` seconds is added between each job. This helps + distribute system load generated by the jobs more evenly + over time. + + :param delay_seconds: A delay added between every executed job + """ + logger.info('Running *all* %i jobs with %is delay inbetween', + len(self.jobs), delay_seconds) + for job in self.jobs[:]: + self._run_job(job) + time.sleep(delay_seconds) + + def clear(self, tag=None): + """ + Deletes scheduled jobs marked with the given tag, or all jobs + if tag is omitted. + + :param tag: An identifier used to identify a subset of + jobs to delete + """ + if tag is None: + del self.jobs[:] + else: + self.jobs[:] = (job for job in self.jobs if tag not in job.tags) + + def cancel_job(self, job): + """ + Delete a scheduled job. + + :param job: The job to be unscheduled + """ + try: + self.jobs.remove(job) + except ValueError: + pass + + def every(self, interval=1): + """ + Schedule a new periodic job. + + :param interval: A quantity of a certain time unit + :return: An unconfigured :class:`Job ` + """ + job = Job(interval, self) + return job + + def _run_job(self, job): + ret = job.run() + if isinstance(ret, CancelJob) or ret is CancelJob: + self.cancel_job(job) + + @property + def next_run(self): + """ + Datetime when the next job should run. + + :return: A :class:`~datetime.datetime` object + """ + if not self.jobs: + return None + return min(self.jobs).next_run + + @property + def idle_seconds(self): + """ + :return: Number of seconds until + :meth:`next_run `. + """ + return (self.next_run - datetime.datetime.now()).total_seconds() + + +class Job(object): + """ + A periodic job as used by :class:`Scheduler`. + + :param interval: A quantity of a certain time unit + :param scheduler: The :class:`Scheduler ` instance that + this job will register itself with once it has + been fully configured in :meth:`Job.do()`. + + Every job runs at a given fixed time interval that is defined by: + + * a :meth:`time unit ` + * a quantity of `time units` defined by `interval` + + A job is usually created and returned by :meth:`Scheduler.every` + method, which also defines its `interval`. + """ + def __init__(self, interval, scheduler=None): + self.interval = interval # pause interval * unit between runs + self.latest = None # upper limit to the interval + self.job_func = None # the job job_func to run + self.unit = None # time units, e.g. 'minutes', 'hours', ... + self.at_time = None # optional time at which this job runs + self.last_run = None # datetime of the last run + self.next_run = None # datetime of the next run + self.period = None # timedelta between runs, only valid for + self.start_day = None # Specific day of the week to start on + self.tags = set() # unique set of tags for the job + self.scheduler = scheduler # scheduler to register with + + def __lt__(self, other): + """ + PeriodicJobs are sortable based on the scheduled time they + run next. + """ + return self.next_run < other.next_run + + def __str__(self): + return ( + "Job(interval={}, " + "unit={}, " + "do={}, " + "args={}, " + "kwargs={})" + ).format(self.interval, + self.unit, + self.job_func.__name__, + self.job_func.args, + self.job_func.keywords) + + def __repr__(self): + def format_time(t): + return t.strftime('%Y-%m-%d %H:%M:%S') if t else '[never]' + + def is_repr(j): + return not isinstance(j, Job) + + timestats = '(last run: %s, next run: %s)' % ( + format_time(self.last_run), format_time(self.next_run)) + + if hasattr(self.job_func, '__name__'): + job_func_name = self.job_func.__name__ + else: + job_func_name = repr(self.job_func) + args = [repr(x) if is_repr(x) else str(x) for x in self.job_func.args] + kwargs = ['%s=%s' % (k, repr(v)) + for k, v in self.job_func.keywords.items()] + call_repr = job_func_name + '(' + ', '.join(args + kwargs) + ')' + + if self.at_time is not None: + return 'Every %s %s at %s do %s %s' % ( + self.interval, + self.unit[:-1] if self.interval == 1 else self.unit, + self.at_time, call_repr, timestats) + else: + fmt = ( + 'Every %(interval)s ' + + ('to %(latest)s ' if self.latest is not None else '') + + '%(unit)s do %(call_repr)s %(timestats)s' + ) + + return fmt % dict( + interval=self.interval, + latest=self.latest, + unit=(self.unit[:-1] if self.interval == 1 else self.unit), + call_repr=call_repr, + timestats=timestats + ) + + @property + def second(self): + if self.interval != 1: + raise IntervalError('Use seconds instead of second') + return self.seconds + + @property + def seconds(self): + self.unit = 'seconds' + return self + + @property + def minute(self): + if self.interval != 1: + raise IntervalError('Use minutes instead of minute') + return self.minutes + + @property + def minutes(self): + self.unit = 'minutes' + return self + + @property + def hour(self): + if self.interval != 1: + raise IntervalError('Use hours instead of hour') + return self.hours + + @property + def hours(self): + self.unit = 'hours' + return self + + @property + def day(self): + if self.interval != 1: + raise IntervalError('Use days instead of day') + return self.days + + @property + def days(self): + self.unit = 'days' + return self + + @property + def week(self): + if self.interval != 1: + raise IntervalError('Use weeks instead of week') + return self.weeks + + @property + def weeks(self): + self.unit = 'weeks' + return self + + @property + def monday(self): + if self.interval != 1: + raise IntervalError('Use mondays instead of monday') + self.start_day = 'monday' + return self.weeks + + @property + def tuesday(self): + if self.interval != 1: + raise IntervalError('Use tuesdays instead of tuesday') + self.start_day = 'tuesday' + return self.weeks + + @property + def wednesday(self): + if self.interval != 1: + raise IntervalError('Use wednesdays instead of wednesday') + self.start_day = 'wednesday' + return self.weeks + + @property + def thursday(self): + if self.interval != 1: + raise IntervalError('Use thursdays instead of thursday') + self.start_day = 'thursday' + return self.weeks + + @property + def friday(self): + if self.interval != 1: + raise IntervalError('Use fridays instead of friday') + self.start_day = 'friday' + return self.weeks + + @property + def saturday(self): + if self.interval != 1: + raise IntervalError('Use saturdays instead of saturday') + self.start_day = 'saturday' + return self.weeks + + @property + def sunday(self): + if self.interval != 1: + raise IntervalError('Use sundays instead of sunday') + self.start_day = 'sunday' + return self.weeks + + def tag(self, *tags): + """ + Tags the job with one or more unique indentifiers. + + Tags must be hashable. Duplicate tags are discarded. + + :param tags: A unique list of ``Hashable`` tags. + :return: The invoked job instance + """ + if not all(isinstance(tag, Hashable) for tag in tags): + raise TypeError('Tags must be hashable') + self.tags.update(tags) + return self + + def at(self, time_str): + """ + Specify a particular time that the job should be run at. + + :param time_str: A string in one of the following formats: `HH:MM:SS`, + `HH:MM`,`:MM`, `:SS`. The format must make sense given how often + the job is repeating; for example, a job that repeats every minute + should not be given a string in the form `HH:MM:SS`. The difference + between `:MM` and `:SS` is inferred from the selected time-unit + (e.g. `every().hour.at(':30')` vs. `every().minute.at(':30')`). + :return: The invoked job instance + """ + if (self.unit not in ('days', 'hours', 'minutes') + and not self.start_day): + raise ScheduleValueError('Invalid unit') + if not isinstance(time_str, str): + raise TypeError('at() should be passed a string') + if self.unit == 'days' or self.start_day: + if not re.match(r'^([0-2]\d:)?[0-5]\d:[0-5]\d$', time_str): + raise ScheduleValueError('Invalid time format') + if self.unit == 'hours': + if not re.match(r'^([0-5]\d)?:[0-5]\d$', time_str): + raise ScheduleValueError(('Invalid time format for' + ' an hourly job')) + if self.unit == 'minutes': + if not re.match(r'^:[0-5]\d$', time_str): + raise ScheduleValueError(('Invalid time format for' + ' a minutely job')) + time_values = time_str.split(':') + if len(time_values) == 3: + hour, minute, second = time_values + elif len(time_values) == 2 and self.unit == 'minutes': + hour = 0 + minute = 0 + _, second = time_values + else: + hour, minute = time_values + second = 0 + if self.unit == 'days' or self.start_day: + hour = int(hour) + if not (0 <= hour <= 23): + raise ScheduleValueError('Invalid number of hours') + elif self.unit == 'hours': + hour = 0 + elif self.unit == 'minutes': + hour = 0 + minute = 0 + minute = int(minute) + second = int(second) + self.at_time = datetime.time(hour, minute, second) + return self + + def to(self, latest): + """ + Schedule the job to run at an irregular (randomized) interval. + + The job's interval will randomly vary from the value given + to `every` to `latest`. The range defined is inclusive on + both ends. For example, `every(A).to(B).seconds` executes + the job function every N seconds such that A <= N <= B. + + :param latest: Maximum interval between randomized job runs + :return: The invoked job instance + """ + self.latest = latest + return self + + def do(self, job_func, *args, **kwargs): + """ + Specifies the job_func that should be called every time the + job runs. + + Any additional arguments are passed on to job_func when + the job runs. + + :param job_func: The function to be scheduled + :return: The invoked job instance + """ + self.job_func = functools.partial(job_func, *args, **kwargs) + try: + functools.update_wrapper(self.job_func, job_func) + except AttributeError: + # job_funcs already wrapped by functools.partial won't have + # __name__, __module__ or __doc__ and the update_wrapper() + # call will fail. + pass + self._schedule_next_run() + self.scheduler.jobs.append(self) + return self + + @property + def should_run(self): + """ + :return: ``True`` if the job should be run now. + """ + return datetime.datetime.now() >= self.next_run + + def run(self): + """ + Run the job and immediately reschedule it. + + :return: The return value returned by the `job_func` + """ + logger.info('Running job %s', self) + ret = self.job_func() + self.last_run = datetime.datetime.now() + self._schedule_next_run() + return ret + + def _schedule_next_run(self): + """ + Compute the instant when this job should run next. + """ + if self.unit not in ('seconds', 'minutes', 'hours', 'days', 'weeks'): + raise ScheduleValueError('Invalid unit') + + if self.latest is not None: + if not (self.latest >= self.interval): + raise ScheduleError('`latest` is greater than `interval`') + interval = random.randint(self.interval, self.latest) + else: + interval = self.interval + + self.period = datetime.timedelta(**{self.unit: interval}) + self.next_run = datetime.datetime.now() + self.period + if self.start_day is not None: + if self.unit != 'weeks': + raise ScheduleValueError('`unit` should be \'weeks\'') + weekdays = ( + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + 'sunday' + ) + if self.start_day not in weekdays: + raise ScheduleValueError('Invalid start day') + weekday = weekdays.index(self.start_day) + days_ahead = weekday - self.next_run.weekday() + if days_ahead <= 0: # Target day already happened this week + days_ahead += 7 + self.next_run += datetime.timedelta(days_ahead) - self.period + if self.at_time is not None: + if (self.unit not in ('days', 'hours', 'minutes') + and self.start_day is None): + raise ScheduleValueError(('Invalid unit without' + ' specifying start day')) + kwargs = { + 'second': self.at_time.second, + 'microsecond': 0 + } + if self.unit == 'days' or self.start_day is not None: + kwargs['hour'] = self.at_time.hour + if self.unit in ['days', 'hours'] or self.start_day is not None: + kwargs['minute'] = self.at_time.minute + self.next_run = self.next_run.replace(**kwargs) + # If we are running for the first time, make sure we run + # at the specified time *today* (or *this hour*) as well + if not self.last_run: + now = datetime.datetime.now() + if (self.unit == 'days' and self.at_time > now.time() and + self.interval == 1): + self.next_run = self.next_run - datetime.timedelta(days=1) + elif self.unit == 'hours' \ + and self.at_time.minute > now.minute \ + or (self.at_time.minute == now.minute + and self.at_time.second > now.second): + self.next_run = self.next_run - datetime.timedelta(hours=1) + elif self.unit == 'minutes' \ + and self.at_time.second > now.second: + self.next_run = self.next_run - \ + datetime.timedelta(minutes=1) + if self.start_day is not None and self.at_time is not None: + # Let's see if we will still make that time we specified today + if (self.next_run - datetime.datetime.now()).days >= 7: + self.next_run -= self.period + + +# The following methods are shortcuts for not having to +# create a Scheduler instance: + +#: Default :class:`Scheduler ` object +default_scheduler = Scheduler() + +#: Default :class:`Jobs ` list +jobs = default_scheduler.jobs # todo: should this be a copy, e.g. jobs()? + + +def every(interval=1): + """Calls :meth:`every ` on the + :data:`default scheduler instance `. + """ + return default_scheduler.every(interval) + + +def run_pending(): + """Calls :meth:`run_pending ` on the + :data:`default scheduler instance `. + """ + default_scheduler.run_pending() + + +def run_all(delay_seconds=0): + """Calls :meth:`run_all ` on the + :data:`default scheduler instance `. + """ + default_scheduler.run_all(delay_seconds=delay_seconds) + + +def clear(tag=None): + """Calls :meth:`clear ` on the + :data:`default scheduler instance `. + """ + default_scheduler.clear(tag) + + +def cancel_job(job): + """Calls :meth:`cancel_job ` on the + :data:`default scheduler instance `. + """ + default_scheduler.cancel_job(job) + + +def next_run(): + """Calls :meth:`next_run ` on the + :data:`default scheduler instance `. + """ + return default_scheduler.next_run + + +def idle_seconds(): + """Calls :meth:`idle_seconds ` on the + :data:`default scheduler instance `. + """ + return default_scheduler.idle_seconds \ No newline at end of file diff --git a/platformcode/config.py b/platformcode/config.py index 707261fe..e94b0dc8 100644 --- a/platformcode/config.py +++ b/platformcode/config.py @@ -189,29 +189,7 @@ def get_all_settings_addon(): def open_settings(): - settings_pre = get_all_settings_addon() __settings__.openSettings() - settings_post = get_all_settings_addon() - - from specials import videolibrary - from platformcode import xbmc_videolibrary - if settings_pre.get('downloadpath', None) != settings_post.get('downloadpath', None): - xbmc_videolibrary.update_sources(settings_post.get('downloadpath', None), settings_pre.get('downloadpath', None)) - - # si se ha cambiado la ruta de la videoteca llamamos a comprobar directorios para que lo cree y pregunte - # automaticamente si configurar la videoteca - if settings_pre.get("videolibrarypath", None) != settings_post.get("videolibrarypath", None) or \ - settings_pre.get("folder_movies", None) != settings_post.get("folder_movies", None) or \ - settings_pre.get("folder_tvshows", None) != settings_post.get("folder_tvshows", None): - videolibrary.move_videolibrary(settings_pre.get("videolibrarypath", None), settings_post.get("videolibrarypath", None), - settings_pre.get("folder_movies", None), settings_post.get("folder_movies", None), - settings_pre.get("folder_tvshows", None), settings_post.get("folder_tvshows", None)) - - # si se ha puesto que se quiere autoconfigurar y se había creado el directorio de la videoteca - if not settings_pre.get("videolibrary_kodi", None) and settings_post.get("videolibrary_kodi", None): - xbmc_videolibrary.ask_set_content(silent=True) - elif settings_pre.get("videolibrary_kodi", None) and not settings_post.get("videolibrary_kodi", None): - xbmc_videolibrary.clean() def get_setting(name, channel="", server="", default=None): diff --git a/resources/language/English/strings.po b/resources/language/English/strings.po index 028ef826..fc3915ed 100644 --- a/resources/language/English/strings.po +++ b/resources/language/English/strings.po @@ -5928,7 +5928,7 @@ msgid "Logging" msgstr "" msgctxt "#70789" -msgid "* Change by opening the settings from KoD main menu" +msgid "" msgstr "" msgctxt "#70790" diff --git a/resources/language/Italian/strings.po b/resources/language/Italian/strings.po index bbbd3ad2..af2d7bd4 100644 --- a/resources/language/Italian/strings.po +++ b/resources/language/Italian/strings.po @@ -5928,8 +5928,8 @@ msgid "Logging" msgstr "Logging" msgctxt "#70789" -msgid "* Change by opening the settings from KoD main menu" -msgstr "* Cambia aprendo le impostazioni dal menu principale di KoD" +msgid "" +msgstr "" msgctxt "#70790" msgid "RAR download in progress" diff --git a/resources/settings.xml b/resources/settings.xml index 5603e8ce..bb6b3820 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -33,7 +33,6 @@ - @@ -95,7 +94,6 @@ - diff --git a/service.py b/service.py index c7e1e585..219093f4 100644 --- a/service.py +++ b/service.py @@ -1,426 +1,384 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------ -# Service for updating new episodes on library series -# ------------------------------------------------------------ - -import datetime, imp, math, threading, traceback, sys, glob, time - -from platformcode import config -try: - import xbmc, os - librerias = xbmc.translatePath(os.path.join(config.get_runtime_path(), 'lib')) - sys.path.append(librerias) -except: - import os - librerias = os.path.join(config.get_runtime_path(), 'lib') - sys.path.append(librerias) - - -from core import channeltools, filetools, videolibrarytools -from platformcode import logger -from platformcode import platformtools -from specials import videolibrary -from platformcode import updater - - -def update(path, p_dialog, i, t, serie, overwrite): - logger.info("Updating " + path) - insertados_total = 0 - - head_nfo, it = videolibrarytools.read_nfo(path + '/tvshow.nfo') - # videolibrarytools.check_renumber_options(it) - videolibrarytools.update_renumber_options(it, head_nfo, path) - category = serie.category - - # logger.debug("%s: %s" %(serie.contentSerieName,str(list_canales) )) - for channel, url in serie.library_urls.items(): - serie.channel = channel - serie.url = url - - ###### Redirección al canal NewPct1.py si es un clone, o a otro canal y url si ha intervención judicial - try: - head_nfo, it = videolibrarytools.read_nfo(path + '/tvshow.nfo') #Refresca el .nfo para recoger actualizaciones - if it.emergency_urls: - serie.emergency_urls = it.emergency_urls - serie.category = category - except: - logger.error(traceback.format_exc()) - - channel_enabled = channeltools.is_enabled(serie.channel) - - if channel_enabled: - - heading = config.get_localized_string(20000) - p_dialog.update(int(math.ceil((i + 1) * t)), heading, config.get_localized_string(60389) % (serie.contentSerieName, - serie.channel.capitalize())) - try: - pathchannels = filetools.join(config.get_runtime_path(), "channels", serie.channel + '.py') - logger.info("Cargando canal: " + pathchannels + " " + - serie.channel) - - if serie.library_filter_show: - serie.show = serie.library_filter_show.get(serie.channel, serie.contentSerieName) - - obj = imp.load_source(serie.channel, pathchannels) - itemlist = obj.episodios(serie) - - try: - if int(overwrite) == 3: - # Sobrescribir todos los archivos (tvshow.nfo, 1x01.nfo, 1x01 [canal].json, 1x01.strm, etc...) - insertados, sobreescritos, fallidos, notusedpath = videolibrarytools.save_tvshow(serie, itemlist) - #serie= videolibrary.check_season_playcount(serie, serie.contentSeason) - #if filetools.write(path + '/tvshow.nfo', head_nfo + it.tojson()): - # serie.infoLabels['playcount'] = serie.playcount - else: - insertados, sobreescritos, fallidos = videolibrarytools.save_episodes(path, itemlist, serie, - silent=True, - overwrite=overwrite) - #it = videolibrary.check_season_playcount(it, it.contentSeason) - #if filetools.write(path + '/tvshow.nfo', head_nfo + it.tojson()): - # serie.infoLabels['playcount'] = serie.playcount - insertados_total += insertados - - except Exception as ex: - logger.error("Error al guardar los capitulos de la serie") - template = "An exception of type %s occured. Arguments:\n%r" - message = template % (type(ex).__name__, ex.args) - logger.error(message) - - except Exception as ex: - logger.error("Error al obtener los episodios de: %s" % serie.show) - template = "An exception of type %s occured. Arguments:\n%r" - message = template % (type(ex).__name__, ex.args) - logger.error(message) - - else: - logger.debug("Canal %s no activo no se actualiza" % serie.channel) - - #Sincronizamos los episodios vistos desde la videoteca de Kodi con la de Alfa - try: - if config.is_xbmc(): #Si es Kodi, lo hacemos - from platformcode import xbmc_videolibrary - xbmc_videolibrary.mark_content_as_watched_on_alfa(path + '/tvshow.nfo') - except: - logger.error(traceback.format_exc()) - - return insertados_total > 0 - - -def check_for_update(overwrite=True): - logger.info("Update Series...") - p_dialog = None - serie_actualizada = False - update_when_finished = False - hoy = datetime.date.today() - estado_verify_playcount_series = False - - try: - if config.get_setting("update", "videolibrary") != 0 or overwrite: - config.set_setting("updatelibrary_last_check", hoy.strftime('%Y-%m-%d'), "videolibrary") - - heading = config.get_localized_string(60389) - p_dialog = platformtools.dialog_progress_bg(config.get_localized_string(20000), heading) - p_dialog.update(0, '') - show_list = [] - - for path, folders, files in filetools.walk(videolibrarytools.TVSHOWS_PATH): - show_list.extend([filetools.join(path, f) for f in files if f == "tvshow.nfo"]) - - if show_list: - t = float(100) / len(show_list) - - for i, tvshow_file in enumerate(show_list): - head_nfo, serie = videolibrarytools.read_nfo(tvshow_file) - path = filetools.dirname(tvshow_file) - - logger.info("serie=" + serie.contentSerieName) - p_dialog.update(int(math.ceil((i + 1) * t)), heading, serie.contentSerieName) - - #Verificamos el estado del serie.library_playcounts de la Serie por si está incompleto - try: - estado = False - #Si no hemos hecho la verificación o no tiene playcount, entramos - estado = config.get_setting("verify_playcount", "videolibrary") - if not estado or estado == False or not serie.library_playcounts: #Si no se ha pasado antes, lo hacemos ahora - serie, estado = videolibrary.verify_playcount_series(serie, path) #También se pasa si falta un PlayCount por completo - except: - logger.error(traceback.format_exc()) - else: - if estado: #Si ha tenido éxito la actualización... - estado_verify_playcount_series = True #... se marca para cambiar la opción de la Videoteca - - interval = int(serie.active) # Podria ser del tipo bool - - if not serie.active: - # si la serie no esta activa descartar - if not overwrite: - #Sincronizamos los episodios vistos desde la videoteca de Kodi con la de Alfa, aunque la serie esté desactivada - try: - if config.is_xbmc(): #Si es Kodi, lo hacemos - from platformcode import xbmc_videolibrary - xbmc_videolibrary.mark_content_as_watched_on_alfa(path + '/tvshow.nfo') - except: - logger.error(traceback.format_exc()) - - continue - - # obtenemos las fecha de actualizacion y de la proxima programada para esta serie - update_next = serie.update_next - if update_next: - y, m, d = update_next.split('-') - update_next = datetime.date(int(y), int(m), int(d)) - else: - update_next = hoy - - update_last = serie.update_last - if update_last: - y, m, d = update_last.split('-') - update_last = datetime.date(int(y), int(m), int(d)) - else: - update_last = hoy - - # si la serie esta activa ... - if overwrite or config.get_setting("updatetvshows_interval", "videolibrary") == 0: - # ... forzar actualizacion independientemente del intervalo - serie_actualizada = update(path, p_dialog, i, t, serie, overwrite) - if not serie_actualizada: - update_next = hoy + datetime.timedelta(days=interval) - - elif interval == 1 and update_next <= hoy: - # ...actualizacion diaria - serie_actualizada = update(path, p_dialog, i, t, serie, overwrite) - if not serie_actualizada and update_last <= hoy - datetime.timedelta(days=7): - # si hace una semana q no se actualiza, pasar el intervalo a semanal - interval = 7 - update_next = hoy + datetime.timedelta(days=interval) - - elif interval == 7 and update_next <= hoy: - # ...actualizacion semanal - serie_actualizada = update(path, p_dialog, i, t, serie, overwrite) - if not serie_actualizada: - if update_last <= hoy - datetime.timedelta(days=14): - # si hace 2 semanas q no se actualiza, pasar el intervalo a mensual - interval = 30 - - update_next += datetime.timedelta(days=interval) - - elif interval == 30 and update_next <= hoy: - # ...actualizacion mensual - serie_actualizada = update(path, p_dialog, i, t, serie, overwrite) - if not serie_actualizada: - update_next += datetime.timedelta(days=interval) - - if serie_actualizada: - update_last = hoy - update_next = hoy + datetime.timedelta(days=interval) - - head_nfo, serie = videolibrarytools.read_nfo(tvshow_file) #Vuelve a leer el.nfo, que ha sido modificado - if interval != int(serie.active) or update_next.strftime('%Y-%m-%d') != serie.update_next or update_last.strftime('%Y-%m-%d') != serie.update_last: - serie.update_last = update_last.strftime('%Y-%m-%d') - if update_next > hoy: - serie.update_next = update_next.strftime('%Y-%m-%d') - serie.active = interval - serie.channel = "videolibrary" - serie.action = "get_seasons" - filetools.write(tvshow_file, head_nfo + serie.tojson()) - - if serie_actualizada: - if config.get_setting("search_new_content", "videolibrary") == 0: - # Actualizamos la videoteca de Kodi: Buscar contenido en la carpeta de la serie - if config.is_xbmc() and config.get_setting("videolibrary_kodi"): - from platformcode import xbmc_videolibrary - xbmc_videolibrary.update(folder=filetools.basename(path)) - else: - update_when_finished = True - - if estado_verify_playcount_series: #Si se ha cambiado algún playcount, ... - estado = config.set_setting("verify_playcount", True, "videolibrary") #... actualizamos la opción de Videolibrary - - if config.get_setting("search_new_content", "videolibrary") == 1 and update_when_finished: - # Actualizamos la videoteca de Kodi: Buscar contenido en todas las series - if config.is_xbmc() and config.get_setting("videolibrary_kodi"): - from platformcode import xbmc_videolibrary - xbmc_videolibrary.update() - - p_dialog.close() - - else: - logger.info("No actualiza la videoteca, está desactivado en la configuración de alfa") - - except Exception as ex: - logger.error("Se ha producido un error al actualizar las series") - template = "An exception of type %s occured. Arguments:\n%r" - message = template % (type(ex).__name__, ex.args) - logger.error(message) - - if p_dialog: - p_dialog.close() - - from core.item import Item - item_dummy = Item() - videolibrary.list_movies(item_dummy, silent=True) - - -def start(thread=True): - if thread: - t = threading.Thread(target=start, args=[False]) - t.setDaemon(True) - t.start() - else: - import time - - update_wait = [0, 10000, 20000, 30000, 60000] - wait = update_wait[int(config.get_setting("update_wait", "videolibrary"))] - if wait > 0: - time.sleep(wait) - - if not config.get_setting("update", "videolibrary") == 2: - check_for_update(overwrite=False) - - # Se ejecuta ciclicamente - while True: - monitor_update() - time.sleep(3600) # cada hora - - -def monitor_update(): - update_setting = config.get_setting("update", "videolibrary") - - # "Actualizar "Una sola vez al dia" o "al inicar Kodi y al menos una vez al dia" - - if update_setting == 2 or update_setting == 3: - hoy = datetime.date.today() - last_check = config.get_setting("updatelibrary_last_check", "videolibrary") - if last_check: - y, m, d = last_check.split('-') - last_check = datetime.date(int(y), int(m), int(d)) - else: - last_check = hoy - datetime.timedelta(days=1) - - update_start = config.get_setting("everyday_delay", "videolibrary") * 4 - - # logger.info("Ultima comprobacion: %s || Fecha de hoy:%s || Hora actual: %s" % - # (last_check, hoy, datetime.datetime.now().hour)) - # logger.info("Atraso del inicio del dia: %i:00" % update_start) - - if last_check <= hoy and datetime.datetime.now().hour == int(update_start): - logger.info("Inicio actualizacion programada para las %s h.: %s" % (update_start, datetime.datetime.now())) - check_for_update(overwrite=False) - - if not config.dev_mode(): - period = float(config.get_setting('addon_update_timer')) * 3600 - curTime = time.time() - lastCheck = config.get_setting("updater_last_check", "videolibrary", '0') - if lastCheck: - lastCheck = float(lastCheck) - else: - lastCheck = 0 - - if curTime - lastCheck > period: - updated, needsReload = updater.check(background=True) - config.set_setting("updater_last_check", str(curTime), "videolibrary") - if needsReload: - xbmc.executescript(__file__) - exit(0) - -# always bypass al websites that use cloudflare at startup, so there's no need to wait 5 seconds when opened -def callCloudflare(): - from core import httptools, support - import json - channels_path = os.path.join(config.get_runtime_path(), "channels", '*.json') - channel_files = [os.path.splitext(os.path.basename(c))[0] for c in glob.glob(channels_path)] - for channel_name in channel_files: - channel_parameters = channeltools.get_channel_parameters(channel_name) - if 'cloudflare' in channel_parameters and channel_parameters["cloudflare"]: - channel = __import__('channels.%s' % channel_name, fromlist=["channels.%s" % channel_name]) - try: - channel.findhost() - except: - pass - httptools.downloadpage(channel.host) - - servers_path = os.path.join(config.get_runtime_path(), "servers", '*.json') - servers_files = glob.glob(servers_path) - for server in servers_files: - with open(server) as server: - server_parameters = json.load(server) - if 'cloudflare' in server_parameters and server_parameters["cloudflare"]: - patternUrl = server_parameters["find_videos"]["patterns"][0]["url"] - url = '/'.join(patternUrl.split('/')[:3]) - httptools.downloadpage(url) - - -def viewmodeMonitor(monitor): - import xbmcgui - while not monitor.abortRequested(): - time.sleep(0.5) - try: - currentModeName = xbmc.getInfoLabel('Container.Viewmode') - win = xbmcgui.Window(xbmcgui.getCurrentWindowId()) - currentMode = int(win.getFocusId()) - if currentModeName and 'plugin.video.kod' in xbmc.getInfoLabel('Container.FolderPath') and currentMode < 1000 and currentMode > 50: # inside addon and in itemlist view - content, Type = platformtools.getCurrentView() - defaultMode = int(config.get_setting('view_mode_%s' % content).split(',')[-1]) - if currentMode != defaultMode: - logger.info('viewmode changed: ' + currentModeName + '-' + str(currentMode) + ' - content: ' + content) - config.set_setting('view_mode_%s' % content, currentModeName + ', ' + str(currentMode)) - except: - logger.error(traceback.print_exc()) - - -if __name__ == "__main__": - # threading.Thread(target=callCloudflare()) - # Se ejecuta en cada inicio - import xbmc - import time - - # mark as stopped all downloads (if we are here, probably kodi just started) - from specials.downloads import stop_all - stop_all() - - update_wait = [0, 10000, 20000, 30000, 60000] - wait = update_wait[int(config.get_setting("update_wait", "videolibrary"))] - if wait > 0: - xbmc.sleep(wait) - - - # Verificar quick-fixes al abrirse Kodi, y dejarlo corriendo como Thread - if not config.dev_mode(): - updated, needsReload = updater.check(background=True) - config.set_setting("updater_last_check", str(time.time()), "videolibrary") - if needsReload: - xbmc.executescript(__file__) - exit(0) - - # Copia Custom code a las carpetas de Alfa desde la zona de Userdata - # from platformcode import custom_code - # custom_code.init() - from threading import Thread - Thread(target=viewmodeMonitor).start() - from servers import torrent - Thread(target=torrent.elementum_monitor).start() - - if not config.get_setting("update", "videolibrary") == 2: - check_for_update(overwrite=False) - - - # Se ejecuta ciclicamente - if config.get_platform(True)['num_version'] >= 14: - monitor = xbmc.Monitor() # For Kodi >= 14 - else: - monitor = None # For Kodi < 14 - - from threading import Thread - - Thread(target=viewmodeMonitor, args=(monitor,)).start() - - if monitor: - while not monitor.abortRequested(): - monitor_update() - if monitor.waitForAbort(3600): # cada hora - break - else: - while not xbmc.abortRequested: - monitor_update() - xbmc.sleep(3600) - +# -*- coding: utf-8 -*- +import datetime +import math +import os +import sys +import threading +import traceback +import xbmc +import xbmcgui +from platformcode import config +librerias = xbmc.translatePath(os.path.join(config.get_runtime_path(), 'lib')) +sys.path.insert(0, librerias) + +from core import videolibrarytools, filetools, channeltools +from lib import schedule +from platformcode import logger, platformtools, updater +from specials import videolibrary + + +def update(path, p_dialog, i, t, serie, overwrite): + logger.info("Updating " + path) + insertados_total = 0 + + head_nfo, it = videolibrarytools.read_nfo(path + '/tvshow.nfo') + # videolibrarytools.check_renumber_options(it) + videolibrarytools.update_renumber_options(it, head_nfo, path) + category = serie.category + + # logger.debug("%s: %s" %(serie.contentSerieName,str(list_canales) )) + for channel, url in serie.library_urls.items(): + serie.channel = channel + serie.url = url + + ###### Redirección al canal NewPct1.py si es un clone, o a otro canal y url si ha intervención judicial + try: + head_nfo, it = videolibrarytools.read_nfo(path + '/tvshow.nfo') #Refresca el .nfo para recoger actualizaciones + if it.emergency_urls: + serie.emergency_urls = it.emergency_urls + serie.category = category + except: + logger.error(traceback.format_exc()) + + channel_enabled = channeltools.is_enabled(serie.channel) + + if channel_enabled: + + heading = config.get_localized_string(20000) + p_dialog.update(int(math.ceil((i + 1) * t)), heading, config.get_localized_string(60389) % (serie.contentSerieName, + serie.channel.capitalize())) + try: + pathchannels = filetools.join(config.get_runtime_path(), "channels", serie.channel + '.py') + logger.info("Cargando canal: " + pathchannels + " " + + serie.channel) + + if serie.library_filter_show: + serie.show = serie.library_filter_show.get(serie.channel, serie.contentSerieName) + + obj = __import__('channels.%s' % serie.channel, fromlist=[pathchannels]) + + itemlist = obj.episodios(serie) + + try: + if int(overwrite) == 3: + # Sobrescribir todos los archivos (tvshow.nfo, 1x01.nfo, 1x01 [canal].json, 1x01.strm, etc...) + insertados, sobreescritos, fallidos, notusedpath = videolibrarytools.save_tvshow(serie, itemlist) + #serie= videolibrary.check_season_playcount(serie, serie.contentSeason) + #if filetools.write(path + '/tvshow.nfo', head_nfo + it.tojson()): + # serie.infoLabels['playcount'] = serie.playcount + else: + insertados, sobreescritos, fallidos = videolibrarytools.save_episodes(path, itemlist, serie, + silent=True, + overwrite=overwrite) + #it = videolibrary.check_season_playcount(it, it.contentSeason) + #if filetools.write(path + '/tvshow.nfo', head_nfo + it.tojson()): + # serie.infoLabels['playcount'] = serie.playcount + insertados_total += insertados + + except Exception as ex: + logger.error("Error al guardar los capitulos de la serie") + template = "An exception of type %s occured. Arguments:\n%r" + message = template % (type(ex).__name__, ex.args) + logger.error(message) + + except Exception as ex: + logger.error("Error al obtener los episodios de: %s" % serie.show) + template = "An exception of type %s occured. Arguments:\n%r" + message = template % (type(ex).__name__, ex.args) + logger.error(message) + + else: + logger.debug("Canal %s no activo no se actualiza" % serie.channel) + + #Sincronizamos los episodios vistos desde la videoteca de Kodi con la de Alfa + try: + if config.is_xbmc(): #Si es Kodi, lo hacemos + from platformcode import xbmc_videolibrary + xbmc_videolibrary.mark_content_as_watched_on_alfa(path + '/tvshow.nfo') + except: + logger.error(traceback.format_exc()) + + return insertados_total > 0 + + +def check_for_update(overwrite=True): + logger.info("Update Series...") + p_dialog = None + serie_actualizada = False + update_when_finished = False + hoy = datetime.date.today() + estado_verify_playcount_series = False + + try: + if config.get_setting("update", "videolibrary") != 0 or overwrite: + config.set_setting("updatelibrary_last_check", hoy.strftime('%Y-%m-%d'), "videolibrary") + + heading = config.get_localized_string(60389) + p_dialog = platformtools.dialog_progress_bg(config.get_localized_string(20000), heading) + p_dialog.update(0, '') + show_list = [] + + for path, folders, files in filetools.walk(videolibrarytools.TVSHOWS_PATH): + show_list.extend([filetools.join(path, f) for f in files if f == "tvshow.nfo"]) + + if show_list: + t = float(100) / len(show_list) + + for i, tvshow_file in enumerate(show_list): + head_nfo, serie = videolibrarytools.read_nfo(tvshow_file) + path = filetools.dirname(tvshow_file) + + logger.info("serie=" + serie.contentSerieName) + p_dialog.update(int(math.ceil((i + 1) * t)), heading, serie.contentSerieName) + + #Verificamos el estado del serie.library_playcounts de la Serie por si está incompleto + try: + estado = False + #Si no hemos hecho la verificación o no tiene playcount, entramos + estado = config.get_setting("verify_playcount", "videolibrary") + if not estado or estado == False or not serie.library_playcounts: #Si no se ha pasado antes, lo hacemos ahora + serie, estado = videolibrary.verify_playcount_series(serie, path) #También se pasa si falta un PlayCount por completo + except: + logger.error(traceback.format_exc()) + else: + if estado: #Si ha tenido éxito la actualización... + estado_verify_playcount_series = True #... se marca para cambiar la opción de la Videoteca + + interval = int(serie.active) # Podria ser del tipo bool + + if not serie.active: + # si la serie no esta activa descartar + if not overwrite: + #Sincronizamos los episodios vistos desde la videoteca de Kodi con la de Alfa, aunque la serie esté desactivada + try: + if config.is_xbmc(): #Si es Kodi, lo hacemos + from platformcode import xbmc_videolibrary + xbmc_videolibrary.mark_content_as_watched_on_alfa(path + '/tvshow.nfo') + except: + logger.error(traceback.format_exc()) + + continue + + # obtenemos las fecha de actualizacion y de la proxima programada para esta serie + update_next = serie.update_next + if update_next: + y, m, d = update_next.split('-') + update_next = datetime.date(int(y), int(m), int(d)) + else: + update_next = hoy + + update_last = serie.update_last + if update_last: + y, m, d = update_last.split('-') + update_last = datetime.date(int(y), int(m), int(d)) + else: + update_last = hoy + + # si la serie esta activa ... + if overwrite or config.get_setting("updatetvshows_interval", "videolibrary") == 0: + # ... forzar actualizacion independientemente del intervalo + serie_actualizada = update(path, p_dialog, i, t, serie, overwrite) + if not serie_actualizada: + update_next = hoy + datetime.timedelta(days=interval) + + elif interval == 1 and update_next <= hoy: + # ...actualizacion diaria + serie_actualizada = update(path, p_dialog, i, t, serie, overwrite) + if not serie_actualizada and update_last <= hoy - datetime.timedelta(days=7): + # si hace una semana q no se actualiza, pasar el intervalo a semanal + interval = 7 + update_next = hoy + datetime.timedelta(days=interval) + + elif interval == 7 and update_next <= hoy: + # ...actualizacion semanal + serie_actualizada = update(path, p_dialog, i, t, serie, overwrite) + if not serie_actualizada: + if update_last <= hoy - datetime.timedelta(days=14): + # si hace 2 semanas q no se actualiza, pasar el intervalo a mensual + interval = 30 + + update_next += datetime.timedelta(days=interval) + + elif interval == 30 and update_next <= hoy: + # ...actualizacion mensual + serie_actualizada = update(path, p_dialog, i, t, serie, overwrite) + if not serie_actualizada: + update_next += datetime.timedelta(days=interval) + + if serie_actualizada: + update_last = hoy + update_next = hoy + datetime.timedelta(days=interval) + + head_nfo, serie = videolibrarytools.read_nfo(tvshow_file) #Vuelve a leer el.nfo, que ha sido modificado + if interval != int(serie.active) or update_next.strftime('%Y-%m-%d') != serie.update_next or update_last.strftime('%Y-%m-%d') != serie.update_last: + serie.update_last = update_last.strftime('%Y-%m-%d') + if update_next > hoy: + serie.update_next = update_next.strftime('%Y-%m-%d') + serie.active = interval + serie.channel = "videolibrary" + serie.action = "get_seasons" + filetools.write(tvshow_file, head_nfo + serie.tojson()) + + if serie_actualizada: + if config.get_setting("search_new_content", "videolibrary") == 0: + # Actualizamos la videoteca de Kodi: Buscar contenido en la carpeta de la serie + if config.is_xbmc() and config.get_setting("videolibrary_kodi"): + from platformcode import xbmc_videolibrary + xbmc_videolibrary.update(folder=filetools.basename(path)) + else: + update_when_finished = True + + if estado_verify_playcount_series: #Si se ha cambiado algún playcount, ... + estado = config.set_setting("verify_playcount", True, "videolibrary") #... actualizamos la opción de Videolibrary + + if config.get_setting("search_new_content", "videolibrary") == 1 and update_when_finished: + # Actualizamos la videoteca de Kodi: Buscar contenido en todas las series + if config.is_xbmc() and config.get_setting("videolibrary_kodi"): + from platformcode import xbmc_videolibrary + xbmc_videolibrary.update() + + p_dialog.close() + + else: + logger.info("No actualiza la videoteca, está desactivado en la configuración de alfa") + + except Exception as ex: + logger.error("Se ha producido un error al actualizar las series") + template = "An exception of type %s occured. Arguments:\n%r" + message = template % (type(ex).__name__, ex.args) + logger.error(message) + + if p_dialog: + p_dialog.close() + + from core.item import Item + item_dummy = Item() + videolibrary.list_movies(item_dummy, silent=True) + + +def viewmodeMonitor(): + try: + currentModeName = xbmc.getInfoLabel('Container.Viewmode') + win = xbmcgui.Window(xbmcgui.getCurrentWindowId()) + currentMode = int(win.getFocusId()) + if currentModeName and 'plugin.video.kod' in xbmc.getInfoLabel( + 'Container.FolderPath') and currentMode < 1000 and currentMode > 50: # inside addon and in itemlist view + content, Type = platformtools.getCurrentView() + defaultMode = int(config.get_setting('view_mode_%s' % content).split(',')[-1]) + if currentMode != defaultMode: + logger.info('viewmode changed: ' + currentModeName + '-' + str(currentMode) + ' - content: ' + content) + config.set_setting('view_mode_%s' % content, currentModeName + ', ' + str(currentMode)) + except: + logger.error(traceback.print_exc()) + + +def updaterCheck(): + # updater check + updated, needsReload = updater.check(background=True) + if needsReload: + xbmc.executescript(__file__) + exit(0) + + +def run_threaded(job_func, args): + job_thread = threading.Thread(target=job_func, args=args) + job_thread.start() + + +class AddonMonitor(xbmc.Monitor): + def __init__(self): + self.settings_pre = config.get_all_settings_addon() + + self.updaterPeriod = None + self.update_setting = None + self.update_hour = None + self.scheduleUpdater() + + # videolibrary wait + update_wait = [0, 10000, 20000, 30000, 60000] + wait = update_wait[int(config.get_setting("update_wait", "videolibrary"))] + if wait > 0: + xbmc.sleep(wait) + if not config.get_setting("update", "videolibrary") == 2: + check_for_update(overwrite=False) + run_threaded(check_for_update, (False,)) + self.scheduleVideolibrary() + super(AddonMonitor, self).__init__() + + def onSettingsChanged(self): + logger.info('settings changed') + settings_post = config.get_all_settings_addon() + from platformcode import xbmc_videolibrary + + if self.settings_pre.get('downloadpath', None) != settings_post.get('downloadpath', None): + xbmc_videolibrary.update_sources(settings_post.get('downloadpath', None), + self.settings_pre.get('downloadpath', None)) + + # si se ha cambiado la ruta de la videoteca llamamos a comprobar directorios para que lo cree y pregunte + # automaticamente si configurar la videoteca + if self.settings_pre.get("videolibrarypath", None) != settings_post.get("videolibrarypath", None) or \ + self.settings_pre.get("folder_movies", None) != settings_post.get("folder_movies", None) or \ + self.settings_pre.get("folder_tvshows", None) != settings_post.get("folder_tvshows", None): + videolibrary.move_videolibrary(self.settings_pre.get("videolibrarypath", None), + settings_post.get("videolibrarypath", None), + self.settings_pre.get("folder_movies", None), + settings_post.get("folder_movies", None), + self.settings_pre.get("folder_tvshows", None), + settings_post.get("folder_tvshows", None)) + + # si se ha puesto que se quiere autoconfigurar y se había creado el directorio de la videoteca + if not self.settings_pre.get("videolibrary_kodi", None) and settings_post.get("videolibrary_kodi", None): + xbmc_videolibrary.ask_set_content(silent=True) + elif self.settings_pre.get("videolibrary_kodi", None) and not settings_post.get("videolibrary_kodi", None): + xbmc_videolibrary.clean() + + if self.settings_pre.get('addon_update_timer') != settings_post.get('addon_update_timer'): + schedule.clear('updater') + self.scheduleUpdater() + + if self.update_setting != config.get_setting("update", "videolibrary") or self.update_hour != config.get_setting("everyday_delay", "videolibrary") * 4: + schedule.clear('videolibrary') + self.scheduleVideolibrary() + + self.settings_pre = settings_post + + def onScreensaverActivated(self): + logger.info('screensaver activated, un-scheduling screen-on jobs') + schedule.clear('screenOn') + + def onScreensaverDeactivated(self): + logger.info('screensaver deactivated, re-scheduling screen-on jobs') + schedule.every().second.do(viewmodeMonitor).tag('screenOn') + + def scheduleUpdater(self): + if not config.dev_mode(): + updaterCheck() + self.updaterPeriod = float(config.get_setting('addon_update_timer')) * 3600 + schedule.every(self.updaterPeriod).hour.do(updaterCheck).tag('updater') + logger.info('scheduled updater every ' + str(self.updaterPeriod) + ' hours') + + def scheduleVideolibrary(self): + self.update_setting = config.get_setting("update", "videolibrary") + # 2= daily 3=daily and when kodi starts + if self.update_setting == 2 or self.update_setting == 3: + self.update_hour = config.get_setting("everyday_delay", "videolibrary") * 4 + schedule.every().day.at(str(self.update_hour).zfill(2) + ':00').do(run_threaded, check_for_update, (False,)).tag('videolibrary') + logger.info('scheduled videolibrary at ' + str(self.update_hour).zfill(2) + ':00') + + +if __name__ == "__main__": + logger.info('Starting KoD service') + monitor = AddonMonitor() + + # mark as stopped all downloads (if we are here, probably kodi just started) + from specials.downloads import stop_all + stop_all() + + # other things + schedule.every().second.do(viewmodeMonitor).tag('screenOn') + + while True: + schedule.run_pending() + + if monitor.waitForAbort(1): # every second + break