Added exception and event handling: gui & plugin
[mdictionary] / trunk / src / plugins / xdxf / src / 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 */
24
25 #include "xdxfplugin.h"
26 #include <QDebug>
27 #include <QFile>
28 #include <QXmlStreamReader>
29 #include <QtPlugin>
30 #include "TranslationXdxf.h"
31 #include "../../../includes/settings.h"
32 #include "../../../includes/Notify.h"
33
34 XdxfPlugin::XdxfPlugin(QObject *parent) : CommonDictInterface(parent),
35                     _langFrom(tr("")), _langTo(tr("")),_name(tr("")),
36                     _type(tr("xdxf")), _infoNote(tr("")) {
37     _wordsCount = -1;
38     _settings = new Settings();
39     _dictDialog = new XdxfDictDialog(this, this);
40     cachingDialog = new XdxfCachingDialog(this);
41
42     connect(cachingDialog, SIGNAL(cancelCaching()),
43             this, SLOT(stop()));
44
45     _settings->setValue("type","xdxf");
46
47     stopped = false;
48
49     _icon = QIcon(":/icons/xdxf.png");
50 }
51
52 XdxfPlugin::~XdxfPlugin()
53 {
54     db.removeDatabase(_type+path);
55 }
56
57 QString XdxfPlugin::langFrom() const {   
58     return _langFrom;
59 }
60
61 QString XdxfPlugin::langTo() const {
62     return  _langTo;
63 }
64
65 QString XdxfPlugin::name() const {
66     return  _name;
67 }
68
69 QString XdxfPlugin::type() const {
70 //    return _settings->value("type");
71     return _type;
72 }
73
74 QString XdxfPlugin::infoNote() const {
75     return  _infoNote;
76 }
77
78 QList<Translation*> XdxfPlugin::searchWordList(QString word, int limit) {
79     if(word.indexOf("*")==-1 && word.indexOf("?")==-1 && word.indexOf("_")==-1
80        && word.indexOf("%")==-1)
81         word+="*";
82     if(isCached())
83         return searchWordListCache(word,limit);
84     return searchWordListFile(word, limit);
85 }
86
87 QList<Translation*> XdxfPlugin::searchWordListCache(QString word, int limit) {
88
89     QSet<Translation*> translations;
90     QString cacheFilePath = _settings->value("cache_path");
91         db.setDatabaseName(cacheFilePath);
92         if(!db.open()) {
93             qDebug() << "Database error" << db.lastError().text() << endl;
94             Q_EMIT notify(Notify::Warning, QString("Cache database cannot be "
95                     "opened for %1 dictionary. Searching in xdxf file. "
96                     "You may want to recache.").arg(name()));
97             return searchWordListFile(word, limit);
98         }
99
100         stopped = false;
101         word = word.toLower();
102         word = word.replace("*", "%");
103         word = word.replace("?", "_");
104         word = removeAccents(word);
105
106         QSqlQuery cur(db);
107         if(limit !=0)
108             cur.prepare("select word from dict where word like ? limit ?");
109         else
110             cur.prepare("select word from dict where word like ?");
111         cur.addBindValue(word);
112         if(limit !=0)
113             cur.addBindValue(limit);
114         cur.exec();
115         while(cur.next()){
116             bool ok=true;
117             Translation *tran;
118             foreach(tran,translations) {
119                 if(tran->key().toLower()==cur.value(0).toString().toLower())
120                         ok=false;
121             }
122             if(ok)  /*add key word to list*/
123                 translations.insert(new TranslationXdxf(
124                         cur.value(0).toString().toLower(),
125                         _infoNote, this));
126         }
127         db.close();
128         return translations.toList();
129 }
130
131 QList<Translation*> XdxfPlugin::searchWordListFile(QString word, int limit) {
132     QSet<Translation*> translations;
133     QFile dictionaryFile(path);
134
135     word = word.toLower();
136     word = removeAccents(word);
137
138     stopped = false;
139     QRegExp regWord(word);
140     regWord.setCaseSensitivity(Qt::CaseInsensitive);
141     regWord.setPatternSyntax(QRegExp::Wildcard);
142     if(!dictionaryFile.open(QFile::ReadOnly | QFile::Text)) {
143         qDebug()<<"Error: could not open file";
144         Q_EMIT notify(Notify::Warning,
145                 QString("Xdxf file cannot be read for %1").arg(name()));
146         return translations.toList();
147     }
148
149     QXmlStreamReader reader(&dictionaryFile);
150     /*search words list*/
151     QString a;
152     int i=0;
153     while(!reader.atEnd() && !stopped){
154         reader.readNextStartElement();
155         if(reader.name()=="ar") {
156             while(reader.name()!="k" && !reader.atEnd())
157                 reader.readNextStartElement();
158             if(!reader.atEnd())
159                 a = reader.readElementText();
160             if(regWord.exactMatch(removeAccents(a)) && (i<limit || limit==0)) {
161                 bool ok=true;
162                 Translation *tran;
163                 foreach(tran,translations) {
164                     if(tran->key().toLower()==a.toLower())
165                         ok=false;  /*if key word is in the dictionary more that one */
166                 }
167                 if(ok)  /*add key word to list*/
168                     translations<<(new TranslationXdxf(a.toLower(),
169                                 _infoNote,this));
170                 i++;
171                 if(i>=limit && limit!=0)
172                     break;
173             }
174         }
175         this->thread()->yieldCurrentThread();
176     }
177     stopped=false;
178     dictionaryFile.close();
179     return translations.toList();
180 }
181
182 QString XdxfPlugin::search(QString key) {
183 //    if(_settings->value("cached") == "true")
184     if(isCached())
185         return searchCache(key);
186     return searchFile(key);
187 }
188
189 QString XdxfPlugin::searchCache(QString key) {
190     QString result("");
191     QString cacheFilePath = _settings->value("cache_path");
192     db.setDatabaseName(cacheFilePath);
193     key = key.toLower();
194
195     if(!db.open()) {
196         qDebug() << "Database error" << db.lastError().text() << endl;
197         Q_EMIT notify(Notify::Warning, QString("Cache database cannot be "
198                 "opened for %1 dictionary. Searching in xdxf file. "
199                 "You may want to recache.").arg(name()));
200         return searchFile(key);
201     }
202
203     QSqlQuery cur(db);
204     cur.prepare("select translation from dict where word like ?");
205     cur.addBindValue(key);
206     cur.exec();
207     while(cur.next())
208         result += cur.value(0).toString();
209     db.close();
210     return result;
211
212 }
213
214 QString XdxfPlugin::searchFile(QString key) {
215     key = key.toLower();
216     QFile dictionaryFile(path);
217     QString resultString("");
218     if(!dictionaryFile.open(QFile::ReadOnly | QFile::Text)) {
219         Q_EMIT notify(Notify::Warning,
220                 QString("Xdxf file cannot be read for %1").arg(name()));
221         qDebug()<<"Error: could not open file";
222         return "";
223     }
224     QXmlStreamReader reader(&dictionaryFile);
225
226
227
228     QString a;
229
230     bool match =false;
231     stopped = false;
232     while (!reader.atEnd()&& !stopped) {
233         reader.readNext();
234         if(reader.tokenType() == QXmlStreamReader::StartElement) {
235             if(reader.name()=="k") {
236                 a = reader.readElementText();
237                 if(a.toLower()==key.toLower())
238                     match = true;
239             }
240         }
241         if(match) {
242             QString temp("");
243             while(reader.name()!="ar" && !reader.atEnd()) {
244                 if(reader.name()!="" && reader.name()!="k") {
245                     if(reader.tokenType()==QXmlStreamReader::EndElement)
246                         temp+=tr("</");
247                     if(reader.tokenType()==QXmlStreamReader::StartElement)
248                         temp+=tr("<");
249                     temp+=reader.name().toString();
250                     if(reader.name().toString()=="c" && reader.tokenType()==QXmlStreamReader::StartElement)
251                        temp= temp + tr(" c=\"") + reader.attributes().value(tr("c")).toString() + tr("\"");
252                     temp+=tr(">");
253                 }
254                 temp+= reader.text().toString().replace("<","&lt;").replace(">","&gt;");
255                 reader.readNext();
256             }
257             if(temp.at(0)==QChar('\n'))
258                 temp.remove(0,1);
259             resultString+=tr("<key>") + a +tr("</key>");
260             resultString+=tr("<t>") + temp + tr("</t>");
261             match=false;
262         }
263         this->thread()->yieldCurrentThread();
264     }
265     stopped=false;
266     dictionaryFile.close();
267
268     return resultString;
269 }
270
271 void XdxfPlugin::stop() {
272     stopped=true;
273 }
274
275 DictDialog* XdxfPlugin::dictDialog() {
276      return _dictDialog;
277 }
278
279 void XdxfPlugin::setPath(QString path){
280     this->path=path;
281     _settings->setValue("path",path);
282     //getDictionaryInfo();
283 }
284
285 CommonDictInterface* XdxfPlugin::getNew(const Settings *settings) const {
286     XdxfPlugin *plugin = new XdxfPlugin();
287     static int a=0;
288     if(settings){
289         plugin->setPath(settings->value("path"));
290         QStringList list = settings->keys();
291         foreach(QString key, list)
292             plugin->settings()->setValue(key, settings->value(key));
293
294         a=a+1;
295         plugin->db_name = plugin->_settings->value("type")
296                + plugin->_settings->value("path");
297 //        if(!plugin->db.connectionName().isEmpty() || settings->value("generateCache")=="true")
298             plugin->db = QSqlDatabase::addDatabase("QSQLITE", plugin->db_name);
299
300         if(settings->value("cached").isEmpty() &&
301            settings->value("generateCache") == "true") {
302             plugin->makeCache("");
303         }
304         delete settings;
305     }
306     plugin->getDictionaryInfo();
307     return  plugin;
308 }
309
310 bool XdxfPlugin::isAvailable() const {
311     return true;
312 }
313
314 void XdxfPlugin::setHash(uint _hash) {
315     this->_hash=_hash;
316 }
317
318 uint XdxfPlugin::hash() const {
319    return _hash;
320 }
321
322 Settings* XdxfPlugin::settings() {
323     return _settings;
324 }
325
326 bool XdxfPlugin::isCached() {
327     if(_settings->value("cached") == "true")
328         return true;
329     return false;
330 }
331
332 void XdxfPlugin::setSettings(Settings *settings) {
333
334     QString oldPath = _settings->value("path");
335     if(oldPath != settings->value("path")) {
336         setPath(settings->value("path"));
337     }
338
339     if((_settings->value("cached") == "false" ||
340         _settings->value("cached").isEmpty()) &&
341        settings->value("generateCache") == "true") {
342         makeCache("");
343     }
344     else {
345        _settings->setValue("cached", "false");
346     }
347     delete settings;
348
349     emit settingsChanged();
350 }
351
352 void XdxfPlugin::getDictionaryInfo() {
353     QFile dictionaryFile(path);
354     if(!dictionaryFile.open(QFile::ReadOnly | QFile::Text)) {
355        Q_EMIT notify(Notify::Warning,
356                QString("Xdxf file cannot be read dictionary"));
357         qDebug()<<"Error: could not open file";
358         return;
359     }
360
361     QXmlStreamReader reader(&dictionaryFile);
362     reader.readNextStartElement();
363     if(reader.name()=="xdxf") {
364       if(reader.attributes().hasAttribute("lang_from"))
365         _langFrom = reader.attributes().value("lang_from").toString();
366       if(reader.attributes().hasAttribute("lang_to"))
367         _langTo = reader.attributes().value("lang_to").toString();
368     }
369     reader.readNextStartElement();
370     if(reader.name()=="full_name")
371         _name=reader.readElementText();
372     reader.readNextStartElement();
373     if(reader.name()=="description")
374         _infoNote=reader.readElementText();
375
376     QString format = "png";
377     QString initialPath = QDir::currentPath() + tr("/xdxf.") + format;
378
379     _infoNote="<info path=\""+initialPath+"\">"+"\n" + _name + " [" + _langFrom + "-" + _langTo + "] "+ "(" + _type + ")"  + "</info>";
380
381
382
383     dictionaryFile.close();
384 }
385
386 QString XdxfPlugin::removeAccents(QString string) {
387     string = string.replace(QString::fromUtf8("ł"), "l", Qt::CaseInsensitive);
388     QString normalized = string.normalized(QString::NormalizationForm_D);
389     normalized = normalized;
390     for(int i=0; i<normalized.size(); i++) {
391         if( !normalized[i].isLetterOrNumber() &&
392             !normalized[i].isSpace() &&
393             !normalized[i].isDigit() &&
394             normalized[i] != '*' &&
395             normalized[i] != '%' &&
396             normalized[i] != '_' &&
397             normalized[i] != '?' ) {
398             normalized.remove(i,1);
399         }
400     }
401     return normalized;
402 }
403
404 QIcon* XdxfPlugin::icon() {
405     return &_icon;
406 }
407
408 int XdxfPlugin::countWords() {
409     if(_wordsCount > 0)
410         return _wordsCount;
411
412     QFile dictionaryFile(path);
413     if(!dictionaryFile.open(QFile::ReadOnly | QFile::Text)) {
414         Q_EMIT notify(Notify::Warning,
415                 QString("Xdxf file cannot be read for %1 dictionary")
416                 .arg(name()));
417         qDebug()<<"Error: could not open file";
418         return -1;
419     }
420
421     dictionaryFile.seek(0);
422
423     long wordsCount = 0;
424
425     QString line;
426     while(!dictionaryFile.atEnd()) {
427         line = dictionaryFile.readLine();
428         if(line.contains("<k>")) {
429             wordsCount++;
430         }
431     }
432     _wordsCount = wordsCount;
433     dictionaryFile.close();
434     return wordsCount;
435 }
436
437 bool XdxfPlugin::makeCache(QString dir) {
438     cachingDialog->setVisible(true);
439     QCoreApplication::processEvents();
440     stopped = false;
441     QFileInfo dictFileN(_settings->value("path"));
442     QString cachePathN;
443     cachePathN = QDir::homePath() + "/.mdictionary/"
444                  + dictFileN.completeBaseName() + ".cache";
445
446     QFile dictionaryFile(dictFileN.filePath());
447
448
449     if (!dictionaryFile.open(QFile::ReadOnly | QFile::Text)) {
450         Q_EMIT updateCachingProgress(100, 0);
451         Q_EMIT notify(Notify::Warning,
452                 QString("Xdxf file cannot be read for %1 dictionary")
453                 .arg(name()));
454         return 0;
455     }
456
457     QXmlStreamReader reader(&dictionaryFile);
458
459     db.setDatabaseName(cachePathN);
460     if(!db.open()) {
461         qDebug() << "Database error" << db.lastError().text() << endl;
462         Q_EMIT updateCachingProgress(100, 0);
463         Q_EMIT notify(Notify::Warning, QString("Cache database cannot be "
464                 "opened for %1 dictionary. Searching in xdxf file. "
465                 "You may want to recache.").arg(name()));
466         return false;
467     }
468     QCoreApplication::processEvents();
469     QSqlQuery cur(db);
470     cur.exec("PRAGMA synchronous = 0");
471     cur.exec("drop table dict");
472     QCoreApplication::processEvents();
473     cur.exec("create table dict(word text ,translation text)");
474     int counter = 0;
475     cur.exec("BEGIN;");
476
477     QString a;
478     bool match = false;
479     QTime timer;
480     timer.start();
481     countWords();
482
483     int lastProg = -1;
484
485
486     counter=0;
487     while (!reader.atEnd() && !stopped) {
488
489         QCoreApplication::processEvents();
490         reader.readNext();
491
492         if(reader.tokenType() == QXmlStreamReader::StartElement) {
493             if(reader.name()=="k"){
494                 a = reader.readElementText();
495                 match = true;
496             }
497         }
498         if(match) {
499             QString temp("");
500             while(reader.name()!="ar" && !reader.atEnd()) {
501                 if(reader.name()!="" && reader.name()!="k") {
502                     if(reader.tokenType()==QXmlStreamReader::EndElement)
503                         temp+=tr("</");
504                     if(reader.tokenType()==QXmlStreamReader::StartElement)
505                         temp+=tr("<");
506                     temp+=reader.name().toString();
507                     if(reader.name().toString()=="c" && reader.tokenType()==QXmlStreamReader::StartElement)
508                        temp= temp + tr(" c=\"") + reader.attributes().value(tr("c")).toString() + tr("\"");
509                     temp+=tr(">");
510                 }
511                 temp+= reader.text().toString().replace("<","&lt;").replace(">","&gt;");;
512                 reader.readNext();
513             }
514             if(temp.at(0)==QChar('\n'))
515                 temp.remove(0,1);
516             temp=tr("<key>") + a + tr("</key>") + tr("<t>") + temp+ tr("</t>");
517             match=false;
518             cur.prepare("insert into dict values(?,?)");
519             cur.addBindValue(a);
520             cur.addBindValue(temp);
521             cur.exec();
522             counter++;
523             int prog = counter*100/_wordsCount;
524             if(prog % 5 == 0 && lastProg != prog) {
525                 Q_EMIT updateCachingProgress(prog,
526                                              timer.restart());
527                 lastProg = prog;
528             }
529         }
530     }
531
532     cur.exec("END;");
533     cur.exec("select count(*) from dict");
534
535     countWords();
536     cachingDialog->setVisible(false);
537
538     if(!cur.next() || countWords() != cur.value(0).toInt())
539     {
540         Q_EMIT updateCachingProgress(100, timer.restart());
541         Q_EMIT notify(Notify::Warning,
542                 QString("Database caching error, please try againg."));
543         db.close();
544         return false;
545     }
546     _settings->setValue("cache_path", cachePathN);
547     _settings->setValue("cached", "true");
548
549     db.close();
550     return true;
551 }
552
553 Q_EXPORT_PLUGIN2(xdxf, XdxfPlugin)