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