source: branches/release-0.3.0-rc1/src/MediaDBLib/dbbackup.cpp @ 217

Last change on this file since 217 was 217, checked in by Joachim Langenbach, 11 years ago
  • closes #100
  • DBBackup
    • renamed from DBDump, because it should contain also Restoring functionality later on
    • supports the export of views
    • supports the export of Integer, BLOB and Strings (all other than int and blobs are also treated as strings)
    • provides a static function to escape all MySQL-Specialchars
  • SetupObjectDBDump
    • is inserted as first widget into SetupWizard?, if a DBInit widget is present
    • this way it should made sure, that the user backedup his database before we manipulate it
File size: 12.0 KB
Line 
1/*
2    MediaDB - A media management software
3    Copyright (C) 2010  Joachim Langenbach
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18*/
19
20#include "config.h"
21
22#include "dbbackup.h"
23#include "msqldatabase.h"
24
25#include <QSqlQuery>
26#include <QSqlRecord>
27#include <QSqlError>
28#include <QSettings>
29#include <QDate>
30#include <QDateTime>
31#include <QTextCodec>
32
33#ifdef KDE
34        #include <KFileDialog>
35        #include <KUrl>
36#else
37        #include <QFileDialog>
38#endif
39
40DBBackup::DBBackup(SetupWizardObject* newObject, QWidget* parent, Qt::WindowFlags flags)
41        : SetupWizardWidget(newObject, parent, flags)
42{
43        codecName = "UTF-8";
44        doAbort = false;
45       
46        setupUi(this);
47        connect(fileButton, SIGNAL(clicked()), this, SLOT(setDumpFile()));
48#if QT_VERSION_MINOR < 6
49        fileButton->setIcon(QIcon(":/icons/find.png"));
50#else
51        fileButton->setIcon(QIcon::fromTheme("edit-find",
52                                                                                                                                                                 QIcon(":/icons/find.png")));
53#endif
54       
55        // create default file name
56        QSettings settings;
57        QString lastDir = settings.value("lastDumpDir", "").toString();
58        if(!lastDir.isEmpty()){
59                dumpFile.setFile(lastDir +"/"+ settings.value("appNameShort").toString() +"-"+ QDate::currentDate().toString("yyyyMMdd") +".sql");
60                fileEdit->setText(dumpFile.absoluteFilePath());
61        }
62}
63
64QString DBBackup::mysqlEscape(QString input)
65{
66        input.replace("\\", "\\\\");
67        input.replace("\\x00", "\\\\x00");
68        input.replace("\\n", "\\\\n");
69        input.replace("\\r", "\\\\r");
70        input.replace("\'", "\\\'");
71        input.replace("\"", "\\\"");
72        input.replace("\\x1a", "\\\\x1a");
73        return input;
74}
75
76bool DBBackup::finish()
77{
78        logEdit->setPlainText("");
79        doAbort = false;
80        emit exportStarted();
81        actualStep(tr("Opening file"));
82        // create file if needed and open it for writing
83        QFile file(dumpFile.absoluteFilePath());
84        if(!dumpFile.isWritable()){
85                if(dumpFile.exists()){
86                        log(tr("Output file is not writable, please select a writeable file and try again!\n"
87                                                                                "File: %1")
88                                                                                .arg(dumpFile.absoluteFilePath()));
89                        setDumpFile();
90                        emit exportFinished(false);
91                        return false;
92                }
93                else{
94                        // create it
95                        if(!file.open(QFile::WriteOnly)){
96                                log(tr("Output file is not writable, please select a writeable file and try again!\n"
97                                                                                        "File: %1")
98                                                                                        .arg(dumpFile.absoluteFilePath()));
99                                setDumpFile();
100                                emit exportFinished(false);
101                                return false;
102                        }
103                }
104        }
105        if(!file.isOpen()){
106                if(!file.open(QFile::WriteOnly)){
107                        log(tr("Output file is not writable, please select a writeable file and try again!\n"
108                                                                                "File: %1")
109                                                                                .arg(dumpFile.absoluteFilePath()));
110                        setDumpFile();
111                        emit exportFinished(false);
112                        return false;
113                }
114        }
115        QSettings settings;
116        settings.setValue("lastDumpDir", dumpFile.absolutePath());
117       
118        // try to open database
119        MSqlDatabase db = MSqlDatabase::database();
120        bool closeDB = !db.isOpen();
121        if(closeDB){
122                if(!db.open()){
123                        log(tr("Could not connect to Database"));
124                        file.close();
125                        emit exportFinished(false);
126                        return false;
127                }
128        }
129       
130        // configure stream
131        actualStep(tr("Configure file"));
132        QTextStream stream(&file);
133        stream.setCodec(QTextCodec::codecForName(codecName.toAscii()));
134       
135        // write header
136        actualStep(tr("Writing header"));
137        stream << QString("-- %1 %2\n").arg(settings.value("appNameShort").toString()).arg(settings.value("appVersion").toString());
138        stream << QString("-- Host: %1\n").arg(db.hostName());
139        stream << QString("-- Database: %1\n").arg(db.databaseName());
140        stream << QString("-- User: %1\n").arg(db.userName());
141        stream << QString("-- Date: %1\n").arg(QDateTime::currentDateTime().toString(tr("MM/dd/yyyy hh:mm:ss")));
142        stream << QString("-- Codec: %1\n").arg(codecName);
143        stream << QString("-- -----------------------------------------------------------------\n");
144        stream << "\n";
145        stream << "-- Disableing foreignkeychecks during inserts\n";
146        stream << "SET foreign_key_checks = 0;\n";
147        stream << "\n";
148
149        // get list of tables and views
150        QStringList dbTables = db.tables ( QSql::Tables );
151        QStringList dbViews = db.tables ( QSql::Views );
152        setRange(dbTables.size() + dbViews.size());
153        bool result = true;
154       
155        // dump tables
156        if(!doAbort){
157                actualStep(tr("Dumping tables"));
158                foreach(QString table, dbTables){
159                        QString resString = tr("OK");
160                        if(!dumpTable(table, stream)){
161                                result = false;
162                                resString = tr("FAILED");
163                        }
164                        stream << "\n\n";
165                        log(QString("  %1 - %2").arg(table).arg(resString));
166                        finishedStep();
167                        if(doAbort){
168                                result = false;
169                                break;
170                        }
171                        QApplication::processEvents();
172                }
173        }
174       
175        // dump views
176        if(!doAbort){
177                actualStep(tr("Sorting views"));
178                dbViews = sortViews(dbViews);
179               
180                actualStep(tr("Dump views"));
181                foreach(QString view, dbViews){
182                        QString resString = tr("OK");
183                        if(!dumpView(view, stream)){
184                                result = false;
185                                resString = tr("FAILED");
186                        }
187                        stream << "\n\n";
188                        log(QString("  %1 - %2").arg(view).arg(resString));
189                        finishedStep();
190                        if(doAbort){
191                                result = false;
192                                break;
193                        }
194                        QApplication::processEvents();
195                }
196        }
197       
198        if(!doAbort){
199                actualStep(tr("Writing footer"));
200                stream << "\n";
201                stream << "-- Enable foreignkeychecks after inserts\n";
202                stream << "SET foreign_key_checks = 1;\n";
203        }
204       
205        if(closeDB)
206                db.close();
207        file.close();
208       
209        if(doAbort){
210                actualStep(tr("Aborted"));
211        }
212        else{
213                if(result){
214                        actualStep(tr("Finished!"));
215                }
216                else{
217                        actualStep(tr("Finished with errors!"));
218                }
219        }
220       
221        emit exportFinished(result);
222        return result;
223}
224
225bool DBBackup::saveSettings()
226{
227        return true;
228}
229
230void DBBackup::abort()
231{
232        doAbort = true;
233}
234
235void DBBackup::setDumpFile()
236{
237  QStringList filters;
238#ifdef KDE
239        QString defaultFilter = QString("*.sql|%1").arg(tr("SQL-Files"));
240#else
241        QString defaultFilter = QString("%1 (*.sql)").arg(tr("SQL-Files"));
242#endif
243filters << defaultFilter;
244#ifdef KDE
245        filters << QString("*.*|%1").arg(tr("All Files"));
246#else
247        filters << QString("%1 (*.*)").arg(tr("All Files"));
248#endif
249       
250       
251        QSettings settings;
252        QString lastDir = dumpFile.absolutePath();
253        if(lastDir.isEmpty()){
254                lastDir = settings.value("lastDumpDir", "").toString();
255        }
256        QString wTitle = tr("Select export file");
257#ifdef KDE
258        dumpFile = QFileInfo(KFileDialog::getSaveFileName(KUrl(lastDir), filters.join("\n"), this, wTitle));
259#else
260        dumpFile = QFileInfo(QFileDialog::getSaveFileName(this, wTitle, lastDir, filters.join(";;"), &defaultFilter));
261#endif
262        fileEdit->setText(dumpFile.absoluteFilePath());
263}
264
265bool DBBackup::dumpTable(QString table, QTextStream &stream)
266{
267        tableLabel->setText(table +":");
268        QSqlQuery query;
269       
270        // dump create statement
271        query.exec(QString("SHOW CREATE TABLE %1").arg(table));
272        if(!query.first())
273                return false;
274       
275        QString tableCreateStatement = query.record().value("Create Table").toString();
276        if(tableCreateStatement.isEmpty())
277                return false;
278       
279        stream << "--\n";
280        stream << QString("-- Table structure for table `%1`\n").arg(table);
281        stream << "--\n";
282        stream << QString("DROP TABLE IF EXISTS `%1`;\n").arg(table);
283        stream << tableCreateStatement +";";
284        stream << "\n";
285       
286        // dump content
287        query.exec(QString("SELECT * FROM %1").arg(table));
288        if(query.size() < 1){
289                stream << "--\n";
290                stream << QString("-- No data in table `%1`\n").arg(table);
291                stream << "--\n";
292        }
293        else{
294                stream << "--\n";
295                stream << QString("-- Dumping data for table `%1`\n").arg(table);
296                stream << "--\n";
297                stream << QString("LOCK TABLES `%1` WRITE;\n").arg(table);
298       
299                tableProgress->setRange(0, query.size());
300                stream << QString("INSERT INTO `%1` VALUES ").arg(table);
301                bool first = true;
302                while(query.next()){
303                        if(!first)
304                                stream << ",\n  ";
305                        if(first)
306                                first = false;
307                        stream << "(";
308                        for(int i = 0; i < query.record().count(); i++){
309                                QVariant field = query.record().value(i);
310                                if(field.isNull()){
311                                        stream << "NULL";
312                                }
313                                else if(field.type() == QMetaType::QByteArray){
314                                        // we store byte stream as hex value, therefore 0x is prepended
315                                        // to indicate MySQL that it is an hex string
316                                        stream << "0x" + field.toByteArray().toHex();
317                                }
318                                else{
319                                        bool check;
320                                        field.toInt(&check);
321                                        if(check){
322                                                stream << field.toString();
323                                        }
324                                        else{
325                                                stream << "'"+ mysqlEscape(field.toString()) +"'";
326                                        }
327                                }
328                                if(i < query.record().count() - 1)
329                                        stream << ",";
330                        }
331                        stream << ")";
332                        tableProgress->setValue(tableProgress->value() + 1);
333                }
334                stream << ";\n";
335                stream << "UNLOCK TABLES;\n";
336        }
337       
338        tableLabel->setText("");
339        tableProgress->setRange(0,100);
340        tableProgress->setValue(0);
341        return true;
342}
343
344bool DBBackup::dumpView(QString view, QTextStream& stream)
345{
346        QSqlQuery query;
347       
348        // dump create statement
349        query.exec(QString("SHOW CREATE VIEW %1").arg(view));
350        if(!query.first())
351                return false;
352       
353        QString viewCreateStatement = query.record().value("Create View").toString();
354        if(viewCreateStatement.isEmpty())
355                return false;
356       
357        stream << "--\n";
358        stream << QString("-- Create statement for view `%1`\n").arg(view);;
359        stream << "--\n";
360        stream << QString("DROP VIEW IF EXISTS `%1`;\n").arg(view);
361        stream << viewCreateStatement +";";
362        stream << "\n";
363       
364        return true;
365}
366
367QStringList DBBackup::sortViews(QStringList views)
368{
369        QRegExp fromRegExp("from [\\(]+`([\\w\\d]+)`[\\s\\.]{1}");
370        QRegExp joinRegExp("join `([\\w\\d]+)`[\\s\\.]{1}");
371        QMultiMap<QString, QString> dependencies;
372        QStringList result;
373       
374        foreach(QString view, views){
375                if(doAbort)
376                        return result;
377                QSqlQuery query;
378       
379                // dump create statement
380                query.exec(QString("SHOW CREATE VIEW %1").arg(view));
381                if(!query.first())
382                        return result;
383       
384                QString viewCreateStatement = query.record().value("Create View").toString();
385                if(viewCreateStatement.isEmpty())
386                        return result;
387               
388                QStringList deps;
389                QStringList fromList = viewCreateStatement.split(QRegExp("from [\\(]+`"));
390                // the first one is not interesting, because it's the part in front of the from
391                fromList.removeFirst();
392                foreach(QString from, fromList){
393                        QString subStr = from.left(from.indexOf("`"));
394                        if(!subStr.isEmpty())
395                                deps << subStr;
396                }
397               
398                QStringList joinList = viewCreateStatement.split(QRegExp("join `"));
399                joinList.removeFirst();
400                foreach(QString join, joinList){
401                        QString subStr = join.left(join.indexOf("`"));
402                        if(!subStr.isEmpty())
403                                deps << subStr;
404                }
405               
406                // insert all views to the list
407                foreach(QString dep, deps){
408                        if(views.contains(dep))
409                                dependencies.insert(view, dep);
410                }
411               
412                QApplication::processEvents();
413        }
414       
415        // sorting views
416        // first put all without any dependencies
417        foreach(QString view, views){
418                if(doAbort)
419                        return result;
420                if(dependencies.values(view).size() == 0){
421                        result << view;
422                        views.removeAll(view);
423                }
424                QApplication::processEvents();
425        }
426       
427        // try to add all other in some rounds
428        // we try 15 times all views, if this is not enough, we give up
429        int maxTries = views.size() * 15;
430        int i = 0;
431       
432        QStringList::const_iterator iter;
433        iter = views.constBegin();
434        while(views.size() > 0){
435                if(doAbort)
436                        return result;
437                if(i >= maxTries){
438                        log(tr("Could not calculate dependencies of all views, someviews are missing!"));
439                        break;
440                }
441                if(iter == views.constEnd())
442                        iter = views.constBegin();
443               
444                bool allFound = true;
445                foreach(QString dep, dependencies.values((*iter))){
446                        if(result.indexOf(dep) < 0)
447                                allFound = false;
448                }
449                if(allFound){
450                        result << (*iter);
451                        views.removeAll((*iter));
452                }
453
454                iter++;
455                i++;
456               
457                QApplication::processEvents();
458        }
459       
460        return result;
461}
462
463void DBBackup::setRange(int num)
464{
465        progressBar->setRange(0, num);
466}
467
468void DBBackup::finishedStep()
469{
470        progressBar->setValue ( progressBar->value() + 1 );
471}
472
473void DBBackup::actualStep(QString msg)
474{
475        stepLabel->setText(msg);
476        logEdit->appendPlainText(msg);
477}
478
479void DBBackup::log(QString msg)
480{
481        logEdit->appendPlainText(msg);
482}
483
484#include "dbbackup.moc"
Note: See TracBrowser for help on using the repository browser.