implemented backup
authorkonrad <konrad@6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33>
Sun, 21 Dec 2008 13:06:05 +0000 (13:06 +0000)
committerkonrad <konrad@6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33>
Sun, 21 Dec 2008 13:06:05 +0000 (13:06 +0000)
git-svn-id: https://silmor.de/svn/softmagic/smoke/trunk@219 6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33

src/overview.cpp
src/overview.h
src/webrequest.cpp
www/admin.php
www/config.php.template
www/inc/db/db.php
www/inc/db/db_mysql.php
www/inc/db/db_scheme.php
www/machine.php

index 073b9c9..8b32fac 100644 (file)
@@ -59,6 +59,19 @@ MOverview::MOverview(MWebRequest*mw,QString pk)
        rtimer.setInterval(QSettings().value("profiles/"+pk+"/refresh",300).toInt()*1000);
        rtimer.start();
        connect(&rtimer,SIGNAL(timeout()),this,SLOT(refreshData()));
+       //check backup timing
+       int btm=QSettings().value("profiles/"+pk+"/backupinterval",0).toInt()*86400;
+       if(btm){
+               //adjust for last backup time
+               int iv=QDateTime::currentDateTime().toTime_t() - QSettings().value("profiles/"+pk+"/backuptime",0).toInt();
+               if(iv>=btm)btm=0;
+               else btm-=iv;
+               //earliest allowed backup is 30s from now
+               if(btm<30)btm=30;
+               //start timer
+               baktimer.start(btm*1000);
+               connect(&baktimer,SIGNAL(timeout()),this,SLOT(doBackup()));
+       }
        
        //menu
        QMenuBar*mb=menuBar();
@@ -104,10 +117,10 @@ MOverview::MOverview(MWebRequest*mw,QString pk)
        m->addAction(tr("&Server Access settings..."),this,SLOT(webSettings()));
        
        m=mb->addMenu(tr("&Admin"));
-       m->setEnabled(req->hasRole("_admin"));
-       m->addAction(tr("&Schedule Backup..."));
-       m->addAction(tr("&Backup now..."));
-       m->addAction(tr("&Restore..."));
+       m->addAction(tr("Backup &Settings..."),this,SLOT(backupSettings()))
+        ->setEnabled(req->hasRole("backup"));
+       m->addAction(tr("&Backup now..."),this,SLOT(doBackup()))
+        ->setEnabled(req->hasRole("backup"));
        
        //tabs
        setCentralWidget(tab=new QTabWidget);
@@ -1416,6 +1429,120 @@ void MOverview::webSettings(bool showdlg)
        req->setTimeout(timeout*1000);
 }
 
+void MOverview::doBackup()
+{
+       baktimer.stop();
+       //sanity check
+       if(!req->hasRole("backup"))return;
+       //get settings
+       QSettings set;
+       set.beginGroup("profiles/"+profilekey);
+       QString path=set.value("backupfile",req->dataDir()+"/backup").toString();
+       int gens=set.value("backupgenerations",3).toInt();
+       //get data
+       bool ok=req->request("backup");
+       if(ok)ok=req->responseStatus()==MWebRequest::Ok;
+       if(!ok){
+               QMessageBox::warning(this,tr("Warning"),tr("Backup failed with error: %1").arg(req->errorString()));
+               return;
+       }
+       //rotate files
+       QFile(path+"."+QString::number(gens)).remove();
+       for(int i=gens-1;i>=0;i--){
+               if(i)
+                       QFile(path+"."+QString::number(i)).rename(path+"."+QString::number(i+1));
+               else
+                       QFile(path).rename(path+".1");
+       }
+       //store new data
+       QFile fd(path);
+       if(fd.open(QIODevice::WriteOnly|QIODevice::Truncate)){
+               fd.write(req->responseBody());
+               fd.close();
+               set.setValue("backuptime",QDateTime::currentDateTime().toTime_t());
+               QMessageBox::information(this,tr("Backup"),tr("The backup was successful."));
+               int tm=set.value("backupinterval",0).toInt()*86400000;
+               if(tm)baktimer.start(tm);
+       }else
+               QMessageBox::warning(this,tr("Warning"),tr("Cannot create backup file."));
+}
+
+void MOverview::backupSettings()
+{
+       //show dialog
+       MBackupDialog d(this,profilekey,req);
+       d.exec();
+       //reset timer
+       int btm=QSettings().value("profiles/"+profilekey+"/backupinterval",0).toInt()*86400;
+       if(btm){
+               //adjust for last backup time
+               int iv=QDateTime::currentDateTime().toTime_t() - QSettings().value("profiles/"+profilekey+"/backuptime",0).toInt();
+               if(iv>=btm)btm=0;
+               else btm-=iv;
+               //start timer
+               baktimer.start(btm*1000);
+       }
+}
+
+/**********************************************/
+
+MBackupDialog::MBackupDialog(QWidget*par,QString pk,MWebRequest*req)
+       :QDialog(par),profilekey(pk)
+{
+       QSettings set;
+       set.beginGroup("profiles/"+profilekey);
+       QString path=set.value("backupfile",req->dataDir()+"/backup").toString();
+       int gens=set.value("backupgenerations",3).toInt();
+       int tm=set.value("backupinterval",0).toInt();
+       //dialog
+       setWindowTitle(tr("Backup Settings"));
+       QGridLayout*gl;
+       QHBoxLayout*hl;
+       setLayout(gl=new QGridLayout);
+       QPushButton*p;
+       gl->addWidget(new QLabel(tr("Backup File:")),0,0);
+       gl->addWidget(lpath=new QLineEdit(path),0,1);
+       gl->addWidget(p=new QPushButton(tr("...")),0,2);
+       connect(p,SIGNAL(clicked()),this,SLOT(getpath()));
+       
+       gl->addWidget(new QLabel(tr("Generations to keep:")),1,0);
+       gl->addWidget(gener=new QSpinBox,1,1,1,2);
+       gener->setRange(1,16);
+       gener->setValue(gens);
+       
+       gl->addWidget(new QLabel(tr("Automatic Backup:")),2,0);
+       gl->addWidget(autob=new QCheckBox,2,1,1,2);
+       autob->setChecked(tm>0);
+       
+       gl->addWidget(new QLabel(tr("Interval in days:")),3,0);
+       gl->addWidget(interv=new QSpinBox,3,1,1,2);
+       interv->setRange(1,24);
+       interv->setValue(tm>0?tm:1);
+       
+       gl->setRowMinimumHeight(4,15);
+       gl->addLayout(hl=new QHBoxLayout,5,0,1,3);
+       hl->addStretch(10);
+       hl->addWidget(p=new QPushButton(tr("&OK")));
+       connect(p,SIGNAL(clicked()),this,SLOT(accept()));
+       connect(p,SIGNAL(clicked()),this,SLOT(save()));
+       hl->addWidget(p=new QPushButton(tr("&Cancel")));
+       connect(p,SIGNAL(clicked()),this,SLOT(reject()));
+}
+
+void MBackupDialog::getpath()
+{
+       QString path=QFileDialog::getSaveFileName(this,tr("Backup File"),lpath->text());
+       if(path!="")lpath->setText(path);
+}
+
+void MBackupDialog::save()
+{
+       QSettings set;
+       set.beginGroup("profiles/"+profilekey);
+       set.setValue("backupfile",lpath->text());
+       set.setValue("backupgenerations",gener->value());
+       set.setValue("backupinterval",autob->isChecked()?interv->value():0);
+}
 /**********************************************/
 
 MPasswordChange::MPasswordChange(QWidget*par,QString user)
index 1a7bfb1..bda83a5 100644 (file)
 
 class MWebRequest;
 class QAction;
+class QCheckBox;
 class QComboBox;
 class QLabel;
 class QLineEdit;
 class QPushButton;
+class QSpinBox;
 class QStandardItemModel;
 class QTabWidget;
 class QTableView;
@@ -146,6 +148,12 @@ class MOverview:public QMainWindow
                /**web request settings dialog; shows the dialog per default, just copies settings from registry to webrequest object if false*/
                void webSettings(bool dlg=true);
                
+               /**do a backup now*/
+               void doBackup();
+               
+               /**settings for backup*/
+               void backupSettings();
+               
        private:
                //my session object
                QPointer<MWebRequest>req;
@@ -169,7 +177,7 @@ class MOverview:public QMainWindow
                QString lastbarcode;
                QDateTime lastbcscan;
                //refresh timers
-               QTimer rtimer;
+               QTimer rtimer,baktimer;
 };
 
 /**Helper dialog for changing passwords*/
@@ -202,5 +210,21 @@ class MCartTableDelegate:public QItemDelegate
                                  const QModelIndex &index) const;
 };
 
+/**Helper class for Backup settings*/
+class MBackupDialog:public QDialog
+{
+       Q_OBJECT
+       public:
+               MBackupDialog(QWidget*,QString,MWebRequest*);
+               
+       private slots:
+               void getpath();
+               void save();
+       private:
+               QLineEdit*lpath;
+               QSpinBox*interv,*gener;
+               QCheckBox*autob;
+               QString profilekey;
+};
 
 #endif
index b9a072b..74d6656 100644 (file)
@@ -75,7 +75,7 @@ static inline QString esc(const QByteArray ar)
        for(int i=0;i<ar.size();i++){
                unsigned char a=ar[i];
                if(a=='\\')r+="\\\\";else
-               if(a>=32 && a<=127)r+=(char)a;
+               if(a=='\n' || a=='\r' || (a>=32 && a<=127))r+=(char)a;
                else r+="\\x"+QByteArray((char*)&a,1).toHex();
        }
        return r;
index b4f2422..ae90962 100644 (file)
@@ -10,6 +10,14 @@ if(!$db->checkAdmin()){
        exit;
 }
 #phpinfo();
+function form($m="GET")
+{
+       if($m=="FILE"){
+               print("<form method=\"POST\" enctype=\"multipart/form-data\" action=\"".$_SERVER["SCRIPT_NAME"]."\">\n");
+//             print("<input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"".???
+       }else
+               print("<form action=\"".$_SERVER["SCRIPT_NAME"]."\" method=\"".$m."\">\n");
+}
 ?>
 <h1>Magic Smoke Admin Utility</h1>
 
@@ -30,7 +38,7 @@ if($canUseDb){
 ?>
 Database exists and is usable.<p>
 
-<form action="admin.php" method="GET">
+<? form() ?>
 <input type="submit" value="Show SQL Script to create DB." name="ShowCreateDB"/>
 </form>
 
@@ -38,7 +46,7 @@ Database exists and is usable.<p>
 }else{
 ?>
 Database is not usable. Create?<br>
-<form action="admin.php" method="GET">
+<? form() ?>
 <input type="submit" value="Yes, Create DB now." name="CreateDB"/>
 <input type="submit" value="Show SQL Script to create DB." name="ShowCreateDB"/>
 </form>
@@ -57,6 +65,29 @@ if(!$canUseDb)
        exit();
 ?>
 
+<h2>Restore Backups</h2>
+
+<? form("FILE") ?>
+Backup file to restore:
+<input type="file" name="restore"/><br/>
+<input type="checkbox" name="overwrite" value="ovr">Overwrite existing data<br>
+<input type="submit" name="restorenow" value="Restore Data Now"/>
+</form><p>
+<font color="red"><b>Warning:</b></font> don't attempt to restore data unless you really lost it! Ideally restore should only be made on an empty database.<p>
+
+<?
+if(isset($_POST["restorenow"])){
+       echo "<h3>Restoring Data...</h3>\n";
+       $ov=false;
+       if(isset($_POST["overwrite"]))$ov=($_POST["overwrite"]=="ovr");
+       if(!is_uploaded_file($_FILES["restore"]["tmp_name"]))
+               die("Trying to work on non-uploaded file. Abort.");
+       $db->restoreData($_FILES["restore"]["tmp_name"],$ov);
+       unlink($_FILES["restore"]["tmp_name"]);
+}
+?>
+
+
 <h2>Checking for Admin Users</h2>
 
 <?
@@ -108,4 +139,6 @@ for($i=0;$i<count($admlst);$i++){
 <tr><td>Repeat Password:</td><td><input type="password" name="adminpwd2"></td></tr>
 </table>
 <input type="submit" value="Create">
-</form>
\ No newline at end of file
+</form>
+
+</html>
\ No newline at end of file
index 97d3662..2f9a895 100644 (file)
@@ -17,8 +17,8 @@ $template="./template/";
 // create engine: server, user, password
 $db = new MysqlEngine("localhost","smoke","");
 // set database name
-//$db->setDbName("smoke");
-$db->setDbName("DB396352");
+$db->setDbName("smoke2");
+//$db->setDbName("DB396352");
 // set table-prefix (optional)
 $db->setPrefix("smoke_");
 //set this to one of the supported MySQL storage engines for DB creation
index f6f9e8a..a047153 100644 (file)
@@ -54,6 +54,12 @@ abstract class DbEngine
        /**ends a transaction with a rollback; must be implemented by driver; returns true on success; use sqlRollbackTransaction to create the SQL statement!*/
        public abstract function rollbackTransaction();
        
+       /**locks the database - only used by the backup functions; if necessary should also start a transaction; default just starts a transaction*/
+       protected function lockDB($writelock){$this->beginTransaction();}
+       
+       /**unlocks the database - only used by the backup functions; if necessary should also commit a transaction; default just commits*/
+       protected function unlockDB(){$this->commitTransaction();}
+       
        /**gets some data from the database; $table is the name of the table, $cols is the list of columns to return or "*" for all, $where is the where clause of the SQL-statement, $orderby may contain additional ORDER BY or GROUP BY clauses; returns array of rows, which are in *_fetch_array format; returns false on error; use sqlSelect to create the SQL statement!*/
        public abstract function select($table,$cols,$where,$orderby="");
        
@@ -300,6 +306,18 @@ abstract class DbEngine
                                return "NULL";
                }
        }
+       
+       /**generic escape routine: queries the schema for the correct escape mechanism and then returns the appropriately escaped value*/
+       public function escapeColumn($table,$col,$val)
+       {
+               global $dbScheme;
+               if($dbScheme->isIntColumn($table,$col))return $this->escapeInt($val);
+               if($dbScheme->isStringColumn($table,$col))return $this->escapeString($val);
+               if($dbScheme->isBlobColumn($table,$col))return $this->escapeBlob($val);
+               if($dbScheme->isBoolColumn($table,$col))return $this->escapeBool($val);
+               //fallback: NULL
+               return "NULL";
+       }
 
        /**returns a configuration setting*/
        public function getConfig($key)
@@ -368,7 +386,7 @@ abstract class DbEngine
                        }
                }
                //close transaction
-               print(htmlentities($this->sqlCommitTransaction()).";\n<pre>\n");
+               print(htmlentities($this->sqlCommitTransaction()).";\n</pre>\n");
        }
        
        /**returns the error string of the last operation*/
@@ -380,6 +398,146 @@ abstract class DbEngine
                if($val===false || $val===null)return true;
                else return false;
        }
+       
+       /**helper: encode backup data for transport*/
+       private function escapeBackup($tab,$col,$val)
+       {
+               //check for NULL
+               if($this->isNull($val))return "NULL NULL";
+               //get type
+               global $dbScheme;
+               if($dbScheme->isIntColumn($tab,$col))return "int ".($val+0);
+               if($dbScheme->isBoolColumn($tab,$col)){
+                       if($val)return "bool 1";
+                       else return "bool 0";
+               }
+               //string and blob are base64 encoded
+               return "str ".base64_encode($val);
+       }
+       
+       /**dump a backup to stdout*/
+       public function dumpBackup()
+       {
+               global $dbScheme;
+               //make sure nobody else can disturb us (read lock)
+               $this->lockDB(false);
+               //dump basic stuff
+               print("startbackup\n");
+               print("backupversion 0\n");
+               print("dbversion ".$dbScheme->version()."\n");
+               //go through backup tables
+               foreach($dbScheme->backupTables() as $tab){
+                       print("table ".$tab."\n");
+                       //get columns
+                       $cols=$dbScheme->tableColumns($tab);
+                       //go through rows
+                       $res=$this->select($tab,"*","1=1");
+                       foreach($res as $row){
+                               foreach($cols as $col){
+                                       $val=$row[$col];
+                                       print("value $col ".$this->escapeBackup($tab,$col,$val)."\n");
+                               }
+                               print("insert\n");
+                       }
+               }
+               print("endofbackup\n");
+               //release lock & commit
+               $this->unlockDB();
+       }
+       
+       /**helper: decode backup data from transport format*/
+       private function unescapeBackup($fmt,$val)
+       {
+               switch($fmt){
+                       case "NULL":return false;
+                       case "int":return $val+0;
+                       case "str":return base64_decode($val);
+                       case "bool":return $val+0;
+                       default:
+                               print("Warning: encountered unknown data encoding \"".htmlspecialchars($fmt)."\". Using NULL instead.<br>\n");
+                               return false;
+               }
+       }
+       
+       /**helper: inserts data from backup*/
+       private function backupInsert($table,$data,$overwrite)
+       {
+               global $dbScheme;
+               //get primary keys
+               $pk=$dbScheme->primaryKeyColumns($table);
+               //scan whether data is existent
+               $pks="";$q="";
+               foreach($pk as $p){
+                       if($pks!="")$pks.=",";
+                       $pks.=$p;
+                       if($q!="")$q.=" AND ";
+//                     print("pk $p ");
+                       $q.=$p."=".$this->escapeColumn($table,$p,$data[$p]);
+               }
+//             print("pk=$pks q=$q<br>");
+               $res=$this->select($table,$pks,$q);
+               if(count($res)>0){
+                       if($overwrite){
+                               $b=$this->update($table,$data,$q);
+                               print("update data: ".($b===false?"failed":"success")."<br>\n");
+                       }else{
+                               print("ignoring existing row<br>\n");
+                       }
+               }else{
+                       $b=$this->insert($table,$data);
+                       print("insert data: ".($b===false?"failed":"success")."<br>\n");
+               }
+       }
+       
+       /**called from admin.php: restore a backup*/
+       public function restoreData($file,$overwrite)
+       {
+               //prep
+               $this->lockDB(true);
+               $data=array();
+               $table="";
+               //open file
+               $fd=fopen($file,"r");
+               while(!feof($fd)){
+                       $line=trim(fgets($fd));
+                       if($line=="")continue;
+                       $cmd=explode(" ",$line);
+                       switch($cmd[0]){
+                               case "startbackup":
+                               case "backupversion":
+                               case "dbversion":
+                                       /*currently ignored, maybe we do something with it later*/
+                                       break;
+                               case "endofbackup":
+                                       print("Reached End of Backup.<br>\n");
+                                       break 2;
+                               case "table":
+                                       $data=array();
+                                       $table=$cmd[1];
+                                       if(!$this->haveTable($table)){
+                                               print("Switching to non-existing table ".htmlspecialchars($table)." - ignoring data that follows.<br>\n");
+                                               $table="";
+                                       }else
+                                               print("Switching to table ".htmlspecialchars($table)."<br>\n");
+                                       break;
+                               case "value":
+                                       $data[$cmd[1]]=$this->unescapeBackup($cmd[2],$cmd[3]);
+                                       break;
+                               case "insert":
+                                       if($table==""){
+                                               print("Warning: insert on non-existing table, ignoring data.<br>\n");
+                                       }else{
+                                               $this->backupInsert($table,$data,$overwrite);
+                                       }
+                                       break;
+                               default:
+                                       print("Warning: unknown statement \"".htmlspecialchars($cmd[0])."\" in backup file. Ignoring it.<br>\n");
+                                       break;
+                       }
+               }
+               fclose($fd);
+               $this->unlockDB();
+       }
 };
 
 ?>
\ No newline at end of file
index 2539f83..316fa05 100644 (file)
@@ -63,6 +63,8 @@ class MysqlEngine extends DbEngine
        
        public function haveTable($tnm)
        {
+               global $dbScheme;
+               if(!$dbScheme->haveTable($tnm))return false;
                $res=mysqli_query($this->dbhdl,"select * from ".$this->tableName($tnm)." where 1=2");
                if($res===false)return false;
                mysqli_free_result($res);
@@ -83,6 +85,26 @@ class MysqlEngine extends DbEngine
                return mysqli_query($this->dbhdl,$this->sqlRollbackTransaction());
        }
        
+       protected function lockDB($wl)
+       {
+               global $dbScheme;
+               $req="SET autocommit = 0 ; LOCK TABLES ";
+               $i=0;
+               foreach($dbScheme->backupTables() as $tab){
+                       if($i)$req.=",";
+                       $i++;
+                       $req.=$this->tableName($tab);
+                       if($wl)$req.=" WRITE";
+                       else $req.=" READ";
+               }
+               mysqli_query($this->dbhdl,$req);
+       }
+       
+       protected function unlockDB()
+       {
+               mysqli_query($this->dbhdl,"UNLOCK TABLES");
+       }
+       
        public function sqlBeginTransaction(){return "BEGIN";}
        
        public function sqlCommitTransaction(){return "COMMIT";}
index 5f18636..4c679a2 100644 (file)
@@ -4,6 +4,7 @@ class DbScheme {
        private static $scheme;
        private static $preset;
        private static $sversion;
+       private static $backup;
        
        function __construct()
        {
@@ -25,6 +26,7 @@ class DbScheme {
                        array("ckey"=>"TicketIDChars","cval"=>"10"),
                        array("ckey"=>"VoucherIDChars","cval"=>"10")
                );
+               $this->backup[]="config";
                
                // ////////////////////
                // Machine Interface Stuff
@@ -40,6 +42,7 @@ class DbScheme {
                        array("hostname"=>translate("SpecialHost","_anon")),
                        array("hostname"=>translate("SpecialHost","_online"))
                );
+               $this->backup[]="host";
 
                //client users (ticket sellers, admins, etc.; for customers and web logins see below)
                $this->scheme["users"]=array(
@@ -48,14 +51,19 @@ class DbScheme {
                        //more detailed data that can be displayed to admins
                        "description" => array("text")
                );
+               $this->backup[]="users";
                $this->scheme["userrole"]=array(
                        "uname" =>array("string:64","notnull","foreignkey:users:uname","index"),
-                       "role" =>array("string:32","notnull")
+                       "role" =>array("string:32","notnull"),
+                       ":primarykey"=>array("uname","role")
                );
+               $this->backup[]="userrole";
                $this->scheme["userhosts"]=array(
                        "uname" => array("string:64","notnull","foreignkey:users:uname","index"),
-                       "host" => array("string:64","notnull","foreignkey:host:hostname")
+                       "host" => array("string:64","notnull","foreignkey:host:hostname"),
+                       ":primarykey" =>array("uname","host")
                );
+               $this->backup[]="userhosts";
                //sessions
                $this->scheme["session"]=array(
                        "sessionid" => array("string:64","primarykey"),
@@ -75,6 +83,7 @@ class DbScheme {
                        "content" => array("blob"),
                        "hash" => array("string:32","notnull") //md5
                );
+               $this->backup[]="template";
                
                
                // //////////////////////
@@ -86,6 +95,7 @@ class DbScheme {
                        "capacity" => array("int32","notnull"),
                        "description" => array("text")
                );
+               $this->backup[]="room";
                //event
                $this->scheme["event"]=array(
                        "eventid" => array("seq32","primarykey"),
@@ -93,7 +103,7 @@ class DbScheme {
                        "title" => array("string","notnull"),
                        "artist" => array("string","notnull"),
                        "description" => array("text"),
-                       //timing and location
+                       //timing and location (needs to change to 64bit in 2038)
                        "starttime" => array("int32","notnull"),
                        "endtime" => array("int32","notnull"),
                        "roomid" => array("string:64","foreignkey:room:roomid"),
@@ -104,6 +114,7 @@ class DbScheme {
                        //if not null/empty: event has been cancelled
                        "cancelreason" => array("string")
                );
+               $this->backup[]="event";
                //customer
                $this->scheme["customer"]=array(
                        "customerid" => array("seq32","primarykey"),
@@ -113,12 +124,14 @@ class DbScheme {
                        "contact" => array("string"),//phone or something
                        "comments" => array("text")
                );
+               $this->backup[]="customer";
                $this->scheme["webuser"]=array(
                        //online login data
                        "email" => array("string","primarykey"),
                        "customerid" => array("int32","unique","foreignkey:customer:customerid"),
                        "passwd" => array("string:64"),//salted SHA-1 hash of passwd
                );
+               $this->backup[]="webuser";
                
                //kinds of shipping that are available (templates)
                $this->scheme["shipping"]=array(
@@ -128,6 +141,7 @@ class DbScheme {
                        "canallusers" => array("bool","defaultbool:true"), //all GUI users may select it
                        "description" => array("string") //description for the shipping type
                );
+               $this->backup[]="shipping";
                
                //orders by customers
                $this->scheme["order"]=array(
@@ -154,6 +168,7 @@ class DbScheme {
                        //pointer to shipping type (none per default, programmatic default is in config)
                        "shippingtype" => array("int32","null","foreignkey:shipping:shipid")
                );
+               $this->backup[]="order";
                //tickets
                $this->scheme["ticket"]=array(
                        //a 8-32 char code (code39: case-insensitive letters+digits) for the ticket
@@ -169,6 +184,7 @@ class DbScheme {
                        //sold to someone (may be NULL for direct sales or reserves)
                        "orderid" => array("int32","foreignkey:order:orderid")
                );
+               $this->backup[]="ticket";
                //vouchers and re-imbursments
                $this->scheme["voucher"]=array(
                        //a 8-32 char code (code39: case-insensitive letters+digits) for the voucher
@@ -182,6 +198,7 @@ class DbScheme {
                        //remaining value in cents (0 for cancelled)
                        "value" => array("int32","notnull")
                );
+               $this->backup[]="voucher";
                
                
                // /////////////////////////
@@ -235,6 +252,18 @@ class DbScheme {
                return array_keys($this->scheme);
        }
        
+       /**returns whether a table exists in the schema*/
+       public function haveTable($t)
+       {
+               return in_array($t,array_keys($this->scheme));
+       }
+       
+       /**return the tables that are included in the backup*/
+       public function backupTables()
+       {
+               return $this->backup;
+       }
+       
        /**return the full definition of a table, or false if it does not exist*/
        public function tableDefinition($tab)
        {
@@ -248,7 +277,11 @@ class DbScheme {
        {
                if(!isset($this->scheme[$tab]))
                        return false;
-               return array_keys($this->scheme[$tab]);
+               $r=array();
+               foreach(array_keys($this->scheme[$tab]) as $c)
+                       if(substr($c,0,1)!=":")
+                               $r[]=$c;
+               return $r;
        }
        
        /**return default lines of the table for the initialization; returns empty array if there are none*/
@@ -344,6 +377,23 @@ class DbScheme {
                                return false;
                }
        }
+       
+       /**returns the names of all primary key columns of the table*/
+       public function primaryKeyColumns($tab)
+       {
+               $r=array();
+               //search for direct mark
+               foreach($this->scheme[$tab] as $col=>$def)
+                       if(in_array("primarykey",$def))
+                               $r[]=$col;
+               //search for special mark
+               if(isset($this->scheme[$tab][":primarykey"]))
+                       foreach($this->scheme[$tab][":primarykey"] as $col)
+                               if(!in_array($col,$r))
+                                       $r[]=$col;
+               //return result
+               return $r;
+       }
 };
 $dbScheme=new DbScheme;
 ?>
\ No newline at end of file
index dab527c..8116499 100644 (file)
@@ -49,7 +49,9 @@ $ALLOWEDREQUESTS=array(
        tr("getvoucherprices"),tr("cancelvoucher"),tr("emptyvoucher"),tr("usevoucher"),tr("getvoucher"),
        //templates
        tr("gettemplatelist"),tr("gettemplate"),tr("settemplate"),tr("settemplatedescription"),
-       tr("deletetemplate")
+       tr("deletetemplate"),
+       //backup
+       tr("backup")
 );
 /**special roles begin with _ and are listed here (in lower case and wrapped in tr())*/
 $SPECIALROLES=array(
@@ -463,6 +465,14 @@ if($SMOKEREQUEST=="getvoucher"){
        exit();
 }
 
+
+//retrieve backup
+if($SMOKEREQUEST=="backup"){
+       header("X-MagicSmoke-Status: Ok");
+       $db->dumpBackup();
+       exit();
+}
+
 //EOF
 header("X-MagicSmoke-Status: Error");
 die(tr("Internal Error: unknown command, hiccup in code structure."));