Skip to content

Commit

Permalink
[#34] New Feature: programming Timers with full date, not only day of…
Browse files Browse the repository at this point in the history
… the week

- cleanup outdated timers at startup of Kodi
- Bugfixes
  • Loading branch information
Heckie75 committed Aug 18, 2024
1 parent 891e1e0 commit fac3c3d
Show file tree
Hide file tree
Showing 16 changed files with 699 additions and 43 deletions.
Binary file not shown.
2 changes: 2 additions & 0 deletions script.timers/addon.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import migration
import service
from resources.lib.utils import housekeeper

if __name__ == "__main__":

migration.migrate()
housekeeper.cleanup_outdated_timers()
service.run()
4 changes: 2 additions & 2 deletions script.timers/addon.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.timers" name="Timers" version="4.0.0-pre-202408171225" provider-name="Heckie">
<addon id="script.timers" name="Timers" version="4.0.0-pre-202408182230" provider-name="Heckie">
<requires>
<import addon="xbmc.python" version="3.0.0" />
</requires>
<extension point="xbmc.service" library="addon.py" />
<extension point="xbmc.service" library="addon.py" />
<extension point="xbmc.python.script" library="script.py" />
<extension point="kodi.context.item">
<menu id="kodi.core.main">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,18 @@ msgctxt "#32183"
msgid "always ask"
msgstr "jedes mal fragen"

msgctxt "#32190"
msgid "Housekeeping"
msgstr "Haushaltung"

msgctxt "#32191"
msgid "Cleanup outdated timers at startup"
msgstr "Räume vergangende Timer beim Starten auf"

msgctxt "#32192"
msgid "Cleanup missed timers when Kodi starts. This can happen if Kodi was not running when timer has been scheduled."
msgstr "Räume verpasste Timer auf wenn Kodi gestartet wird. Das passiert, wenn Kodi nicht lief während ein Timer programmiert war."

msgctxt "#32200"
msgid "Monday"
msgstr "Montag"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,18 @@ msgctxt "#32183"
msgid "always ask"
msgstr ""

msgctxt "#32190"
msgid "Housekeeping"
msgstr ""

msgctxt "#32191"
msgid "Cleanup outdated timers at startup"
msgstr ""

msgctxt "#32192"
msgid "Cleanup missed timers when Kodi starts. This can happen if Kodi was not running when timer has been scheduled."
msgstr ""

msgctxt "#32200"
msgid "Monday"
msgstr ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def _get_timer_preselection(self, timerid: int, label: str, path: str) -> 'tuple
startDate = datetime_utils.parse_xbmc_shortdate(
xbmc.getInfoLabel("ListItem.Date").split(" ")[0])

timer.set_timer_by_date(date=startDate.strftime("%Y-%m-%d"))
timer.set_timer_by_date(date=datetime_utils.to_date_str(startDate))
timer.start = xbmc.getInfoLabel("ListItem.StartTime")
duration = xbmc.getInfoLabel("ListItem.Duration")
if len(duration) == 5:
Expand Down
2 changes: 1 addition & 1 deletion script.timers/resources/lib/contextmenu/set_timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def ask_date(self, label: str, path: str, is_epg: bool, timer: Timer) -> str:
if date == "":
return None
else:
return datetime_utils.to_date_str(datetime_utils.parse_date_from_xbmcdialog(date).strftime("%Y-%m-%d"))
return datetime_utils.to_date_str(datetime_utils.parse_date_from_xbmcdialog(date))

def ask_starttime(self, label: str, path: str, is_epg: bool, timer: Timer) -> str:

Expand Down
2 changes: 1 addition & 1 deletion script.timers/resources/lib/timer/pause_timers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def reset_pause() -> None:
_set(from_=None, until=None)


def _set(from_: datetime, until: datetime | None) -> None:
def _set(from_: datetime, until: 'datetime | None') -> None:

if not until:
date_from = "2001-01-01"
Expand Down
8 changes: 4 additions & 4 deletions script.timers/resources/lib/timer/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

class Period:

def __init__(self, start: timedelta | datetime, end: timedelta | datetime) -> None:
def __init__(self, start: 'timedelta | datetime', end: 'timedelta | datetime') -> None:

if type(start) != type(end):
raise Exception(
"types of <start> and <end> must be identically!!!")

self.start: timedelta | datetime = start
self.end: timedelta | datetime = end
self.start: 'timedelta | datetime' = start
self.end: 'timedelta | datetime' = end

def _compareByWeekdays(self, period_start: timedelta, period_end: timedelta) -> 'tuple[timedelta,timedelta,timedelta]':

Expand Down Expand Up @@ -55,7 +55,7 @@ def compare(self, period: 'Period') -> 'tuple[timedelta,timedelta,timedelta]':
else:
return self._compareByDates(period.start, period.end)

def hit(self, timestamp: timedelta | datetime, base: datetime = None) -> 'tuple[timedelta,timedelta,bool]':
def hit(self, timestamp: 'timedelta | datetime', base: datetime = None) -> 'tuple[timedelta,timedelta,bool]':

if type(self.start) == timedelta and type(timestamp) == timedelta:
s, e, l = self._compareByWeekdays(timestamp, timestamp)
Expand Down
37 changes: 8 additions & 29 deletions script.timers/resources/lib/timer/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,42 +151,21 @@ def _find_item_index(self, storage: 'list[dict]', id: int) -> int:

return -1

def save_timer(self, timer: Timer) -> None:
def replace_storage(self, timers: 'list[Timer]') -> None:

timer.init()
storage = [timer.to_dict() for timer in timers]
self._save_to_storage(storage)

item = {
"days": timer.days,
"date": timer.date,
"duration": timer.duration,
"duration_offset": timer.duration_offset,
"end": timer.end,
"end_offset": timer.end_offset,
"end_type": timer.end_type,
"fade": timer.fade,
"id": timer.id,
"label": timer.label,
"media_action": timer.media_action,
"media_type": timer.media_type,
"notify": timer.notify,
"path": timer.path,
"priority": timer.priority,
"repeat": timer.repeat,
"resume": timer.resume,
"shuffle": timer.shuffle,
"start": timer.start,
"start_offset": timer.start_offset,
"system_action": timer.system_action,
"vol_min": timer.vol_min,
"vol_max": timer.vol_max
}
def save_timer(self, timer: Timer) -> None:

storage = self._load_from_storage()

timer.init()
idx = self._find_item_index(storage, timer.id)
if idx == -1:
storage.append(item)
storage.append(timer.to_dict())
else:
storage[idx] = item
storage[idx] = timer.to_dict()

self._save_to_storage(storage)

Expand Down
42 changes: 39 additions & 3 deletions script.timers/resources/lib/timer/timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def __init__(self, i: int) -> None:

def init(self) -> None:

def _build_end_time(start: timedelta | datetime, end_type: int, duration_timedelta: timedelta, end: str, end_offset=0, duration_offset=0) -> 'tuple[timedelta | datetime, timedelta | datetime]':
def _build_end_time(start: 'timedelta | datetime', end_type: int, duration_timedelta: timedelta, end: str, end_offset=0, duration_offset=0) -> 'tuple[timedelta | datetime, timedelta | datetime]':

if end_type == END_TYPE_DURATION:
end_time = start + duration_timedelta + \
Expand Down Expand Up @@ -368,9 +368,13 @@ def set_timer_by_date(self, date: str) -> None:
self.days = [TIMER_BY_DATE]
self.date = date

def to_timer_by_date(self, base: datetime) -> bool:
def to_timer_by_date(self, base: 'datetime | None') -> bool:

if self.is_weekly_timer():
if not base:
return False

elif self.is_weekly_timer():
self.date = ""
return False

elif self.is_timer_by_date():
Expand All @@ -393,6 +397,10 @@ def is_weekly_timer(self) -> bool:

return TIMER_WEEKLY in self.days

def is_off(self) -> bool:

return not self.days

def is_fading_timer(self) -> bool:

return self.fade != FADE_OFF and self.end_type != END_TYPE_NO
Expand Down Expand Up @@ -433,6 +441,34 @@ def is_system_execution_timer(self) -> bool:

return self.system_action != SYSTEM_ACTION_NONE

def to_dict(self) -> 'dict':

return {
"days": self.days,
"date": self.date,
"duration": self.duration,
"duration_offset": self.duration_offset,
"end": self.end,
"end_offset": self.end_offset,
"end_type": self.end_type,
"fade": self.fade,
"id": self.id,
"label": self.label,
"media_action": self.media_action,
"media_type": self.media_type,
"notify": self.notify,
"path": self.path,
"priority": self.priority,
"repeat": self.repeat,
"resume": self.resume,
"shuffle": self.shuffle,
"start": self.start,
"start_offset": self.start_offset,
"system_action": self.system_action,
"vol_min": self.vol_min,
"vol_max": self.vol_max
}

def __str__(self) -> str:

return "Timer[id=%i, label=%s, state=%s, prio=%i, days=%s, date=%s, start=%s:%02i, endtype=%s, duration=%s:%02i, end=%s:%02i, systemaction=%s, mediaaction=%s, path=%s, type=%s, repeat=%s, shuffle=%s, resume=%s, fade=%s, min=%i, max=%i, returnvol=%i, notify=%s]" % (self.id, self.label, ["waiting", "starting", "running", "ending"][self.state], self.priority,
Expand Down
2 changes: 1 addition & 1 deletion script.timers/resources/lib/utils/datetime_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def datetime_diff(t1: datetime, t2: datetime) -> int:
return int((t2 - t1).total_seconds())


def time_diff(t1: timedelta | datetime, t2: timedelta | datetime, base: datetime = None) -> int:
def time_diff(t1: 'timedelta | datetime', t2: 'timedelta | datetime', base: datetime = None) -> int:

def _datetimedelta_diff(dt1: datetime, td2: timedelta, base: datetime) -> int:

Expand Down
77 changes: 77 additions & 0 deletions script.timers/resources/lib/utils/housekeeper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from datetime import datetime

import xbmc
import xbmcaddon
from resources.lib.timer.storage import Storage
from resources.lib.timer.timer import TIMER_BY_DATE, Timer
from resources.lib.utils import datetime_utils

ACTION_NOTHING = 0
ACTION_UPDATE = 1
ACTION_DELETE = 2


def check_timer(timer: Timer, threshold: datetime) -> int:

if timer.is_weekly_timer() or timer.is_off():
return ACTION_NOTHING

timer.init()
timer.apply(dtd=datetime_utils.DateTimeDelta(threshold))
if timer.date == "":
timer.to_timer_by_date(timer.upcoming_event)
return ACTION_UPDATE

last_known_date = datetime_utils.parse_date_str(timer.date)
if timer.upcoming_event:
timer.date = datetime_utils.to_date_str(timer.upcoming_event)

weekdays_to_remove = list()
has_upcoming = False
for p in timer.periods:
s, e, hit = p.hit(threshold, last_known_date)
start = threshold + s
end = threshold + e
if not hit and end < threshold:
weekdays_to_remove.append(start.weekday())
elif hit and timer.is_timer_by_date():
return ACTION_NOTHING
elif threshold <= start <= end:
has_upcoming = True

timer.days = [day for day in timer.days if day not in weekdays_to_remove]
if not timer.days or timer.days == [TIMER_BY_DATE] and not has_upcoming:
return ACTION_DELETE

elif weekdays_to_remove:
return ACTION_UPDATE

return ACTION_NOTHING


def cleanup_outdated_timers() -> None:

addon = xbmcaddon.Addon()
if not addon.getSettingBool("clean_outdated"):
return

storage = Storage()
timers = storage.load_timers_from_storage()

updated_any = False
timers_to_remove = list()

now = datetime.today()
for timer in timers:
action = check_timer(timer, now)
if action == ACTION_UPDATE:
updated_any = True
elif action == ACTION_DELETE:
timers_to_remove.append(timer)

for timer in timers_to_remove:
xbmc.log(f"remove outdated timer: {str(timer)}", xbmc.LOGINFO)
timers.remove(timer)

if updated_any or timers_to_remove:
storage.replace_storage(timers)
3 changes: 2 additions & 1 deletion script.timers/resources/lib/utils/settings_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import xbmcaddon
import xbmcgui
from resources.lib.utils import housekeeper
from resources.lib.player.mediatype import VIDEO
from resources.lib.timer.storage import Storage
from resources.lib.timer.timer import (END_TYPE_NO, FADE_OFF,
Expand Down Expand Up @@ -178,7 +179,7 @@ def delete_timer() -> None:

def outdated_timers(t: Timer) -> bool:

return t.is_timer_by_date() and datetime_utils.parse_datetime_str(f"{t.date} {t.start}") < now
return housekeeper.check_timer(t, now) == housekeeper.ACTION_DELETE

timers, idx = select_timer(multi=True, preselect_strategy=outdated_timers)
if idx is None:
Expand Down
7 changes: 7 additions & 0 deletions script.timers/resources/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,13 @@
<visible>false</visible>
</setting>
</group>
<group id="g_database" label="32190">
<setting id="clean_outdated" type="boolean" label="32191" help="32192">
<level>3</level>
<default>false</default>
<control type="toggle" />
</setting>
</group>
</category>
<category id="c_extras" label="32002" help="">
<group id="g_extras" label="32002">
Expand Down
Loading

0 comments on commit fac3c3d

Please sign in to comment.