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();
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);
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)
class MWebRequest;
class QAction;
+class QCheckBox;
class QComboBox;
class QLabel;
class QLineEdit;
class QPushButton;
+class QSpinBox;
class QStandardItemModel;
class QTabWidget;
class QTableView;
/**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;
QString lastbarcode;
QDateTime lastbcscan;
//refresh timers
- QTimer rtimer;
+ QTimer rtimer,baktimer;
};
/**Helper dialog for changing passwords*/
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
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;
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>
?>
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>
}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>
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>
<?
<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
// 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
/**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="");
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)
}
}
//close transaction
- print(htmlentities($this->sqlCommitTransaction()).";\n<pre>\n");
+ print(htmlentities($this->sqlCommitTransaction()).";\n</pre>\n");
}
/**returns the error string of the last operation*/
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
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);
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";}
private static $scheme;
private static $preset;
private static $sversion;
+ private static $backup;
function __construct()
{
array("ckey"=>"TicketIDChars","cval"=>"10"),
array("ckey"=>"VoucherIDChars","cval"=>"10")
);
+ $this->backup[]="config";
// ////////////////////
// Machine Interface Stuff
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(
//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"),
"content" => array("blob"),
"hash" => array("string:32","notnull") //md5
);
+ $this->backup[]="template";
// //////////////////////
"capacity" => array("int32","notnull"),
"description" => array("text")
);
+ $this->backup[]="room";
//event
$this->scheme["event"]=array(
"eventid" => array("seq32","primarykey"),
"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"),
//if not null/empty: event has been cancelled
"cancelreason" => array("string")
);
+ $this->backup[]="event";
//customer
$this->scheme["customer"]=array(
"customerid" => array("seq32","primarykey"),
"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(
"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(
//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
//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
//remaining value in cents (0 for cancelled)
"value" => array("int32","notnull")
);
+ $this->backup[]="voucher";
// /////////////////////////
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)
{
{
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*/
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
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(
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."));