mirror of
https://github.com/meisnate12/Plex-Meta-Manager
synced 2024-11-26 06:20:23 +00:00
Add schedule to library
This commit is contained in:
parent
203ee10ca9
commit
37c719e505
5 changed files with 127 additions and 97 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
113
modules/util.py
113
modules/util.py
|
@ -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…
Reference in a new issue