From 9654388d7a2ab4d74cbc0813b8b1372de17da4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Schneider?= Date: Tue, 7 Aug 2012 12:46:12 +0200 Subject: [PATCH] Added assertions and upgrade wizard Former-commit-id: 77fe87da78e00fec08fc135b865ed39704de32cb --- .../0.2.1/Frameworks/Baikal/Core/Tools.php | 45 ++++++++++----- .../Baikal/Model/Config/Distrib.php | 57 +++++++++++++++++++ .../Frameworks/Baikal/Model/Config/System.php | 4 ++ .../Controller/Install/VersionUpgrade.php | 54 +++++++++++++++++- .../Controller/Settings/System.php | 11 +++- .../BaikalAdmin/WWWRoot/install/index.php | 2 +- .../0.2.1/Frameworks/Flake/Core/Database.php | 2 + .../Frameworks/Flake/Core/Database/Mysql.php | 15 +++++ .../Frameworks/Flake/Core/Database/Sqlite.php | 19 +++++++ .../0.2.1/Frameworks/Flake/Framework.php | 18 +++++- .../0.2.1/Resources/Db/db.empty.mysql.sql | 16 +++--- 11 files changed, 215 insertions(+), 28 deletions(-) create mode 100644 CoreVersions/0.2.1/Frameworks/Baikal/Model/Config/Distrib.php diff --git a/CoreVersions/0.2.1/Frameworks/Baikal/Core/Tools.php b/CoreVersions/0.2.1/Frameworks/Baikal/Core/Tools.php index b41a6c4..8092f78 100755 --- a/CoreVersions/0.2.1/Frameworks/Baikal/Core/Tools.php +++ b/CoreVersions/0.2.1/Frameworks/Baikal/Core/Tools.php @@ -43,8 +43,9 @@ class Tools { } # Asserting PDO::SQLite - if(!in_array('sqlite', \PDO::getAvailableDrivers())) { - die('Baikal Fatal Error: PDO::sqlite is unavailable. It\'s required by Baikal.'); + $aPDODrivers = \PDO::getAvailableDrivers(); + if(!in_array('sqlite', $aPDODrivers) && !in_array('mysql', $aPDODrivers)) { + die('Baikal Fatal Error: Both PDO::sqlite and PDO::mysql are unavailable. One of them at least is required by Baikal.'); } } @@ -59,19 +60,15 @@ class Tools { public static function assertBaikalIsOk() { - # Asserting DB file exists - if(!file_exists(PROJECT_SQLITE_FILE)) { - throw new \Exception("DB file does not exist. To create it, please copy 'Core/Resources/Db/db.empty.sqlite' to '" . PROJECT_SQLITE_FILE . "'"); + # DB connexion has not been asserted earlier by Flake, to give us a chance to trigger the install tool + # We assert it right now + if(!\Flake\Framework::isDBInitialized()) { + throw new \Exception("Fatal error: no connection to a database is available."); } - # Asserting DB file is readable - if(!is_readable(PROJECT_SQLITE_FILE)) { - throw new \Exception("DB file is not readable. Please give read permissions to httpd user on file '" . PROJECT_SQLITE_FILE . "'."); - } - - # Asserting DB file is writable - if(!is_writable(PROJECT_SQLITE_FILE)) { - throw new \Exception("DB file is not writable. Please give write permissions to httpd user on file '" . PROJECT_SQLITE_FILE . "'."); + # Asserting that the database is structurally complete + if(($aMissingTables = self::isDBStructurallyComplete($GLOBALS["DB"])) !== TRUE) { + throw new \Exception("Fatal error: Database is not structurally complete; missing tables are: " . implode(", ", $aMissingTables) . ""); } # Asserting config file exists @@ -105,6 +102,28 @@ class Tools { } } + public static function isDBStructurallyComplete(\Flake\Core\Database $oDB) { + + $aRequiredTables = array( + "addressbooks", + "calendarobjects", + "calendars", + "cards", + "groupmembers", + "locks", + "principals", + "users", + ); + + $aPresentTables = $oDB->tables(); + $aIntersect = array_intersect($aRequiredTables, $aPresentTables); + if(count($aIntersect) !== count($aRequiredTables)) { + return array_diff($aRequiredTables, $aIntersect); + } + + return TRUE; + } + public static function bashPrompt($prompt) { print $prompt; @flush(); diff --git a/CoreVersions/0.2.1/Frameworks/Baikal/Model/Config/Distrib.php b/CoreVersions/0.2.1/Frameworks/Baikal/Model/Config/Distrib.php new file mode 100644 index 0000000..fa0c3e3 --- /dev/null +++ b/CoreVersions/0.2.1/Frameworks/Baikal/Model/Config/Distrib.php @@ -0,0 +1,57 @@ + +# All rights reserved +# +# http://baikal.codr.fr +# +# This script is part of the Baïkal Server project. The Baïkal +# Server project is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# This copyright notice MUST APPEAR in all copies of the script! +################################################################# + +namespace Baikal\Model\Config; + +class Distrib extends \Baikal\Model\Config { + + protected $aConstants = array( + "BAIKAL_VERSION" => array( + "type" => "string", + ), + "BAIKAL_HOMEPAGE" => array( + "type" => "string", + ), + "PROJECT_PACKAGE" => array( + "type" => "string", + ), + ); + + protected $aData = array( + "BAIKAL_VERSION" => "", + "BAIKAL_HOMEPAGE" => "", + "PROJECT_PACKAGE" => "", + ); + + public function formMorphologyForThisModelInstance() { + $oMorpho = new \Formal\Form\Morphology(); + return $oMorpho; + } + + public function label() { + return "Baïkal distribution info"; + } +} \ No newline at end of file diff --git a/CoreVersions/0.2.1/Frameworks/Baikal/Model/Config/System.php b/CoreVersions/0.2.1/Frameworks/Baikal/Model/Config/System.php index 35b825c..d7a7281 100755 --- a/CoreVersions/0.2.1/Frameworks/Baikal/Model/Config/System.php +++ b/CoreVersions/0.2.1/Frameworks/Baikal/Model/Config/System.php @@ -65,6 +65,9 @@ class System extends \Baikal\Model\Config { "PROJECT_DB_MYSQL_PASSWORD" => array( "type" => "string", ), + "BAIKAL_CONFIGURED_VERSION" => array( + "type" => "string", + ), ); protected $aData = array( @@ -80,6 +83,7 @@ class System extends \Baikal\Model\Config { "PROJECT_DB_MYSQL_DBNAME" => "", "PROJECT_DB_MYSQL_USERNAME" => "", "PROJECT_DB_MYSQL_PASSWORD" => "", + "BAIKAL_CONFIGURED_VERSION" => "", ); public function formMorphologyForThisModelInstance() { diff --git a/CoreVersions/0.2.1/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php b/CoreVersions/0.2.1/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php index f21d679..6471917 100755 --- a/CoreVersions/0.2.1/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php +++ b/CoreVersions/0.2.1/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php @@ -32,6 +32,9 @@ class VersionUpgrade extends \Flake\Core\Controller { protected $oModel; protected $oForm; # \Formal\Form + protected $aErrors = array(); + protected $aSuccess = array(); + public function execute() { } @@ -53,11 +56,58 @@ class VersionUpgrade extends \Flake\Core\Controller { HTML; - $sHtml .= $this->upgrade(BAIKAL_CONFIGURED_VERSION, BAIKAL_VERSION); + $bSuccess = $this->upgrade(BAIKAL_CONFIGURED_VERSION, BAIKAL_VERSION); + + if(!empty($this->aErrors)) { + $sHtml .= "

Errors

" . implode("
\n", $this->aErrors); + } + + if(!empty($this->aSuccess)) { + $sHtml .= "

Successful operations

" . implode("
\n", $this->aSuccess); + } + + if($bSuccess === FALSE) { + $sHtml .= "

Error Baïkal has not been upgraded. See the section 'Errors' for details.

"; + } else { + $sHtml .= "

Baïkal has been successfully upgraded. You may now Access the Baïkal admin

"; + } + return $sHtml; } protected function upgrade($sVersionFrom, $sVersionTo) { - return ""; + + if($sVersionFrom === "0.2.0" && $sVersionTo === "0.2.1") { + + $sOldDbFilePath = PROJECT_PATH_SPECIFIC . "Db/.ht.db.sqlite"; + + if(PROJECT_SQLITE_FILE === $sOldDbFilePath) { + $sNewDbFilePath = PROJECT_PATH_SPECIFIC . "Db/db.sqlite"; + + # Move old db from Specific/Db/.ht.db.sqlite to Specific/Db/db.sqlite + if(!file_exists($sNewDbFilePath)) { + if(!is_writable(dirname($sNewDbFilePath))) { + $this->aErrors[] = "DB file path '" . dirname($sNewDbFilePath) . "' is not writable"; + return FALSE; + } + + if(!@copy($sOldDbFilePath, $sNewDbFilePath)) { + $this->aErrors[] = "DB could not be copied from '" . $sOldDbFilePath . "' to '" . $sNewDbFilePath . "'."; + return FALSE; + } + + $this->aSuccess[] = "SQLite database has been renamed from '" . $sOldDbFilePath . "' to '" . $sNewDbFilePath . "'"; + } + } + } + + $this->updateConfiguredVersion($sVersionTo); + return TRUE; + } + + protected function updateConfiguredVersion($sVersionTo) { + $oConfig = new \Baikal\Model\Config\System(PROJECT_PATH_SPECIFIC . "config.system.php"); + $oConfig->set("BAIKAL_CONFIGURED_VERSION", $sVersionTo); + $oConfig->persist(); } } \ No newline at end of file diff --git a/CoreVersions/0.2.1/Frameworks/BaikalAdmin/Controller/Settings/System.php b/CoreVersions/0.2.1/Frameworks/BaikalAdmin/Controller/Settings/System.php index 358855e..c6eb25b 100755 --- a/CoreVersions/0.2.1/Frameworks/BaikalAdmin/Controller/Settings/System.php +++ b/CoreVersions/0.2.1/Frameworks/BaikalAdmin/Controller/Settings/System.php @@ -95,8 +95,6 @@ class System extends \Flake\Core\Controller { $sUsername, $sPassword ); - - unset($oDB); } catch(\Exception $e) { $sMessage = "MySQL error: " . $e->getMessage(); $sMessage .= "
Nothing has been saved"; @@ -104,8 +102,17 @@ class System extends \Flake\Core\Controller { $oForm->declareError($oMorpho->element("PROJECT_DB_MYSQL_DBNAME")); $oForm->declareError($oMorpho->element("PROJECT_DB_MYSQL_USERNAME")); $oForm->declareError($oMorpho->element("PROJECT_DB_MYSQL_PASSWORD")); + return; } + if(($aMissingTables = \Baikal\Core\Tools::isDBStructurallyComplete($oDB)) !== TRUE) { + $sMessage = "MySQL error: These tables, required by Baïkal, are missing: " . implode(", ", $aMissingTables) . "
"; + $sMessage .= "You may want create these tables using the file Core/Resources/Db/db.empty.mysql.sql"; + $sMessage .= "

Nothing has been saved"; + + $oForm->declareError($oMorpho->element("PROJECT_DB_MYSQL"), $sMessage); + return; + } } } } \ No newline at end of file diff --git a/CoreVersions/0.2.1/Frameworks/BaikalAdmin/WWWRoot/install/index.php b/CoreVersions/0.2.1/Frameworks/BaikalAdmin/WWWRoot/install/index.php index ab6837f..0e361ea 100755 --- a/CoreVersions/0.2.1/Frameworks/BaikalAdmin/WWWRoot/install/index.php +++ b/CoreVersions/0.2.1/Frameworks/BaikalAdmin/WWWRoot/install/index.php @@ -65,7 +65,7 @@ if(!defined("BAIKAL_CONFIGURED_VERSION")) { # we have to set an admin password $oPage->zone("Payload")->addBlock(new \BaikalAdmin\Controller\Install\Initialize()); } else { - # we have to initialize Baïkal (new installation) + # we have to upgrade Baïkal $oPage->zone("Payload")->addBlock(new \BaikalAdmin\Controller\Install\VersionUpgrade()); } diff --git a/CoreVersions/0.2.1/Frameworks/Flake/Core/Database.php b/CoreVersions/0.2.1/Frameworks/Flake/Core/Database.php index 8a1b3d8..5a03571 100755 --- a/CoreVersions/0.2.1/Frameworks/Flake/Core/Database.php +++ b/CoreVersions/0.2.1/Frameworks/Flake/Core/Database.php @@ -203,4 +203,6 @@ abstract class Database extends \Flake\Core\FLObject { public function __destruct() { $this->close(); } + + public abstract function tables(); } \ No newline at end of file diff --git a/CoreVersions/0.2.1/Frameworks/Flake/Core/Database/Mysql.php b/CoreVersions/0.2.1/Frameworks/Flake/Core/Database/Mysql.php index b3a376d..490a80c 100644 --- a/CoreVersions/0.2.1/Frameworks/Flake/Core/Database/Mysql.php +++ b/CoreVersions/0.2.1/Frameworks/Flake/Core/Database/Mysql.php @@ -49,4 +49,19 @@ class Mysql extends \Flake\Core\Database { $this->sPassword ); } + + public function tables() { + $aTables = array(); + + $sSql = "SHOW TABLES FROM " . $this->sDbName; + $oStmt = $this->query($sSql); + + while(($aRs = $oStmt->fetch()) !== FALSE) { + $aTables[] = array_shift($aRs); + } + + asort($aTables); + reset($aTables); + return $aTables; + } } \ No newline at end of file diff --git a/CoreVersions/0.2.1/Frameworks/Flake/Core/Database/Sqlite.php b/CoreVersions/0.2.1/Frameworks/Flake/Core/Database/Sqlite.php index 173f2cd..d241855 100755 --- a/CoreVersions/0.2.1/Frameworks/Flake/Core/Database/Sqlite.php +++ b/CoreVersions/0.2.1/Frameworks/Flake/Core/Database/Sqlite.php @@ -38,4 +38,23 @@ class Sqlite extends \Flake\Core\Database { $this->sDbPath = $sDbPath; $this->oDb = new \PDO('sqlite:' . $this->sDbPath); } + + # Taken from http://dev.kohanaframework.org/issues/2985 + public function tables() { + $aTables = array(); + + # Find all user level table names + $oStmt = $this->query('SELECT name ' + .'FROM sqlite_master ' + .'WHERE type=\'table\' AND name NOT LIKE \'sqlite_%\' ' + .'ORDER BY name'); + + while(($aRs = $oStmt->fetch()) !== FALSE) { + // Get the table name from the results + $aTables[] = array_shift($aRs); + } + + reset($aTables); + return $aTables; + } } \ No newline at end of file diff --git a/CoreVersions/0.2.1/Frameworks/Flake/Framework.php b/CoreVersions/0.2.1/Frameworks/Flake/Framework.php index 8b73863..4868ea3 100755 --- a/CoreVersions/0.2.1/Frameworks/Flake/Framework.php +++ b/CoreVersions/0.2.1/Frameworks/Flake/Framework.php @@ -150,6 +150,12 @@ class Framework extends \Flake\Core\Framework { # Include Project config + # NOTE: DB initialization and App config files inclusion + # do not break execution if not properly executed, as + # these errors will have to be caught later in the process + # notably by the App install tool, if available; breaking right now + # would forbid such install tool forwarding, for instance + $sConfigPath = PROJECT_PATH_SPECIFIC . "config.php"; $sConfigSystemPath = PROJECT_PATH_SPECIFIC . "config.system.php"; @@ -165,7 +171,7 @@ class Framework extends \Flake\Core\Framework { } protected static function initDb() { - + if(defined("PROJECT_DB_MYSQL") && PROJECT_DB_MYSQL === TRUE) { self::initDbMysql(); } else { @@ -176,7 +182,7 @@ class Framework extends \Flake\Core\Framework { protected static function initDbSqlite() { # Asserting DB filepath is set if(!defined("PROJECT_SQLITE_FILE")) { - return; + return FALSE; } # Asserting DB file exists @@ -196,7 +202,10 @@ class Framework extends \Flake\Core\Framework { if(file_exists(PROJECT_SQLITE_FILE) && is_readable(PROJECT_SQLITE_FILE) && !isset($GLOBALS["DB"])) { $GLOBALS["DB"] = new \Flake\Core\Database\Sqlite(PROJECT_SQLITE_FILE); + return TRUE; } + + return FALSE; } protected static function initDbMysql() { @@ -230,5 +239,10 @@ class Framework extends \Flake\Core\Framework { # We now setup the connexion to use UTF8 $GLOBALS["DB"]->query("SET NAMES UTF8"); + return TRUE; + } + + public static function isDBInitialized() { + return \Flake\Util\Tools::is_a($GLOBALS["DB"], "\Flake\Core\Database"); } } \ No newline at end of file diff --git a/CoreVersions/0.2.1/Resources/Db/db.empty.mysql.sql b/CoreVersions/0.2.1/Resources/Db/db.empty.mysql.sql index 3dc2323..bc42d2f 100644 --- a/CoreVersions/0.2.1/Resources/Db/db.empty.mysql.sql +++ b/CoreVersions/0.2.1/Resources/Db/db.empty.mysql.sql @@ -4,14 +4,14 @@ # http://code.google.com/p/sabredav/ # -CREATE TABLE users ( +CREATE TABLE IF NOT EXISTS users ( id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50), digesta1 VARCHAR(32), UNIQUE(username) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -CREATE TABLE principals ( +CREATE TABLE IF NOT EXISTS principals ( id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, uri VARCHAR(200) NOT NULL, email VARCHAR(80), @@ -20,14 +20,14 @@ CREATE TABLE principals ( UNIQUE(uri) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -CREATE TABLE groupmembers ( +CREATE TABLE IF NOT EXISTS groupmembers ( id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, principal_id INTEGER UNSIGNED NOT NULL, member_id INTEGER UNSIGNED NOT NULL, UNIQUE(principal_id, member_id) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -CREATE TABLE locks ( +CREATE TABLE IF NOT EXISTS locks ( id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, owner VARCHAR(100), timeout INTEGER UNSIGNED, @@ -38,7 +38,7 @@ CREATE TABLE locks ( uri text ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -CREATE TABLE calendarobjects ( +CREATE TABLE IF NOT EXISTS calendarobjects ( id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, calendardata MEDIUMBLOB, uri VARCHAR(200), @@ -47,7 +47,7 @@ CREATE TABLE calendarobjects ( UNIQUE(calendarid, uri) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -CREATE TABLE calendars ( +CREATE TABLE IF NOT EXISTS calendars ( id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, principaluri VARCHAR(100), displayname VARCHAR(100), @@ -61,7 +61,7 @@ CREATE TABLE calendars ( UNIQUE(principaluri, uri) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -CREATE TABLE addressbooks ( +CREATE TABLE IF NOT EXISTS addressbooks ( id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, principaluri VARCHAR(255), displayname VARCHAR(255), @@ -71,7 +71,7 @@ CREATE TABLE addressbooks ( UNIQUE(principaluri, uri) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -CREATE TABLE cards ( +CREATE TABLE IF NOT EXISTS cards ( id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, addressbookid INT(11) UNSIGNED NOT NULL, carddata MEDIUMBLOB,