Add schedule to library

This commit is contained in:
meisnate12 2021-12-17 22:02:24 -05:00
parent 203ee10ca9
commit 37c719e505
5 changed files with 127 additions and 97 deletions

View file

@ -369,91 +369,9 @@ class CollectionBuilder:
raise Failed(f"{self.Type} Error: schedule attribute is blank") raise Failed(f"{self.Type} Error: schedule attribute is blank")
else: else:
logger.debug(f"Value: {self.data[methods['schedule']]}") logger.debug(f"Value: {self.data[methods['schedule']]}")
skip_collection = True
schedule_list = util.get_list(self.data[methods["schedule"]])
next_month = self.current_time.replace(day=28) + timedelta(days=4)
last_day = next_month - timedelta(days=next_month.day)
for schedule in schedule_list:
run_time = str(schedule).lower()
if run_time.startswith(("day", "daily")):
skip_collection = False
elif run_time == "never":
self.schedule += f"\nNever scheduled to run"
elif run_time.startswith(("hour", "week", "month", "year", "range")):
match = re.search("\\(([^)]+)\\)", run_time)
if not match:
logger.error(f"{self.Type} Error: failed to parse schedule: {schedule}")
continue
param = match.group(1)
if run_time.startswith("hour"):
try: try:
if 0 <= int(param) <= 23: util.schedule_check(self.data[methods['schedule']], self.current_time, self.config.run_hour)
self.schedule += f"\nScheduled to run only on the {util.make_ordinal(int(param))} hour" except NotScheduled as e:
if self.config.run_hour == int(param):
skip_collection = False
else:
raise ValueError
except ValueError:
logger.error(f"{self.Type} Error: hourly schedule attribute {schedule} invalid must be an integer between 0 and 23")
elif run_time.startswith("week"):
if param.lower() not in util.days_alias:
logger.error(f"{self.Type} Error: weekly schedule attribute {schedule} invalid must be a day of the week i.e. weekly(Monday)")
continue
weekday = util.days_alias[param.lower()]
self.schedule += f"\nScheduled weekly on {util.pretty_days[weekday]}"
if weekday == self.current_time.weekday():
skip_collection = False
elif run_time.startswith("month"):
try:
if 1 <= int(param) <= 31:
self.schedule += f"\nScheduled monthly on the {util.make_ordinal(int(param))}"
if self.current_time.day == int(param) or (self.current_time.day == last_day.day and int(param) > last_day.day):
skip_collection = False
else:
raise ValueError
except ValueError:
logger.error(f"{self.Type} Error: monthly schedule attribute {schedule} invalid must be an integer between 1 and 31")
elif run_time.startswith("year"):
try:
if "/" in param:
opt = param.split("/")
month = int(opt[0])
day = int(opt[1])
self.schedule += f"\nScheduled yearly on {util.pretty_months[month]} {util.make_ordinal(day)}"
if self.current_time.month == month and (self.current_time.day == day or (self.current_time.day == last_day.day and day > last_day.day)):
skip_collection = False
else:
raise ValueError
except ValueError:
logger.error(f"{self.Type} Error: yearly schedule attribute {schedule} invalid must be in the MM/DD format i.e. yearly(11/22)")
elif run_time.startswith("range"):
match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])-(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param)
if not match:
logger.error(f"{self.Type} Error: range schedule attribute {schedule} invalid must be in the MM/DD-MM/DD format i.e. range(12/01-12/25)")
continue
def check_day(_m, _d):
if _m in [1, 3, 5, 7, 8, 10, 12] and _d > 31:
return _m, 31
elif _m in [4, 6, 9, 11] and _d > 30:
return _m, 30
elif _m == 2 and _d > 28:
return _m, 28
else:
return _m, _d
month_start, day_start = check_day(int(match.group(1)), int(match.group(2)))
month_end, day_end = check_day(int(match.group(3)), int(match.group(4)))
month_check, day_check = check_day(self.current_time.month, self.current_time.day)
check = datetime.strptime(f"{month_check}/{day_check}", "%m/%d")
start = datetime.strptime(f"{month_start}/{day_start}", "%m/%d")
end = datetime.strptime(f"{month_end}/{day_end}", "%m/%d")
self.schedule += f"\nScheduled between {util.pretty_months[month_start]} {util.make_ordinal(day_start)} and {util.pretty_months[month_end]} {util.make_ordinal(day_end)}"
if start <= check <= end if start < end else check <= end or check >= start:
skip_collection = False
else:
logger.error(f"{self.Type} Error: schedule attribute {schedule} invalid")
if len(self.schedule) == 0:
skip_collection = False
if skip_collection:
suffix = "" suffix = ""
if self.details["delete_not_scheduled"]: if self.details["delete_not_scheduled"]:
try: try:
@ -463,7 +381,7 @@ class CollectionBuilder:
suffix = f" and was deleted" suffix = f" and was deleted"
except Failed: except Failed:
suffix = f" and could not be found to delete" suffix = f" and could not be found to delete"
raise NotScheduled(f"{self.schedule}\n\nCollection {self.name} not scheduled to run{suffix}") raise NotScheduled(f"{e}\n\nCollection {self.name} not scheduled to run{suffix}")
self.collectionless = "plex_collectionless" in methods and not self.playlist self.collectionless = "plex_collectionless" in methods and not self.playlist

View file

@ -22,7 +22,7 @@ from modules.tautulli import Tautulli
from modules.tmdb import TMDb from modules.tmdb import TMDb
from modules.trakt import Trakt from modules.trakt import Trakt
from modules.tvdb import TVDb from modules.tvdb import TVDb
from modules.util import Failed from modules.util import Failed, NotScheduled
from modules.webhooks import Webhooks from modules.webhooks import Webhooks
from retrying import retry from retrying import retry
from ruamel import yaml from ruamel import yaml
@ -473,6 +473,8 @@ class ConfigFile:
self.libraries = [] self.libraries = []
libs = check_for_attribute(self.data, "libraries", throw=True) libs = check_for_attribute(self.data, "libraries", throw=True)
current_time = datetime.now()
for library_name, lib in libs.items(): for library_name, lib in libs.items():
if self.requested_libraries and library_name not in self.requested_libraries: if self.requested_libraries and library_name not in self.requested_libraries:
continue continue
@ -611,6 +613,18 @@ class ConfigFile:
else: else:
params["metadata_path"] = [("File", os.path.join(default_dir, f"{library_name}.yml"))] params["metadata_path"] = [("File", os.path.join(default_dir, f"{library_name}.yml"))]
params["default_dir"] = default_dir params["default_dir"] = default_dir
params["skip_library"] = False
if lib and "schedule" in lib:
if not lib["schedule"]:
raise Failed(f"Config Error: schedule attribute is blank")
else:
logger.debug(f"Value: {lib['schedule']}")
try:
util.schedule_check(lib["schedule"], current_time, self.run_hour)
except NotScheduled:
params["skip_library"] = True
params["plex"] = { params["plex"] = {
"url": check_for_attribute(lib, "url", parent="plex", var_type="url", default=self.general["plex"]["url"], req_default=True, save=False), "url": check_for_attribute(lib, "url", parent="plex", var_type="url", default=self.general["plex"]["url"], req_default=True, save=False),
"token": check_for_attribute(lib, "token", parent="plex", default=self.general["plex"]["token"], req_default=True, save=False), "token": check_for_attribute(lib, "token", parent="plex", default=self.general["plex"]["token"], req_default=True, save=False),

View file

@ -34,6 +34,7 @@ class Library(ABC):
self.name = params["name"] self.name = params["name"]
self.original_mapping_name = params["mapping_name"] self.original_mapping_name = params["mapping_name"]
self.metadata_path = params["metadata_path"] self.metadata_path = params["metadata_path"]
self.skip_library = params["skip_library"]
self.asset_depth = params["asset_depth"] self.asset_depth = params["asset_depth"]
self.asset_directory = params["asset_directory"] if params["asset_directory"] else [] self.asset_directory = params["asset_directory"] if params["asset_directory"] else []
self.default_dir = params["default_dir"] self.default_dir = params["default_dir"]

View file

@ -277,26 +277,26 @@ def is_locked(filepath):
file_object.close() file_object.close()
return locked return locked
def time_window(time_window): def time_window(tw):
today = datetime.now() today = datetime.now()
if time_window == "today": if tw == "today":
return f"{today:%Y-%m-%d}" return f"{today:%Y-%m-%d}"
elif time_window == "yesterday": elif tw == "yesterday":
return f"{today - timedelta(days=1):%Y-%m-%d}" return f"{today - timedelta(days=1):%Y-%m-%d}"
elif time_window == "this_week": elif tw == "this_week":
return f"{today:%Y-0%V}" return f"{today:%Y-0%V}"
elif time_window == "last_week": elif tw == "last_week":
return f"{today - timedelta(weeks=1):%Y-0%V}" return f"{today - timedelta(weeks=1):%Y-0%V}"
elif time_window == "this_month": elif tw == "this_month":
return f"{today:%Y-%m}" return f"{today:%Y-%m}"
elif time_window == "last_month": elif tw == "last_month":
return f"{today.year}-{today.month - 1 or 12}" return f"{today.year}-{today.month - 1 or 12}"
elif time_window == "this_year": elif tw == "this_year":
return f"{today.year}" return f"{today.year}"
elif time_window == "last_year": elif tw == "last_year":
return f"{today.year - 1}" return f"{today.year - 1}"
else: else:
return time_window return tw
def glob_filter(filter_in): def glob_filter(filter_in):
filter_in = filter_in.translate({ord("["): "[[]", ord("]"): "[]]"}) if "[" in filter_in else filter_in filter_in = filter_in.translate({ord("["): "[[]", ord("]"): "[]]"}) if "[" in filter_in else filter_in
@ -347,3 +347,96 @@ def is_string_filter(values, modifier, data):
if jailbreak: break if jailbreak: break
return (jailbreak and modifier in [".not", ".isnot"]) or (not jailbreak and modifier in ["", ".is", ".begins", ".ends", ".regex"]) return (jailbreak and modifier in [".not", ".isnot"]) or (not jailbreak and modifier in ["", ".is", ".begins", ".ends", ".regex"])
def schedule_check(data, current_time, run_hour):
skip_collection = True
schedule_list = get_list(data)
next_month = current_time.replace(day=28) + timedelta(days=4)
last_day = next_month - timedelta(days=next_month.day)
schedule_str = ""
for schedule in schedule_list:
run_time = str(schedule).lower()
if run_time.startswith(("day", "daily")):
skip_collection = False
elif run_time == "never":
schedule_str += f"\nNever scheduled to run"
elif run_time.startswith(("hour", "week", "month", "year", "range")):
match = re.search("\\(([^)]+)\\)", run_time)
if not match:
logger.error(f"Schedule Error: failed to parse schedule: {schedule}")
continue
param = match.group(1)
if run_time.startswith("hour"):
try:
if 0 <= int(param) <= 23:
schedule_str += f"\nScheduled to run only on the {make_ordinal(int(param))} hour"
if run_hour == int(param):
skip_collection = False
else:
raise ValueError
except ValueError:
logger.error(f"Schedule Error: hourly schedule attribute {schedule} invalid must be an integer between 0 and 23")
elif run_time.startswith("week"):
if param.lower() not in days_alias:
logger.error(f"Schedule Error: weekly schedule attribute {schedule} invalid must be a day of the week i.e. weekly(Monday)")
continue
weekday = days_alias[param.lower()]
schedule_str += f"\nScheduled weekly on {pretty_days[weekday]}"
if weekday == current_time.weekday():
skip_collection = False
elif run_time.startswith("month"):
try:
if 1 <= int(param) <= 31:
schedule_str += f"\nScheduled monthly on the {make_ordinal(int(param))}"
if current_time.day == int(param) or (
current_time.day == last_day.day and int(param) > last_day.day):
skip_collection = False
else:
raise ValueError
except ValueError:
logger.error(f"Schedule Error: monthly schedule attribute {schedule} invalid must be an integer between 1 and 31")
elif run_time.startswith("year"):
try:
if "/" in param:
opt = param.split("/")
month = int(opt[0])
day = int(opt[1])
schedule_str += f"\nScheduled yearly on {pretty_months[month]} {make_ordinal(day)}"
if current_time.month == month and (current_time.day == day or (
current_time.day == last_day.day and day > last_day.day)):
skip_collection = False
else:
raise ValueError
except ValueError:
logger.error(
f"Schedule Error: yearly schedule attribute {schedule} invalid must be in the MM/DD format i.e. yearly(11/22)")
elif run_time.startswith("range"):
match = re.match("^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])-(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$", param)
if not match:
logger.error(f"Schedule Error: range schedule attribute {schedule} invalid must be in the MM/DD-MM/DD format i.e. range(12/01-12/25)")
continue
def check_day(_m, _d):
if _m in [1, 3, 5, 7, 8, 10, 12] and _d > 31:
return _m, 31
elif _m in [4, 6, 9, 11] and _d > 30:
return _m, 30
elif _m == 2 and _d > 28:
return _m, 28
else:
return _m, _d
month_start, day_start = check_day(int(match.group(1)), int(match.group(2)))
month_end, day_end = check_day(int(match.group(3)), int(match.group(4)))
month_check, day_check = check_day(current_time.month, current_time.day)
check = datetime.strptime(f"{month_check}/{day_check}", "%m/%d")
start = datetime.strptime(f"{month_start}/{day_start}", "%m/%d")
end = datetime.strptime(f"{month_end}/{day_end}", "%m/%d")
schedule_str += f"\nScheduled between {pretty_months[month_start]} {make_ordinal(day_start)} and {pretty_months[month_end]} {make_ordinal(day_end)}"
if start <= check <= end if start < end else check <= end or check >= start:
skip_collection = False
else:
logger.error(f"Schedule Error: schedule attribute {schedule} invalid")
if len(schedule_str) == 0:
skip_collection = False
if skip_collection:
raise NotScheduled(schedule_str)

View file

@ -189,6 +189,10 @@ def start(attrs):
def update_libraries(config): def update_libraries(config):
global stats global stats
for library in config.libraries: for library in config.libraries:
if library.skip_library:
logger.info("")
util.separator(f"Skipping {library.name} Library")
continue
try: try:
os.makedirs(os.path.join(default_dir, "logs", library.mapping_name, "collections"), exist_ok=True) os.makedirs(os.path.join(default_dir, "logs", library.mapping_name, "collections"), exist_ok=True)
col_file_logger = os.path.join(default_dir, "logs", library.mapping_name, "library.log") col_file_logger = os.path.join(default_dir, "logs", library.mapping_name, "library.log")