From 6d8764c5366e0b1baf66da50230dac623edb2450 Mon Sep 17 00:00:00 2001
From: Jacek Kowalski <Jacek@jacekk.info>
Date: Sun, 06 Jul 2014 22:25:29 +0000
Subject: [PATCH] [core] Umożliwienie ustawienia katalogu z danymi sesji użytkowników i dostosowanie testów jednostkowych.
---
class/BotSession.php | 299 ++++++++++++++++++++++++------------------
tests/Core/BotSessionTest.php | 72 +++++----
2 files changed, 213 insertions(+), 158 deletions(-)
diff --git a/class/BotSession.php b/class/BotSession.php
index 61511db..28bf0f5 100644
--- a/class/BotSession.php
+++ b/class/BotSession.php
@@ -4,122 +4,99 @@
* w szczególności jego ustawienia.
*/
class BotSession {
- private $PDO;
-
/**
- * Nazwa modułu, którego zmienne klasa przetwarza
- * @var string $class max. 40 znaków
+ * Instancja PDO tworzona w metodzie {@link BotSession::init()}.
+ * @var PDO $PDO
+ */
+ protected $PDO;
+
+ /**
+ * Katalog, w którym trzymane są dane sesyjne użytkowników.
+ * @var string $sessionDir
+ */
+ protected $sessionDir;
+
+ /**
+ * Katalog, w którym trzymane są dane sesyjne użytkowników
+ * z poprzedniej wersji bota.
+ * @var string $legacySessionDir
+ */
+ protected $legacySessionDir;
+
+ /**
+ * Nazwa modułu (max. 40 znaków), którego zmienne klasa aktualnie przetwarza,
+ * ustawiana metodą {@link BotSession::setClass()}.
+ * @var string $class
*/
protected $class = '';
- protected $class_empty = TRUE;
/**
* Pseudo-URL użytkownika.
* @see BotUser
- * @var string $user URL użytkownika
+ * @var string $user
*/
- private $user;
+ protected $user;
+
/**
- * Klasa z identyfikatorem użytkownika
- * @var BotUser $user_struct
+ * Inicjuje klasę dla podanego użytkownika
+ * @param string $user Pseudo-URL użytkownika
+ * @param string $sessionDir Katalog z danymi, domyślnie BOT_TOPDIR/database
+ * @param string $legacySessionDir Katalog z danymi ze starej wersji bota, domyślnie BOT_TOPDIR/db
*/
- private $user_struct;
-
- /**
- * Inicjuje klasę w zależności od użytkownika
- */
- function __construct($user) {
- $this->user = sha1($user);
- $this->user_struct = parse_url($user);
-
- $this->class_empty = FALSE;
+ public function __construct($user, $sessionDir = NULL, $legacySessionDir = NULL) {
+ if(empty($sessionDir)) {
+ $sessionDir = BOT_TOPDIR.'/database';
+ }
+ if(empty($legacySessionDir)) {
+ $legacySessionDir = BOT_TOPDIR.'/db';
+ }
+
+ $this->user = $user;
+ $this->sessionDir = $sessionDir;
+ $this->legacySessionDir = $legacySessionDir;
}
-
- private function init() {
- if(strlen($this->class) == 0 && !$this->class_empty) {
- throw new Exception('Przed użyciem $msg->session należy ustawić nazwę modułu za pomocą metody setClass - patrz "Poradnik tworzenia modułów", dział "Klasa BotMessage", rozdział "Pole $session".');
+
+ /**
+ * Sprawdza ustawienie pola {@link BotSession::$class} oraz, jeśli nie została wykonana wcześniej,
+ * dokonuje inicjalizacji klasy.
+ * Metoda ta winna być wywoływana przez każdą publiczną funkcję operującą na danych.
+ * @throws Exception Wyjątek rzucany, gdy przed użyciem metody, nazwa klasy
+ * nie została ustawiona metodą {@link BotSession::setClass()}
+ */
+ protected function init() {
+ if(empty($this->class)) {
+ throw new Exception('Przed użyciem mechanizmu sesji należy ustawić nazwę modułu za pomocą metody setClass - patrz "Poradnik tworzenia modułów", dział "Klasa BotMessage", rozdział "Pole $session".');
}
if($this->PDO) {
- return NULL;
- }
-
- if(is_file(BOT_TOPDIR.'/database/'.sha1($this->user).'.sqlite')) {
- $this->PDO = new PDO('sqlite:'.BOT_TOPDIR.'/database/'.sha1($this->user).'.sqlite');
- $this->PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- $this->PDO->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING);
-
- $st = $this->PDO->query('SELECT value FROM data WHERE class=\'\' AND name=\'_version\'');
- $row = $st->fetch(PDO::FETCH_ASSOC);
- if(is_array($row)) {
- $version = (int)$row['value'];
- }
- else
- {
- $version = 0;
- }
- $st->closeCursor();
-
- if($version < 1) {
- $this->PDO->query('UPDATE data SET class=\'kino\' WHERE class=\'\' AND name=\'kino\'');
- $this->PDO->query('INSERT OR REPLACE INTO data (class, name, value) VALUES (\'\', \'_version\', 1)');
- $version = 1;
- }
-
- if($version < 4) {
- $this->PDO->query('DELETE FROM data WHERE class IS NULL AND name=\'user_struct\'');
- $this->PDO->query('INSERT OR REPLACE INTO data (class, name, value) VALUES (\'\', \'_version\', 4)');
- $version = 4;
- }
-
+ // Inicjalizacja została już przeprowadzona - wyjdź.
return;
}
-
- try {
- $this->PDO = new PDO('sqlite:'.BOT_TOPDIR.'/database/'.sha1($this->user).'.sqlite');
- $this->PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- $this->PDO->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING);
-
- $this->PDO->query(
- 'CREATE TABLE data (
- class VARCHAR(50) NOT NULL DEFAULT \'\',
- name VARCHAR(40) NOT NULL,
- value TEXT NOT NULL,
- PRIMARY KEY (
- class ASC,
- name ASC
- )
- )'
- );
-
- $this->PDO->query('INSERT INTO data (class, name, value) VALUES (\'\', \'_version\', 4)');
-
- $files = glob(BOT_TOPDIR.'/db/*/'.$this->user_struct['user'].'.ggdb');
- if(!$files) {
- return;
+
+ $dbFile = $this->sessionDir.'/'.sha1(sha1($this->user)).'.sqlite';
+
+ $this->PDO = new PDO('sqlite:'.$dbFile);
+ $this->PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $this->PDO->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING);
+
+ $st = $this->PDO->query('SELECT COUNT(name) FROM sqlite_master WHERE type=\'table\' AND name=\'data\'');
+ $num = $st->fetch(PDO::FETCH_NUM);
+ $schemaExists = $num[0] > 0;
+
+ if($schemaExists) {
+ $this->updateDatabase();
+ } else {
+ try {
+ $this->createSchema();
+ $this->importLegacyData();
}
-
- $this->PDO->beginTransaction();
- $st = $this->PDO->prepare('INSERT OR REPLACE INTO data (class, name, value) VALUES (?, ?, ?)');
-
- foreach($files as $file) {
- $data = unserialize(file_get_contents($file));
- foreach($data as $name => $value) {
- $st->execute(array($this->class, $name, serialize($value)));
+ catch(Exception $e) {
+ // Import danych nie udał się - usuń pozostałości.
+ if(file_exists($dbFile)) {
+ @unlink($dbFile);
}
+ throw $e;
}
-
- $this->PDO->commit();
-
- foreach($files as $file) {
- unlink($file);
- }
- }
- catch(Exception $e) {
- if(file_exists(BOT_TOPDIR.'/database/'.sha1($this->user).'.sqlite')) {
- @unlink(BOT_TOPDIR.'/database/'.sha1($this->user).'.sqlite');
- }
- throw $e;
}
}
@@ -127,16 +104,16 @@
* Ustawia nazwę modułu/klasy, której zmienne będą przetwarzane
* @param string $class Nazwa modułu
*/
- function setClass($class) {
+ public function setClass($class) {
$this->class = $class;
}
/**
- * Pobiera zmienną modułu o podanej nazwie (getter).
- * @param string $name Nazwa zmiennej
- * @return mixed Wartość zmiennej lub NULL
+ * Pobiera zmienną o podanej nazwie (getter).
+ * @param string $name Nazwa zmiennej.
+ * @return mixed Wartość zmiennej lub NULL, jeśli zmienna nie istnieje.
*/
- function __get($name) {
+ public function __get($name) {
$this->init();
$st = $this->PDO->prepare('SELECT value FROM data WHERE class=? AND name=?');
@@ -146,18 +123,16 @@
if(is_array($st)) {
return unserialize($st['value']);
}
- else
- {
- return NULL;
- }
+
+ return NULL;
}
/**
- * Ustawia zmienną o podanej nazwie
- * @param string $name Nazwa zmiennej
- * @param mixed $value Wartość zmiennej
+ * Ustawia zmienną o podanej nazwie.
+ * @param string $name Nazwa zmiennej.
+ * @param mixed $value Wartość do ustawienia.
*/
- function __set($name, $value) {
+ public function __set($name, $value) {
$this->init();
$st = $this->PDO->prepare('INSERT OR REPLACE INTO data (class, name, value) VALUES (?, ?, ?)');
@@ -166,24 +141,24 @@
/**
* Sprawdza czy podana zmienna została ustawiona.
- * @param string $name Nazwa zmiennej
+ * @param string $name Nazwa zmiennej do sprawdzenia.
* @return bool Czy zmienna istnieje?
*/
- function __isset($name) {
+ public function __isset($name) {
$this->init();
$st = $this->PDO->prepare('SELECT COUNT(name) FROM data WHERE class=? AND name=?');
$st->execute(array($this->class, $name));
$st = $st->fetch(PDO::FETCH_NUM);
- return ($st[0]>0);
+ return ($st[0] > 0);
}
/**
- * Usuwa zmienną o podanej nazwie
- * @param string $name Nazwa zmiennej
+ * Usuwa zmienną o podanej nazwie.
+ * @param string $name Nazwa zmiennej do usunięcia.
*/
- function __unset($name) {
+ public function __unset($name) {
$this->init();
$st = $this->PDO->prepare('DELETE FROM data WHERE class=? AND name=?');
@@ -191,10 +166,12 @@
}
/**
- * Zapamiętuje tablicę zmiennych danego modułu
- * @param array $array Tablica zmiennych
+ * Dodaje tablicę zmiennych do danych użytkownika.
+ * @param array $array Tablica zmiennych do dodania.
*/
- function push($array) {
+ public function push($array) {
+ $this->init();
+
$this->PDO->beginTransaction();
foreach($array as $name => $value) {
$this->__set($name, $value);
@@ -203,18 +180,18 @@
}
/**
- * Zwraca wszystkie ustawione zmienne danego modułu
- * @return array Lista wszystkich zmiennych
+ * Zwraca wszystkie ustawione zmienne dla modułu.
+ * @return array Lista wszystkich zmiennych.
*/
- function pull() {
+ public function pull() {
$this->init();
$st = $this->PDO->prepare('SELECT name, value FROM data WHERE class=?');
$st->execute(array($this->class));
- $st = $st->fetchAll(PDO::FETCH_ASSOC);
+ $rows = $st->fetchAll(PDO::FETCH_ASSOC);
$return = array();
- foreach($st as $row) {
+ foreach($rows as $row) {
$return[$row['name']] = unserialize($row['value']);
}
@@ -224,11 +201,81 @@
/**
* Usuwa wszystkie zmienne sesyjne danego modułu.
*/
- function truncate() {
+ public function truncate() {
$this->init();
$st = $this->PDO->prepare('DELETE FROM data WHERE class=?');
$st->execute(array($this->class));
}
+
+ /**
+ * Aktualizuje schemat bazy danych oraz dane, w szczególności poprawia błędy
+ * wprowadzone we wcześniejszych wersjach (np. brak ustawionej nazwy klasy).
+ */
+ private function updateDatabase() {
+ $st = $this->PDO->query('SELECT value FROM data WHERE class=\'\' AND name=\'_version\'');
+ $row = $st->fetch(PDO::FETCH_ASSOC);
+
+ $version = 0;
+ if (is_array($row)) {
+ $version = (int)$row['value'];
+ }
+
+ $st->closeCursor();
+
+ switch($version) {
+ case 1:
+ $this->PDO->query('UPDATE data SET class=\'kino\' WHERE class=\'\' AND name=\'kino\'');
+ $this->PDO->query('INSERT OR REPLACE INTO data (class, name, value) VALUES (\'\', \'_version\', 1)');
+ case 2:
+ case 3:
+ $this->PDO->query('DELETE FROM data WHERE class IS NULL AND name=\'user_struct\'');
+ $this->PDO->query('INSERT OR REPLACE INTO data (class, name, value) VALUES (\'\', \'_version\', 4)');
+ break;
+ }
+ }
+
+ /**
+ * Tworzy schemat bazy danych sesyjnych.
+ */
+ private function createSchema() {
+ $this->PDO->query(
+ 'CREATE TABLE data (
+ class VARCHAR(50) NOT NULL DEFAULT \'\',
+ name VARCHAR(40) NOT NULL,
+ value TEXT NOT NULL,
+ PRIMARY KEY (
+ class ASC,
+ name ASC
+ )
+ )'
+ );
+ $this->PDO->query('INSERT INTO data (class, name, value) VALUES (\'\', \'_version\', 4)');
+ }
+
+ /**
+ * Importuje dane użytkowników z poprzedniej wersji bota.
+ */
+ private function importLegacyData() {
+ $userData = parse_url($this->user);
+ $files = glob($this->legacySessionDir.'/*/'.$userData['user'].'.ggdb');
+ if(!$files) {
+ return;
+ }
+
+ $this->PDO->beginTransaction();
+ $st = $this->PDO->prepare('INSERT OR REPLACE INTO data (class, name, value) VALUES (?, ?, ?)');
+
+ foreach($files as $file) {
+ $data = unserialize(file_get_contents($file));
+ foreach($data as $name => $value) {
+ $st->execute(array($this->class, $name, serialize($value)));
+ }
+ }
+ $this->PDO->commit();
+
+ foreach($files as $file) {
+ unlink($file);
+ }
+ }
}
-?>
\ No newline at end of file
diff --git a/tests/Core/BotSessionTest.php b/tests/Core/BotSessionTest.php
index 1b2209f..62a79d7 100644
--- a/tests/Core/BotSessionTest.php
+++ b/tests/Core/BotSessionTest.php
@@ -1,68 +1,77 @@
<?php
class BotSessionTest extends PHPUnit_Framework_TestCase {
- function testSessionFolder() {
- $dbFolder = dirname(__FILE__).'/../../database';
+ private static $dataFolder;
+ private static $legacyFolder;
+
+ private static function tmpdir() {
+ $tmpName = tempnam(sys_get_temp_dir(), 'Bot');
+ unlink($tmpName);
+ mkdir($tmpName);
+ return $tmpName;
+ }
+
+ private static function rmdir($dir) {
+ foreach(glob($dir.'/*', GLOB_NOSORT) as $name) {
+ if($name == '.' || $name == '..') continue;
+
+ if(is_dir($name)) {
+ self::rmdir($name);
+ } else {
+ unlink($name);
+ }
+ }
- $this->assertTrue(is_writable($dbFolder));
- $this->assertTrue(count(glob($dbFolder.'/*.sqlite')) == 0);
+ rmdir($dir);
}
/**
- * @depends testSessionFolder
+ * Create one-time directories for testing purposes.
*/
+ static function setUpBeforeClass() {
+ self::$dataFolder = self::tmpdir();
+ self::$legacyFolder = self::tmpdir();
+ }
+
function testPullEmpty() {
- $dbFolder = dirname(__FILE__).'/../../database';
-
- $session = new BotSession('test://user1@test');
+ $session = new BotSession('test://user1@test', self::$dataFolder, self::$legacyFolder);
$session->setClass('test');
$this->assertEquals(array(), $session->pull());
- $this->assertTrue(count(glob($dbFolder.'/*.sqlite')) == 1);
+ $this->assertTrue(count(glob(self::$dataFolder.'/*.sqlite')) == 1);
}
/**
- * @depends testPullEmpty
* @expectedException Exception
*/
function testSetClass() {
- $session = new BotSession('test://user1');
+ $session = new BotSession('test://testException', self::$dataFolder, self::$legacyFolder);
$session->pull();
}
- /**
- * @depends testPullEmpty
- */
function testLegacyImport() {
- $dbFolder = dirname(__FILE__).'/../../database';
- $oldDbFolder = $dbFolder = dirname(__FILE__).'/../../db';
-
$data = array('test' => true, 'other' => 'yes, sir!');
$data_serialized = serialize($data);
- $this->assertTrue(mkdir($oldDbFolder));
- $this->assertTrue(is_writable($oldDbFolder));
- $this->assertTrue(mkdir($oldDbFolder.'/test'));
+ $this->assertTrue(mkdir(self::$legacyFolder.'/test'));
- $filename = $oldDbFolder.'/test/testUser.ggdb';
+ $filename = self::$legacyFolder.'/test/legacyUser.ggdb';
$this->assertEquals(strlen($data_serialized), file_put_contents($filename, $data_serialized));
$this->assertEquals($data_serialized, file_get_contents($filename));
- $session = new BotSession('test://testUser@test');
+ $session = new BotSession('test://legacyUser@test', self::$dataFolder, self::$legacyFolder);
$session->setClass('test');
$this->assertTrue(isset($session->test));
$this->assertEquals($data, $session->pull());
$this->assertFalse(file_exists($filename));
- $this->assertTrue(rmdir($oldDbFolder.'/test'));
- $this->assertTrue(rmdir($oldDbFolder));
}
/**
* @depends testPullEmpty
*/
function testManualExample() {
- $session = new BotSession('test://user1@test');
+ $session = new BotSession('test://user1@test', self::$dataFolder, self::$legacyFolder);
$session->setClass('test');
// Ustawienie pojedynczej wartości
@@ -108,7 +117,7 @@
* @depends testManualExample
*/
function testManualExample2() {
- $session = new BotSession('test://user1@test');
+ $session = new BotSession('test://user1@test', self::$dataFolder, self::$legacyFolder);
$session->setClass('test');
$array = array(
@@ -124,13 +133,12 @@
$this->assertEquals(array(), $session->pull());
}
- /**
- * @depends testManualExample2
- */
- function testCleanup() {
- $dbFolder = dirname(__FILE__).'/../../database';
- foreach(glob($dbFolder.'/*.sqlite') as $file) {
+ static function tearDownAfterClass() {
+ foreach(glob(self::$dataFolder.'/*.sqlite') as $file) {
unlink($file);
}
+
+ self::rmdir(self::$dataFolder);
+ self::rmdir(self::$legacyFolder);
}
}
--
Gitblit v1.9.1