From eac42923fdcb3b8a282b2ced577360ed914377cf Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 14 Jul 2026 12:15:34 -0400 Subject: [PATCH] Implement auto-sync functionality in ChronoTrack API, including new methods for managing auto-sync events and handling expiration. Update push panel messages for clarity on cron job execution and enhance user feedback. This change supports improved synchronization management and lays the foundation for future enhancements. --- auto_chronotrack_sync.php | 44 ++++++ php/chronotrack_api/fx_chronotrack_admin.php | 10 +- php/chronotrack_api/fx_chronotrack_config.php | 56 ++++++- php/chronotrack_api/fx_chronotrack_sync.php | 140 ++++++++++++++++++ 4 files changed, 244 insertions(+), 6 deletions(-) create mode 100644 auto_chronotrack_sync.php diff --git a/auto_chronotrack_sync.php b/auto_chronotrack_sync.php new file mode 100644 index 0000000..5ad278a --- /dev/null +++ b/auto_chronotrack_sync.php @@ -0,0 +1,44 @@ + /dev/null 2>&1 + * + * La fréquence réelle d’envoi par événement est celle choisie dans l’admin + * (1 / 5 / 15 / 30 / 60 min) + date/heure de fin. + */ + +ini_set('display_errors', 0); +ini_set('log_errors', 1); +ini_set('error_log', __DIR__ . '/log/php_log.txt'); + +@set_time_limit(300); + +$strLockPath = rtrim(sys_get_temp_dir(), '/\\') . DIRECTORY_SEPARATOR . 'ms1_chronotrack_auto_sync.lock'; +$fpLock = @fopen($strLockPath, 'c'); +if ($fpLock === false || !flock($fpLock, LOCK_EX | LOCK_NB)) { + // Un passage précédent tourne encore — on saute ce tick. + if (is_resource($fpLock)) { + fclose($fpLock); + } + header('Content-Type: text/plain; charset=utf-8'); + echo 'busy'; + exit; +} + +session_start(); + +require_once __DIR__ . '/php/inc_fonctions.php'; +require_once __DIR__ . '/php/chronotrack_api/inc_bootstrap.php'; + +$arrResult = fxChronotrackApiSyncRunAutoCron(); +$strLine = 'auto_chronotrack_sync [' . date('Y-m-d H:i:s') . '] ' + . ($arrResult['message'] ?? 'done'); +error_log($strLine); + +header('Content-Type: text/plain; charset=utf-8'); +echo $strLine . "\n"; + +flock($fpLock, LOCK_UN); +fclose($fpLock); diff --git a/php/chronotrack_api/fx_chronotrack_admin.php b/php/chronotrack_api/fx_chronotrack_admin.php index f9380e1..7ca13df 100644 --- a/php/chronotrack_api/fx_chronotrack_admin.php +++ b/php/chronotrack_api/fx_chronotrack_admin.php @@ -298,11 +298,11 @@ function fxChronotrackApiAdminRenderPushPanel($intEveId, $blnClosed = false, $ar echo ''; echo '

'; if ($blnAutoOn && $strUntilRaw !== '') { - echo 'Config enregistrée — exécution serveur (cron) à brancher. Arrêt prévu : ' + echo 'Config enregistrée — cron auto_chronotrack_sync.php (toutes les minutes). Arrêt : ' . fxChronotrackApiAdminEsc($strUntilRaw) . '.'; } else { - echo 'Quand c’est coché, le serveur (cron) poussera le différentiel à cet intervalle jusqu’à la date de fin. ' - . 'Pas encore actif côté serveur.'; + echo 'Cochez, choisissez fréquence + date/heure de fin, puis Enregistrer. ' + . 'Le cron serveur doit appeler auto_chronotrack_sync.php chaque minute.'; } echo '

'; echo '
'; @@ -1174,10 +1174,10 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed $msg.addClass('text-success').text(res.message || 'Enregistré'); if (res.auto_sync) { if (res.auto_sync.enabled && res.auto_sync.until) { - $hint.text('Config enregistrée — exécution serveur (cron) à brancher. Arrêt prévu : ' + $hint.text('Config OK — cron toutes les minutes lit cette config (auto_chronotrack_sync.php). Arrêt : ' + res.auto_sync.until + '.'); } else { - $hint.text('Sync auto désactivée. Le cron (phase B) ne traitera pas cet événement.'); + $hint.text('Sync auto désactivée.'); } } }, 'json').fail(function () { diff --git a/php/chronotrack_api/fx_chronotrack_config.php b/php/chronotrack_api/fx_chronotrack_config.php index 19ed628..6a71cc3 100644 --- a/php/chronotrack_api/fx_chronotrack_config.php +++ b/php/chronotrack_api/fx_chronotrack_config.php @@ -197,7 +197,7 @@ function fxChronotrackApiConfigSaveAutoSync($intEveId, $blnEnabled, $intInterval 'state' => 'ok', 'message' => $blnEnabled ? ('Sync auto enregistrée (toutes les ' . $intIntervalMin . ' min jusqu’au ' - . trim((string)($arrFresh['auto_sync_until'] ?? '')) . '). Le cron démarrera en phase B.') + . trim((string)($arrFresh['auto_sync_until'] ?? '')) . '). Cron : auto_chronotrack_sync.php') : 'Sync auto désactivée.', 'auto_sync' => array( 'enabled' => intval($arrFresh['auto_sync_enabled'] ?? 0) === 1, @@ -208,6 +208,60 @@ function fxChronotrackApiConfigSaveAutoSync($intEveId, $blnEnabled, $intInterval ); } +/** + * MSIN-4328 — events avec sync auto ON (pas fermés). + */ +function fxChronotrackApiConfigListAutoSyncEnabled() { + global $objDatabase; + + $sql = "SELECT c.*" + . " FROM api_chronotrack_config c" + . " WHERE IFNULL(c.auto_sync_enabled, 0) = 1" + . " AND c.sync_status <> '" . $objDatabase->fxEscape(MSIN_API_CHRONOTRACK_SYNC_FERME) . "'" + . " ORDER BY c.eve_id ASC"; + $arrRows = $objDatabase->fxGetResults($sql); + $tabOut = array(); + if (!is_array($arrRows)) { + return $tabOut; + } + for ($i = 1; $i <= count($arrRows); $i++) { + $tabOut[] = $arrRows[$i]; + } + return $tabOut; +} + +/** + * MSIN-4328 — horodatage dernier passage cron auto. + */ +function fxChronotrackApiConfigTouchAutoRun($intEveId) { + global $objDatabase; + + $intEveId = intval($intEveId); + if ($intEveId <= 0) { + return false; + } + $sql = "UPDATE api_chronotrack_config SET last_auto_run_at = '" . fxGetDateTime() . "'" + . ", updated_at = '" . fxGetDateTime() . "'" + . " WHERE eve_id = " . $intEveId; + return (bool)$objDatabase->fxQuery($sql); +} + +/** + * MSIN-4328 — coupe la sync auto (échéance atteinte). + */ +function fxChronotrackApiConfigDisableAutoSync($intEveId) { + global $objDatabase; + + $intEveId = intval($intEveId); + if ($intEveId <= 0) { + return false; + } + $sql = "UPDATE api_chronotrack_config SET auto_sync_enabled = 0" + . ", updated_at = '" . fxGetDateTime() . "'" + . " WHERE eve_id = " . $intEveId; + return (bool)$objDatabase->fxQuery($sql); +} + function fxChronotrackApiRaceMapGetForEvent($intEveId) { global $objDatabase; diff --git a/php/chronotrack_api/fx_chronotrack_sync.php b/php/chronotrack_api/fx_chronotrack_sync.php index 6cda232..5a25787 100644 --- a/php/chronotrack_api/fx_chronotrack_sync.php +++ b/php/chronotrack_api/fx_chronotrack_sync.php @@ -1661,3 +1661,143 @@ function fxChronotrackApiSyncListCtOrphans($intEveId) { 'orphans' => $tabOrphans, ); } + +/** + * MSIN-4328 — push différentiel complet pour un event (cron auto). + */ +function fxChronotrackApiSyncRunAutoPushForEvent($intEveId) { + $intEveId = intval($intEveId); + $arrPrep = fxChronotrackApiSyncPushPrepare($intEveId); + if (($arrPrep['state'] ?? '') !== 'ok') { + // Rien à pousser = succès « idle » + $strMsg = (string)($arrPrep['message'] ?? 'prepare failed'); + if (stripos($strMsg, 'Aucun changement') !== false || intval($arrPrep['pending_push'] ?? -1) === 0) { + fxChronotrackApiSyncLog($intEveId, 0, 'auto_push', 'ok', 'idle — ' . $strMsg); + return array('state' => 'ok', 'message' => $strMsg, 'pushed_ok' => 0, 'idle' => true); + } + fxChronotrackApiSyncLog($intEveId, 0, 'auto_push', 'error', $strMsg); + return $arrPrep; + } + + $intOffset = 0; + $intLimit = MSIN_API_CHRONOTRACK_SYNC_CHUNK_SIZE; + $intOkSum = 0; + $intErrSum = 0; + $intLoops = 0; + $intMaxLoops = 500; + + while ($intLoops < $intMaxLoops) { + $intLoops++; + $arrChunk = fxChronotrackApiSyncPushEvent($intEveId, array( + 'offset' => $intOffset, + 'limit' => $intLimit, + )); + $intOkSum += intval($arrChunk['pushed_ok'] ?? 0); + $intErrSum += intval($arrChunk['pushed_error'] ?? 0); + if (!empty($arrChunk['done'])) { + break; + } + if (($arrChunk['state'] ?? '') === 'error' && intval($arrChunk['pushed_ok'] ?? 0) === 0) { + break; + } + $intOffset = intval($arrChunk['next_offset'] ?? ($intOffset + $intLimit)); + } + + $strState = ($intErrSum === 0) ? 'ok' : (($intOkSum > 0) ? 'partial' : 'error'); + $strMsg = 'auto push ok=' . $intOkSum . ' err=' . $intErrSum + . ' total_job=' . intval($arrPrep['total'] ?? 0); + fxChronotrackApiSyncLog($intEveId, 0, 'auto_push', ($strState === 'ok') ? 'ok' : 'error', $strMsg); + + return array( + 'state' => $strState, + 'message' => $strMsg, + 'pushed_ok' => $intOkSum, + 'pushed_error' => $intErrSum, + 'idle' => false, + ); +} + +/** + * MSIN-4328 — passage cron : events auto ON, fréquence / fin respectées. + */ +function fxChronotrackApiSyncRunAutoCron() { + if (!fxChronotrackApiSettingsConfigured()) { + return array( + 'state' => 'error', + 'message' => fxChronotrackApiSettingsErrorMessage(), + 'ran' => 0, + 'skipped' => 0, + ); + } + + $tabConfigs = fxChronotrackApiConfigListAutoSyncEnabled(); + $intRan = 0; + $intSkipped = 0; + $intExpired = 0; + $tabDetails = array(); + $intNow = time(); + + foreach ($tabConfigs as $arrCfg) { + $intEveId = intval($arrCfg['eve_id'] ?? 0); + if ($intEveId <= 0) { + continue; + } + + $strUntil = trim((string)($arrCfg['auto_sync_until'] ?? '')); + if ($strUntil !== '' && $strUntil !== '0000-00-00 00:00:00') { + $intUntil = strtotime($strUntil); + if ($intUntil !== false && $intNow > $intUntil) { + fxChronotrackApiConfigDisableAutoSync($intEveId); + fxChronotrackApiSyncLog( + $intEveId, + 0, + 'auto_sync', + 'ok', + 'Échéance atteinte (' . $strUntil . ') — sync auto désactivée' + ); + $intExpired++; + $tabDetails[] = array('eve_id' => $intEveId, 'action' => 'expired'); + continue; + } + } else { + // Pas de fin = on ne tourne pas (UI exige une fin à l’enregistrement) + $intSkipped++; + $tabDetails[] = array('eve_id' => $intEveId, 'action' => 'skip_no_until'); + continue; + } + + $intInterval = intval($arrCfg['auto_sync_interval_min'] ?? 15); + if (!in_array($intInterval, fxChronotrackApiConfigAutoSyncIntervals(), true)) { + $intInterval = 15; + } + $strLast = trim((string)($arrCfg['last_auto_run_at'] ?? '')); + if ($strLast !== '' && $strLast !== '0000-00-00 00:00:00') { + $intLast = strtotime($strLast); + if ($intLast !== false && ($intNow - $intLast) < ($intInterval * 60)) { + $intSkipped++; + $tabDetails[] = array('eve_id' => $intEveId, 'action' => 'skip_interval'); + continue; + } + } + + $arrRes = fxChronotrackApiSyncRunAutoPushForEvent($intEveId); + fxChronotrackApiConfigTouchAutoRun($intEveId); + $intRan++; + $tabDetails[] = array( + 'eve_id' => $intEveId, + 'action' => 'run', + 'result' => $arrRes['state'] ?? 'error', + 'message' => $arrRes['message'] ?? '', + 'ok' => intval($arrRes['pushed_ok'] ?? 0), + ); + } + + return array( + 'state' => 'ok', + 'message' => 'cron auto — ran=' . $intRan . ' skipped=' . $intSkipped . ' expired=' . $intExpired, + 'ran' => $intRan, + 'skipped' => $intSkipped, + 'expired' => $intExpired, + 'details' => $tabDetails, + ); +}