Preserve watchlist scan timestamps for UI display instead of wiping on lookback changes

pull/253/head
Broque Thomas 2 months ago
parent 32f1cc946c
commit e3fdb12d78

@ -792,16 +792,33 @@ class WatchlistScanner:
time.sleep(0.3) # 300ms breathing room
# Determine cutoff date for filtering
cutoff_timestamp = last_scan_timestamp
# If no last scan timestamp, use lookback period setting
if cutoff_timestamp is None:
lookback_period = self._get_lookback_period_setting()
if lookback_period != 'all':
# Convert period to days and create cutoff date (use UTC)
days = int(lookback_period)
cutoff_timestamp = datetime.now(timezone.utc) - timedelta(days=days)
logger.info(f"Using lookback period: {lookback_period} days (cutoff: {cutoff_timestamp})")
lookback_period = self._get_lookback_period_setting()
# If lookback is 'all', always return everything regardless of scan timestamp
if lookback_period == 'all':
cutoff_timestamp = None
elif last_scan_timestamp is not None:
cutoff_timestamp = last_scan_timestamp
# Check if a lookback period change requires a one-time wider window
rescan_cutoff = self._get_rescan_cutoff()
if rescan_cutoff == 'all':
logger.info(f"Lookback period changed to 'all' — returning full discography")
cutoff_timestamp = None
elif rescan_cutoff is not None:
scan_ts = cutoff_timestamp
if scan_ts.tzinfo is None:
scan_ts = scan_ts.replace(tzinfo=timezone.utc)
if rescan_cutoff.tzinfo is None:
rescan_cutoff = rescan_cutoff.replace(tzinfo=timezone.utc)
if rescan_cutoff < scan_ts:
logger.info(f"Lookback period change detected — expanding cutoff from {cutoff_timestamp} to {rescan_cutoff}")
cutoff_timestamp = rescan_cutoff
else:
# No scan timestamp — use lookback period
days = int(lookback_period)
cutoff_timestamp = datetime.now(timezone.utc) - timedelta(days=days)
logger.info(f"Using lookback period: {lookback_period} days (cutoff: {cutoff_timestamp})")
# Filter by release date if we have a cutoff timestamp
if cutoff_timestamp:
@ -998,6 +1015,45 @@ class WatchlistScanner:
logger.warning(f"Error getting lookback period setting, defaulting to 30 days: {e}")
return '30'
def _get_rescan_cutoff(self):
"""
Check if a lookback period change requires a one-time wider scan window.
When the lookback period is expanded, a 'watchlist_rescan_cutoff' metadata key
is set with the new cutoff date. This method returns that cutoff so the scanner
can use the wider window for artists scanned before the change. After a full
scan cycle, the key is cleared by _clear_rescan_cutoff().
Returns:
datetime cutoff if a rescan is pending with a specific date,
'all' string if lookback was set to entire discography,
None if no rescan is pending
"""
try:
with self.database._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT value FROM metadata WHERE key = 'watchlist_rescan_cutoff'")
row = cursor.fetchone()
if row is not None:
val = row['value']
if val == '':
return 'all' # Lookback set to 'all' — scan everything
return datetime.fromisoformat(val)
except Exception as e:
logger.debug(f"Error reading rescan cutoff: {e}")
return None
def _clear_rescan_cutoff(self):
"""Clear the one-time rescan cutoff after a full scan cycle completes."""
try:
with self.database._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM metadata WHERE key = 'watchlist_rescan_cutoff'")
conn.commit()
logger.info("Cleared watchlist rescan cutoff flag")
except Exception as e:
logger.debug(f"Error clearing rescan cutoff: {e}")
def is_album_after_timestamp(self, album, timestamp: datetime) -> bool:
"""Check if album was released after the given timestamp"""
try:

@ -4895,19 +4895,14 @@ class MusicDatabase:
return 0
def clear_wishlist(self, profile_id: int = 1) -> bool:
"""Clear all tracks from the wishlist for the given profile and reset scan timestamps"""
"""Clear all tracks from the wishlist for the given profile"""
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM wishlist_tracks WHERE profile_id = ?", (profile_id,))
cleared_count = cursor.rowcount
# Reset last_scan_timestamp on this profile's watchlist artists so the next scan
# uses the lookback period setting (e.g. "entire discography") instead
# of only finding albums released after the old scan date
cursor.execute("UPDATE watchlist_artists SET last_scan_timestamp = NULL WHERE profile_id = ?", (profile_id,))
reset_count = cursor.rowcount
conn.commit()
logger.info(f"Cleared {cleared_count} tracks from wishlist, reset scan timestamps on {reset_count} artists (profile: {profile_id})")
logger.info(f"Cleared {cleared_count} tracks from wishlist (profile: {profile_id})")
return True
except Exception as e:
logger.error(f"Error clearing wishlist: {e}")

@ -17367,14 +17367,25 @@ def set_discovery_lookback_period():
VALUES ('discovery_lookback_period', ?, CURRENT_TIMESTAMP)
""", (period,))
# When expanding the lookback window (especially to "entire disco"),
# reset scan timestamps so the next scan re-discovers older releases
# that were filtered out under the previous narrower setting
cursor.execute("UPDATE watchlist_artists SET last_scan_timestamp = NULL")
reset_count = cursor.rowcount
# Set a one-time rescan cutoff so the next scan cycle uses the new
# lookback window for artists that were already scanned under the old setting.
# This avoids wiping last_scan_timestamp (which is needed for UI display).
if period == 'all':
# 'all' means no cutoff — store empty to signal "scan everything"
rescan_value = ''
else:
from datetime import datetime, timedelta, timezone
cutoff = datetime.now(timezone.utc) - timedelta(days=int(period))
rescan_value = cutoff.isoformat()
cursor.execute("""
INSERT OR REPLACE INTO metadata (key, value, updated_at)
VALUES ('watchlist_rescan_cutoff', ?, CURRENT_TIMESTAMP)
""", (rescan_value,))
conn.commit()
print(f"✅ Discovery lookback period set to: {period}, reset scan timestamps on {reset_count} artists")
print(f"✅ Discovery lookback period set to: {period}")
return jsonify({"success": True, "period": period})
except Exception as e:
@ -28821,6 +28832,12 @@ def start_watchlist_scan():
# Resume enrichment workers if we paused them
_resume_enrichment_workers(_ew_state, 'watchlist scan')
# Clear one-time rescan cutoff after full scan cycle
try:
scanner._clear_rescan_cutoff()
except Exception:
pass
# Always reset flag when scan completes (success or error)
with watchlist_timer_lock:
watchlist_auto_scanning = False
@ -29809,6 +29826,12 @@ def _process_watchlist_scan_automatically(automation_id=None):
# Resume enrichment workers if we paused them
_resume_enrichment_workers(_ew_state, 'auto-watchlist scan')
# Clear one-time rescan cutoff after full scan cycle
try:
scanner._clear_rescan_cutoff()
except Exception:
pass
# Always reset flag
with watchlist_timer_lock:
watchlist_auto_scanning = False

Loading…
Cancel
Save