From 5664c266997bb4bf5b7c6825bac2b29f5b41d7d4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Mart=C3=ADn=20Marrese?= Date: Wed, 17 Mar 2004 20:52:24 +0000 Subject: [PATCH 1/1] =?utf8?q?Se=20agrega=20la=20funcionalidad=20de=20migr?= =?utf8?q?aci=C3=B3n=20de=20un=20sistema=20completo=20de=20un=20host=20a?= =?utf8?q?=20otro.=20Falta=20el=20testing.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- lib/SAMURAI/Migrar.php | 530 ++++++++++++++++++++++++++++ sistema/conf/confSecciones.php | 4 +- sistema/www/consultas/consultas.php | 17 +- sistema/www/consultas/migrar.php | 96 +++++ sistema/www/images/migrar.gif | Bin 0 -> 1099 bytes 5 files changed, 641 insertions(+), 6 deletions(-) create mode 100644 lib/SAMURAI/Migrar.php create mode 100644 sistema/www/consultas/migrar.php create mode 100644 sistema/www/images/migrar.gif diff --git a/lib/SAMURAI/Migrar.php b/lib/SAMURAI/Migrar.php new file mode 100644 index 0000000..e4b1077 --- /dev/null +++ b/lib/SAMURAI/Migrar.php @@ -0,0 +1,530 @@ +_db_dest->nextId('sistema'); + $fecha_inicio = $this->_sistema->getFechaInicio(); + $fecha_fin = $this->_sistema->getFechaFin(); + $fecha_implementacion = $this->_sistema->getFechaImplementacion(); + //Inserto los datos + $datos = array( + 'id_sistema' => $id_sistema, + 'nombre_sistema' => $this->_sistema->getNombre(), + 'desc_sistema' => $this->_sistema->getDescripcion(), + 'fecha_inicio' => $fecha_inicio ? + $fecha_inicio->format("%Y-%m-%d") : null, + 'fecha_fin' => $fecha_fin ? + $fecha_fin->format("%Y-%m-%d") : null, + 'fecha_implementacion' => $fecha_implementacion ? + $fecha_implementacion->format("%Y-%m-%d") : null, + 'contacto' => $this->_sistema->getContacto(), + 'estado' => $this->_sistema->getEstado(), + 'responsable' => $this->_responsable + ); + $res = $this->_db_dest->autoExecute('samurai.sistema', $datos, + DB_AUTOQUERY_INSERT); + if (PEAR::isError($res)) { + return $res; + } + return $id_sistema; + } + + /** + * Se encarga de migrar los datos de los permisos asociados al sistema. + * + * @return mixed + * @access protected + */ + function _migrarPermisos() { + //Obtengo la lista de permisos del sistema origen. + $permisos_source = SAMURAI_Permiso::getArrayPermisos($this->_db_source, + $this->_sistema->getId()); + $permisos_dest = SAMURAI_Permiso::getArrayPermisos($this->_db_dest); + foreach ($permisos_source as $key => $value) { + if (in_array($value, $permisos_dest)) { + //Existe. Guardo el ID. + $res[$key] = array_search($value, $permisos_dest); + } + else { + //No existe. Lo agrego a la base y guardo el id. + //Obtengo el siguiente ID + $id_permiso = $this->_db_dest->nextId('permiso'); + //Grabo la info + $datos = array ( + 'id_permiso' => $id_permiso, + 'desc_permiso' => $value, + 'responsable' => $this->_responsable, + ); + $res = $this->_db->autoExecute('samurai.permiso', $datos, DB_AUTOQUERY_INSERT); + if (PEAR::isError($res)) { + return $res; + } + $res[$key] = $id_permiso; + + } + } + return $res; + } + + /** + * Se encarga de migrar los datos de los perfiles asociados al sistema. + * + * @return mixed + * @access protected + */ + function _migrarPerfiles() { + //Obtengo la lista de perfiles del sistema origen. + $perfiles_source = SAMURAI_Perfil::getArrayPerfiles($this->_db_source, + null, $this->_sistema->getId()); + $perfiles_dest = SAMURAI_Perfil::getArrayPerfiles($this->_db_dest); + foreach ($perfiles_source as $key => $value) { + if (in_array($value, $perfiles_dest)) { + //Existe. Guardo el ID. + $res[$key] = array_search($value, $perfiles_dest); + } + else { + //No existe. Lo agrego a la base y guardo el id. + //Obtengo el siguiente ID + $id_perfil = $this->_db->nextId('perfil'); + //Grabo la info + $datos = array ( + 'id_perfil' => $id_perfil, + 'desc_perfil' => $value, + 'responsable' => $this->_responsable, + ); + $res = $this->_db->autoExecute('samurai.perfil', $datos, DB_AUTOQUERY_INSERT); + if (PEAR::isError($res)) { + return $res; + } + $res[$key] = $id_perfil; + + } + } + return $res; + } + + /** + * Se encarga de migrar los datos de los usuarios asociados al sistema. + * + * @return mixed + * @access protected + */ + function _migrarUsuarios() { + //No hago nada porque solamente tengo que armar las relaciones, si el + //usuario no existe tiene que loguearse al menos una vez en la intranet + //del host destino. Cuando se logue va a existir, por lo tanto va a + //tener el permiso dado. + $res = SAMURAI_Usuario::getArrayUsuarios($this->_db_source, + $this->_sistema->getId()) ; + return $res; + } + + /** + * Se encarga de migrar las relaciones del sistema seleccionado. + * + * @param int $sistema Identificador del sistmea en el host destino. + * @param array $permisos Array $permiso[clave_source] = $clave_destino. + * @param array $perfiles Array $perfil[clave_source] = $clave_destino. + * @param array $usuarios Array $usuario[login] = nombre. Usuarios del + * sistema en el host source. + * + * @return mixed + * @access protected + */ + function _migrarRelaciones($sistema, $permisos, $perfiles, $usuarios) { + $sistema_source = $this->_sistema->getId(); + //PERMISO - SISTEMA perm_sist (OBSERVACIONES) + $res = $this->_relacionPermisoSistema($sistema, $permisos); + if (PEAR::isError($res)) { + return $res; + } + //PERFIL - SISTEMA perfil_sist + $res = $this->_relacionPerfilSistema($sistema, $perfiles); + if (PEAR::isError($res)) { + return $res; + } + //PERMISO - PERFIL - SISTEMA perm_perfil_sist + $res = $this->_relacionPermisoPerfilSistema($sistema, $perfiles, + $permisos); + if (PEAR::isError($res)) { + return $res; + } + //PERFIL - SISTEMA - USUARIO perfil_sist_usuario + $res = $this->_relacionPerfilSistemaUsuario($sistema, $perfiles, + $usuarios); + if (PEAR::isError($res)) { + return $res; + } + } + + /** + * Migra la relacion de los permisos del sistema de un host a otro. + * + * @param int $id_sistema Identificador del sistmea en el host destino. + * @param array $permisos Array $permiso[clave_source] = $clave_destino. + * + * @return mixed + * @access protected + */ + function _relacionPermisoSistema($id_sistema, $permisos) { + $sistema_source = $this->_sistema->getId(); + $sql = " + SELECT id_permiso, observaciones + FROM samurai.perm_sist + WHERE id_sistema = $sistema_source + "; + $res = $this->_db_source->getAssoc($sql); + if (PEAR::isError($res)) { + return $res; + } + $sql = $this->_db_dest->prepare(" + INSERT INTO samurai.perm_sist + (id_permiso, id_sistema, observaciones, responsable) + VALUES (?, ? , ? , ?) + "); + foreach ($res as $key => $value) { + //Busco el nuevo id del permiso + if (array_key_exists($key, $permisos)) { + $id_permiso = $permisos[$key]; + } + else { + return new PEAR_Error ('Se estan realizando modificaciones en el + host source.', null, null, null, 'HTML'); + } + //Agrego la info en la base + $res = $this->_db_dest->execute($sql, array ($id_permiso, + $id_sistema, $value, $this->_responsable)); + if (PEAR::isError($res)) { + return $res; + } + } + } + + /** + * Migra la relacion de los perfiles del sistema de un host a otro. + * + * @param int $id_sistema Identificador del sistmea en el host destino. + * @param array $perfiles Array $perfil[clave_source] = $clave_destino. + * + * @return mixed + * @access protected + */ + function _relacionPerfilSistema($id_sistema, $perfiles) { + $sistema_source = $this->_sistema->getId(); + //Asocio los perfiles al sistema + $sql = " + SELECT id_perfil + FROM samurai.perfil_sist + WHERE id_sistema = $sistema_source + "; + $res = $this->_db_source->getCol($sql); + if (PEAR::isError($res)) { + return $res; + } + $sql = $this->_db_dest->prepare(" + INSERT INTO samurai.perfil_sist + (id_perfil, id_sistema, responsable) + VALUES (?, ? , ?) + "); + foreach ($res as $key) { + //Busco el nuevo id del perfil + if (array_key_exists($key, $perfiles)) { + $id_perfil = $perfiles[$key]; + } + else { + return new PEAR_Error ('Se estan realizando modificaciones en el + host source.', null, null, null, 'HTML'); + } + //Agrego la info en la base + $res = $this->_db_dest->execute($sql, array ($id_perfil, + $id_sistema, $this->_responsable)); + if (PEAR::isError($res)) { + return $res; + } + } + } + + /** + * Migra la relacion de los perfiles del sistema con permisos de un host a + * otro. + * + * @param int $id_sistema Identificador del sistmea en el host destino. + * @param array $perfiles Array $perfil[clave_source] = $clave_destino. + * @param array $permisos Array $permiso[clave_source] = $clave_destino. + * + * @return mixed + * @access protected + */ + function _relacionPermisoPerfilSistema($id_sistema, $perfiles, $permisos) { + $sistema_source = $this->_sistema->getId(); + //Asocio los perfiles al sistema con permisos + $sql = " + SELECT id_permiso, id_perfil, id_sistema, observaciones + FROM samurai.perm_perfil_sist + WHERE id_sistema = $sistema_source + "; + $res = $this->_db_source->getAll($sql); + if (PEAR::isError($res)) { + return $res; + } + $sql = $this->_db_dest->prepare(" + INSERT INTO samurai.perm_perfil_sist + (id_permiso, id_perfil, id_sistema, observaciones, responsable) + VALUES (?, ?, ?, ?, ?) + "); + foreach ($res as $value) { + if (array_key_exists($value[0], $permisos)) { + $id_permiso = $permisos[$value[0]]; + } + else { + return new PEAR_Error ('Se estan realizando modificaciones en el + host source.', null, null, null, 'HTML'); + } + if (array_key_exists($value[1], $perfiles)) { + $id_perfil = $perfiles[$value[1]]; + } + else { + return new PEAR_Error ('Se estan realizando modificaciones en el + host source.', null, null, null, 'HTML'); + } + $res = $this->_db_dest->execute($sql, array ($id_permiso, + $id_perfil, $id_sistema, $value[2], $this->_responsable)); + if (PEAR::isError($res)) { + return $res; + } + } + } + + /** + * Migra la relacion de los usuarios con perfiles del sistema con permisos + * de un host a otro. + * + * @param int $id_sistema Identificador del sistmea en el host destino. + * @param array $perfiles Array $perfil[clave_source] = $clave_destino. + * @param array $usuarios Array $usuario[login] = nombre. + * + * @return mixed + * @access protected + */ + function _relacionPerfilSistemaUsuarios($id_sistema, $perfiles, $usuarios) { + $sistema_source = $this->_sistema->getId(); + //Asocio los usuarios al perfil en le sistema + $sql = " + SELECT login, id_perfil, id_sistema + FROM samurai.perfil_sist_usuario + WHERE id_sistema = $sistema_source + "; + $res = $this->_db_source->getAll($sql); + if (PEAR::isError($res)) { + return $res; + } + $sql = $this->_db_dest->prepare(" + INSERT INTO samurai.perfil_sist_usuario + (login, id_perfil, id_sistema, responsable) + VALUES (?, ?, ?, ?) + "); + foreach ($res as $value) { + if (array_key_exists($value[1], $perfiles)) { + $id_perfil = $perfiles[$value[1]]; + } + else { + return new PEAR_Error ('Se estan realizando modificaciones en el + host source.', null, null, null, 'HTML'); + } + $res = $this->_db_dest->execute($sql, array ($value[0], $id_perfil, + $id_sistema, $this->_responsable)); + if (PEAR::isError($res)) { + return $res; + } + } + } + + /** + * Verifica si existe en el host destino un sistema con igual nombre. + * + * @return mixed + * @access protected + */ + function _verificarExisteSistema() { + $sql = " + SELECT count(*) AS cuenta + FROM samurai.sistema + WHERE nombre_sistema = ". + $this->_db_dest->quote($this->_sistema->getNombre()); + $res = $this->_db_dest->getCol($sql); + if (PEAR::isError($res)) { + return $res; + } + elseif (array_shift($res)) { + return new PEAR_Error ('El sistema ya existe en el host + seleccionado.', null, null, null, 'HTML'); + } + } + + /** + * Realiza la conexion con el host destino + * + * @param string $db_host Identificador del servidor MySQL. + * @param string $db_user Identificador del usuario MySQL con permisos en la + * base samurai en el host ingresado. + * @param string $db_pass Clave del usuario MySQL. + * + * @return mixed + * @access protected + */ + function _dbDestino($db_host, $db_user, $db_pass) { + return DB::connect( + "mysql://$db_user:$db_pass@$db_host/samurai" + ,true + ); + } + + /** + * Constructor. + * + * @param DB &$db_source Conexion a la base de datos. + * @param string $db_host Identificador del servidor MySQL. + * @param string $db_user Identificador del usuario MySQL con permisos en la + * base samurai en el host ingresado. + * @param string $db_pass Clave del usuario MySQL. + * @param string $responsable Login del responsable de la migracion. + * + * @return void + * @access public + */ + function SAMURAI_Migrar(&$db_source, $db_host, $db_user, $db_pass, + $responsable) { + $this->_db_source =& $db_source; + $this->_responsable = $responsable; + //Armo la conexion con el host destino + $this->_db_dest =& $this->_dbDestino($db_host, $db_user, $db_pass); + if (PEAR::isError($this->_db_dest)) { + trigger_error('Error: ' . $this->_db_dest->getMessage() . "\n", E_USER_ERROR); + } + } + + /** + * Se encarga de migrar el sistema, que se selecciono, completo (permisos, + * sistema, perfiles, usuarios) al destino ingresado. + * + * @param int $id_sistema Identificador del sistema. + * + * @return mixed + * @access public + */ + function migrarSistema($id_sistema) { + + //Lockear las tablas en el host source para escritura con la sentencia + //sql lock + + $this->_sistema =& new SAMURAI_Sistema($this->_db_source, $id_sistema); + + //Verifico si existe el sistema. + $res = $this->_verificarExisteSistema(); + if (PEAR::isError($res)) { + return $res + } + //Migro los permisos. + $permisos = $this->_migrarPermisos(); + if (PEAR::isError($permisos)) { + return $permisos; + } + //Migro el sistema. + $sistema = $this->_migrarSistema(); + if (PEAR::isError($sistema)) { + return $sistema; + } + //Migro los perfiles. + $perfiles = $this->_migrarPerfiles(); + if (PEAR::isError($perfiles)) { + return $perfiles; + } + //Migro los usuarios. + $usuarios = $this->_migrarUsuarios(); + if (PEAR::isError($usuarios)) { + return $usuarios; + } + //Migro las relaciones + $relaciones = $this->_migrarRelaciones($sistema, $permisos, $perfiles, + $usuarios); + if (PEAR::isError($relaciones)) { + return $relaciones; + } + //Deslockear las tablas en el host source para escritura con la sentencia + //sql lock + } +} +?> diff --git a/sistema/conf/confSecciones.php b/sistema/conf/confSecciones.php index 84f542b..b2c8e2d 100644 --- a/sistema/conf/confSecciones.php +++ b/sistema/conf/confSecciones.php @@ -99,7 +99,9 @@ array ( 'nombre' => 'Usuarios', 'link' => 'consultas/usuarios', ), - + array ( 'nombre' => 'Migrar', + 'link' => 'consultas/migrar', + ), ), ), //}}} diff --git a/sistema/www/consultas/consultas.php b/sistema/www/consultas/consultas.php index 4902e0b..d0298e5 100644 --- a/sistema/www/consultas/consultas.php +++ b/sistema/www/consultas/consultas.php @@ -42,13 +42,16 @@ $MARCO =& new MECON_Marco ('/var/www/sistemas/samurai/sistema/conf/confSecciones de los usuarios.', array('accion'=>'filtrar')); $LINK3 =& new MECON_HTML_Link ('php-constantes', 'Bajar la definición de constantes de un sistema.'); + $LINK5 =& new MECON_HTML_Link ('migrar', 'Migrar Sistema.'); - $IMG0 =& new MECON_HTML_Image('../images/verinfodesist.gif'); - $IMG1 =& new MECON_HTML_Image('../images/activar.gif'); - $IMG2 =& new MECON_HTML_Image('../images/verusuario.gif'); - $IMG3 =& new MECON_HTML_Image('../images/bajar.gif'); + $IMG0 =& new MECON_HTML_Image('../images/verinfodesist.gif', '(->)'); + $IMG1 =& new MECON_HTML_Image('../images/activar.gif', '(->)'); + $IMG2 =& new MECON_HTML_Image('../images/verusuario.gif', '(->)'); + $IMG3 =& new MECON_HTML_Image('../images/bajar.gif', '(->)'); + $IMG5 =& new MECON_HTML_Image('../images/migrar.gif', '(->)'); - $IMG4 =& new MECON_HTML_Image('/MECON/images/blanco.gif'); + $IMG4 =& new MECON_HTML_Image('/MECON/images/blanco.gif', + '------------------------------------------------------'); $LINK00 =& new MECON_HTML_Link('sistemas', $IMG0, array('accion'=>'info_listado')); @@ -57,6 +60,7 @@ $MARCO =& new MECON_Marco ('/var/www/sistemas/samurai/sistema/conf/confSecciones $LINK22 =& new MECON_HTML_Link('usuarios', $IMG2, array('accion'=>'filtrar')); $LINK33 =& new MECON_HTML_Link('php-constantes', $IMG3); + $LINK55 =& new MECON_HTML_Link('migrar', $IMG5); //}}} //AGREGO LA INFO A LAS TABLAS {{{ $TABLA->addRow(array($LINK00->toHtml(),$LINK0->toHtml()), 'align="left"'); @@ -66,6 +70,9 @@ $MARCO =& new MECON_Marco ('/var/www/sistemas/samurai/sistema/conf/confSecciones $TABLA->addRow(array($LINK22->toHtml(),$LINK2->toHtml()), 'align="left"'); $TABLA->addRow(array($IMG4->toHtml()), 'colspan="2"'); $TABLA->addRow(array($LINK33->toHtml(),$LINK3->toHtml()), 'align="left"'); + $TABLA->addRow(array($IMG4->toHtml()), 'colspan="2"'); + $TABLA->addRow(array($LINK55->toHtml(),$LINK5->toHtml()), 'align="left"'); + $TABLA->addRow(array($IMG4->toHtml()), 'colspan="2"'); //}}} //DIBUJO LA PAGINA {{{ $MARCO->addBodyContent($TABLA); diff --git a/sistema/www/consultas/migrar.php b/sistema/www/consultas/migrar.php new file mode 100644 index 0000000..ef96e5f --- /dev/null +++ b/sistema/www/consultas/migrar.php @@ -0,0 +1,96 @@ + +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +//Verifico si se tiene acceso a la pagina{{{ +$SAMURAI_PERM->setSistema(SAMURAI_PERM); +if (!$SAMURAI_PERM->tiene(SAMURAI_PERM_DEVELOPER)) { + $SAMURAI_PERM->chequear(SAMURAI_PERM_DEVELOPER); +} +$MARCO =& new MECON_Marco ('/var/www/sistemas/samurai/sistema/conf/confSecciones.php', $SAMURAI_PERM); +//}}} + +//Require once {{{ +require_once 'MECON/HTML/Error.php'; +require_once 'MECON/HTML/QuickForm.php'; +require_once 'SAMURAI/Sistema.php'; +require_once 'SAMURAI/Migrar.php'; +//}}} + +//Obtengo la info de los sistemas de la base {{{ +$SISTEMAS = SAMURAI_Sistema::getArraySistemas($DB); +//}}} + +//Agrego los elementos del form {{{ +$TABLA =& new MECON_HTML_Tabla ('width="400"', 'comun'); +$TABLA->addLink('volver', 'consultas'); +$FORM =& new MECON_HTML_QuickForm ('migrar','post','migrar'); +$FORM->renderer->setTable($TABLA); +$FORM->addElement ('header', 'cabecera', 'Datos de la migración.'); +$sistema =& $FORM->addElement ('select', 'sistema', 'Sistema', $SISTEMAS, array('size' => '1')); +$db_host =& $FORM->addElement ('text', 'db_host', 'DB Host Dest.', '', array ('size'=>50)); +$db_user =& $FORM->addElement ('text', 'db_user', 'DB User Dest.', '', array ('size'=>50)); +$db_pass =& $FORM->addElement ('password', 'db_pass', 'DB Pass Dest.', '', array ('size'=>50)); +$group[] = HTML_QuickForm::createElement('submit', 'aceptar' , 'Migrar'); +$FORM->addGroup($group,'botones', '', ', '); + +//Agrego las reglas de validación +$FORM->addRule('sistema', 'Debe seleccionar un sistema.', 'required', '', 'client'); +$FORM->addRule('db_host', 'Debe indicar el MYSQL Server destino.', 'required', '', 'client'); +$FORM->addRule('db_user', 'Debe ingresar el usuario MySQL destino.', 'required', '', 'client'); +$FORM->addRule('db_pass', 'Debe ingresar el password MySQL destino.', 'required', '', 'client'); +//}}} + +//Valido el formulario {{{ +if ($FORM->validate()) { + //Migro el sistema {{{ + $migrar =& new SAMURAI_Migrar ( + $DB, + $db_host->getValue(), + $db_user->getValue(), + $db_pass->getValue(), + $_SESSION['usuario'] + ); + $res = $migrar->migrarSistema(array_shift($sistema->getSelected())); + if (PEAR::isError($res)) { + switch ($res->getUserInfo()) { + case 'HTML': + $ERROR =& new MECON_HTML_Error ('Error: '. $res->getMessage()); + break; + default: + trigger_error('Error: ' . $res->getMessage() . "\n", E_USER_ERROR); + break; + } + } + else { +// $header ('Location: consultas'); + } + //}}} +} +//}}} + +//Dibujo la pagina {{{ +$MARCO->addBody($FORM); +@$MARCO->addBody($ERROR); +$MARCO->setTitle('Migrar Sistema'); +$MARCO->display(); +//}}} +?> diff --git a/sistema/www/images/migrar.gif b/sistema/www/images/migrar.gif new file mode 100644 index 0000000000000000000000000000000000000000..fa2fb7de68b74fc0d2ccd16c5b2f82d0a3379cd9 GIT binary patch literal 1099 zcmZ?wbhEHbRANwKc*el+|NsBbU%r3({O#GxcTZouy?OW9`TMVK+qn9@wzOwPql})?PY}|Ev#jdlrgC?im}qXKd=2wxM;(x`rtm8YZuAnzXjQcV$iAs=A&Pl|3t~ zyOx)Attf3I=!}^-t;cj&JjdYjcTewhOMa4X(8gtg#BLG4v<}`io&G0L7mytRf5^ z3_2jkg5rdM{eOc;Q*%o*E3Z_i6fbjYUw9{J8e$9) z91RZ5UaQ#}jRd(5_H!Q4sG0DfVID)9Lhen8EBPJ_%wqisb669PHCD5Td0A{ve8|wz zloRN)(nzA2_p4Z4K|*3Pvj}%7*P@Vy=c?YCPG?FKM2<2t>g&ib1T;uaS|`QRQ}OG{ zt0rD9#vF%LWsXtH1poK7m|i$|$tzOC;(^0%f!2%6`esK&7`P_JL`rJi?LFGy$Wv`( z?lED*!)71eBE$2&Ul*K6Viri^`0+tetzmPG_`VBkByTkH9I%Mc_{DhKeL`*E2D?AG ztgKuXVGb7@trR_&z7=Hjy?!Lg%q$=ECc=SeQGuj4pMEOXw1UmDo*c?B|d7jn5)5YThQxnt1 z2^X08mS1gl;%DLzIKm`WpfF*Dw(>-li3aYG+omxzXntW7^kTYvUeLnK;s}FGMEXot z=iG*7CyhM;O(vV3Z01)y{lS2N({T?&14E{h8iQb6C4(R1o{YyQjGR>p0(_U}vL0Z! zz0eTObW?@-q|q;y4cBuQ%QiMK%LNEDXuET)>d`&Za6y#Mg5{z!zmZHJ1ILOP3YmJG s42hfqdK!xwxwpw(PStBNdBCKV!kc(#{Q3GB8*J00qp0wg3PC literal 0 HcmV?d00001 -- 2.43.0