auto_restart.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import os
  2. import sys
  3. import time
  4. import logging
  5. import subprocess
  6. import threading
  7. from datetime import datetime, timedelta
  8. from typing import Optional, Callable
  9. from .models import AutoRestartConfig, RestartInterval, RestartTarget
  10. logger = logging.getLogger(__name__)
  11. class AutoRestartScheduler:
  12. def __init__(self, config: AutoRestartConfig, app_path: Optional[str] = None):
  13. self.config = config
  14. self.app_path = app_path or sys.executable
  15. self._running = False
  16. self._thread: Optional[threading.Thread] = None
  17. self._last_trigger_minute = None
  18. self.on_restart_triggered: Optional[Callable[[], None]] = None
  19. def start(self):
  20. if not self.config.enabled:
  21. logger.info("AutoRestart disabled")
  22. return
  23. if self._running:
  24. return
  25. self._running = True
  26. self._thread = threading.Thread(target=self._loop, daemon=True)
  27. self._thread.start()
  28. logger.info(f"AutoRestart started: interval={self.config.interval.value}, "
  29. f"time={self.config.hour:02d}:{self.config.minute:02d}, "
  30. f"target={self.config.target.value}")
  31. def stop(self):
  32. self._running = False
  33. def _loop(self):
  34. while self._running:
  35. now = datetime.now()
  36. if self._should_trigger(now):
  37. minute_key = now.strftime("%Y-%m-%d %H:%M")
  38. if self._last_trigger_minute != minute_key:
  39. self._last_trigger_minute = minute_key
  40. logger.info(f"AutoRestart triggered at {now}")
  41. if self.on_restart_triggered:
  42. self.on_restart_triggered()
  43. else:
  44. self.perform_restart()
  45. time.sleep(30)
  46. def _should_trigger(self, now: datetime) -> bool:
  47. if now.hour != self.config.hour or now.minute != self.config.minute:
  48. return False
  49. interval = self.config.interval
  50. if interval == RestartInterval.DAY:
  51. return True
  52. elif interval == RestartInterval.WEEK:
  53. return now.weekday() == self.config.weekdays
  54. elif interval == RestartInterval.MONTH:
  55. return now.day == self.config.day_of_month
  56. return False
  57. def perform_restart(self):
  58. logger.info(f"Performing restart: target={self.config.target.value}")
  59. if self.config.target == RestartTarget.DEVICE:
  60. self._restart_device()
  61. else:
  62. self._restart_app()
  63. def _restart_app(self):
  64. try:
  65. args = sys.argv[:]
  66. if getattr(sys, 'frozen', False):
  67. subprocess.Popen([self.app_path] + args[1:])
  68. else:
  69. subprocess.Popen([sys.executable] + args)
  70. logger.info("App restart command sent")
  71. os._exit(0)
  72. except Exception as e:
  73. logger.error(f"Failed to restart app: {e}")
  74. def _restart_device(self):
  75. try:
  76. if os.name == 'nt':
  77. subprocess.run(['shutdown', '/r', '/t', '5', '/c', 'AutoRestart scheduled'], check=False)
  78. else:
  79. subprocess.run(['sudo', 'reboot'], check=False)
  80. logger.info("Device restart command sent")
  81. except Exception as e:
  82. logger.error(f"Failed to restart device: {e}")
  83. def next_restart_time(self) -> Optional[datetime]:
  84. now = datetime.now()
  85. interval = self.config.interval
  86. target_time = now.replace(hour=self.config.hour, minute=self.config.minute, second=0, microsecond=0)
  87. if target_time <= now:
  88. if interval == RestartInterval.DAY:
  89. target_time += timedelta(days=1)
  90. elif interval == RestartInterval.WEEK:
  91. days_ahead = self.config.weekdays - now.weekday()
  92. if days_ahead <= 0:
  93. days_ahead += 7
  94. target_time += timedelta(days=days_ahead)
  95. elif interval == RestartInterval.MONTH:
  96. if now.month == 12:
  97. next_month = 1
  98. next_year = now.year + 1
  99. else:
  100. next_month = now.month + 1
  101. next_year = now.year
  102. try:
  103. target_time = target_time.replace(year=next_year, month=next_month, day=self.config.day_of_month)
  104. except ValueError:
  105. target_time += timedelta(days=1)
  106. return target_time