From 7e26bd0c57d74d8ee7a9d301e624c91c7020c347 Mon Sep 17 00:00:00 2001 From: konrad Date: Sun, 21 Dec 2008 13:06:05 +0000 Subject: [PATCH] implemented backup git-svn-id: https://silmor.de/svn/softmagic/smoke/trunk@219 6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33 --- src/overview.cpp | 135 +++++++++++++++++++++++++++++++++++++- src/overview.h | 26 +++++++- src/webrequest.cpp | 2 +- www/admin.php | 39 ++++++++++- www/config.php.template | 4 +- www/inc/db/db.php | 160 +++++++++++++++++++++++++++++++++++++++++++++- www/inc/db/db_mysql.php | 22 ++++++ www/inc/db/db_scheme.php | 58 ++++++++++++++++- www/machine.php | 12 +++- 9 files changed, 441 insertions(+), 17 deletions(-) diff --git a/src/overview.cpp b/src/overview.cpp index 073b9c9..8b32fac 100644 --- a/src/overview.cpp +++ b/src/overview.cpp @@ -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) diff --git a/src/overview.h b/src/overview.h index 1a7bfb1..bda83a5 100644 --- a/src/overview.h +++ b/src/overview.h @@ -24,10 +24,12 @@ 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 QPointerreq; @@ -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 diff --git a/src/webrequest.cpp b/src/webrequest.cpp index b9a072b..74d6656 100644 --- a/src/webrequest.cpp +++ b/src/webrequest.cpp @@ -75,7 +75,7 @@ static inline QString esc(const QByteArray ar) for(int i=0;i=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; diff --git a/www/admin.php b/www/admin.php index b4f2422..ae90962 100644 --- a/www/admin.php +++ b/www/admin.php @@ -10,6 +10,14 @@ if(!$db->checkAdmin()){ exit; } #phpinfo(); +function form($m="GET") +{ + if($m=="FILE"){ + print("
\n"); +// print("\n"); +} ?>

Magic Smoke Admin Utility

@@ -30,7 +38,7 @@ if($canUseDb){ ?> Database exists and is usable.

- +

@@ -38,7 +46,7 @@ Database exists and is usable.

}else{ ?> Database is not usable. Create?
-

+
@@ -57,6 +65,29 @@ if(!$canUseDb) exit(); ?> +

Restore Backups

+ + +Backup file to restore: +
+Overwrite existing data
+ +

+Warning: don't attempt to restore data unless you really lost it! Ideally restore should only be made on an empty database.

+ +Restoring Data...\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"]); +} +?> + +

Checking for Admin Users

Repeat Password: - \ No newline at end of file + + + \ No newline at end of file diff --git a/www/config.php.template b/www/config.php.template index 97d3662..2f9a895 100644 --- a/www/config.php.template +++ b/www/config.php.template @@ -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 diff --git a/www/inc/db/db.php b/www/inc/db/db.php index f6f9e8a..a047153 100644 --- a/www/inc/db/db.php +++ b/www/inc/db/db.php @@ -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
\n");
+		print(htmlentities($this->sqlCommitTransaction()).";\n
\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.
\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
"); + $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")."
\n"); + }else{ + print("ignoring existing row
\n"); + } + }else{ + $b=$this->insert($table,$data); + print("insert data: ".($b===false?"failed":"success")."
\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.
\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.
\n"); + $table=""; + }else + print("Switching to table ".htmlspecialchars($table)."
\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.
\n"); + }else{ + $this->backupInsert($table,$data,$overwrite); + } + break; + default: + print("Warning: unknown statement \"".htmlspecialchars($cmd[0])."\" in backup file. Ignoring it.
\n"); + break; + } + } + fclose($fd); + $this->unlockDB(); + } }; ?> \ No newline at end of file diff --git a/www/inc/db/db_mysql.php b/www/inc/db/db_mysql.php index 2539f83..316fa05 100644 --- a/www/inc/db/db_mysql.php +++ b/www/inc/db/db_mysql.php @@ -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";} diff --git a/www/inc/db/db_scheme.php b/www/inc/db/db_scheme.php index 5f18636..4c679a2 100644 --- a/www/inc/db/db_scheme.php +++ b/www/inc/db/db_scheme.php @@ -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 diff --git a/www/machine.php b/www/machine.php index dab527c..8116499 100644 --- a/www/machine.php +++ b/www/machine.php @@ -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.")); -- 1.7.2.5