From 94a7a1c24594a433469ca91d65cd22ec64cfe95a Mon Sep 17 00:00:00 2001 From: novgorodschi catalin Date: Fri, 17 Jul 2026 11:58:32 +0300 Subject: [PATCH] Refactor Village.php #326 Refactor Village.php #326 --- .../Database/DatabaseVillageQueries.php | 9 +- GameEngine/Village.php | 401 ++++++++++-------- 2 files changed, 226 insertions(+), 184 deletions(-) diff --git a/GameEngine/Database/DatabaseVillageQueries.php b/GameEngine/Database/DatabaseVillageQueries.php index b2f82b0c..6eb9de27 100644 --- a/GameEngine/Database/DatabaseVillageQueries.php +++ b/GameEngine/Database/DatabaseVillageQueries.php @@ -1219,9 +1219,14 @@ trait DatabaseVillageQueries { } } - function modifyResource($vid, $wood, $clay, $iron, $crop, $mode) { + // FIX #1b (Day 1): param nou optional $setLastupdate - cand e true, lastupdate + // se seteaza in aceeasi interogare (Village::processProduction nu mai are nevoie + // de updateVillage() separat). Default false => zero impact pe restul call-site-urilor. + function modifyResource($vid, $wood, $clay, $iron, $crop, $mode, $setLastupdate = false) { list($vid, $wood, $clay, $iron, $crop, $mode) = $this->escape_input((int) $vid, $wood, $clay, $iron, $crop, $mode); $sign = (!$mode ? '-' : '+'); + $lastupdateSql = ($setLastupdate ? ", + lastupdate = " . time() : ""); $q = " UPDATE " . TB_PREFIX . "vdata @@ -1229,7 +1234,7 @@ trait DatabaseVillageQueries { wood = IF(wood $sign $wood < 0, 0, IF(wood $sign $wood > maxstore, maxstore, wood $sign $wood)), clay = IF(clay $sign $clay < 0, 0, IF(clay $sign $clay > maxstore, maxstore, clay $sign $clay)), iron = IF(iron $sign $iron < 0, 0, IF(iron $sign $iron > maxstore, maxstore, iron $sign $iron)), - crop = IF(crop $sign $crop < 0, 0, IF(crop $sign $crop > maxcrop, maxcrop, crop $sign $crop)) + crop = IF(crop $sign $crop < 0, 0, IF(crop $sign $crop > maxcrop, maxcrop, crop $sign $crop))$lastupdateSql WHERE wref = " . $vid ; diff --git a/GameEngine/Village.php b/GameEngine/Village.php index 379060c6..1d34bd7c 100755 --- a/GameEngine/Village.php +++ b/GameEngine/Village.php @@ -18,6 +18,12 @@ ## License : TravianZ Project ## ## Copyright : TravianZ (c) 2010-2026. All rights reserved. ## ## --------------------------------------------------------------------------- ## +## Refactor D1 : clamp productie in memorie = clamp SQL; 1 UPDATE/hit; ## +## eliminat wid=wref; flock pe lock-ul de automation ## +## Refactor W1 : getProdFor() unic (fara cele 4 metode copy-paste); ## +## scan f1..f38 o singura data; globals trase in constructor ## +## Refactor W2 : constructor fara side effects; tick() + ActionControl() ## +## explicite in bootstrap; garduri statice eliminate ## ################################################################################# include_once("Session.php"); @@ -46,49 +52,89 @@ class Village { private $oasisowned, $ocounter = []; private $oasisFactor = 0.25; - // single-load guards - private static $loadedWid = null; - private static $cacheCleared = false; + // W1 #6: dependinte trase o singura data din globals, in constructor + private $db; + private $sess; + private $tech; + private $bids = []; + // W1 #4: index-ul campurilor f1..f38, construit lazy, o data per incarcare + private $fieldIndex = null; + + /** + * W1 #4: configuratia per resursa pentru getProdFor(). + * field = tipul campului de productie (si nr. tabelei $bidN de productie), + * bonus = gid-urile cladirilor bonus (Sawmill/Brickyard/Foundry/Grain Mill+Bakery), + * oasis = indexul in $this->ocounter, sessionBonus = flag-ul de bonus 25%. + */ + private const PROD_CONFIG = [ + 'wood' => ['field' => 1, 'bonus' => [5], 'oasis' => 0, 'sessionBonus' => 'bonus1'], + 'clay' => ['field' => 2, 'bonus' => [6], 'oasis' => 1, 'sessionBonus' => 'bonus2'], + 'iron' => ['field' => 3, 'bonus' => [7], 'oasis' => 2, 'sessionBonus' => 'bonus3'], + 'crop' => ['field' => 4, 'bonus' => [8, 9], 'oasis' => 3, 'sessionBonus' => 'bonus4'], + ]; + + /** + * W2 #7: constructorul DOAR incarca. Fara scrieri in DB, fara redirect, + * fara tick de productie. "new Village" e sigur de apelat oriunde + * (instante multiple, admin switch). Gardurile statice loadedWid / + * cacheCleared au fost eliminate (W2 #10) - nu mai sunt necesare cu + * side effects explicite. + */ function __construct() { - global $session, $database; + global $database, $session, $technology, + $bid1, $bid2, $bid3, $bid4, $bid5, $bid6, $bid7, $bid8, $bid9, $bid45; + + $this->db = $database; + $this->sess = $session; + $this->tech = $technology; + $this->bids = [ + 1 => $bid1, 2 => $bid2, 3 => $bid3, 4 => $bid4, + 5 => $bid5, 6 => $bid6, 7 => $bid7, 8 => $bid8, 9 => $bid9, + 45 => $bid45, // Waterworks (Egipteni) - folosit de getOasisBonusFactor() + ]; // set village id if (isset($_SESSION['wid'])) $this->wid = $_SESSION['wid']; - else $this->wid = $session->villages[0]; + else $this->wid = $this->sess->villages[0]; // preload caches (safe warmup) $this->preloadVillagesData(); // validate existence - if (!$database->checkVilExist($this->wid)) { - $this->wid = $database->getVillageID($session->uid); + if (!$this->db->checkVilExist($this->wid)) { + $this->wid = $this->db->getVillageID($this->sess->uid); $_SESSION['wid'] = $this->wid; } - // prevent double init in same request - if (self::$loadedWid === $this->wid) { - return; - } - self::$loadedWid = $this->wid; - $this->LoadTown(); - $database->cacheResourceLevels($this->wid); + $this->db->cacheResourceLevels($this->wid); + } + + /** + * W2 #8: tick explicit - calculeaza si aplica productia. Apelat din + * bootstrap-ul de la finalul fisierului (nu din dorf1/dorf2 individual: + * 28 de pagini includ Village.php si toate au nevoie de resurse la zi). + */ + public function tick(): void { + // W2 #10: clear-ul de cache era gardat de static::$cacheCleared ca sa + // ruleze o data per request; cu tick explicit (apelat o data, din + // bootstrap) gardul nu mai e necesar. Un clear repetat la o a doua + // instanta inseamna doar cache rece, nu un bug de corectitudine. + $db = get_class($this->db); + $db::clearVillageCache(); $this->calculateProduction(); $this->processProduction(); - $this->ActionControl(); } /** * preload user + world cache */ - private function preloadVillagesData() { - global $database, $session; - - $database->getProfileVillages($session->uid, 5); - $database->cacheVillageByWorldIDs($session->uid); + private function preloadVillagesData(): void { + $this->db->getProfileVillages($this->sess->uid, 5); + $this->db->cacheVillageByWorldIDs($this->sess->uid); } public function getProd($type) { @@ -96,57 +142,54 @@ class Village { } public function getAllUnits($vid) { - global $database, $technology; - - return $technology->getUnits( - $database->getUnit($vid), - $database->getEnforceVillage($vid, 0) + return $this->tech->getUnits( + $this->db->getUnit($vid), + $this->db->getEnforceVillage($vid, 0) ); } /** * LOAD VILLAGE DATA (single source of truth) + * W2: parametrul mort $second_run eliminat (nu era pasat de nicaieri; + * getResearching primea mereu use_cache = true). */ - private function LoadTown($second_run = false) { - global $database, $session, $logging, $technology; + private function LoadTown(): void { + global $logging; - // prevent duplicate reload in same request - if (self::$loadedWid === $this->wid && !$second_run && !empty($this->infoarray)) { - return; - } + $this->infoarray = $this->db->getVillage($this->wid); - $this->infoarray = $database->getVillage($this->wid); - - if ($this->infoarray['owner'] != $session->uid && !$session->isAdmin) { + if ($this->infoarray['owner'] != $this->sess->uid && !$this->sess->isAdmin) { unset($_SESSION['wid']); - $logging->addIllegal($session->uid, $this->wid, 1); + $logging->addIllegal($this->sess->uid, $this->wid, 1); - $this->wid = $session->villages[0]; - $this->infoarray = $database->getVillage($this->wid); + $this->wid = $this->sess->villages[0]; + $this->infoarray = $this->db->getVillage($this->wid); } - $this->resarray = $database->getResourceLevel($this->wid); - $this->coor = $database->getCoor($this->wid); - $this->type = $database->getVillageType($this->wid); - $this->oasisowned = $database->getOasis($this->wid); + $this->resarray = $this->db->getResourceLevel($this->wid); + $this->coor = $this->db->getCoor($this->wid); + $this->type = $this->db->getVillageType($this->wid); + $this->oasisowned = $this->db->getOasis($this->wid); $this->ocounter = $this->sortOasis(); $this->oasisFactor = $this->getOasisBonusFactor(); - $this->unitarray = $database->getUnit($this->wid); - $this->enforcetome = $database->getEnforceVillage($this->wid, 0); - $this->enforcetoyou = $database->getEnforceVillage($this->wid, 1); - $this->enforceoasis = $database->getOasisEnforce($this->wid, 0); + $this->unitarray = $this->db->getUnit($this->wid); + $this->enforcetome = $this->db->getEnforceVillage($this->wid, 0); + $this->enforcetoyou = $this->db->getEnforceVillage($this->wid, 1); + $this->enforceoasis = $this->db->getOasisEnforce($this->wid, 0); - $this->unitall = $technology->getAllUnits($this->wid); - $this->techarray = $database->getTech($this->wid); - $this->abarray = $database->getABTech($this->wid); - $this->researching = $database->getResearching($this->wid, !$second_run); + $this->unitall = $this->tech->getAllUnits($this->wid); + $this->techarray = $this->db->getTech($this->wid); + $this->abarray = $this->db->getABTech($this->wid); + $this->researching = $this->db->getResearching($this->wid); $this->capital = $this->infoarray['capital']; $this->natar = $this->infoarray['natar']; $this->currentcel = $this->infoarray['celebration']; $this->currentfestival = $this->infoarray['festival']; - $this->wid = $this->infoarray['wref']; + // FIX #2 (Day 1): eliminat "$this->wid = $this->infoarray['wref']" - suprascria + // id-ul satului la mijlocul incarcarii; in flux normal era no-op (wref == wid), + // dar masca orice rand gresit venit din cache. wid ramane sursa de adevar. $this->vname = $this->infoarray['name']; $this->awood = $this->infoarray['wood']; @@ -163,41 +206,31 @@ class Village { $this->allcrop = $this->getCropProd(); $this->loyalty = $this->infoarray['loyalty']; - $this->master = count($database->getMasterJobs($this->wid)); + $this->master = count($this->db->getMasterJobs($this->wid)); - // overflow fix - $updates = []; - - if ($this->awood > $this->maxstore) { $this->awood = $this->maxstore; $updates['wood'] = $this->maxstore; } - if ($this->aclay > $this->maxstore) { $this->aclay = $this->maxstore; $updates['clay'] = $this->maxstore; } - if ($this->airon > $this->maxstore) { $this->airon = $this->maxstore; $updates['iron'] = $this->maxstore; } - if ($this->acrop > $this->maxcrop) { $this->acrop = $this->maxcrop; $updates['crop'] = $this->maxcrop; } - - if (count($updates)) { - $database->updateResource($this->wid, array_keys($updates), array_values($updates)); - } + // FIX #1a (Day 1): overflow fix doar in memorie - fara updateResource() aici. + // Clamp-ul SQL din modifyResource() (IF > maxstore -> maxstore) corecteaza + // oricum DB-ul in acelasi request, in processProduction(). Scapa un UPDATE. + if ($this->awood > $this->maxstore) $this->awood = $this->maxstore; + if ($this->aclay > $this->maxstore) $this->aclay = $this->maxstore; + if ($this->airon > $this->maxstore) $this->airon = $this->maxstore; + if ($this->acrop > $this->maxcrop) $this->acrop = $this->maxcrop; + // NOTA (pre-existent, nemodificat): $this->atotal e calculat mai sus din + // valorile ne-clamp-uite si inainte de tick-ul de productie. } /** * PRODUCTION CALC */ - private function calculateProduction() { - global $technology, $database; + private function calculateProduction(): void { + $upkeep = $this->tech->getUpkeep($this->unitall, 0, $this->wid); - if (!self::$cacheCleared) { - $db = get_class($database); - $db::clearVillageCache(); - self::$cacheCleared = true; - } - - $upkeep = $technology->getUpkeep($this->unitall, 0, $this->wid); - - $this->production['wood'] = $this->getWoodProd(); - $this->production['clay'] = $this->getClayProd(); - $this->production['iron'] = $this->getIronProd(); + $this->production['wood'] = $this->getProdFor('wood'); + $this->production['clay'] = $this->getProdFor('clay'); + $this->production['iron'] = $this->getProdFor('iron'); $this->production['crop'] = - $this->getCropProd() + $this->getProdFor('crop') - (!$this->natar ? $this->pop : round($this->pop / 2)) - $upkeep; } @@ -205,119 +238,99 @@ class Village { /** * APPLY PRODUCTION (NO MORE LOADTOWN RELOAD) */ - private function processProduction() { - global $database; - - $timepast = time() - $this->infoarray['lastupdate']; + private function processProduction(): void { + // hardening: nu lasa un lastupdate din viitor (clock skew) sa produca delta negativ + $timepast = max(0, time() - $this->infoarray['lastupdate']); $nwood = min(($this->production['wood'] / 3600) * $timepast, $this->maxstore); $nclay = min(($this->production['clay'] / 3600) * $timepast, $this->maxstore); $niron = min(($this->production['iron'] / 3600) * $timepast, $this->maxstore); $ncrop = min(($this->production['crop'] / 3600) * $timepast, $this->maxcrop); - if (!self::$cacheCleared) { - $db = get_class($database); - $db::clearVillageCache(); - self::$cacheCleared = true; - } - $database->modifyResource($this->wid, $nwood, $nclay, $niron, $ncrop, 1); - $database->updateVillage($this->wid); + // FIX #1b (Day 1): un singur UPDATE - lastupdate e setat in aceeasi interogare + // cu resursele (param $setLastupdate in modifyResource); updateVillage() eliminat. + $this->db->modifyResource($this->wid, $nwood, $nclay, $niron, $ncrop, 1, true); // lightweight sync ONLY (no DB reload) $this->infoarray['lastupdate'] = time(); - $this->awood = min($this->awood + $nwood, $this->maxstore); - $this->aclay = min($this->aclay + $nclay, $this->maxstore); - $this->airon = min($this->airon + $niron, $this->maxstore); - $this->acrop = min($this->acrop + $ncrop, $this->maxcrop); - } - - private function getWoodProd() { - - global $bid1, $bid5, $session; - $wood = $sawmill = 0; - $holder = []; - for ($i = 1; $i <= 38; $i++) { - if ($this->resarray['f'.$i.'t'] == 1) $holder[] = 'f'.$i; - if ($this->resarray['f'.$i.'t'] == 5) $sawmill = $this->resarray['f'.$i]; - } - $cnt = count($holder); - for ($i = 0; $i < $cnt; $i++) { - $wood += $bid1[$this->resarray[$holder[$i]]]['prod']; - } - $wood += $wood * $this->oasisFactor * $this->ocounter[0]; - if ($sawmill >= 1) { - $wood += $wood / 100 * $bid5[$sawmill]['attri']; - } - if ($session->bonus1 == 1) $wood *= 1.25; - return round($wood * SPEED); + // FIX #0 (Day 1): sincronizarea in memorie oglindeste exact clamp-ul SQL + // din modifyResource: podea la 0 (inainte, crop-ul negativ din starvation + // putea cobori sub 0 in memorie in timp ce DB-ul ramanea la 0 -> divergenta). + $this->awood = min(max($this->awood + $nwood, 0), $this->maxstore); + $this->aclay = min(max($this->aclay + $nclay, 0), $this->maxstore); + $this->airon = min(max($this->airon + $niron, 0), $this->maxstore); + $this->acrop = min(max($this->acrop + $ncrop, 0), $this->maxcrop); } - private function getClayProd() { - - global $bid2, $bid6, $session; - $clay = $brick = 0; - $holder = []; - for ($i = 1; $i <= 38; $i++) { - if ($this->resarray['f'.$i.'t'] == 2) $holder[] = 'f'.$i; - if ($this->resarray['f'.$i.'t'] == 6) $brick = $this->resarray['f'.$i]; + // W1 #4: cele 4 metode raman ca wrappere de o linie - zero schimbari de API, + // toata logica e in getProdFor(). + private function getWoodProd() { return $this->getProdFor('wood'); } + private function getClayProd() { return $this->getProdFor('clay'); } + private function getIronProd() { return $this->getProdFor('iron'); } + private function getCropProd() { return $this->getProdFor('crop'); } + + /** + * W1 #4: productia bruta pentru o resursa. Inlocuieste cele 4 metode + * copy-paste (~120 linii). Ordinea operatiilor e pastrata 1:1 cu originalul: + * suma campuri -> bonus oaze -> bonus cladire (%) -> bonus cont (x1.25) + * -> round(x * SPEED). + */ + private function getProdFor(string $type): float { + $cfg = self::PROD_CONFIG[$type]; + $index = $this->getFieldIndex(); + + $amount = 0; + if (!empty($index['levels'][$cfg['field']])) { + foreach ($index['levels'][$cfg['field']] as $level) { + $amount += $this->bids[$cfg['field']][$level]['prod']; + } } - $cnt = count($holder); - for ($i = 0; $i < $cnt; $i++) { - $clay += $bid2[$this->resarray[$holder[$i]]]['prod']; + + $amount += $amount * $this->oasisFactor * $this->ocounter[$cfg['oasis']]; + + // NOTA: garda isset() pe 'attri' exista in original doar la crop; aici e + // aplicata peste tot (identic numeric pe date valide, fara warning pe date + // corupte). Nivelul cladirii bonus = valoarea ULTIMULUI camp de acel tip, + // exact ca in original (in date reale exista cel mult o cladire per tip). + $bonusPct = 0; + foreach ($cfg['bonus'] as $buildingType) { + $level = $index['last'][$buildingType] ?? 0; + if ($level >= 1 && isset($this->bids[$buildingType][$level]['attri'])) { + $bonusPct += $this->bids[$buildingType][$level]['attri']; + } } - $clay += $clay * $this->oasisFactor * $this->ocounter[1]; - if ($brick >= 1) { - $clay += $clay / 100 * $bid6[$brick]['attri']; + if ($bonusPct > 0) { + $amount += $amount / 100 * $bonusPct; } - if ($session->bonus2 == 1) $clay *= 1.25; - return round($clay * SPEED); + + if ($this->sess->{$cfg['sessionBonus']} == 1) $amount *= 1.25; + + return round($amount * SPEED); } - private function getIronProd() { - - global $bid3, $bid7, $session; - $iron = $foundry = 0; - $holder = []; - for ($i = 1; $i <= 38; $i++) { - if ($this->resarray['f'.$i.'t'] == 3) $holder[] = 'f'.$i; - if ($this->resarray['f'.$i.'t'] == 7) $foundry = $this->resarray['f'.$i]; + /** + * W1 #4: scaneaza campurile f1..f38 O SINGURA data per incarcare (in loc de + * 4 treceri) si le bucket-uieste dupa tip. + * 'levels' => [tip => [nivelele fiecarui camp de acel tip]] + * 'last' => [tip => nivelul (raw) al ultimului camp de acel tip] - + * semantica identica cu atribuirile suprascrise din original. + */ + private function getFieldIndex(): array { + if ($this->fieldIndex !== null) { + return $this->fieldIndex; } - $cnt = count($holder); - for ($i = 0; $i < $cnt; $i++) { - $iron += $bid3[$this->resarray[$holder[$i]]]['prod']; - } - $iron += $iron * $this->oasisFactor * $this->ocounter[2]; - if ($foundry >= 1) { - $iron += $iron / 100 * $bid7[$foundry]['attri']; - } - if ($session->bonus3 == 1) $iron *= 1.25; - return round($iron * SPEED); - } - private function getCropProd() { - - global $bid4, $bid8, $bid9, $session; - $crop = $grainmill = $bakery = 0; - $holder = []; + $levels = []; + $last = []; for ($i = 1; $i <= 38; $i++) { - if ($this->resarray['f'.$i.'t'] == 4) $holder[] = 'f'.$i; - if ($this->resarray['f'.$i.'t'] == 8) $grainmill = $this->resarray['f'.$i]; - if ($this->resarray['f'.$i.'t'] == 9) $bakery = $this->resarray['f'.$i]; + $t = (int) $this->resarray['f'.$i.'t']; + $lvl = $this->resarray['f'.$i]; + $levels[$t][] = $lvl; + $last[$t] = $lvl; } - $cnt = count($holder); - for ($i = 0; $i < $cnt; $i++) { - $crop += $bid4[$this->resarray[$holder[$i]]]['prod']; - } - $crop += $crop * $this->oasisFactor * $this->ocounter[3]; - if ($grainmill >= 1 || $bakery >= 1) { - $crop += $crop / 100 * ( - (isset($bid8[$grainmill]['attri']) ? $bid8[$grainmill]['attri'] : 0) + - (isset($bid9[$bakery]['attri']) ? $bid9[$bakery]['attri'] : 0) - ); - } - if ($session->bonus4 == 1) $crop *= 1.25; - return round($crop * SPEED); + + return $this->fieldIndex = ['levels' => $levels, 'last' => $last]; } /** @@ -326,22 +339,23 @@ class Village { /** * Bonusul de baza al oazei (25%) crescut de Waterworks (gid 45, Egipteni): * attri = +5% relativ per nivel, pana la +100% la nivel 20 (0.25 -> 0.50). + * NOTA: ramane intentionat in afara getFieldIndex() - scaneaza f19..f40 + * (include f39/f40, peste limita de 38 a index-ului). */ - private function getOasisBonusFactor() { - global $bid45; + private function getOasisBonusFactor(): float { $wwLevel = 0; for ($i = 19; $i <= 40; $i++) { if (isset($this->resarray['f'.$i.'t']) && (int)$this->resarray['f'.$i.'t'] == 45) { $wwLevel = max($wwLevel, (int)$this->resarray['f'.$i]); } } - if ($wwLevel > 0 && isset($bid45[$wwLevel]['attri'])) { - return 0.25 * (1 + $bid45[$wwLevel]['attri']); + if ($wwLevel > 0 && isset($this->bids[45][$wwLevel]['attri'])) { + return 0.25 * (1 + $this->bids[45][$wwLevel]['attri']); } return 0.25; } - private function sortOasis() { + private function sortOasis(): array { $wood = $clay = $iron = $crop = 0; if (!empty($this->oasisowned)) { foreach ($this->oasisowned as $oasis) { @@ -360,27 +374,50 @@ class Village { return [$wood, $clay, $iron, $crop]; } - private function ActionControl() { - - global $session; + /** + * W2 #9: scos din constructor; apelat explicit din bootstrap-ul de mai jos. + * Isi pastreaza singur garda pe build.php, deci efectul e identic cu a-l + * pune la inceputul build.php, dar fara sa editam paginile de intrare. + */ + public function ActionControl(): void { $page = $_SERVER['SCRIPT_NAME']; if (!SERVER_WEB_ROOT) { $page = basename($_SERVER['SCRIPT_NAME']); } - if ($page == "build.php" && $session->uid != $this->infoarray['owner']) { + if ($page == "build.php" && $this->sess->uid != $this->infoarray['owner']) { unset($_SESSION['wid']); header("Location: dorf1.php"); exit; } } } + +// W2: bootstrap cu side effects EXPLICITE, in aceeasi ordine ca vechiul +// constructor (load -> tick productie -> ActionControl). Village.php e +// include_once-uit, deci blocul ruleaza o singura data per request. +// "new Village" ramane sigur pentru instante suplimentare (admin switch). $village = new Village; +$village->tick(); +$village->ActionControl(); $building = new Building; // automation -if (!file_exists(AUTOMATION_LOCK_FILE_NAME)) { - define('AUTOMATION_MANUAL_RUN', true); - file_put_contents(AUTOMATION_LOCK_FILE_NAME, ''); - include_once("Automation.php"); +// FIX #3 (Day 1): file_exists() + file_put_contents() era un TOCTOU clasic - doua +// request-uri simultane treceau amandoua de check si rulau Automation dublu. +// fopen('c') + flock(LOCK_EX|LOCK_NB) e atomic; lock-ul se elibereaza singur si +// daca procesul moare. Se foloseste ACELASI fisier, deci gardul intern din +// Automation.php (calea cron/directa: file_exists + mtime < 60s) ramane functional; +// touch() tine mtime proaspat cat rulam noi. +$__automationLock = @fopen(AUTOMATION_LOCK_FILE_NAME, 'c'); +if ($__automationLock !== false) { + if (flock($__automationLock, LOCK_EX | LOCK_NB)) { + define('AUTOMATION_MANUAL_RUN', true); + @touch(AUTOMATION_LOCK_FILE_NAME); + include_once("Automation.php"); + // Automation.php sterge singur fisierul la final (comportament pastrat + // pentru calea cron); pe Linux flock ramane valid pe inode pana la fclose. + flock($__automationLock, LOCK_UN); + } + fclose($__automationLock); } -?> \ No newline at end of file +?>