Add schedule to library

pull/528/head
meisnate12 3 years ago
parent 203ee10ca9
commit 37c719e505

@ -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 try:
schedule_list = util.get_list(self.data[methods["schedule"]]) util.schedule_check(self.data[methods['schedule']], self.current_time, self.config.run_hour)
next_month = self.current_time.replace(day=28) + timedelta(days=4) except NotScheduled as e:
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:
if 0 <= int(param) <= 23:
self.schedule += f"\nScheduled to run only on the {util.make_ordinal(int(param))} hour"
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

@ -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),

@ -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"]

@ -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)

@ -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")

Loading…
Cancel
Save