1 /**********************************************************************************************
2     Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>
3 
4     This program is free software: you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation, either version 3 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 
17 **********************************************************************************************/
18 
19 #include "qlgt/CQlb.h"
20 #include "qlgt/CQlgtDb.h"
21 #include "qlgt/CQlgtFolder.h"
22 #include "qlgt/CQlgtRoute.h"
23 #include "qlgt/CQlgtTrack.h"
24 #include "qlgt/CQlgtWpt.h"
25 #include "qlgt/CQmsDb.h"
26 #include "qlgt/IQlgtOverlay.h"
27 #include "tool/CImportDatabase.h"
28 
29 #include "gis/db/macros.h"
30 #include "gis/ovl/CGisItemOvlArea.h"
31 #include "gis/rte/CGisItemRte.h"
32 #include "gis/trk/CGisItemTrk.h"
33 #include "gis/wpt/CGisItemWpt.h"
34 #include "helpers/CProgressDialog.h"
35 #include "helpers/CWptIconManager.h"
36 
37 #include "CMainWindow.h"
38 
39 #include <QtSql>
40 #include <QtWidgets>
41 
42 #define DB_QLGT_VERSION 9
43 
CQlgtDb(const QString & filename,CImportDatabase * parent)44 CQlgtDb::CQlgtDb(const QString& filename, CImportDatabase* parent)
45     : gui(parent)
46     , nItems(0)
47     , nFolders(0)
48     , nWpt(0)
49     , nTrk(0)
50     , nRte(0)
51     , nOvl(0)
52     , nDiary(0)
53 {
54     db = QSqlDatabase::addDatabase("QSQLITE", "qlandkarte");
55     db.setDatabaseName(filename);
56     db.open();
57 
58     QFileInfo fi(filename);
59     path = fi.absoluteDir();
60     name = fi.fileName();
61 
62     QSqlQuery query(db);
63 
64     if(!query.exec("PRAGMA locking_mode=EXCLUSIVE"))
65     {
66         return;
67     }
68 
69     if(!query.exec("PRAGMA synchronous=OFF"))
70     {
71         return;
72     }
73 
74     if(!query.exec("PRAGMA temp_store=MEMORY"))
75     {
76         return;
77     }
78 
79     if(!query.exec("PRAGMA default_cache_size=1000"))
80     {
81         return;
82     }
83 
84     if(!query.exec("PRAGMA page_size=8192"))
85     {
86         return;
87     }
88 
89     if(!query.exec("SELECT version FROM versioninfo"))
90     {
91         initDB();
92     }
93     else if(query.next())
94     {
95         int version = query.value(0).toInt();
96         if(version != DB_QLGT_VERSION)
97         {
98             migrateDB(version);
99         }
100     }
101     else
102     {
103         initDB();
104     }
105 
106     printStatistic();
107 }
108 
~CQlgtDb()109 CQlgtDb::~CQlgtDb()
110 {
111 }
112 
initDB()113 void CQlgtDb::initDB()
114 {
115     qDebug() << "void CGeoDB::initDB()";
116     QSqlQuery query(db);
117 
118     if(query.exec( "CREATE TABLE versioninfo ( version TEXT )"))
119     {
120         query.prepare( "INSERT INTO versioninfo (version) VALUES(:version)");
121         query.bindValue(":version", DB_QLGT_VERSION);
122         QUERY_EXEC();
123     }
124 
125     if(!query.exec( "CREATE TABLE folders ("
126                     "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
127                     "type           INTEGER,"
128                     "date           DATETIME DEFAULT CURRENT_TIMESTAMP,"
129                     "icon           TEXT NOT NULL,"
130                     "name           TEXT NOT NULL,"
131                     "comment        TEXT,"
132                     "locked       BOOLEAN DEFAULT FALSE"
133                     ")"))
134     {
135         qDebug() << query.lastQuery();
136         qDebug() << query.lastError();
137     }
138 
139     if(!query.exec( "CREATE TABLE items ("
140                     "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
141                     "type           INTEGER,"
142                     "key            TEXT NOT NULL,"
143                     "date           DATETIME DEFAULT CURRENT_TIMESTAMP,"
144                     "icon           TEXT NOT NULL,"
145                     "name           TEXT NOT NULL,"
146                     "comment        TEXT,"
147                     "data           BLOB NOT NULL"
148                     ")"))
149     {
150         qDebug() << query.lastQuery();
151         qDebug() << query.lastError();
152     }
153 
154     if(!query.exec( "CREATE TABLE workspace ("
155                     "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
156                     "type           INTEGER NOT NULL,"
157                     "changed        BOOLEAN DEFAULT FALSE,"
158                     "data           BLOB NOT NULL,"
159                     "key            TEXT NOT NULL"
160                     ")"))
161     {
162         qDebug() << query.lastQuery();
163         qDebug() << query.lastError();
164     }
165 
166     if(!query.exec("INSERT INTO folders (icon, name, comment) VALUES ('', 'database', '')"))
167     {
168         qDebug() << query.lastQuery();
169         qDebug() << query.lastError();
170     }
171 
172     if(!query.exec( "CREATE TABLE folder2folder ("
173                     "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
174                     "parent         INTEGER NOT NULL,"
175                     "child          INTEGER NOT NULL,"
176                     "FOREIGN KEY(parent) REFERENCES folders(id),"
177                     "FOREIGN KEY(child) REFERENCES folders(id)"
178                     ")"))
179     {
180         qDebug() << query.lastQuery();
181         qDebug() << query.lastError();
182     }
183 
184     if(!query.exec( "CREATE TABLE folder2item ("
185                     "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
186                     "parent         INTEGER NOT NULL,"
187                     "child          INTEGER NOT NULL,"
188                     "FOREIGN KEY(parent) REFERENCES folders(id),"
189                     "FOREIGN KEY(child) REFERENCES items(id)"
190                     ")"))
191     {
192         qDebug() << query.lastQuery();
193         qDebug() << query.lastError();
194     }
195 
196     if(!query.exec( "CREATE TABLE diarys ("
197                     "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
198                     "parent         INTEGER NOT NULL,"
199                     "key            TEXT NOT NULL,"
200                     "date           DATETIME DEFAULT CURRENT_TIMESTAMP,"
201                     "data           BLOB NOT NULL,"
202                     "FOREIGN KEY(parent) REFERENCES folders(id)"
203                     ")"))
204     {
205         qDebug() << query.lastQuery();
206         qDebug() << query.lastError();
207     }
208 }
209 
210 
migrateDB(int version)211 void CQlgtDb::migrateDB(int version)
212 {
213     qDebug() << "void CGeoDB::migrateDB(int version)" << version;
214     QSqlQuery query(db);
215 
216     for(version++; version <= DB_QLGT_VERSION; version++)
217     {
218         switch(version)
219         {
220         case 1:
221             break;
222 
223         case 2:
224         {
225             if(!query.exec( "CREATE TABLE workspace ("
226                             "id             INTEGER PRIMARY KEY NOT NULL,"
227                             "changed        BOOLEAN DEFAULT FALSE,"
228                             "data           BLOB NOT NULL"
229                             ")"))
230             {
231                 qDebug() << query.lastQuery();
232                 qDebug() << query.lastError();
233                 return;
234             }
235 
236             break;
237         }
238 
239         case 3:
240         {
241             if(!query.exec("ALTER TABLE workspace ADD COLUMN key TEXT"))
242             {
243                 qDebug() << query.lastQuery();
244                 qDebug() << query.lastError();
245                 return;
246             }
247             if(!query.exec("ALTER TABLE workspace ADD COLUMN type INTEGER"))
248             {
249                 qDebug() << query.lastQuery();
250                 qDebug() << query.lastError();
251                 return;
252             }
253             break;
254         }
255 
256         case 4:
257         {
258             if(!query.exec("ALTER TABLE folders ADD COLUMN type INTEGER"))
259             {
260                 qDebug() << query.lastQuery();
261                 qDebug() << query.lastError();
262                 return;
263             }
264 
265             query.prepare("UPDATE folders SET type=:type WHERE icon=:icon");
266             query.bindValue("type", eFolder1);
267             query.bindValue("icon", ":/icons/iconFolderBlue16x16.png");
268             if(!query.exec())
269             {
270                 qDebug() << query.lastQuery();
271                 qDebug() << query.lastError();
272                 return;
273             }
274 
275             query.prepare("UPDATE folders SET type=:type WHERE icon=:icon");
276             query.bindValue(":type", eFolder2);
277             query.bindValue(":icon", ":/icons/iconFolderGreen16x16.png");
278             if(!query.exec())
279             {
280                 qDebug() << query.lastQuery();
281                 qDebug() << query.lastError();
282                 return;
283             }
284 
285             break;
286         }
287 
288         case 5:
289         {
290             if (QFile::exists(path.absoluteFilePath("qlgt_save_v4.db")))
291             {
292                 QFile::remove(path.absoluteFilePath("qlgt_save_v4.db"));
293             }
294             QFile f(path.absoluteFilePath(name));
295             f.copy(path.absoluteFilePath("qlgt_save_v4.db"));
296 
297             QSqlQuery query2(db);
298 
299             if(!query.exec("SELECT id, type, icon FROM items"))
300             {
301                 qDebug() << query.lastQuery();
302                 qDebug() << query.lastError();
303                 return;
304             }
305 
306             const int total = query.size();
307             quint32 progCnt = 0;
308             PROGRESS_SETUP(tr("Migrating database from version 4 to 5."), 0, total, CMainWindow::getBestWidgetForParent());
309 
310             while(query.next())
311             {
312                 QPixmap pixmap;
313 
314                 if(query.value(1).toInt() == eWpt || query.value(1).toInt() == eRte)
315                 {
316                     QPointF focus;
317                     pixmap = CWptIconManager::self().getWptIconByName(query.value(2).toString(), focus);
318                 }
319                 else if(query.value(1).toInt() == eTrk)
320                 {
321                     pixmap = QPixmap(16, 16);
322                     pixmap.fill(query.value(2).toString());
323                 }
324                 else
325                 {
326                     pixmap = QPixmap(query.value(2).toString());
327                 }
328 
329                 QByteArray bytes;
330                 QBuffer buffer(&bytes);
331                 buffer.open(QIODevice::WriteOnly);
332                 pixmap.save(&buffer, "XPM");
333 
334                 query2.prepare("UPDATE items SET icon=:icon WHERE id=:id");
335                 query2.bindValue(":id", query.value(0).toULongLong());
336                 query2.bindValue(":icon", bytes);
337                 if(!query2.exec())
338                 {
339                     qDebug() << query.lastQuery();
340                     qDebug() << query.lastError();
341                     return;
342                 }
343 
344                 PROGRESS(progCnt++, continue);
345             }
346             break;
347         }
348 
349         case 6:
350         {
351             QSqlQuery query2(db);
352 
353             if(!query.exec("SELECT id, data, type FROM items"))
354             {
355                 qDebug() << query.lastQuery();
356                 qDebug() << query.lastError();
357                 return;
358             }
359 
360             const int total = query.size();
361             quint32 progCnt = 0;
362             PROGRESS_SETUP(tr("Migrating database from version 5 to 6."), 0, total, CMainWindow::getBestWidgetForParent());
363 
364             while(query.next())
365             {
366                 QByteArray array = query.value(1).toByteArray();
367                 QBuffer buffer(&array);
368                 CQlb qlb(this);
369                 qlb.load(&buffer);
370 
371                 switch(query.value(2).toInt())
372                 {
373                 case eWpt:
374                     array = qlb.waypoints();
375                     break;
376 
377                 case eTrk:
378                     array = qlb.tracks();
379                     break;
380 
381                 case eRte:
382                     array = qlb.routes();
383                     break;
384 
385                 case eOvl:
386                     array = qlb.overlays();
387                     break;
388                 }
389 
390                 query2.prepare("UPDATE items SET data=:data WHERE id=:id");
391                 query2.bindValue(":data", array);
392                 query2.bindValue(":id", query.value(0));
393 
394                 if(!query2.exec())
395                 {
396                     qDebug() << query.lastQuery();
397                     qDebug() << query.lastError();
398                     return;
399                 }
400 
401                 PROGRESS(progCnt++, continue);
402             }
403 
404             break;
405         }
406 
407         case 7:
408         {
409             QSqlQuery query2(db);
410 
411             if(!query.exec("SELECT id, data, type FROM items"))
412             {
413                 qDebug() << query.lastQuery();
414                 qDebug() << query.lastError();
415                 return;
416             }
417 
418             const int total = query.size();
419             quint32 progCnt = 0;
420             PROGRESS_SETUP(tr("Migrating database from version 6 to 7."), 0, total, CMainWindow::getBestWidgetForParent());
421 
422             while(query.next())
423             {
424                 QString comment;
425                 QByteArray array = query.value(1).toByteArray();
426                 QDataStream stream(&array, QIODevice::ReadOnly);
427                 stream.setVersion(QDataStream::Qt_4_5);
428 
429                 quint64 id = query.value(0).toULongLong();
430 
431                 switch(query.value(2).toInt())
432                 {
433                 case eWpt:
434                 {
435                     CQlgtWpt wpt(id, nullptr);
436                     stream >> wpt;
437                     comment = wpt.getInfo();
438                 }
439                 break;
440 
441                 case eTrk:
442                 {
443                     CQlgtTrack trk(id, nullptr);
444                     stream >> trk;
445                     comment = trk.getInfo();
446                 }
447                 break;
448 
449                 case eRte:
450                 {
451                     CQlgtRoute rte(id, nullptr);
452                     stream >> rte;
453                     comment = rte.getInfo();
454                 }
455                 break;
456 
457                 case eOvl:
458                 {
459                     //                            IOverlay ovl(0);
460                     //                            stream >> ovl;
461                     //                            comment = ovl.getInfo();
462                     continue;
463                 }
464                 break;
465                 }
466 
467                 query2.prepare("UPDATE items SET comment=:comment WHERE id=:id");
468                 query2.bindValue(":comment", comment);
469                 query2.bindValue(":id", query.value(0));
470 
471                 if(!query2.exec())
472                 {
473                     qDebug() << query.lastQuery();
474                     qDebug() << query.lastError();
475                     return;
476                 }
477 
478                 PROGRESS(progCnt++, continue);
479             }
480 
481             break;
482         }
483 
484         case 8:
485         {
486             PROGRESS_SETUP(tr("Migrating database from version 7 to 8."), 0, 1, CMainWindow::getBestWidgetForParent());
487 
488             if(!query.exec( "CREATE TABLE diarys ("
489                             "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
490                             "parent         INTEGER NOT NULL,"
491                             "key            TEXT NOT NULL,"
492                             "date           DATETIME DEFAULT CURRENT_TIMESTAMP,"
493                             "data           BLOB NOT NULL,"
494                             "FOREIGN KEY(parent) REFERENCES folders(id)"
495                             ")"))
496             {
497                 qDebug() << query.lastQuery();
498                 qDebug() << query.lastError();
499                 return;
500             }
501 
502             PROGRESS(1, return );
503             break;
504         }
505 
506         case 9:
507         {
508             if (QFile::exists(path.absoluteFilePath(name)))
509             {
510                 QFile::remove(path.absoluteFilePath("qlgt_save_v8.db"));
511             }
512             QFile f(path.absoluteFilePath(name));
513             f.copy(path.absoluteFilePath("qlgt_save_v4.db"));
514 
515             PROGRESS_SETUP(tr("Migrating database from version 8 to 9."), 0, 1, CMainWindow::getBestWidgetForParent());
516 
517             if(!query.exec("ALTER TABLE folders ADD COLUMN locked BOOLEAN DEFAULT FALSE"))
518             {
519                 qDebug() << query.lastQuery();
520                 qDebug() << query.lastError();
521                 return;
522             }
523 
524             PROGRESS(1, return );
525             break;
526         }
527         }
528     }
529     query.prepare( "UPDATE versioninfo set version=:version");
530     query.bindValue(":version", version - 1);
531     QUERY_EXEC();
532 }
533 
printStatistic()534 void CQlgtDb::printStatistic()
535 {
536     QSqlQuery query(db);
537 
538     gui->stdOut(tr("Open database: %1").arg(db.databaseName()));
539 
540     nItems = 0;
541 
542     query.prepare("SELECT COUNT() FROM folders");
543     QUERY_EXEC();
544     if(query.next())
545     {
546         nFolders = query.value(0).toInt();
547         gui->stdOut(tr("Folders:          %1").arg(nFolders));
548     }
549 
550     query.prepare("SELECT COUNT() FROM items WHERE type=:type");
551     query.bindValue(":type", eTrk);
552     QUERY_EXEC();
553     if(query.next())
554     {
555         nItems += query.value(0).toInt();
556         gui->stdOut(tr("Tracks:           %1").arg(query.value(0).toInt()));
557     }
558     query.prepare("SELECT COUNT() FROM items WHERE type=:type");
559     query.bindValue(":type", eRte);
560     QUERY_EXEC();
561     if(query.next())
562     {
563         nItems += query.value(0).toInt();
564         gui->stdErr(tr("Routes:           %1 (Only the basic route will be copied)").arg(query.value(0).toInt()));
565     }
566     query.prepare("SELECT COUNT() FROM items WHERE type=:type");
567     query.bindValue(":type", eWpt);
568     QUERY_EXEC();
569     if(query.next())
570     {
571         nItems += query.value(0).toInt();
572         gui->stdOut(tr("Waypoints:        %1").arg(query.value(0).toInt()));
573     }
574     query.prepare("SELECT COUNT() FROM items WHERE type=:type");
575     query.bindValue(":type", eOvl);
576     QUERY_EXEC();
577     if(query.next())
578     {
579         nItems += query.value(0).toInt();
580         gui->stdErr(tr("Overlays:         %1 (areas will be converted as areas, distance lines will be converted to tracks, all other overlay items will be lost)").arg(query.value(0).toInt()));
581     }
582     query.prepare("SELECT COUNT() FROM diarys");
583     query.bindValue(":type", eDry);
584     QUERY_EXEC();
585     if(query.next())
586     {
587         gui->stdOut(tr("Diaries:          %1").arg(query.value(0).toInt()));
588     }
589     query.prepare("SELECT COUNT() FROM items WHERE type=:type");
590     query.bindValue(":type", eMap);
591     QUERY_EXEC();
592     if(query.next())
593     {
594         gui->stdErr(tr("Map selections:   %1 (can't be converted to QMapShack)").arg(query.value(0).toInt()));
595     }
596 }
597 
start(const QString & filename)598 void CQlgtDb::start(const QString& filename)
599 {
600     gui->stdOut(tr("------ Start to convert database to %1------").arg(filename));
601     dbQms = new CQmsDb(filename, gui);
602     if(!dbQms->isValid())
603     {
604         gui->stdErr(tr("Failed to create target database."));
605         gui->stdOut(tr("------ Abort ------"));
606         return;
607     }
608 
609 
610     xferItems();
611     xferFolders();
612 
613     QSqlQuery query(db);
614     query.prepare("Select parent, child FROM folder2folder");
615     QUERY_EXEC(return );
616     while(query.next())
617     {
618         quint64 idParent = query.value(0).toULongLong();
619         quint64 idChild = query.value(1).toULongLong();
620         dbQms->addFolder2FolderRelation(idParent, idChild);
621     }
622 
623     query.prepare("Select parent, child FROM folder2item");
624     QUERY_EXEC(return );
625     while(query.next())
626     {
627         quint64 idParent = query.value(0).toULongLong();
628         quint64 idChild = query.value(1).toULongLong();
629         dbQms->addFolder2ItemRelation(idParent, idChild);
630     }
631 
632     delete dbQms;
633     gui->stdOut(tr("------ Done ------"));
634 }
635 
xferFolders()636 void CQlgtDb::xferFolders()
637 {
638     nDiary = 0;
639 
640     quint32 cnt = 1;
641     PROGRESS_SETUP(tr("Restore folders..."), 0, nFolders, gui);
642 
643     QSqlQuery query(db);
644     query.prepare("SELECT id FROM folders");
645     QUERY_EXEC(return );
646     while(query.next())
647     {
648         PROGRESS(cnt++, break);
649 
650         quint64 idFolder = query.value(0).toULongLong();
651 
652         CQlgtFolder folder1(idFolder, db);
653         if(folder1.diary)
654         {
655             nDiary++;
656         }
657 
658         dbQms->addFolder(folder1);
659     }
660     progress.setValue(100);
661     gui->stdOut(tr("Imported %1 folders and %2 diaries").arg(nFolders).arg(nDiary));
662 }
663 
xferItems()664 void CQlgtDb::xferItems()
665 {
666     quint32 cnt = 1;
667     PROGRESS_SETUP(tr("Copy items..."), 0, nItems, gui);
668 
669     nWpt = 0;
670     nTrk = 0;
671     nRte = 0;
672     nOvl = 0;
673 
674 
675     QSqlQuery query(db);
676     query.prepare("SELECT id FROM items");
677     QUERY_EXEC(return );
678     while(query.next())
679     {
680         PROGRESS(cnt++, break);
681         xferItem(query.value(0).toULongLong());
682     }
683     progress.setValue(100);
684 
685     gui->stdOut(tr("Imported %1 tracks, %2 waypoints, %3 routes, %4 areas").arg(nTrk).arg(nWpt).arg(nRte).arg(nOvl));
686     gui->stdOut(tr("Import folders..."));
687 
688     query.prepare("SELECT id FROM folders");
689     QUERY_EXEC(return );
690 }
691 
xferItem(quint64 id)692 void CQlgtDb::xferItem(quint64 id)
693 {
694     QSqlQuery query(db);
695     query.prepare("SELECT type, data FROM items WHERE id=:id");
696     query.bindValue(":id", id);
697     QUERY_EXEC(return );
698 
699     if(query.next())
700     {
701         QByteArray data = query.value(1).toByteArray();
702         QDataStream stream(&data, QIODevice::ReadOnly);
703         stream.setVersion(QDataStream::Qt_4_5);
704 
705 
706         switch(query.value(0).toInt())
707         {
708         case eWpt:
709         {
710             CQlgtWpt wpt1(id, 0);
711             stream >> wpt1;
712             dbQms->addWpt(wpt1);
713             nWpt++;
714             break;
715         }
716 
717         case eTrk:
718         {
719             CQlgtTrack trk1(id, 0);
720             stream >> trk1;
721             dbQms->addTrk(trk1);
722             nTrk++;
723             break;
724         }
725 
726         case eRte:
727         {
728             CQlgtRoute rte1(id, 0);
729             stream >> rte1;
730             dbQms->addRte(rte1);
731             nRte++;
732             break;
733         }
734 
735         case eOvl:
736         {
737             IQlgtOverlay ovl1(id, 0);
738             stream >> ovl1;
739             if(ovl1.type == "Area")
740             {
741                 dbQms->addArea(ovl1);
742                 nOvl++;
743             }
744             else if(ovl1.type == "Distance")
745             {
746                 dbQms->addTrk(ovl1);
747                 nTrk++;
748             }
749             else
750             {
751                 gui->stdErr(tr("Overlay of type '%1' cant be converted").arg(ovl1.type));
752                 nOvl++;
753                 break;
754             }
755 
756 
757             break;
758         }
759         }
760     }
761 }
762