Preserve watchlist scan timestamps for UI display instead of wiping on lookback changes
This commit is contained in:
parent
32f1cc946c
commit
e3fdb12d78
3 changed files with 96 additions and 22 deletions
|
|
@ -792,16 +792,33 @@ class WatchlistScanner:
|
|||
time.sleep(0.3) # 300ms breathing room
|
||||
|
||||
# Determine cutoff date for filtering
|
||||
cutoff_timestamp = last_scan_timestamp
|
||||
lookback_period = self._get_lookback_period_setting()
|
||||
|
||||
# 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})")
|
||||
# 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…
Reference in a new issue