ai_upgrade_assistant-0.2.0-alpha2/src/Service/UpdateSchedulerService.php
src/Service/UpdateSchedulerService.php
<?php
namespace Drupal\ai_upgrade_assistant\Service;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Datetime\DrupalDateTime;
/**
* Service for scheduling and managing automated updates.
*
* This service:
* - Determines optimal update windows
* - Schedules updates based on complexity and risk
* - Manages update queues
* - Tracks update history
*/
class UpdateSchedulerService {
/**
* The config factory service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The logger factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $loggerFactory;
/**
* The update monitor service.
*
* @var \Drupal\ai_upgrade_assistant\Service\UpdateMonitorService
*/
protected $updateMonitor;
/**
* The project analyzer service.
*
* @var \Drupal\ai_upgrade_assistant\Service\ProjectAnalyzer
*/
protected $projectAnalyzer;
/**
* Constructs a new UpdateSchedulerService.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Core\State\StateInterface $state
* The state service.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger factory service.
* @param \Drupal\ai_upgrade_assistant\Service\UpdateMonitorService $update_monitor
* The update monitor service.
* @param \Drupal\ai_upgrade_assistant\Service\ProjectAnalyzer $project_analyzer
* The project analyzer service.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
StateInterface $state,
LoggerChannelFactoryInterface $logger_factory,
UpdateMonitorService $update_monitor,
ProjectAnalyzer $project_analyzer
) {
$this->configFactory = $config_factory;
$this->state = $state;
$this->loggerFactory = $logger_factory;
$this->updateMonitor = $update_monitor;
$this->projectAnalyzer = $project_analyzer;
}
/**
* Creates an update schedule for available updates.
*
* @return array
* Scheduled updates with timing information.
*/
public function createSchedule() {
// Get available updates
$updates = $this->updateMonitor->checkForUpdates();
$advisories = $this->updateMonitor->checkSecurityAdvisories();
$schedule = [];
$config = $this->configFactory->get('ai_upgrade_assistant.settings');
// Get update window settings
$window_start = $config->get('update_window_start') ?? '01:00';
$window_end = $config->get('update_window_end') ?? '05:00';
$max_updates_per_window = $config->get('max_updates_per_window') ?? 3;
// Sort updates by priority
$prioritized = $this->prioritizeUpdates($updates, $advisories);
// Get next available window
$next_window = $this->getNextUpdateWindow($window_start, $window_end);
$updates_in_window = 0;
foreach ($prioritized as $module_name => $update) {
if ($updates_in_window >= $max_updates_per_window) {
$next_window = $this->getNextUpdateWindow($window_start, $window_end, $next_window);
$updates_in_window = 0;
}
$schedule[$module_name] = [
'module' => $module_name,
'current_version' => $update['current_version'],
'target_version' => $update['available_version'],
'security_update' => $update['security_update'],
'complexity' => $update['complexity'],
'scheduled_time' => $next_window->format('Y-m-d H:i:s'),
'estimated_duration' => $this->estimateUpdateDuration($update),
'dependencies' => $this->getUpdateDependencies($module_name),
];
$updates_in_window++;
}
// Store schedule
$this->state->set('ai_upgrade_assistant.update_schedule', $schedule);
return $schedule;
}
/**
* Prioritizes updates based on security risk and complexity.
*
* @param array $updates
* Available updates.
* @param array $advisories
* Security advisories.
*
* @return array
* Prioritized updates.
*/
protected function prioritizeUpdates(array $updates, array $advisories) {
$prioritized = [];
// First priority: Security updates
foreach ($updates as $name => $update) {
if ($update['security_update']) {
$prioritized[$name] = $update;
unset($updates[$name]);
}
}
// Second priority: Modules affected by advisories
foreach ($advisories as $advisory) {
foreach ($advisory['affected_modules'] as $module) {
if (isset($updates[$module])) {
$prioritized[$module] = $updates[$module];
unset($updates[$module]);
}
}
}
// Third priority: Sort remaining by complexity (low to high)
$complexity_weights = [
'low' => 1,
'medium' => 2,
'high' => 3,
];
uasort($updates, function ($a, $b) use ($complexity_weights) {
return $complexity_weights[$a['complexity']] <=> $complexity_weights[$b['complexity']];
});
return $prioritized + $updates;
}
/**
* Gets the next available update window.
*
* @param string $window_start
* Window start time (HH:MM).
* @param string $window_end
* Window end time (HH:MM).
* @param \Drupal\Core\Datetime\DrupalDateTime|null $after
* Optional datetime to start looking after.
*
* @return \Drupal\Core\Datetime\DrupalDateTime
* Next available window start time.
*/
protected function getNextUpdateWindow($window_start, $window_end, DrupalDateTime $after = NULL) {
if (!$after) {
$after = new DrupalDateTime();
}
// Parse window times
list($start_hour, $start_minute) = explode(':', $window_start);
list($end_hour, $end_minute) = explode(':', $window_end);
$window = new DrupalDateTime();
$window->setTime($start_hour, $start_minute);
// If current time is past today's window, move to tomorrow
if ($after->format('H:i') > $window_end) {
$window->modify('+1 day');
}
return $window;
}
/**
* Estimates the duration of an update.
*
* @param array $update
* Update information.
*
* @return int
* Estimated duration in minutes.
*/
protected function estimateUpdateDuration(array $update) {
$base_duration = [
'low' => 15,
'medium' => 30,
'high' => 60,
];
$duration = $base_duration[$update['complexity']];
// Add time for security updates (more testing)
if ($update['security_update']) {
$duration += 15;
}
// Add time for major version updates
if ($this->isMajorVersionChange($update['current_version'], $update['target_version'])) {
$duration *= 1.5;
}
return (int) $duration;
}
/**
* Gets dependencies that need to be updated first.
*
* @param string $module_name
* Module name.
*
* @return array
* List of dependent modules that need updating.
*/
protected function getUpdateDependencies($module_name) {
$dependencies = [];
$analysis = $this->projectAnalyzer->analyzeModule($module_name);
if (!empty($analysis['dependencies'])) {
foreach ($analysis['dependencies'] as $dependency) {
$updates = $this->state->get('ai_upgrade_assistant.available_updates', []);
if (isset($updates[$dependency])) {
$dependencies[] = $dependency;
}
}
}
return $dependencies;
}
/**
* Checks if update is a major version change.
*
* @param string $current_version
* Current version.
* @param string $target_version
* Target version.
*
* @return bool
* TRUE if major version change.
*/
protected function isMajorVersionChange($current_version, $target_version) {
$current_parts = explode('.', $current_version);
$target_parts = explode('.', $target_version);
return isset($current_parts[0]) && isset($target_parts[0]) &&
$current_parts[0] !== $target_parts[0];
}
}
