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