mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-09-26 15:26:13 +00:00
Env var - PAGE_WATCH_LIMIT enhancements (#4359)
This commit is contained in:
@@ -5,7 +5,8 @@ from changedetectionio.strtobool import strtobool
|
||||
from changedetectionio.validate_url import is_safe_valid_url
|
||||
|
||||
from flask import (
|
||||
flash
|
||||
flash,
|
||||
has_request_context
|
||||
)
|
||||
from flask_babel import gettext
|
||||
|
||||
@@ -661,9 +662,8 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore):
|
||||
# NOTE: dict() is shallow copy but safe since add_watch() deepcopies it
|
||||
with self.lock:
|
||||
extras = dict(self.data['watching'][uuid])
|
||||
new_uuid = self.add_watch(url=url, extras=extras)
|
||||
watch = self.data['watching'][new_uuid]
|
||||
return new_uuid
|
||||
# None when add_watch() refused it (e.g. PAGE_WATCH_LIMIT), having already flashed why
|
||||
return self.add_watch(url=url, extras=extras)
|
||||
|
||||
def url_exists(self, url):
|
||||
|
||||
@@ -679,6 +679,38 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore):
|
||||
self.__data['watching'][uuid].clear_watch()
|
||||
self.__data['watching'][uuid].commit()
|
||||
|
||||
@property
|
||||
def watch_limit(self):
|
||||
"""Total watches allowed by PAGE_WATCH_LIMIT, or None when there is no limit.
|
||||
|
||||
None means "unlimited" and is the normal case - the env var being absent, empty or
|
||||
unparseable all leave the limit switched off entirely. There is no default.
|
||||
"""
|
||||
limit = os.getenv('PAGE_WATCH_LIMIT')
|
||||
if not limit:
|
||||
return None
|
||||
try:
|
||||
return int(limit)
|
||||
except ValueError:
|
||||
logger.warning(f"Invalid PAGE_WATCH_LIMIT value: {limit}, ignoring limit check")
|
||||
return None
|
||||
|
||||
def watch_limit_reached(self):
|
||||
"""True when the limit is set and leaves no room for another watch.
|
||||
|
||||
add_watch() enforces this on its own, but it can only return None. Callers that can
|
||||
report something better - a 429 in the API, one flash instead of one per row in the
|
||||
importers - should check this first.
|
||||
"""
|
||||
limit = self.watch_limit
|
||||
return limit is not None and len(self.__data['watching']) >= limit
|
||||
|
||||
def watch_limit_message(self):
|
||||
"""The single wording for a blocked add, so every UI surface says the same thing."""
|
||||
return gettext("Watch limit reached ({current}/{limit} watches). Cannot add more watches.").format(
|
||||
current=len(self.__data['watching']), limit=self.watch_limit
|
||||
)
|
||||
|
||||
def add_watch(self, url, tag='', extras=None, tag_uuids=None, save_immediately=True, seed_data_dir=None):
|
||||
"""
|
||||
seed_data_dir: optional path to an existing directory (already in the watch's
|
||||
@@ -742,25 +774,21 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore):
|
||||
return False
|
||||
|
||||
if not is_safe_valid_url(url):
|
||||
from flask import has_request_context
|
||||
if has_request_context():
|
||||
flash(gettext('Watch protocol is not permitted or invalid URL format'), 'error')
|
||||
else:
|
||||
logger.error(f"add_watch: URL '{url}' is not permitted or invalid, skipping.")
|
||||
return None
|
||||
|
||||
# Check PAGE_WATCH_LIMIT if set
|
||||
page_watch_limit = os.getenv('PAGE_WATCH_LIMIT')
|
||||
if page_watch_limit:
|
||||
try:
|
||||
page_watch_limit = int(page_watch_limit)
|
||||
current_watch_count = len(self.__data['watching'])
|
||||
if current_watch_count >= page_watch_limit:
|
||||
logger.error(f"Watch limit reached: {current_watch_count}/{page_watch_limit} watches. Cannot add {url}")
|
||||
flash(gettext("Watch limit reached ({current}/{limit} watches). Cannot add more watches.").format(current=current_watch_count, limit=page_watch_limit), 'error')
|
||||
return None
|
||||
except ValueError:
|
||||
logger.warning(f"Invalid PAGE_WATCH_LIMIT value: {page_watch_limit}, ignoring limit check")
|
||||
# Backstop for PAGE_WATCH_LIMIT - every add path funnels through here, so nothing can
|
||||
# get past the limit even if a caller forgets to pre-check watch_limit_reached().
|
||||
if self.watch_limit_reached():
|
||||
logger.error(f"Watch limit reached: {len(self.__data['watching'])}/{self.watch_limit} watches. Cannot add {url}")
|
||||
# The CLI (-u) and the API's background import thread have no request context,
|
||||
# where flash() raises instead of reporting anything
|
||||
if has_request_context():
|
||||
flash(self.watch_limit_message(), 'error')
|
||||
return None
|
||||
|
||||
if tag and type(tag) == str:
|
||||
# Then it's probably a string of the actual tag by name, split and add it
|
||||
|
||||
Reference in New Issue
Block a user