121 lines
3.9 KiB
Python
121 lines
3.9 KiB
Python
"""Service for scheduled page visits."""
|
|
|
|
import asyncio
|
|
import random
|
|
import logging
|
|
from typing import Optional, Callable, Awaitable
|
|
|
|
from config.settings import settings
|
|
from services.browser_service import BrowserService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class VisitScheduler:
|
|
"""
|
|
Scheduler for periodic page visits.
|
|
|
|
Features:
|
|
- Schedule periodic visits
|
|
- Configurable delays and reading times
|
|
- Progress tracking
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
browser_service: BrowserService,
|
|
url: str,
|
|
min_delay: int = None,
|
|
max_delay: int = None,
|
|
min_reading: int = None,
|
|
max_reading: int = None,
|
|
max_visits: Optional[int] = None,
|
|
on_visit_complete: Optional[Callable] = None,
|
|
on_progress: Optional[Callable] = None
|
|
):
|
|
self.browser_service = browser_service
|
|
self.url = url
|
|
self.min_delay = min_delay or settings.DEFAULT_MIN_DELAY
|
|
self.max_delay = max_delay or settings.DEFAULT_MAX_DELAY
|
|
self.min_reading = min_reading or settings.DEFAULT_MIN_READING
|
|
self.max_reading = max_reading or settings.DEFAULT_MAX_READING
|
|
self.max_visits = max_visits
|
|
|
|
self.on_visit_complete = on_visit_complete
|
|
self.on_progress = on_progress
|
|
|
|
self.visit_count = 0
|
|
self.successful = 0
|
|
self.failed = 0
|
|
|
|
async def run(self) -> dict:
|
|
"""
|
|
Run scheduled visits.
|
|
|
|
Returns:
|
|
Statistics dict
|
|
"""
|
|
total_reading_time = 0
|
|
loop = asyncio.get_event_loop()
|
|
|
|
try:
|
|
while True:
|
|
if self.max_visits and self.visit_count >= self.max_visits:
|
|
logger.info(f"Visit limit reached: {self.max_visits}")
|
|
break
|
|
|
|
# Randomize parameters once per cycle
|
|
current_reading = random.randint(self.min_reading, self.max_reading)
|
|
current_delay = random.randint(self.min_delay, self.max_delay)
|
|
|
|
self.visit_count += 1
|
|
|
|
# Record start time so the next visit fires strictly on the timer
|
|
tick_start = loop.time()
|
|
|
|
logger.info(
|
|
f"Visit {self.visit_count}/{'∞' if not self.max_visits else self.max_visits}: "
|
|
f"reading={current_reading}s delay={current_delay}s"
|
|
)
|
|
|
|
# Visit page
|
|
result = await self.browser_service.visit_page(
|
|
self.url, current_reading
|
|
)
|
|
|
|
if result.success:
|
|
self.successful += 1
|
|
total_reading_time += current_reading
|
|
else:
|
|
self.failed += 1
|
|
|
|
# Notify about visit completion
|
|
if self.on_visit_complete:
|
|
await self.on_visit_complete(
|
|
self.visit_count, result, current_delay, current_reading
|
|
)
|
|
|
|
if self.max_visits and self.visit_count >= self.max_visits:
|
|
break
|
|
|
|
# Wait only the remaining time so that the period between visit
|
|
# starts is exactly current_delay (independent of visit duration)
|
|
elapsed = loop.time() - tick_start
|
|
remaining = current_delay - elapsed
|
|
|
|
if self.on_progress:
|
|
await self.on_progress(self.visit_count, current_delay)
|
|
|
|
if remaining > 0:
|
|
await asyncio.sleep(remaining)
|
|
|
|
except asyncio.CancelledError:
|
|
logger.info("Visit scheduler cancelled")
|
|
raise
|
|
|
|
return {
|
|
"total_visits": self.visit_count,
|
|
"successful": self.successful,
|
|
"failed": self.failed,
|
|
"avg_reading_time": total_reading_time / self.successful if self.successful else 0
|
|
} |