Add model class (DictManagerModel) to manage loaded dictionaries data and settings...
[mdictionary] / src / plugins / xdxf / xdxfplugin.cpp
1 /*******************************************************************************
2
3     This file is part of mDictionary.
4
5     mDictionary 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     mDictionary 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 mDictionary.  If not, see <http://www.gnu.org/licenses/>.
17
18     Copyright 2010 Comarch S.A.
19
20 *******************************************************************************/
21 /*!
22     \file xdxfplugin.cpp
23     \brief Implementation of xdxf plugin's main class.
24
25     \author Jakub Jaszczynski <j.j.jaszczynski@gmail.com>
26 */
27
28 #include "xdxfplugin.h"
29 #include <QDebug>
30 #include "../../include/Notify.h"
31 #include "DownloadDict.h"
32 #include "XdxfDictDownloader.h"
33
34 XdxfDictDownloader XdxfPlugin::dictDownloader;
35
36
37 bool XdxfPlugin::dictDownloaderInitialized = false;
38
39
40 XdxfPlugin::XdxfPlugin(QObject *parent) : CommonDictInterface(parent),
41                     _langFrom(""), _langTo(""),_name(""), _infoNote("") {
42     _settings = new Settings();
43     _dictDialog = new XdxfDictDialog(this, this);
44
45     connect(_dictDialog, SIGNAL(notify(Notify::NotifyType,QString)),
46             this, SIGNAL(notify(Notify::NotifyType,QString)));
47
48     if(!dictDownloaderInitialized) {
49         connect(&dictDownloader, SIGNAL(notify(Notify::NotifyType,QString)),
50                 this, SIGNAL(notify(Notify::NotifyType,QString)));
51         dictDownloaderInitialized = true;
52     }
53
54     _settings->setValue("type","xdxf");
55     _iconPath = "/usr/share/mdictionary/xdxf.png";
56     _icon = QIcon(_iconPath);
57     _wordsCount = -1;
58     stopped = false;
59
60     initAccents();
61 }
62
63
64 void XdxfPlugin::retranslate() {
65     QString locale = QLocale::system().name();
66     QTranslator *translator = new QTranslator(this);
67
68     if(!translator->load(":/xdxf/translations/" + locale)) {
69         translator->load(":/xdxf/translations/en_US");
70     }
71     QCoreApplication::installTranslator(translator);
72 }
73
74
75 XdxfPlugin::~XdxfPlugin() {
76     delete _settings;
77     delete _dictDialog;
78 }
79
80
81 QString XdxfPlugin::langFrom() const {   
82     return _langFrom;
83 }
84
85
86 QString XdxfPlugin::langTo() const {
87     return  _langTo;
88 }
89
90
91 QString XdxfPlugin::name() const {
92     return  _name;
93 }
94
95
96 QString XdxfPlugin::type() const {
97     return QString("xdxf");
98 }
99
100
101 QString XdxfPlugin::infoNote() const {
102     return _infoNote;
103 }
104
105
106 QList<Translation*> XdxfPlugin::searchWordList(QString word, int limit) {
107     if( word.indexOf("*")==-1 && word.indexOf("?")==-1 &&
108         word.indexOf("_")==-1 && word.indexOf("%")==-1)
109         word+="*";
110
111     if(isCached())
112         return searchWordListCache(word,limit);
113     return searchWordListFile(word, limit);
114 }
115
116
117 QList<Translation*> XdxfPlugin::searchWordListCache(QString word, int limit) {
118     QSet<Translation*> translations;
119     QString cacheFilePath = _settings->value("cache_path");
120
121     db.setDatabaseName(cacheFilePath);
122     if(!QFile::exists(cacheFilePath) || !db.open()) {
123         qDebug() << "Database error" << db.lastError().text() << endl;
124         Q_EMIT notify(Notify::Warning, QString(tr("Cache database cannot be "
125                 "opened for %1 dictionary. Searching in XDXF file. "
126                 "You may want to recache.").arg(name())));
127         _settings->setValue("cached","false");
128         return searchWordListFile(word, limit);
129     }
130
131     stopped = false;
132     word = word.toLower();
133     word = word.replace("*", "%");
134     word = word.replace("?", "_");
135
136     QSqlQuery cur(db);
137     if(limit !=0)
138         cur.prepare("select word from dict where word like ? or normalized "
139                     "like ? limit ?");
140     else
141         cur.prepare("select word from dict where word like ? or normalized "
142                     "like ?");
143     cur.addBindValue(word);
144     cur.addBindValue(word);
145     if(limit !=0)
146         cur.addBindValue(limit);
147     cur.exec();
148
149     while(cur.next() && (translations.size()<limit || limit==0)) {
150        translations.insert(new TranslationXdxf(
151             cur.value(0).toString(),
152             _dictionaryInfo, this));
153     }
154     db.close();
155     return translations.toList();
156 }
157
158
159 QList<Translation*> XdxfPlugin::searchWordListFile(QString word, int limit) {
160     QSet<Translation*> translations;
161     QFile dictionaryFile(_settings->value("path"));
162     word = word.toLower();
163     stopped = false;
164
165     QRegExp regWord(word);
166     regWord.setCaseSensitivity(Qt::CaseInsensitive);
167     regWord.setPatternSyntax(QRegExp::Wildcard);
168
169     /*check xdxf file exist*/
170     if(!QFile::exists(_settings->value("path"))
171                 || !dictionaryFile.open(QFile::ReadOnly | QFile::Text)) {
172         qDebug()<<"Error: could not open file";
173         Q_EMIT notify(Notify::Warning,
174                 QString(tr("XDXF file cannot be read for %1").arg(name())));
175         return translations.toList();
176     }
177
178     QXmlStreamReader reader(&dictionaryFile);
179     QString readKey;
180     int i=0;
181
182     /*search words list*/
183     while(!reader.atEnd() && !stopped){
184         reader.readNextStartElement();
185         if(reader.name()=="ar") {
186             while(reader.name()!="k" && !reader.atEnd())
187                 reader.readNextStartElement();
188             if(!reader.atEnd())
189                 readKey = reader.readElementText();
190             if((regWord.exactMatch(readKey)
191                     || regWord.exactMatch(removeAccents(readKey)))
192                     && (i<limit || limit==0) && !reader.atEnd())  {
193                 translations<<(new TranslationXdxf(readKey.toLower(),
194                                _dictionaryInfo,this));
195                 if(translations.size()==limit && limit!=0)
196                     break;
197             }
198         }
199         this->thread()->yieldCurrentThread();
200     }
201     stopped=false;
202     dictionaryFile.close();
203     return translations.toList();
204 }
205
206
207 QString XdxfPlugin::search(QString key) {
208     if(isCached())
209         return searchCache(key);
210     return searchFile(key);
211 }
212
213
214 QString XdxfPlugin::searchCache(QString key) {
215     QString result("");
216     QString cacheFilePath = _settings->value("cache_path");
217     db.setDatabaseName(cacheFilePath);
218     key = key.toLower();
219
220     if(!QFile::exists(cacheFilePath) || !db.open()) {
221         qDebug() << "Database error" << db.lastError().text() << endl;
222         Q_EMIT notify(Notify::Warning, QString(tr("Cache database cannot be "
223                 "opened for %1 dictionary. Searching in XDXF file. "
224                 "You may want to recache.").arg(name())));
225         _settings->setValue("cached","false");
226         return searchFile(key);
227     }
228
229     QSqlQuery cur(db);
230
231     cur.prepare("select translation from dict where word like ?");
232     cur.addBindValue(key);
233     cur.exec();
234     while(cur.next())
235         result += cur.value(0).toString();
236     db.close();
237
238     return result;
239 }
240
241
242 QString XdxfPlugin::searchFile(QString key) {
243     QFile dictionaryFile(_settings->value("path"));
244     QString resultString("");
245     key = key.toLower();
246
247     /*check xdxf file exist*/
248     if(!QFile::exists(_settings->value("path"))
249                 || !dictionaryFile.open(QFile::ReadOnly | QFile::Text)) {
250         Q_EMIT notify(Notify::Warning,
251                 QString(tr("XDXF file cannot be read for %1").arg(name())));
252         qDebug()<<"Error: could not open file";
253         return "";
254     }
255
256     QXmlStreamReader reader(&dictionaryFile);
257     QString readKey;
258     bool match =false;
259     stopped = false;
260
261     /*search translations for word*/
262     while (!reader.atEnd()&& !stopped) {
263         reader.readNext();
264         if(reader.tokenType() == QXmlStreamReader::StartElement) {
265             if(reader.name()=="k") {
266                 readKey = reader.readElementText();
267                 if(readKey.toLower()==key.toLower())
268                     match = true;
269             }
270         }
271         if(match) {
272             QString temp("");
273             while(reader.name()!="ar" && !reader.atEnd()) {
274                 if(reader.name()!="" && reader.name()!="k") {
275                     if(reader.tokenType()==QXmlStreamReader::EndElement)
276                         temp+="</";
277                     if(reader.tokenType()==QXmlStreamReader::StartElement)
278                         temp+="<";
279                     temp+=reader.name().toString();
280                     if(reader.name().toString()=="c" &&
281                             reader.tokenType()==QXmlStreamReader::StartElement)
282                        temp= temp + " c=\"" + reader.attributes().
283                                value("c").toString() + "\"";
284                     temp+=">";
285                 }
286                 temp+= reader.text().toString().replace("<","&lt;").
287                         replace(">","&gt;");
288                 reader.readNext();
289             }
290             if(temp.at(0)==QChar('\n'))
291                 temp.remove(0,1);
292             resultString+="<key>" + readKey +"</key>";
293             resultString+="<t>" + temp + "</t>";
294             match=false;
295         }
296         this->thread()->yieldCurrentThread();
297     }
298     stopped=false;
299     dictionaryFile.close();
300     return resultString;
301 }
302
303
304 void XdxfPlugin::stop() {
305     stopped=true;
306 }
307
308
309 DictDialog* XdxfPlugin::dictDialog() {
310      return _dictDialog;
311 }
312
313
314 CommonDictInterface* XdxfPlugin::getNew(const Settings *settings) const {
315     XdxfPlugin *plugin = new XdxfPlugin();
316
317     connect(plugin, SIGNAL(notify(Notify::NotifyType,QString)),
318             this, SIGNAL(notify(Notify::NotifyType,QString)));
319
320     ((XdxfDictDialog*)plugin->dictDialog())->setLastDialogParent(_dictDialog->lastDialogParent());
321
322     if(settings && plugin->setSettings(settings)) {
323         disconnect(plugin, SIGNAL(notify(Notify::NotifyType,QString)),
324                 this, SIGNAL(notify(Notify::NotifyType,QString)));
325         return plugin;
326     }
327     else {
328         disconnect(plugin, SIGNAL(notify(Notify::NotifyType,QString)),
329                 this, SIGNAL(notify(Notify::NotifyType,QString)));
330         delete plugin;
331         return 0;
332     }
333 }
334
335
336 bool XdxfPlugin::isAvailable() const {
337     return true;
338 }
339
340
341 Settings* XdxfPlugin::settings() {
342     return _settings;
343 }
344
345
346 bool XdxfPlugin::isCached() {
347     if(_settings->value("cached") == "true")
348         return true;
349     return false;
350 }
351
352
353 bool XdxfPlugin::setSettings(const Settings *settings) {
354     if(settings) {
355         bool isPathChange=false;
356         QString oldPath = _settings->value("path");
357         Settings *oldSettings =  new Settings ;
358
359         if(oldPath != settings->value("path")) {
360             if(oldPath!="" && _settings->value("cache_path")!="")
361                 clean();
362             isPathChange=true;
363         }
364
365         foreach(QString key, _settings->keys())
366             oldSettings->setValue(key, _settings->value(key));
367
368         foreach(QString key, settings->keys())
369            if(key != "generateCache")
370                _settings->setValue(key, settings->value(key));
371
372         if(!getDictionaryInfo()) {
373             Q_EMIT notify(Notify::Warning,
374                 QString(tr("XDXF file is in wrong format")));
375             qDebug()<<"Error: xdxf file is in wrong format";
376             delete _settings;
377             _settings=oldSettings;
378             return false;
379         }
380
381         if(isPathChange) {
382             _wordsCount=0;
383             if(oldPath!="")
384                 _settings->setValue("cached","false");
385             if(_settings->value("cached")=="true"
386                     && _settings->value("cache_path")!="") {
387                 db_name = _settings->value("type")
388                         + _settings->value("cache_path");
389                 db = QSqlDatabase::addDatabase("QSQLITE",db_name);
390             }
391         }
392
393         if((_settings->value("cached") == "false" ||
394             _settings->value("cached").isEmpty()) &&
395             settings->value("generateCache") == "true") {
396             clean();
397             makeCache("");
398         }
399
400         else if (settings->value("generateCache") == "false") {
401             _settings->setValue("cached", "false");
402         }
403     }
404     else
405         return false;
406     Q_EMIT settingsChanged();
407     return true;
408 }
409
410
411 bool XdxfPlugin::getDictionaryInfo() {
412     QFile dictionaryFile(_settings->value("path"));
413     if(!QFile::exists(_settings->value("path"))
414                 || !dictionaryFile.open(QFile::ReadOnly | QFile::Text)) {
415        Q_EMIT notify(Notify::Warning,
416                QString(tr("XDXF dictionary cannot be read from file")));
417         qDebug()<<"Error: could not open file";
418         return false;
419     }
420
421     bool okFormat=false;
422     QXmlStreamReader reader(&dictionaryFile);
423     reader.readNextStartElement();
424     if(reader.name()=="xdxf") {
425         okFormat=true;
426         if(reader.attributes().hasAttribute("lang_from"))
427             _langFrom = reader.attributes().value("lang_from").toString();
428         if(reader.attributes().hasAttribute("lang_to"))
429             _langTo = reader.attributes().value("lang_to").toString();
430     }
431     reader.readNextStartElement();
432     if(reader.name()=="full_name")
433         _name=reader.readElementText();
434     else
435         qDebug()<<"no full_name";
436     reader.readNextStartElement();
437     if(reader.name()=="description")
438         _infoNote=reader.readElementText();
439     else
440         qDebug()<<"no description";
441
442     _dictionaryInfo= _name + " [" + _langFrom + "-"
443                 + _langTo + "]";
444
445     dictionaryFile.close();
446     if(okFormat)
447         return true;
448     return false;
449 }
450
451
452 QIcon* XdxfPlugin::icon() {
453     return &_icon;
454 }
455
456 QString XdxfPlugin::iconPath(){
457     return _iconPath;
458 }
459
460
461 int XdxfPlugin::countWords() {
462     if(_wordsCount>0)
463         return _wordsCount;
464     QFile dictionaryFile(_settings->value("path"));
465     if(!QFile::exists(_settings->value("path"))
466                 || !dictionaryFile.open(QFile::ReadOnly | QFile::Text)) {
467         Q_EMIT notify(Notify::Warning,
468                 QString(tr("XDXF file cannot be read for %1 dictionary")
469                 .arg(name())));
470         qDebug()<<"Error: could not open file";
471         return -1;
472     }
473
474     dictionaryFile.seek(0);
475     long wordsCount = 0;
476
477     QString line;
478     while(!dictionaryFile.atEnd()) {
479         line = dictionaryFile.readLine();
480         if(line.contains("<k>")) {
481             wordsCount++;
482         }
483     }
484     _wordsCount = wordsCount;
485     dictionaryFile.close();
486     return wordsCount;
487 }
488
489
490 bool XdxfPlugin::makeCache(QString) {
491     XdxfCachingDialog d(_dictDialog->lastDialogParent());
492
493     connect(&d, SIGNAL(cancelCaching()),
494             this, SLOT(stop()));
495     connect(this, SIGNAL(updateCachingProgress(int,int)),
496             &d, SLOT(updateCachingProgress(int,int)));
497
498     d.show();
499     QCoreApplication::processEvents();
500     QFileInfo dictFileN(_settings->value("path"));
501     QString cachePathN;
502     stopped = false;
503
504     /*create cache file name*/
505     int i=0;
506     do {
507         cachePathN = QDir::homePath() + "/.mdictionary/"
508                                       + dictFileN.completeBaseName()+"."
509                                       +QString::number(i) + ".cache";
510         i++;
511     } while(QFile::exists(cachePathN));
512
513     db_name = _settings->value("type") + cachePathN;
514     db = QSqlDatabase::addDatabase("QSQLITE",db_name);
515
516     /*checke errors (File open and db open)*/
517     QFile dictionaryFile(dictFileN.filePath());
518     if (!QFile::exists(_settings->value("path"))
519                 || !dictionaryFile.open(QFile::ReadOnly | QFile::Text)) {
520         Q_EMIT updateCachingProgress(100, 0);
521         Q_EMIT notify(Notify::Warning,
522                 QString(tr("XDXF file cannot be read for %1 dictionary")
523                 .arg(name())));
524         return 0;
525     }
526     QXmlStreamReader reader(&dictionaryFile);
527     db.setDatabaseName(cachePathN);
528     if(!db.open()) {
529         qDebug() << "Database error" << db.lastError().text() << endl;
530         Q_EMIT updateCachingProgress(100, 0);
531         Q_EMIT notify(Notify::Warning, QString(tr("Cache database cannot be "
532                 "opened for %1 dictionary. Searching in XDXF file. "
533                 "You may want to recache.").arg(name())));
534         return false;
535     }
536
537     /*inicial sqlQuery*/
538     QCoreApplication::processEvents();
539     QSqlQuery cur(db);
540     cur.exec("PRAGMA synchronous = 0");
541     cur.exec("drop table dict");
542     QCoreApplication::processEvents();
543     cur.exec("create table dict(word text, normalized text ,translation text)");
544     int counter = 0;
545     cur.exec("BEGIN;");
546
547     QString readKey;
548     bool match = false;
549     QTime timer;
550     timer.start();
551     countWords();
552     int lastProg = -1;
553     _settings->setValue("strip_accents", "true");
554     counter=0;
555
556     /*add all words to db*/
557     while (!reader.atEnd() && !stopped) {
558         QCoreApplication::processEvents();
559         reader.readNext();
560         if(reader.tokenType() == QXmlStreamReader::StartElement) {
561             if(reader.name()=="k"){
562                 readKey = reader.readElementText();
563                 match = true;
564             }
565         }
566         if(match) {
567             QString temp("");
568             while(reader.name()!="ar" && !reader.atEnd()) {
569                 if(reader.name()!="" && reader.name()!="k") {
570                     if(reader.tokenType()==QXmlStreamReader::EndElement)
571                         temp+="</";
572                     if(reader.tokenType()==QXmlStreamReader::StartElement)
573                         temp+="<";
574                     temp+=reader.name().toString();
575                     if(reader.name().toString()=="c"
576                         && reader.tokenType()==QXmlStreamReader::StartElement) {
577                         temp= temp + " c=\""
578                                    + reader.attributes().value("c").toString()
579                                    + "\"";
580                     }
581                     temp+=">";
582                 }
583                 temp+= reader.text().toString().replace("<","&lt;").replace(">"
584                               ,"&gt;");
585                 reader.readNext();
586             }
587             if(temp.at(0)==QChar('\n'))
588                 temp.remove(0,1);
589             temp="<key>" + readKey + "</key>" + "<t>" + temp+ "</t>";
590             match=false;
591             cur.prepare("insert into dict values(?,?,?)");
592             cur.addBindValue(readKey.toLower());
593             cur.addBindValue(removeAccents(readKey).toLower());
594             cur.addBindValue(temp);
595             cur.exec();
596             counter++;
597             int prog = counter*100/_wordsCount;
598             if(prog % 2 == 0 && lastProg != prog) {
599                 Q_EMIT updateCachingProgress(prog,timer.restart());
600                 lastProg = prog;
601             }
602         }
603     }
604     cur.exec("END;");
605     cur.exec("select count(*) from dict");
606
607     /*checke errors (wrong number of added words)*/
608     countWords();
609     if(!cur.next() || countWords() != cur.value(0).toInt()) {
610         Q_EMIT updateCachingProgress(100, timer.restart());
611         Q_EMIT notify(Notify::Warning,
612                 QString(tr("Database caching error, please try again.")));
613         db.close();
614         _settings->setValue("cache_path", cachePathN);
615         if(stopped)
616             clean();
617         _settings->setValue("cache_path","");
618         return false;
619     }
620
621     _settings->setValue("cache_path", cachePathN);
622     _settings->setValue("cached", "true");
623
624     disconnect(&d, SIGNAL(cancelCaching()),
625             this, SLOT(stop()));
626     disconnect(this, SIGNAL(updateCachingProgress(int,int)),
627             &d, SLOT(updateCachingProgress(int,int)));
628     db.close();
629     return true;
630 }
631
632
633 void XdxfPlugin::clean() {
634     if(QFile::exists(_settings->value("cache_path"))) {
635         QFile(_settings->value("cache_path")).remove();
636         QSqlDatabase::removeDatabase(db_name);
637     }
638 }
639
640
641 Q_EXPORT_PLUGIN2(xdxf, XdxfPlugin)