PageRenderTime 54ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/class.xmodreplication.inc

https://github.com/jcplat/console-seolan
PHP | 1443 lines | 1202 code | 77 blank | 164 comment | 231 complexity | 2180640b8fcad09b01e71c3835a501f6 MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1, GPL-3.0, Apache-2.0, BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /****c* tzr-5/XModReplication
  3. * NAME
  4. * XModReplication -- module gérer une réplication complète entre serveurs
  5. * DESCRIPTION
  6. * Le système de réplication autorise le fonctionnement en mode
  7. * réparti, avec la certitude de conserver des information a jour sur
  8. * plusieurs serveurs.
  9. ****/
  10. /// Module de gestion de la replication entre deux consoles Seolan
  11. class XModReplication extends XModTable {
  12. // static $singleton=true;
  13. public $suspended = false;
  14. public $table="REPLI";
  15. public $group=0;
  16. protected $_journal=array();
  17. public $packetSize = 500;
  18. public $mailWarning = false;
  19. protected $soapclients = array();
  20. // tables incluses sytémétatiquement dans un jeu d'initialisation
  21. // todo voir xdatasource systable
  22. protected $systtables = array('ACL4','AMSG','BASEBASE','DICT','MODULES','MSGS','SETS', 'TEMPLATES', 'USERS', 'GRP', 'OPTS');
  23. // tables jamais répliquées
  24. // idem voir xdatasource
  25. protected $notToReplicateTables = array('REPLI'=>1, 'TASKS'=>1, 'JOURNAL'=>1,
  26. 'ACL4_CACHE'=>1, '_STATICVARS'=>1, '_VARS'=>1,
  27. '_TMP'=>1, '_CACHE'=>1,
  28. '_MLOGS'=>1, '_MLOGSD'=>1
  29. );
  30. public $identifybyHostName = 0;
  31. public $connectionTimeOut = 10;
  32. public $packetack = false;
  33. private function newChrono() {
  34. $rs=selectQueryByNum("select max(CHRONO)+1 from JOURNAL");
  35. $nchrono=1;
  36. if($o1=$rs->fetch()) {
  37. $nchrono = $o1[0];
  38. }
  39. $rs->closeCursor();
  40. if($nchrono<=0) $nchrono=1;
  41. return $nchrono;
  42. }
  43. function __construct($ar=NULL) {
  44. $ar['moid']=self::getMoid(XMODREPLICATION_TOID);
  45. if(!XSystem::tableExists('REPLI')) XModReplication::createRepli();
  46. if(!XSystem::tableExists('JOURNAL')) XModReplication::createRepli();
  47. parent::__construct($ar);
  48. XLabels::loadLabels('xmodreplication');
  49. $this->group=XLabels::getSysLabel("general","systemproperties");
  50. $this->modulename=XLabels::getSysLabel("xmodreplication.modulename");
  51. $this->_journal=array();
  52. $rs = selectQuery('select * from BASEBASE');
  53. while($rs && $ors = $rs->fetch()){
  54. if(isset($ors['NOTTOREPLI']) && $ors['NOTTOREPLI'] == 1)
  55. $this->notToReplicateTables[$ors['BTAB']] = 1;
  56. }
  57. }
  58. /// enregistrement dans la table SQL du journal
  59. function __destruct() {
  60. $this->sendToJournal();
  61. $this->closeSoapClients();
  62. }
  63. /// termine les clients soap
  64. protected function closeSoapClients(){
  65. foreach($this->soapclients as $oid=>$client){
  66. XLogs::notice('repli', 'close client '.$client->sessid);
  67. if (!empty($client->soapclient))
  68. $client->soapclient->close(array('sessid'=>$client->sessid));
  69. }
  70. }
  71. ///initialisation des propriétés du module
  72. public function initOptions() {
  73. parent::initOptions();
  74. $this->_options->setOpt(XLabels::getSysLabel('xmodreplication.enable'), "enable", 'boolean');
  75. $this->_options->setOpt('Délai de connexion (s)', 'connectionTimeOut', 'text', array('compulsory'=>false), '10');
  76. $this->_options->setOpt('Identification par nom des serveurs', 'identifybyHostName', 'boolean', array('compulsory'=>false));
  77. $this->_options->setOpt('Acquittement par paquet', 'packetack', 'boolean', array('compulsory'=>false));
  78. $this->_options->setOpt('Taille des paquets', 'packetSize', 'text', array('compulsory'=>true), 500);
  79. $this->_options->setOpt('Avertissement par mail', 'mailWarning', 'boolean', array('compulsory'=>false), false);
  80. }
  81. /// affichage de quelques propriétés du module
  82. public function getInfos($ar){
  83. $p=new XParam($ar,array('tplentry'=>TZR_RETURN_DATA));
  84. $tplentry=$p->get('tplentry');
  85. $ar['tplentry']=TZR_RETURN_DATA;
  86. $ret=parent::getInfos($ar);
  87. // dernier numéro de chrono
  88. $rs=selectQuery('select ifnull(min(chrono), 0) as pchrono, ifnull(max(CHRONO), 0) as lchrono, ifnull(count(*), 0) as nb from JOURNAL');
  89. $o1=$rs->fetch();
  90. $rs->closeCursor();
  91. $ret['infos']['lchrno']=(object)array('label'=>'Dernier numéro de chrono','html'=>$o1['lchrono']);
  92. $ret['infos']['pchrno']=(object)array('label'=>'Premier numéro de chrono','html'=>$o1['pchrono']);
  93. $ret['infos']['nblines']=(object)array('label'=>'Nombre de lignes','html'=>$o1['nb']);
  94. $ret['infos']['activate']=(object)array('label'=>'Module activé','html'=>$this->enable);
  95. $ret['infos']['connectionTimeOut']=(object)array('label'=>'Délai connexion','html'=>$this->connectionTimeOut);
  96. $ret['infos']['identifybyHostName']=(object)array('label'=>'Identification des clients par nom du serveur','html'=>$this->identifybyHostName);
  97. $nottotables = array();
  98. foreach($this->notToReplicateTables as $k=>$r){
  99. if ($r == 1)
  100. $nottotables[]=$k;
  101. }
  102. $ret['infos']['nottotables']=(object)array('label'=>'Tables non repliquées','html'=>implode(', ', $nottotables));
  103. $rs = selectQueryGetAll('select name, value from _STATICVARS where name like \'repli%\'');
  104. $chronos = array();
  105. foreach($rs as $ors){
  106. $chronos[] = $ors['name'].' &rArr; '.$ors['value'];
  107. }
  108. $ret['infos']['chronos']=(object)array('label'=>'Chronos mémorisés','html'=>implode('<br>', $chronos));
  109. $ret['infos']['wsdlcache']=(object)array('label'=>'soap.wsdl_cache_enabled', ini_get('soap.wsdl_cache_enabled'));
  110. return XShell::toScreen1($tplentry,$ret);
  111. }
  112. /// purge des logs de replication
  113. protected function purgeLogs(){
  114. // liste des slaves / full / master
  115. $rs = selectQuery('select * from REPLI');
  116. $delay = ' 2 DAY ';
  117. while($rs && $ors = $rs->fetch()){
  118. $nb = selectQuery("select count(*) as nb from LOGS where etype in ('synchronizeServer', 'synchronizeServ', 'getchangeset', 'syncAck','getinitset','applyinitset') and object = '".$ors['KOID']."'")->fetch(PDO::FETCH_COLUMN);
  119. if ($nb <= 100)
  120. continue;
  121. $rs1 = selectQuery("select * from LOGS where etype in ('synchronizeServer', 'synchronizeServ', 'getchangeset', 'syncAck','getinitset','applyinitset') and object='".$ors['KOID']."' and dateupd < DATE_SUB(NOW(), INTERVAL ".$delay.") ");
  122. XLogs::notice('repli', 'archiving '.$rs1->rowCount().' for '.$ors['KOID']);
  123. while($rs1 && $ors1 = $rs1->fetch()){
  124. XArchives::appendData('LOGS',$ors1);
  125. updateQuery('delete from LOGS where KOID=\''.$ors1['KOID'].'\'', false);
  126. }
  127. }
  128. }
  129. /// nettoyage du journal
  130. protected function purgeJournal(){
  131. // plus petit chrono acquitté des slave ou full
  132. $rs = selectQueryGetAll('select koid from REPLI where trepli in (\'full\', \'slave\')');
  133. $minchrono = -1;
  134. foreach($rs as $ors){
  135. $sinfos = $this->getServerInfos($ors['koid']);
  136. $lastackchrono = XDbIni::getStatic('replication:'.$sinfos['_ident'].':chrono','val');
  137. if (empty($lastackchrono))
  138. $lastackchrono = -1;
  139. if ($minchrono == -1 || $lastackchrono < $minchrono)
  140. $minchrono = $lastackchrono;
  141. }
  142. if ($minchrono != -1){
  143. $delay = ' 24 HOUR ';
  144. Xlogs::notice('repli', 'purge du journal, minchrono : '.$minchrono);
  145. $rs = selectQuery('select * from JOURNAL where CHRONO<'.$minchrono.' and UPD < DATE_SUB(NOW(), INTERVAL '.$delay.')');
  146. XLogs::notice('repli', $rs->rowCount().' lignes à purger');
  147. while($rs && $ors = $rs->fetch()){
  148. XArchives::appendData('JOURNAL',$ors);
  149. updateQuery('delete from JOURNAL where CHRONO=\''.$ors['CHRONO'].'\'', false);
  150. }
  151. }
  152. }
  153. /// nettoyage des initSets qui sont dans les points de sauvegarde
  154. protected function purgeInitSet(){
  155. $modadmin = XModule::objectFactory(self::getMoid(XMODADMIN_TOID));
  156. $modadmin->browseCheckpoints();
  157. $cp = XShell::from_screen('cp');
  158. $now = date('Ymd');
  159. $initsets = array();
  160. foreach($cp['list'] as $n=>$data){
  161. $res = array();
  162. if (preg_match('/^([0-9]{8})_[0-9]{6}_iniset$/', $n, $res)){
  163. if ($res[1] < $now)
  164. $initsets[] = $n;
  165. }
  166. $res = array();
  167. if (preg_match('/^Avant ([0-9]{8})_[0-9]{6}_iniset initialisation$/', $data['comment'], $res)){
  168. if ($res[1] < $now)
  169. $initsets[] = $n;
  170. }
  171. }
  172. foreach($initsets as $n){
  173. $r = $modadmin->delCheckpoint(array('checkpoint'=>$n));
  174. Xlogs::notice('repli','suppression des initset : '.$n);
  175. }
  176. }
  177. /// securite des fonctions accessibles par le web
  178. public function secGroups($function,$group=NULL) {
  179. $g=array();
  180. /* ce sont des fonctions interactives +/- de tests */
  181. $g['applyChangeSet']=array('admin');
  182. $g['showChangeSet']=array('admin');
  183. $g['preShowChangeSet']=array('admin');
  184. $g["showInitSet"]=array("admin");
  185. $g['downloadInitSetFile'] = array("none", "ro", "rw", "rwv", "admin");
  186. $g['forceInitset'] = array('admin');
  187. // re initialisation de l'env : appelle un getInitSet et l'applique
  188. $g["applyinitset"]=array("admin");
  189. // fonction initiales (devrait devenir ro voire privées si soap activé)
  190. $g["getinitset"]=array("none","ro","rw","rwv","admin");
  191. $g["getchangeset"]=array("none","ro","rw","rwv","admin");
  192. $g["syncAck"]=array("none","ro","rw","rwv","admin");
  193. $g["getfile"]=array("ro","rw","rwv","admin");
  194. // fonctions du module soap
  195. $g['wgetChangeSet']=array("ro","rw","rwv","admin");
  196. $g['wgetInitSet']=array("ro","rw","rwv","admin");
  197. $g['wgetInitSetFiles']=array("ro","rw","rwv","admin");
  198. $g['wsyncAck']=array("ro","rw","rwv","admin");
  199. $g['wgetFile']=array("ro","rw","rwv","admin");
  200. // liste des tables et statuts divers de replication
  201. $g['tablesStatus'] = array('admin');
  202. $g['procEditTableStatus'] = array('admin');
  203. if(isset($g[$function])) {
  204. if(!empty($group)) return in_array($group, $g[$function]);
  205. return $g[$function];
  206. }
  207. return parent::secGroups($function,$group);
  208. }
  209. // liste des catégories reconnues dans cette classe
  210. public function secList() {
  211. return array('none','ro','rw','rwv', 'admin');
  212. }
  213. /// fonction qui reinitialise l'env de replication
  214. /// !!! incompatible avec un mode ou le client gère son chrono !
  215. public function initSynchro($ar=NULL) {
  216. updateQuery("delete from JOURNAL", false);
  217. XDbIni::clearStatic("replication:%:chrono");
  218. updateQuery('update REPLI set dtack=NULL, chrack=NULL where trepli=\'full\' or trepli=\'slave\'', false);
  219. XShell::setNext($GLOBALS['TZR_SESSION_MANAGER']::complete_self(true, true)."&moid={$this->_moid}&function=browse&template=xmodtable/browse.html&tplentry=br");
  220. }
  221. /// ??? methode pas finie
  222. public function synchronizeFull() {
  223. $rs=selectQuery("select distinct async from REPLI where trepli='full' OR trepli='master'");
  224. while($ors=$rs->fetch()) {
  225. XLogs::notice('repli-info',"full synchro to $url");
  226. $url=$ors['async'];
  227. XLogs::notice('repli-info',"full synchro to $url");
  228. $file64=implode('',file($url."tzr/scripts/admin.php?class=XModReplication&function=getbaselist"));
  229. $baselist=explode(';',base64_decode($file64));
  230. foreach($baselist as $i => $b) {
  231. XLogs::notice('repli-info',"full synchro to $url for table $b");
  232. $file64=implode('',file($url."tzr/scripts/admin.php?class=XModReplication&function=getinitset&table=$b"));
  233. }
  234. }
  235. $rs->closeCursor();
  236. }
  237. /// rend la liste des tables
  238. public function getbaselist($ar) {
  239. $r = XDataSource::getBaseList();
  240. $l=array_keys($r);
  241. $content = base64_encode(implode(";",$l));
  242. header('Content-type: text/plain');
  243. echo chunk_split($content);
  244. exit();
  245. }
  246. /// Ecriture des entrées mémorisées dans la table JOURNAL
  247. public function sendToJournal() {
  248. if(!empty($this->_journal)) {
  249. $chro=self::newChrono();
  250. $sth = NULL;
  251. foreach($this->_journal as $i => $line) {
  252. updateQuery("INSERT INTO JOURNAL (RQ,EX,CHRONO) values('$line','1','$chro')",false);
  253. $chro++;
  254. }
  255. $this->_journal=array();
  256. }
  257. }
  258. //BDOCm* XModReplication/journalize
  259. // NAME
  260. // XModReplication::journalize -- journalisation des actions
  261. // DESCRIPTION
  262. // Cette fonction permet de conserver la trace des actions réalisées
  263. // sur les données et la structure de l'instance du serveur.
  264. // INPUTS
  265. // type - donne le type de modification des données
  266. // rq - donne le contenu de la modification
  267. // SYNOPSIS
  268. // si type vaut sql, alors rq est une requête sql qui doit être
  269. // examinée pour journalisation. Les règles de journalisation sont: si
  270. // la requête est un select, elle n'est pas conservée ; si la requête
  271. // est préfixée par / *-X* /, elle n'est pas journalisée. Toutes les
  272. // autres requêtes sont journalisées.
  273. // Si type vaut upd, il s'agit de la mise à jour ou de la création
  274. // d'un fichier de données. Si type vaut del, il s'agit de la
  275. // suppression d'un fichier, et rq est le nom du fichier.
  276. //EDOC
  277. public function journalize($type, $rq) {
  278. XLogs::debug('repli-info -> '.$type.' '.$rq);
  279. if(!XSystem::tableExists('JOURNAL')||!XSystem::tableExists('REPLI')) return false;
  280. if(empty($this->enable) || $this->suspended) return false;
  281. if (empty($type))
  282. return false;
  283. if($type=="sql") {
  284. if(0==strncasecmp("SELECT",$rq,6)) return false;
  285. if(0==strncasecmp("/*-X*/",$rq,6)) return false;
  286. $r = preg_match('/ *(update) +(low_priority |ignore ){0,2} *([a-z0-9._-]+) +set/i', $rq, $res);
  287. if ($r && !empty($res[3]) && !$this->replicableTable($res[3]))
  288. return false;
  289. $r = preg_match('/^ *(insert|replace) +(low_priority |delayed |ignore ){0,2} *(into){0,1} *([a-z0-9._-]+).*/i', $rq, $res);
  290. if ($r && !empty($res[4]) && !$this->replicableTable($res[4]))
  291. return false;
  292. $r = preg_match('/^ *(delete) +(low_priority |quick |ignore ){0,3} *(from) *([a-z0-9._-]+).*/i', $rq, $res);
  293. if ($r && !empty($res[4]) && !$this->replicableTable($res[4]))
  294. return false;
  295. $r = preg_match('/^ *(truncate) +(low_priority |quick |ignore ){0,3} *([A-Za-z0-9._-]+).*/i', $rq, $res);
  296. if ($r && !empty($res[3]) && !$this->replicableTable($res[3]))
  297. return false;
  298. $rq=addslashes($rq);
  299. }
  300. if($type=="upd") {
  301. $filesize=filesize($rq);
  302. $rq=str_replace($GLOBALS["DATA_DIR"],"",$rq);
  303. list( $btab, $fn) = explode('/', $rq);
  304. if (!$this->replicableTable($btab))
  305. return false;
  306. $rq=$filesize.":".$rq;
  307. }
  308. if($type=="del") {
  309. $rq=str_replace($GLOBALS["DATA_DIR"],"",$rq);
  310. list( $btab, $fn) = explode('/', $rq);
  311. if (!$this->replicableTable($btab))
  312. return false;
  313. }
  314. if(!empty($rq)) {
  315. $this->_journal[]=$type.":".$rq;
  316. }
  317. return true;
  318. }
  319. /// vérifie qu'un serveur (client) à le droit aux données
  320. protected function checkServer($infos, $rights){
  321. $trepli = '(\''.implode('\',\'', $rights).'\')';
  322. if ($this->identifybyHostName)
  323. $rs = selectQuery('select KOID from REPLI where serv = \''.$infos['hostname'].'\' and trepli in '.$trepli);
  324. else
  325. $rs=selectQuery('select KOID from REPLI where ip = \''.$infos['ip'].'\' and trepli in '.$trepli);
  326. if ($rs->rowCount() == 0)
  327. return false;
  328. $ors = $rs->fetch();
  329. return $ors['KOID'];
  330. }
  331. /// retourne les paramètres d'un server (ou client)
  332. protected function getServerInfos($oid){
  333. $rs = selectQuery('select * from REPLI where KOID = \''.$oid.'\'');
  334. if ($rs->rowCount() == 0)
  335. return NULL;
  336. $ors = $rs->fetch();
  337. if ($this->identifybyHostName)
  338. $ors['_ident']=$ors['serv'];
  339. else
  340. $ors['_ident']=$ors['ip'];
  341. return $ors;
  342. }
  343. //BDOCm* XModReplication/getchangeset
  344. // NAME
  345. // XModReplication::getchangeset -- rend le différentiel par rapport à la dernière réplication
  346. // DESCRIPTION
  347. // La fonction rend les opérations différentielles à réaliser depuis la dernière synchro
  348. // INPUTS
  349. // SYNOPSIS
  350. //EDOC
  351. public function getchangeset($ar) {
  352. $p = new XParam($ar, array('mode'=>'soap', 'view'=>0));
  353. $hostname = $p->get('hostname');
  354. $ip = $_SERVER['REMOTE_ADDR'];
  355. $mode = $p->get('mode');
  356. $view = $p->get('view');
  357. // vérification que ce serveur a les droits de venir chercher des infos
  358. $servoid = $this->checkServer(array('ip'=>$ip, 'hostname'=>$hostname), array('full', 'slave'));
  359. if($servoid == false) {
  360. XLogs::critical('repli-error', 'unknown ip or master configuration: '.$ip.' '.$hostname);
  361. if ($mode == 'soap'){
  362. return array('mess'=>'unknown ip or master configuration: '.$ip.' '.$hostname, 'data'=>NULL);
  363. }
  364. die();
  365. }
  366. $sinfos = $this->getServerInfos($servoid);
  367. $lastchrono = XDbIni::getStatic('replication:'.$sinfos['_ident'].':chrono','val');
  368. if (empty($lastchrono))
  369. $lastchrono = -1;
  370. $newstatus = 'GCSet : '.$lastchrono;
  371. if (!$view)
  372. updateQuery('update REPLI set status=\''.$newstatus.'\' where KOID=\''.$sinfos['KOID'].'\'');
  373. XLogs::notice('repli-info',"chrono $lastchrono");
  374. if ($lastchrono == 'forceInitset')
  375. return array('mess' => 'forceInitset');
  376. $rs=selectQuery("select * from JOURNAL where CHRONO > $lastchrono order by CHRONO limit ".$this->packetSize);
  377. $rsm=selectQueryGetAll("select max(CHRONO) as maxchrono from JOURNAL");
  378. $a=array();
  379. $i=0;
  380. $pfirst = $plast = NULL;
  381. while(($o=$rs->fetch()) && ($i<$this->packetSize)) {
  382. if (empty($pfirst))
  383. $pfirst = $o['CHRONO'];
  384. $plast = $o['CHRONO'];
  385. $a[]=array("UPD"=>$o['UPD'],"RQ"=>$o['RQ'],"CHRONO"=>$o['CHRONO']);
  386. $i++;
  387. }
  388. $z = serialize($a);
  389. $rs->closeCursor();
  390. self::lognojournal('getchangeset', $servoid, $hostname.',view ='.$view.',firstChrono = '.$pfirst.',lastChrono = '.$plast.', maxChrono = '.$rsm[0]['maxchrono'], NULL, true);
  391. if ($mode == 'soap'){
  392. return array('mess'=>'ok', 'data'=>$z, 'firstChrono'=>$pfirst, 'lastChrono'=>$plast, 'maxChrono'=>$rsm[0]['maxchrono'] );
  393. }
  394. }
  395. /// récupération d'un jeu d'initialisation complet
  396. /// depuis le serveur donné
  397. public function getinitset($ar){
  398. $p = new XParam($ar, array('mode'=>'soap', 'view'=>0));
  399. $ip = $_SERVER['REMOTE_ADDR'];
  400. $hostname = $p->get('hostname');
  401. $mode = $p->get('mode'); // soap, file
  402. $view = $p->get('view');
  403. // vérification que ce serveur a les droits de venir chercher des infos
  404. $servoid = $this->checkServer(array('ip'=>$ip, 'hostname'=>$hostname), array('full', 'slave'));
  405. if($servoid == false) {
  406. if ($mode == 'soap'){
  407. return array('mess'=>'Client non identifiable', 'data'=>NULL);
  408. }
  409. die('nok');
  410. }
  411. $sinfos = $this->getServerInfos($servoid);
  412. self::lognojournal('getinitset', $servoid, $hostname.',view ='.$view.', mode = '.$mode, NULL);
  413. ini_set('max_execution_time', 600);
  414. set_time_limit (600);
  415. /// lecture des data à reprendre
  416. /// les fichiers pour chaque table à repliquer - si mode file on fait juste un tar
  417. if ($mode == 'soap'){
  418. /// recuperation de la liste des externals des tables repliquées dans la table TMP_DATA
  419. $sqldata = "\n--\n-- LISTE DES DATA A REPRENDRE \n--";
  420. $sqldata .= "\nDROP TABLE IF EXISTS TMP_DATA;";
  421. $sqldata .= "\nCREATE TABLE TMP_DATA (dtab varchar(64), dfield varchar(64), ddata varchar(128));";
  422. } else {
  423. $extfiles = " ";
  424. }
  425. $tables = getMetaTables();
  426. foreach($tables as $atable){
  427. if (!$this->replicableTable($atable))
  428. continue;
  429. if (!XDataSource::sourceExists($atable))
  430. continue;
  431. // la table est replicable
  432. $q = "select * from $atable";
  433. $ds=XDataSource::objectFactoryHelper8('BCLASS=XDSTable&SPECS='.$atable);
  434. $fields = array();
  435. foreach($ds->desc as $fn=>$fo){
  436. if ($fo->hasExternals())
  437. $fields[] = $fn;
  438. }
  439. if (count($fields) == 0)
  440. continue;
  441. // verifier données publiées ?
  442. // recuperation de chaque nom de fichier pour chaque champ
  443. unset($browse);
  444. $browse=$ds->browse(array("select"=>$q, "selectedfields"=>$fields,"tplentry"=>TZR_RETURN_DATA,"pagesize"=>"999999"));
  445. XLogs::notice('repli_info', 'scan externals for table '.$atable.'');
  446. foreach($fields as $i => $f1) {
  447. $f1o=$ds->desc[$f1];
  448. if($f1o->hasExternals()) {
  449. XLogs::notice('repli-info',"field $f1 has files");
  450. foreach($browse['lines_oid'] as $j => $oid) {
  451. XLogs::notice('repli_info', 'scan externals for oid '.$oid.' value = '.$browse['lines_o'.$f1][$j]->raw);
  452. // on recherche la liste des fichiers a repliquer
  453. $ex=$f1o->externals($browse['lines_o'.$f1][$j]->raw);
  454. if(!empty($ex)) {
  455. foreach($ex as $v1i => $f1i) {
  456. XLogs::notice('repli-info', 'inserting upd query for oid'.$oid.' '.$f1i);
  457. // journalisation du transfert de fichier
  458. if ($mode == 'soap')
  459. $sqldata .= "\nINSERT INTO TMP_DATA (dtab, dfield, ddata) values('$atable', '{$f1o->field}', '$f1i');";
  460. elseif ($mode == 'file'){
  461. $extfiles .= "$f1i ";
  462. }
  463. }
  464. } else {
  465. XLogs::notice('repli', 'no file for oid'.$oid);
  466. }
  467. }
  468. }
  469. }
  470. }
  471. /// to do : lock
  472. /// lecture du dernier chrono pour acquittement à réception
  473. $newchrono = selectQuery('select ifnull(max(chrono), 0) as newchrono from JOURNAL')->fetch(PDO::FETCH_COLUMN);
  474. /// récupération de toutes les strutures (ddl)
  475. $fullddl=TZR_TMP_DIR.uniqid().'full.ddl.sql';
  476. list($host,$port)=explode(":",$GLOBALS['DATABASE_HOST']);
  477. if (empty($port))
  478. $port = '3306';
  479. $cmd=TZR_MYSQLDUMP_PATH." --no-data -u".$GLOBALS["DATABASE_USER"]." -p".$GLOBALS['DATABASE_PASSWORD'].
  480. " -h $host -P $port ".$GLOBALS['DATABASE_NAME']." > $fullddl ";
  481. system($cmd);
  482. /// récupération du dictionaire complet et des données répliquées
  483. $datafile=TZR_TMP_DIR.uniqid().'data.sql';
  484. /// récupération de toutes les données replicables
  485. $tables = getMetaTables();
  486. $tablelist = $this->systtables;
  487. foreach($tables as $atable){
  488. if (in_array($atable, $tablelist))
  489. continue;
  490. if ($this->replicableTable($atable))
  491. $tablelist[] = $atable;
  492. }
  493. $cmd=TZR_MYSQLDUMP_PATH." --no-create-info -u ".$GLOBALS["DATABASE_USER"]." -p".$GLOBALS['DATABASE_PASSWORD'].
  494. " -h $host -P $port ".$GLOBALS['DATABASE_NAME']." ".implode(' ', $tablelist)." > $datafile ";
  495. system($cmd);
  496. /// ajout du dernier numéro de chrono qui sera acquité à réception
  497. $sql = "\n--\n-- NOUVEAU CHRONO À PRENDRE EN COMPTE\n--";
  498. $sql .= "\nDROP TABLE IF EXISTS TMP_NEWCHRONO;";
  499. $sql .= "\nCREATE TABLE TMP_NEWCHRONO (CHRONO bigint);";
  500. $sql .= "\nINSERT INTO TMP_NEWCHRONO (CHRONO) values ($newchrono);";
  501. /// ajout des variables statics à passer
  502. $sql = "\n--\n-- _STATICVARS À PRENDRE EN COMPTE\n--";
  503. $sql .= "\nDROP TABLE IF EXISTS TMP_STATICVARS;";
  504. $sql .= "\nCREATE TABLE TMP_STATICVARS like _STATICVARS;";
  505. $sql .= "\nINSERT INTO TMP_STATICVARS (now(), 'upgrades_release', '".XIni::get('upgrades_release')."');";
  506. $sql .= "\nINSERT INTO TMP_STATICVARS (now(), 'lastdbrelease', '".XDbIni::getStatic('lastdbrelease','val')."');";
  507. /// écriture des contenus dans le flux en retour
  508. $all = $sql."\n--\n-- DDL \n--\n".file_get_contents($fullddl)."\n--\n-- DATA\n--\n-- ".implode(' ',$tablelist)."\n".file_get_contents($datafile)."\n".$sqldata;
  509. unlink($fullddl);
  510. unlink($datafile);
  511. if ($mode == 'soap'){
  512. return array('mess'=>'ok', 'data'=>$all);
  513. } elseif ($mode == 'file'){
  514. // partie texte + sql
  515. $tmpdir = TZR_TMP_DIR.uniqid();
  516. mkdir($tmpdir);
  517. $sqlfile = $tmpdir.'/dump_sql.sql';
  518. file_put_contents($sqlfile, $all);
  519. $exttarfile = $tmpdir.'/data_ext.tgz';
  520. system("(cd {$GLOBALS['TZR_WWW_DIR']}data; tar --exclude=*-cache -czf $exttarfile $extfiles )");
  521. $tmpname = TZR_TMP_DIR.'initset_'.$hostname.('_').uniqid().'.data';
  522. system("(cd $tmpdir;tar -cf $tmpname dump_sql.sql data_ext.tgz)");
  523. system('gzip '.$tmpname);
  524. system("rm -rf $tmpdir");
  525. // ajout d'un jeton ...
  526. XDBIni::setStatic('repli::initset::'.$hostname, $tmpname.'.gz');
  527. // on retourne l'url pour lire le fichier
  528. $file = md5($tmpname.'.gz');
  529. $url = $GLOBALS['TZR_SESSION_MANAGER']::complete_self(true, true)."&moid={$this->_moid}&hostname=$hostname&function=downloadInitSetFile&file=$file";
  530. return array('mess'=>'ok', 'data'=>$url);
  531. }
  532. }
  533. /// recuperation d'un fichier initset
  534. function downloadInitSetFile($ar){
  535. $p = new XParam($ar, array());
  536. $hostname = $p->get('hostname');
  537. $file = $p->get('file');
  538. $rs = selectQuery("select value from _STATICVARS where name='repli::initset::$hostname' and md5(value)='$file'");
  539. if (!$rs || $rs->rowCount() != 1){
  540. header("HTTP/1.0 404 Not Found");
  541. exit(0);
  542. } else {
  543. $ors = $rs->fetch();
  544. $size = @filesize($ors['value']);
  545. $mime = '';
  546. header("Content-type: $mime");
  547. header("Content-Length: $size");
  548. @readfile($ors['value']);
  549. unlink($ors['value']);
  550. }
  551. }
  552. /// acquittement d'un chrono sur le serveur distant
  553. protected function ackChrono($client, $chrono, $sinfos){
  554. XLogs::debug('repli-info : synchro packet '.$chrono);
  555. $ret = $client->soapclient->wsyncAck(array('sessid'=>$client->sessid), array('hostname'=>$sinfos['hostname'], 'chrono'=>$chrono));
  556. if ($ret->mess != 'ok'){
  557. XLogs::critical('repli-error', 'synchro packet error: '.$chrono.' : '.$ret->mess);
  558. return false;
  559. }
  560. return true;
  561. }
  562. /// traitement d'une demande d'acquittement par un client
  563. public function syncAck($ar=NULL) {
  564. $p = new XParam($ar, array('mode'=>'soap'));
  565. $chrono=$p->get("chrono");
  566. $ip = $_SERVER['REMOTE_ADDR'];
  567. $hostname = $p->get('hostname');
  568. $mode = $p->get('mode');
  569. // vérification que ce serveur a les droits de venir chercher des infos
  570. $servoid = $this->checkServer(array('ip'=>$ip,'hostname'=>$hostname), array('full', 'slave'));
  571. if ($servoid == false){
  572. if ($mode == 'soap'){
  573. return array('mess'=>'Unknown client ', 'data'=>NULL);
  574. }
  575. exit();
  576. }
  577. $sinfos = $this->getServerInfos($servoid);
  578. $oldchrono=XDbIni::getStatic('replication:'.$sinfos['_ident'].':chrono','val');
  579. if (empty($oldchrono) || $oldchrono == 'forceInitset' || ($oldchrono <= $chrono)) {
  580. XDbIni::setStatic('replication:'.$sinfos['_ident'].':chrono',$chrono);
  581. $this->updateStatus(array('lastackchrono'=>$chrono), $sinfos);
  582. self::lognojournal('syncAck', $servoid, $chrono, NULL, true);
  583. } else {
  584. if ($mode == 'soap'){
  585. return array('mess'=>"$ip $hostname tried to ack $chrono, cannot get back in time now is $oldchrono", 'data'=>NULL);
  586. }
  587. XLogs::notice('repli-error',"$ip $hostname tried to ack $chrono, cannot get back in time now is $oldchrono");
  588. }
  589. if ($mode == 'soap')
  590. return array('mess'=>'ok', 'data'=>NULL);
  591. exit();
  592. }
  593. /// mise à jour du status d'une entree de replication
  594. /// dernier chrono acquité +/- completion atteinte
  595. private function updateStatus($ar, $ors){
  596. if (isset($ar['lastackchrono'])){
  597. $upd = date('Y-m-d H:i:s');
  598. $u = "update REPLI set dtack='{$upd}', chrack = '{$ar['lastackchrono']}' where KOID='{$ors['KOID']}'";
  599. $lj = countSelectQuery("select ifnull(max(chrono), 0) as mc from JOURNAL;");
  600. if ($ar['lastackchrono'] >= $lj){
  601. $u = "update REPLI set status='', dtcompl='{$upd}', dtack='{$upd}', chrack = '{$ar['lastackchrono']}' where KOID='{$ors['KOID']}'";;
  602. }
  603. updateQuery($u, false);
  604. }
  605. }
  606. /// est ce que la table doit être repliquée ou pas
  607. function replicableTable($btab){
  608. return !isset($this->notToReplicateTables[$btab]);
  609. }
  610. /// mise à jour de puis la page etat des tables
  611. /// du statut replication de la table
  612. function procEditTableStatus($ar){
  613. $p = new XParam($ar, array());
  614. $btab = $p->get('btab');
  615. $newstatus = $p->get('newstatus');
  616. $cnt = countSelectQuery("select COUNT(*) from BASEBASE where btab='$btab'");
  617. if ($cnt == 1){
  618. XLogs::notice('repli-info', "procEditTableStatus $btab => $newstatus");
  619. updateQuery("update BASEBASE set NOTTOREPLI=$newstatus where btab='$btab'");
  620. } else {
  621. // table inconnue ...
  622. XLogs::critical('repli-error', "procEditTableStatus $btab not found in BASEBASE");
  623. }
  624. XShell::setNext($p->get('_next'));
  625. }
  626. /// liste des tables et de leur status replication
  627. /// -> le status est dans BASEBASE +/- status de replication des modules qui utilisent
  628. /// la table
  629. public function tablesStatus($ar){
  630. $p = new Xparam($ar, array());
  631. $tplentry = $p->get('tplentry');
  632. $tlist = XDataSource::getBaseList();
  633. $tlistyes = array();
  634. $tlistno = array();
  635. foreach($tlist as $tname=>$tlabel){
  636. // lecture du statut actuel dans BASEBASE
  637. $rs = selectQuery("select * from BASEBASE where btab='{$tname}'");
  638. $ors = $rs->fetch();
  639. if((isset($ors['NOTTOREPLI']) && $ors['NOTTOREPLI'] == 1) || (isset($this->notToReplicateTables[$tname]) && $this->notToReplicateTables[$tname] == 1))
  640. $bbrepli = false;
  641. else
  642. $bbrepli = true;
  643. unset($ors);
  644. $rs->closeCursor();
  645. unset($rs);
  646. $rs = selectQuery("select ifnull(count(*), 0) as nb from $tname");
  647. if (!$ors = $rs->fetch()){
  648. XLogs::critical('repli-error', "unknown table $tname");
  649. }
  650. $tbcount = $ors['nb'];
  651. unset($ors);
  652. $rs->closeCursor();
  653. unset($rs);
  654. // calcul du statut à partir des modules
  655. $moids = XModule::modulesUsingTable($tname, false, false, false);
  656. $moids2 = array();
  657. if (count($moids)>0){
  658. $treplicate = false;
  659. foreach($moids as $moid=>$modname){
  660. $mod = XModule::objectFactory($moid);
  661. if ($mod->replicate){
  662. $treplicate = true;
  663. $moid2 = array('moid'=>$moid, 'name'=>$modname, 'replicate'=>true);
  664. }else{
  665. $moid2 = array('moid'=>$moid, 'name'=>$modname, 'replicate'=>false);
  666. }
  667. $moids2[$moid] = $moid2;
  668. }
  669. if ($treplicate){
  670. $tlistyes[$tname] = array('count'=>$tbcount, 'bbrepli'=>$bbrepli, 'replicate'=>true, 'modules'=>$moids2, 'table'=>array('tname'=>$tname, 'tlabel'=>$tlabel));
  671. }else{
  672. $tlistno[$tname] = array('count'=>$tbcount, 'bbrepli'=>$bbrepli, 'replicate'=>false, 'modules'=>$moids2, 'table'=>array('tname'=>$tname, 'tlabel'=>$tlabel));
  673. }
  674. } else {
  675. $tlistyes[$tname] = array('count'=>$tbcount, 'bbrepli'=>$bbrepli, 'replicate'=>true, 'modules'=>$moids2, 'table'=>array('tname'=>$tname, 'tlabel'=>$tlabel));
  676. }
  677. }
  678. unset($tlist);
  679. if ($p->is_set('order')){
  680. foreach($tlistyes as $k=>$v){
  681. $bnamesy[$k]=$k;
  682. $bcountsy[$k]=$v['count'];
  683. $blabelsy[$k]=$v['table']['tlabel'];
  684. $breplisy[$k]=$v['bbrepli'];
  685. }
  686. foreach($tlistno as $k=>$v){
  687. $bnamesn[$k]=$k;
  688. $bcountsn[$k]=$v['count'];
  689. $blabelsn[$k]=$v['table']['tlabel'];
  690. $breplisn[$k]=$v['bbrepli'];
  691. }
  692. $order = $p->get('order');
  693. if ($order=='brepli'){
  694. array_multisort($breplisy, SORT_DESC, $tlistyes);
  695. array_multisort($breplisn, SORT_DESC, $tlistno);
  696. }
  697. if ($order=='bname'){
  698. array_multisort($bnamesy, SORT_ASC, $tlistyes);
  699. array_multisort($bnamesn, SORT_ASC, $tlistno);
  700. }
  701. if ($order=='blabel'){
  702. array_multisort($blabelsy, SORT_ASC, $tlistyes);
  703. array_multisort($blabelsn, SORT_ASC, $tlistno);
  704. }
  705. if ($order=='bcount'){
  706. array_multisort($bcountsy, SORT_DESC, $tlistyes);
  707. array_multisort($bcountsn, SORT_DESC, $tlistno);
  708. }
  709. }
  710. $res = array('linesyes'=>$tlistyes, 'linesno'=>$tlistno);
  711. if ($tplentry == TZR_RETURN_DATA){
  712. return $res;
  713. } else {
  714. return XShell::toScreen1($tplentry, $res);
  715. }
  716. }
  717. /// synchronisation depuis toutes les sources définies
  718. public function synchronize($ar=NULL) {
  719. $p = new XParam($ar, array());
  720. $rs=selectQuery("select * from REPLI where trepli='full' OR trepli='master'");
  721. while($ors=$rs->fetch()) {
  722. $this->synchronizeServer($ors);
  723. }
  724. $rs->closeCursor();
  725. }
  726. /// instance de client soap pour une source donnée
  727. protected function getSoapClient($ors){
  728. // ? default_socket_timeout
  729. ini_set('default_socket_timeout', $this->connectionTimeOut);
  730. if (!isset($this->soapclients[$ors['KOID']])){
  731. list ($userpassword, $wsdl) = explode('@', $ors['async']);
  732. list ($user, $password) = explode(':', $userpassword);
  733. try{
  734. if(!@file_get_contents($wsdl)) {
  735. throw new SoapFault('Server', 'No WSDL found at ' . $wsdl);
  736. }
  737. $client = new SoapClient($wsdl,array('exceptions'=>true, 'compression' => SOAP_COMPRESSION_ACCEPT, 'connection_timeout'=>$this->connectionTimeOut));
  738. $sessid = $client->auth(array('login'=>$user,'password'=>$password));
  739. $this->soapclients[$ors['KOID']] = (object)array('soapclient'=>$client, 'sessid'=>$sessid, 'hostname'=>$ors['hostname']);
  740. } catch(SoapFault $e){
  741. XLogs::critical('repli-error', 'Erreur connexion soap '.$e->getMessage());
  742. throw $e;
  743. }
  744. }
  745. return $this->soapclients[$ors['KOID']];
  746. }
  747. /// synchronisation depuis une source donnée
  748. public function synchronizeServer($ors){
  749. $sinfos = $this->getServerInfos($ors['KOID']);
  750. if (!isset($ors['_interactive_mode']) && $this->synchroRunning($ors['KOID'])){
  751. XLogs::notice('repli-info', 'synchronizeServer autre synchro en cours');
  752. $this->lognojournal('synchronizeServer', $ors['KOID'], 'autre synchro en cours', NULL);
  753. return;
  754. }
  755. // lecture du prochain paquet à traiter
  756. try{
  757. $client = $this->getSoapClient($sinfos);
  758. $ret = $client->soapclient->wgetChangeSet(array('sessid'=>$client->sessid), array('hostname'=>$client->hostname, 'view'=>0));
  759. if ($ret->mess == 'forceInitset') {
  760. XLogs::notice('repli-info', 'synchronizeServer need reset');
  761. $this->unlockSynchro($ors['KOID']);
  762. $lastChrono = selectQuery('select max(CHRONO) from JOURNAL')->fetch(PDO::FETCH_COLUMN);
  763. // si le distant est full, on vérifie qu'il a pris tout les changements locaux
  764. if ($lastChrono == $ors['chrack'] || $ors['trepli'] == 'master')
  765. return $this->applyinitset(array('oid' => $ors['KOID']));
  766. else
  767. return XLogs::notice('repli-info', 'synchronizeServer waiting completion');
  768. } elseif ($ret->mess == 'ok'){
  769. $file = unserialize($ret->data);
  770. Xlogs::notice('repli-info', "synchronizeServer lastChrono {$ret->lastChrono} firstChronno {$ret->firstChrono} maxChrono {$ret->maxChrono}");
  771. } else {
  772. XLogs::critical('repli-error', 'synchronizeServer error : '.$ret->mess);
  773. return;
  774. }
  775. }catch(SoapFault $e){
  776. XLogs::critical('repli-error', 'Erreur soap wgetmessage '.$e->getMessage());
  777. return;
  778. }
  779. // traitement du paquet
  780. if(!is_array($file)) {
  781. XLogs::critical('repli-error',var_export($file,true));
  782. return;
  783. } else {
  784. XLogs::debug('repli-info '.var_export($file,true));
  785. }
  786. self::lognojournal('synchronizeServer', $ors['KOID'], "lastChrono {$ret->lastChrono} firstChronno {$ret->firstChrono} maxChrono {$ret->maxChrono}", NULL, true);
  787. foreach($file as $li=>$oo) {
  788. XLogs::debug('repli-info: '.var_export($oo,true));
  789. if(!$this->doChange($oo, $client)){
  790. // peut générer une erreur si on est au premier du paquet
  791. $error = $GLOBALS['TZR_DB']->errorInfo();
  792. XLogs::critical('repli-error','synchronizeServer doChange error : '.$oo['CHRONO'].' '.$oo['RQ'].' => '.$error[2]);
  793. $this->lognojournal('doChange error', $ors['KOID'], 'doChange error '.$oo['CHRONO'].' '.$oo['RQ'].' => '.$error[2], NULL); // log local avec l'objet REPLI
  794. XLogs::update('doChange error', NULL, 'on '.$sinfos['hostname'].' '.$oo['CHRONO'].' '.$oo['RQ'].' => '.$error[2]); // log repliqué, host dans le comment
  795. }
  796. // accuse de reception de la mise a jour
  797. if (!$this->packetack){
  798. $this->ackChrono($client, $oo['CHRONO'], $sinfos);
  799. }
  800. }
  801. if ($this->packetack){
  802. $this->ackChrono($client, $oo['CHRONO'],$sinfos);
  803. }
  804. return;
  805. }
  806. /// traitement d'une mise à jour de replication
  807. private function doChange($a, $client) {
  808. list($protocol,$command)=explode(':',$a['RQ'],2);
  809. if($protocol=='sql') {
  810. if(empty($command) || ($command[0]=='-')) return true;
  811. $ret = updateQuery($command,false);
  812. if ($ret === false){
  813. XLogs::critical('repli error',$a['UPD'].':'.$a['CHRONO'].':'.$command);
  814. return false;
  815. } else {
  816. XLogs::notice('repli-info',$a['UPD'].':'.$a['CHRONO'].':'.$command);
  817. return true;
  818. }
  819. }
  820. if($protocol=='upd') {
  821. // on recupère la taille du fichier attendu
  822. list($fsize,$fname)=explode(':',$command);
  823. $command=$fname;
  824. // réception d'un fichier
  825. // construction du répertoire
  826. XDir::mkdir($GLOBALS['DATA_DIR'].$fname,true);
  827. $tmpfile=TZR_TMP_DIR.uniqid();
  828. $ret = $client->soapclient->wgetFile(array('sessid'=>$client->sessid), array('hostname'=>$client->hostname, 'filename'=>$command));
  829. if ($ret->mess != 'ok'){
  830. XLogs::critical('repli error getfile',$a['UPD'].':'.$a['CHRONO'].' '.$ret->mess);
  831. return false;
  832. }
  833. $myfile = $GLOBALS['DATA_DIR'].$command;
  834. if (file_exists($myfile)){
  835. $mtime = filemtime($myfile);
  836. if ($mtime>$ret->mtime){
  837. Xlogs::notice('repli warning', $a['UPD'].':'.$a['CHRONO'].' latest local file '.$GLOBALS['DATA_DIR'].$command);
  838. return true;
  839. }
  840. }
  841. file_put_contents($tmpfile, base64_decode($ret->data));
  842. $bytes=filesize($tmpfile);
  843. if($bytes>0 && $bytes == $ret->bytes) {
  844. copy($tmpfile, $GLOBALS['DATA_DIR'].$command);
  845. unlink($tmpfile);
  846. XLogs::notice('repli-ok',$a['UPD'].':'.$a['CHRONO'].' -> '.$GLOBALS['DATA_DIR'].$command);
  847. return true;
  848. } else {
  849. XLogs::critical('repli-error ','empty file or size error '.$ret->bytes.'/'.$bytes.' '.$a['CHRONO'].' -> '.$GLOBALS['DATA_DIR'].$command);
  850. unlink($tmpfile);
  851. return false;
  852. }
  853. }
  854. if($protocol=='del') {
  855. XLogs::notice('repli-ok',$a['UPD'].':'.$a['CHRONO'].':del'.$command);
  856. XLogs::debug("[XModReplication::doChange]unlink(".$GLOBALS['DATA_DIR'].$command.")");
  857. @unlink($GLOBALS['DATA_DIR'].$command);
  858. return true;
  859. }
  860. return true;
  861. }
  862. /// creation de la structure de la table de replication
  863. static function createRepli() {
  864. if(!XSystem::tableExists('REPLI')) {
  865. $lg = TZR_DEFAULT_LANG;
  866. $ar1["translatable"]="0";
  867. $ar1["auto_translate"]="0";
  868. $ar1["btab"]='REPLI';
  869. $ar1["bname"][$lg]='System - '.XLabels::getSysLabel('xmodreplication.modulename');
  870. XDSTable::procNewSource($ar1);
  871. $x=XDataSource::objectFactoryHelper8('BCLASS=XDSTable&SPECS='.'REPLI');
  872. // ord obl que bro tra mul pub tar
  873. $x->createField('serv','Serveur','XTextDef', '60','3', '1','1','1','0','0','1');
  874. $x->createField('ip','Serveur (ip)','XShortTextDef', '20','4', '1','1','1','0','0','0');
  875. $x->createField('status','Status','XShortTextDef', '20','5', '0','1','1','0','0','0');
  876. $x->createField('async','Url de Synchro','XTextDef', '60','6', '0','1','0','0','0','0');
  877. $x->createField('hostname','Nom de connexion distant','XShortTextDef', '60','7', '0','1','0','0','0','0');
  878. $x->createField('trepli','Type','XShortTextDef', '20','8', '0','1','0','0','0','0');
  879. $x->createField('dtack', 'Dernier acquittement', 'XDateTimeDef', '20','9', '0','1','0','0','0','0');
  880. $x->createField('chrack', 'Dernier chrono', 'XRealDef', '20','10', '0','1','0','0','0','0');
  881. $x->createField('dtcompl', 'Dernière complétion', 'XDateTimeDef', '20','11','0','1','0','0','0','0');
  882. }
  883. if(!XSystem::tableExists('JOURNAL')) {
  884. updateQuery("CREATE TABLE JOURNAL ( UPD TIMESTAMP NOT NULL, PRIO int(11) default '1', ".
  885. "EX tinyint(4) NOT NULL default '1', RQ text, CHRONO bigint) ;", false);
  886. }
  887. }
  888. /// jobs periodique : la replication est executee pendant le demon
  889. protected function _daemon($period) {
  890. parent::_daemon($period);
  891. XLogs::notice('repli','lauching synchro');
  892. $GLOBALS['XREPLI']->synchronize();
  893. $GLOBALS['XREPLI']->checkCompletion();
  894. if ($period == 'daily'){
  895. $this->purgeInitSet();
  896. $this->purgeJournal();
  897. $this->purgeLogs();
  898. }
  899. }
  900. /// liste des actions accessibles en mode interactif
  901. protected function _actionlist(&$my){
  902. parent::_actionlist($my);
  903. $moid = $this->_moid;
  904. $title = XLabels::getSysLabel('xmodreplication','tablesadmin','text');
  905. $o1=new XModuleAction($this,'admintables',$title,
  906. '&amp;moid='.$moid.
  907. '&amp;_function=tablesStatus&amp;template=xmodreplication/tablesstatus.html&amp;tplentry=br',
  908. 'display');
  909. $o1->homepageable=$o1->menuable= true;
  910. $o1->group='actions';
  911. $my['admintables']=$o1;
  912. }
  913. function al_display(&$my) {
  914. parent::al_display($my);
  915. $oid = $_REQUEST['oid'];
  916. if ($this->secure($myoid, 'showChangeSet')) {
  917. }
  918. $o1=new XModuleAction($this, 'showChangeSet', 'Manipuler les changesets',
  919. '&amp;moid='.$this->_moid.
  920. '&amp;_function=preShowChangeSet;template=xmodreplication/showChangeSet.html&amp;tplentry=br&oid='.$oid,
  921. 'edit');
  922. $o1->homepageable=$o1->menuable= true;
  923. $o1->group='edit';
  924. $my['showChangeSet']=$o1;
  925. if ($this->secure($myoid, 'forceInitset')) {
  926. $o1 = new XModuleAction($this, 'forceInitset', 'Forcer un reset',
  927. '&moid='.$this->_moid.'&function=forceInitset&oid='.$oid, 'edit');
  928. $o1->menuable = true;
  929. $my['forceInitset'] = $o1;
  930. }
  931. }
  932. /// affichage ecran info + saisie eventuelle chrono pour voir un changeset
  933. function preShowChangeSet($ar){
  934. $r = $this->display($ar);
  935. $sinfos = array();
  936. foreach($this->xset->desc as $fn=>$fo)
  937. $sinfos[$fn]=$r['o'.$fn]->raw;
  938. XShell::toScreen2('', 'url', $foo=$r['oasync']->raw);
  939. return $r;
  940. }
  941. /// montre un set d'initialisation
  942. function showInitSet($ar){
  943. $ar['tplentry']='br';
  944. $r = $this->preShowChangeSet($ar);
  945. $sinfos = $this->getServerInfos($r['oid']);
  946. try{
  947. $client = $this->getSoapClient($sinfos);
  948. $ret = $client->soapclient->wgetInitSet(array('sessid'=>$client->sessid), array('hostname'=>$client->hostname, 'view'=>1));
  949. } catch(SoapFault $e){
  950. $_REQUEST['message'] = $e->getMessage();
  951. return;
  952. }
  953. $file = NULL;
  954. if ($ret->mess == 'ok'){
  955. $file = $ret->data;
  956. // a voir $file = str_replace(array('>', '<'), array('&gt;', '&lt;'), $file);
  957. XShell::toScreen2('', 'initset', $file);
  958. } else {
  959. $_REQUEST['message'] = $ret->mess;
  960. }
  961. return;
  962. }
  963. /// montre un changeset - on peut demande à partir d'un chrono donné
  964. /// si chrono, dernier acquittement n'est pas pris en compte
  965. function showChangeSet($ar){
  966. $p = new XParam($ar, array());
  967. $ar['tplentry']='br';
  968. $r = $this->preShowChangeSet($ar);
  969. $sinfos = $this->getServerInfos($r['oid']);
  970. try{
  971. $client = $this->getSoapClient($sinfos);
  972. $ret = $client->soapclient->wgetChangeSet(array('sessid'=>$client->sessid), array('hostname'=>$client->hostname, 'view'=>1));
  973. if ($ret->mess == 'ok'){
  974. $file = unserialize($ret->data);
  975. // a voir $file = str_replace(array('>', '<'), array('&gt;', '&lt;'), $file);
  976. XShell::toScreen2('', 'changeset', $file);
  977. $_REQUEST['message'] = "lastCrhono {$ret->lastChrono} firstChronno {$ret->firstChrono} maxChrono {$ret->maxChrono}";
  978. } else {
  979. $_REQUEST['message'] = $ret->mess;
  980. }
  981. }catch(SoapFault $e){
  982. $_REQUEST['message'] = 'Erreur accès serveur '.$e->getMessage();
  983. XLogs::critical('repli-error', 'Erreur soap wgetmessage '.$e->getMessage());
  984. }
  985. return;
  986. }
  987. /// applique un changeset
  988. function applyChangeSet($ar){
  989. $p = new XParam($ar, array());
  990. $oid = $p->get('oid');
  991. $rs=selectQuery("select * from REPLI where KOID='$oid' and trepli='full' OR trepli='master'");
  992. $ors = $rs->fetch();
  993. $ors['_interactive_mode'] = 1;
  994. $this->synchronizeServer($ors);
  995. $rs->closeCursor();
  996. XShell::setNext($this->getMainAction().'&amp;message=done');
  997. }
  998. /// applique un jeu d'initialisation
  999. function applyinitset($ar){
  1000. $p = new XParam($ar, array('tplentry'=>TZR_RETURN_DATA));
  1001. $tplentry = $p->get('tplentry');
  1002. $oid = $p->get('oid');
  1003. $sinfos = $this->getServerInfos($oid);
  1004. if ($this->synchroRunning($oid)){
  1005. XLogs::notice('repli-info', 'applyinitset synchro en cours');
  1006. $this->lognojournal('applyinitset', $oid, 'synchro en cours', NULL);
  1007. if ($tplentry == TZR_RETURN_DATA)
  1008. return 'synchro en cours';
  1009. $_REQUEST['message'] = 'synchro en cours';
  1010. return;
  1011. }
  1012. register_shutdown_function(array($this, 'unlockSynchro'), $oid);
  1013. if ($this->synchroRunning('initset')){
  1014. XLogs::notice('repli-info', 'applyinitset initset en cours');
  1015. $this->lognojournal('applyinitset', $oid, 'initset en cours', NULL);
  1016. if ($tplentry == TZR_RETURN_DATA)
  1017. return 'initset en cours';
  1018. $_REQUEST['message'] = 'initset en cours';
  1019. return;
  1020. }
  1021. register_shutdown_function(array($this, 'unlockSynchro'), 'initset');
  1022. ini_set('max_execution_time', 600);
  1023. set_time_limit (600);
  1024. ini_set('soap.wsdl_cache_enabled', 1);
  1025. try{
  1026. $client = $this->getSoapClient($sinfos);
  1027. $ret = $client->soapclient->wgetInitSet(array('sessid'=>$client->sessid), array('hostname'=>$client->hostname, 'view'=>0));
  1028. if ($ret->mess != 'ok'){
  1029. Xlogs::critical('repli-error', 'applyinitset erreur '.$ret->mess);
  1030. if ($tplentry == TZR_RETURN_DATA)
  1031. return $ret->mess;
  1032. $_REQUEST['message'] = $ret->mess;
  1033. return;
  1034. }
  1035. } catch(SoapFault $e){
  1036. if ($tplentry == TZR_RETURN_DATA)
  1037. return $e->getMessage();
  1038. $_REQUEST['message'] = $e->getMessage();
  1039. return;
  1040. }
  1041. // les données
  1042. $file = $ret->data;
  1043. // injection du jeu dans les points de sauvegarde
  1044. $name = $date = date('Ymd_His').'_iniset';
  1045. $dir=TZR_VAR2_DIR.'checkpoints/'.$date.'/';
  1046. $sqlname=$dir.'database.sql';
  1047. $configname=$dir.'config.ini';
  1048. if(!file_exists(TZR_VAR2_DIR.'checkpoints')) mkdir(TZR_VAR2_DIR.'checkpoints');
  1049. mkdir($dir);
  1050. $config='tzr_version = "'.XIni::get('upgrades_release').'"'."\n";
  1051. $config.='creation_date = "'.date('Y-m-d H:i:s').'"'."\n";
  1052. $comment = 'Jeu d\'initialisation, version est la version actuelle de la console. Jeu non réutilisable !';
  1053. $config.='comment = "'.str_replace('"','\'',stripslashes($comment)).'"';
  1054. // save de certaines tables (REPLI, JOURNAL, _STATICVARS, _VARS) dont on garde la version locale
  1055. // et ajout au jeu reçu
  1056. $mytablesname=TZR_VAR2_DIR.'checkpoints/'.$date.'/mytables.sql';
  1057. $foo=explode(':',$GLOBALS['DATABASE_HOST']);
  1058. system('mysqldump --complete-insert -u'.$GLOBALS['DATABASE_USER'].' -p'.$GLOBALS['DATABASE_PASSWORD'].' '.'-h'.$foo[0].(!empty($foo[1])?' -P'.$foo[1]:'').' '.$GLOBALS['DATABASE_NAME'].' REPLI JOURNAL _STATICVARS _VARS >'.$mytablesname);
  1059. $file = $file."\n--\n-- TABLES LOCALES RECHARGEES \n--\n".file_get_contents($mytablesname);
  1060. file_put_contents($configname,$config);
  1061. file_put_contents($sqlname, $file);
  1062. system('gzip '.$sqlname);
  1063. // sauvegarde standard et mise en place du jeux via la restauration standard
  1064. $db=true;
  1065. $comment='Avant '.$name.' initialisation';
  1066. include($GLOBALS['LIBTHEZORRO'].'scripts/createTZRCheckpoint.php');
  1067. $ret2=include($GLOBALS['LIBTHEZORRO'].'scripts/restoreTZRCheckpoint.php');
  1068. // récupération des externes
  1069. $this->loadExternals($client);
  1070. // staticvars récupérées
  1071. updateQuery('REPLACE into _STATICVARS (select * from TMP_STATICVARS)', false);
  1072. $upgrades_release = selectQuery('select value from TMP_STATICVARS where name="upgrades_release"')->fetch(PDO::FETCH_COLUMN);
  1073. if ($upgrades_release) {
  1074. $ini=new XIni();
  1075. $ini->addVariable(array('section' => 'Upgrades',
  1076. 'variable' => 'upgrades_release',
  1077. 'value' => $upgrades_release));
  1078. }
  1079. // mise en place du nouveau numéro de chrono
  1080. $newchrono = selectQuery('select CHRONO FROM TMP_NEWCHRONO')->fetch(PDO::FETCH_COLUMN);
  1081. $this->ackChrono($client, $newchrono, $sinfos);
  1082. updateQuery('DROP table TMP_STATICVARS', false);
  1083. updateQuery('DROP table TMP_DATA', false);
  1084. updateQuery('DROP table TMP_NEWCHRONO', false);
  1085. if ($tplentry == TZR_RETURN_DATA)
  1086. return $ret2;
  1087. if (!$p->is_set('_next'))
  1088. XShell::setNext($this->getMainAction().'&amp;message=done ? '.$ret2);
  1089. }
  1090. /// chargement des 'data' (via soap std)
  1091. function loadExternals($client){
  1092. // client soap
  1093. $rs = selectQuery('select * from TMP_DATA');
  1094. XLogs::notice('repli-info', $

Large files files are truncated, but you can click here to view the full file