Merge branch 'master' into qmake
[mdictionary] / src / mdictionary / backbone / backbone.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 /*! \file backbone.cpp
22 \brief Backbone/core main file \see Backbone
23
24
25 \author Bartosz Szatkowski <bulislaw@linux.com>
26 */
27
28 #include "backbone.h"
29 #include "ConfigGenerator.h"
30 class ConfigGenerator;
31 #include <QDebug>
32
33 int Backbone::_searchLimit;
34
35 // Sadly QtConcurent::mapped dosent let me use something like calling method of
36 // some class with supplied argument; so i have to sin against art and put
37 // global function and variable so i could supply function with some parametr
38 QString mappedSearch;
39 QList<Translation*> mapSearch(CommonDictInterface *dict) {
40     if(dict)
41         return dict->searchWordList(mappedSearch, Backbone::_searchLimit);
42     return QList<Translation*>();
43 }
44
45
46
47 /*! Smart pointer (kind of) for translation object
48
49     QtConcurent::mapped  use collection of data and one function, what i need is
50     to map signle data object to method calls for multiple objects. TranslationPtr
51     is try to store method call as a data -> moreover QtConcurent allow only for
52     methods without any parameters so TranslationPtr is created with Translation
53     object -> ready to call toHtml() for supplied Translation.
54
55     Another thing is that QtConcurent dont like pointers in data collection
56     so TranslationPtr is way to hide real translation object (pointer for object)
57     */
58 class TranslationPtr {
59     Translation* _tr;
60 public:
61     TranslationPtr(Translation* tr) :_tr(tr) {}
62
63     /*! \return translation text for corresponding Translation object */
64     QString toHtml() const {
65         QString trans;
66         trans = _tr->toHtml();
67         return trans;
68
69     }
70 };
71
72 void Backbone::init() {
73
74    _dictNum = 0;
75    _dir = QDir::homePath() + "/.mdictionary/";
76    if(!QDir(_dir).exists())
77        QDir().mkdir(_dir);
78
79    if(!_configPath.size())
80        _configPath = _dir + "mdictionary.config";
81    if(!_pluginPath.size())
82        _pluginPath = "/usr/lib/mdictionary/plugins";
83
84    //Install default config files
85    ConfigGenerator confGen;
86    confGen.generateCss(_dir + "style.css");
87    confGen.generateDefaultConfig(_configPath);
88
89    loadPrefs(_configPath);
90
91    loadPlugins();
92
93    loadDicts(_configPath);
94
95    connect(&_resultWatcher, SIGNAL(finished()), this, SLOT(translationReady()));
96    connect(&_htmlResultWatcher, SIGNAL(finished()), this,
97            SLOT(htmlTranslationReady()));
98    connect(&_bookmarkWatcher, SIGNAL(finished()), this,
99            SLOT(bookmarksListReady()));
100    connect(&_bookmarkSearchWatcher, SIGNAL(finished()), this,
101            SLOT(translationReady()));
102
103    // In common opinion perfect thread count is cores_number+1 (in qt perfect
104    // thread count is set to cores number
105    QThreadPool::globalInstance()->setMaxThreadCount(
106            QThreadPool::globalInstance()->maxThreadCount()+1);
107
108    _history = new History(_historyLen, this);
109 }
110
111
112
113 Backbone::Backbone(QString pluginPath, QString configPath, bool dry,
114                    QObject *parent)
115     : QObject(parent)
116 {
117     _pluginPath = pluginPath;
118     _configPath = configPath;
119
120     dryRun = false;
121     if(dry)
122         dryRun = true;
123     init();
124 }
125
126
127
128 Backbone::~Backbone()
129 {
130     QListIterator<CommonDictInterface*> it(_dicts.keys());
131
132     while(it.hasNext())
133         delete it.next();
134
135     it = QListIterator<CommonDictInterface*>(_plugins);
136     while(it.hasNext())
137         delete it.next();
138
139     QHashIterator<QString, Translation*> it2(_result);
140     while(it2.hasNext())
141         delete it2.next().value();
142
143 }
144
145
146
147
148 Backbone::Backbone(const Backbone &b) :QObject(b.parent()) {
149     init();
150     _dicts = QHash<CommonDictInterface*, bool > (b._dicts);
151     _plugins = QList<CommonDictInterface* > (b._plugins);
152     _result = QHash<QString, Translation* > (b._result);
153     _searchLimit = b.searchLimit();
154 }
155
156
157
158
159 int Backbone::searchLimit() const {
160     return _searchLimit;
161 }
162
163
164
165 QHash<CommonDictInterface*, bool > Backbone::getDictionaries() {
166     return _dicts;
167 }
168
169
170
171 QList<CommonDictInterface* > Backbone::getPlugins() {
172     return _plugins;
173 }
174
175
176
177 History* Backbone::history() {
178     return _history;
179 }
180
181
182
183 QMultiHash<QString, Translation*> Backbone::result() {
184     return _result;
185 }
186
187
188
189 void Backbone::stopSearching() {
190     if(stopped)
191         return;
192
193     foreach(CommonDictInterface* dict, _dicts.keys())
194         dict->stop();
195     stopped = true;
196     _innerHtmlResult.cancel();
197     _innerResult.cancel();
198     Q_EMIT searchCanceled();
199 }
200
201
202
203 void Backbone::search(QString word){
204     _result.clear();
205     mappedSearch = word.toLower();
206
207     stopped = false;
208
209     // When dictFin and bookmarkFin is set to true then translationReady()
210     // signal is emited see translationReady(),
211     // so when searching only in one of them, coresponding *Fin is set to false
212     // and other to true so program is waiting only for one translation
213     dictFin = !_searchDicts;
214     bookmarkFin = !_searchBookmarks;
215
216     if(!_searchDicts && !_searchBookmarks) {
217         Q_EMIT ready();
218         Q_EMIT notify(Notify::Warning, tr("You have to specify where You want "
219                 "to look for translations"));
220     }
221
222     if (_searchDicts) {
223         _innerResult = QtConcurrent::mapped(activeDicts(), mapSearch);
224         _resultWatcher.setFuture(_innerResult);
225     }
226
227     if(_searchBookmarks) {
228         _innerBookmarks = QtConcurrent::run(_bookmarks,
229                 &Bookmarks::searchWordList, word);
230         _bookmarkSearchWatcher.setFuture(_innerBookmarks);
231     }
232 }
233
234
235
236 void Backbone::selectedDictionaries(QList<CommonDictInterface* > activeDicts) {
237     foreach(CommonDictInterface* dict, _dicts.keys())
238         if(activeDicts.contains(dict))
239             _dicts[dict] = 1;
240         else
241             _dicts[dict] = 0;
242     dictUpdated();
243  }
244
245
246
247 void Backbone::addDictionary(CommonDictInterface *dict, bool active) {
248     addInternalDictionary(dict,active);
249     dictUpdated();
250 }
251
252
253
254  void Backbone::addInternalDictionary(CommonDictInterface* dict, bool active) {
255      dict->setHash(++_dictNum); // Hash must be uniqe in every session but not between
256      _dicts[dict] = active;
257      connect(dict, SIGNAL(settingsChanged()), this, SLOT(dictUpdated()));
258      connect(dict, SIGNAL(notify(Notify::NotifyType,QString)), this,
259              SIGNAL(notify(Notify::NotifyType,QString)));
260  }
261
262
263
264  void Backbone::removeDictionary(CommonDictInterface *dict) {
265      _dicts.remove(dict);
266      if(dict)
267         dict->clean();
268      else
269         qDebug()<<"delete empty dict";
270      delete dict;
271      dictUpdated();
272
273  }
274
275
276
277  void Backbone::quit() {
278     stopSearching();
279     Q_EMIT closeOk();
280 }
281
282
283
284 void Backbone::translationReady() {
285     bool changed = 0; // prevents doubling ready() signal, when both if's are
286                       //  executed in one translationReady() call then second
287                       // translationReady() call doubles ready*() emit
288
289     if(!dictFin && _innerResult.isFinished()) {
290         changed = 1;
291         dictFin = 1;
292         QFutureIterator<QList<Translation*> > it(_innerResult);
293
294         while(it.hasNext()) {
295             QList<Translation* > list = it.next();
296             foreach(Translation* trans, list) {
297                 if(!trans)
298                     continue;
299                 if(!_searchBookmarks)
300                     trans->setBookmark(_bookmarks.
301                             inBookmarks(trans->key()));
302                 _result.insert(trans->key().toLower(), trans);
303            }
304         }
305     }
306
307     if(!bookmarkFin && _innerBookmarks.isFinished()) {
308         changed = 1;
309         bookmarkFin = 1;
310         QList<Translation*> list = _innerBookmarks.result();
311
312         foreach(Translation* trans, list)
313                 _result.insert(trans->key().toLower(), trans);
314     }
315
316     if(!stopped && bookmarkFin && dictFin && changed) {
317         Q_EMIT ready();
318     }
319 }
320
321
322
323
324 QStringList Backbone::getFilesFromDir(QString dir, QStringList nameFilter) {
325     QDir plug(QDir::toNativeSeparators(dir));
326     if(!plug.exists()) {
327         qDebug() << plug.absolutePath() << " folder doesn't exist";
328         Q_EMIT notify(Notify::Warning,
329                 QString("%1 folder doesn't exist.").arg(plug.path()));
330         return QStringList();
331     }
332     plug.setFilter(QDir::Files);
333     QStringList list = plug.entryList(nameFilter);
334
335     for(int i = 0; i < list.size(); i++)
336         list[i] = plug.absoluteFilePath(list.at(i));
337     return list;
338 }
339
340
341 void Backbone::loadPlugins() {
342     if(dryRun)
343         return;
344     QStringList nameFilter;
345     nameFilter << "*.so" << "*.so.*";
346     QStringList files = getFilesFromDir(_pluginPath, nameFilter);
347
348     foreach(QString file, files) {
349         QPluginLoader loader(file);
350         if(!loader.load()) {
351             Q_EMIT notify(Notify::Error,
352                     QString("%1 plugin cannot be loaded: %2.")
353                     .arg(file).arg(loader.errorString()));
354             continue;
355         }
356         QObject *pl = loader.instance();
357
358         bool exists = 0;
359         CommonDictInterface *plugin = qobject_cast<CommonDictInterface*>(pl);
360         foreach(CommonDictInterface* pl, _plugins)
361             if(pl->type() == plugin->type()) {
362                 exists = 1;
363                 break;
364            }
365         if(!exists)
366             _plugins.append(plugin);
367     }
368 }
369
370
371
372 CommonDictInterface* Backbone::plugin(QString type) {
373     foreach(CommonDictInterface* plugin, _plugins)
374         if(plugin->type() == type)
375             return plugin;
376     return 0;
377 }
378
379
380
381 void Backbone::loadPrefs(QString fileName) {
382     if(dryRun)
383         return;
384     QFileInfo file(QDir::toNativeSeparators(fileName));
385     QDir confDir(file.dir());
386     if(!confDir.exists()){
387         qDebug() << "Configuration file doesn't exist ("
388                 << file.filePath() << ")";
389         Q_EMIT notify(Notify::Warning,
390                 QString("%1 configuration file doesn't exist.")
391                 .arg(file.filePath()));
392         return;
393     }
394     QSettings set(file.filePath(), QSettings::IniFormat);
395     _pluginPath = set.value("general/plugin_path", _pluginPath).toString();
396     _historyLen = set.value("general/history_size", 10).toInt();
397     _searchLimit = set.value("general/search_limit", 15).toInt();
398     _searchBookmarks = set.value("general/search_bookmarks",1).toBool();
399     _searchDicts = set.value("general/search_dictionaries",1).toBool();
400     _zoom = set.value("general/zoom", 1.0).toReal();
401 }
402
403
404
405 void Backbone::savePrefs(QSettings *set) {
406     if(dryRun)
407         return;
408     set->setValue("general/plugin_path", _pluginPath);
409     set->setValue("general/history_size", _historyLen);
410     set->setValue("general/search_limit", _searchLimit);
411     set->setValue("general/search_bookmarks", _searchBookmarks);
412     set->setValue("general/search_dictionaries", _searchDicts);
413     set->setValue("general/zoom", _zoom);
414 }
415
416
417
418 void Backbone::loadDicts(QString fileName) {
419     if(dryRun)
420         return;
421
422     QFileInfo file(QDir::toNativeSeparators(fileName));
423     QDir confDir(file.dir());
424     if(!confDir.exists()){
425         qDebug() << "Configuration file doesn't exist ("
426                 << file.filePath() << ")";
427         Q_EMIT notify(Notify::Warning,
428                 QString("%1 configurationfile doesn't exist.")
429                 .arg(file.filePath()));
430         return;
431     }
432
433     QSettings set(file.filePath(), QSettings::IniFormat);
434     QStringList dicts = set.childGroups();
435     foreach(QString dict, dicts) {
436         if(!dict.contains("dictionary_"))
437             continue;
438         CommonDictInterface* plug = plugin
439                                     (set.value(dict + "/type", "").toString());
440         if(!plug) {
441             qDebug() << "Config file error: "
442                     << set.value(dict + "/type", "").toString()
443                     << " doesn't exist";
444             Q_EMIT notify(Notify::Warning,
445                     QString("Configuration file error. %2 plugin doesn't exist.")
446                     .arg(set.value(dict + "/type", "").toString()));
447             continue;
448         }
449         Settings* plugSet = new Settings();
450         set.beginGroup(dict);
451         QStringList items = set.childKeys();
452         foreach(QString item, items) {
453             plugSet->setValue(item, set.value(item, "").toString());
454         }
455         bool active = set.value("active",1).toBool();
456
457         set.endGroup();
458         addInternalDictionary(plug->getNew(plugSet), active);
459         delete plugSet;
460     }
461 }
462
463
464
465 void Backbone::dictUpdated() {
466     if(dryRun)
467         return;
468
469     // For convienence this function is called for each change in dictionaries
470     // and each call dumps configuration for all dictionaries into file.
471     // Maybe better way would be to store new/changed configuration but
472     // parsing settings file and figuring out what was changed, in my opinion,
473     // would take more time
474     _history->setMaxSize(_historyLen);
475     QFileInfo file(QDir::toNativeSeparators(_configPath));
476     QDir confDir(file.dir());
477     if(!confDir.exists())
478         confDir.mkpath(file.dir().path());
479     QSettings set(file.filePath(), QSettings::IniFormat);
480     set.clear();
481
482     savePrefs(&set);
483
484     foreach(CommonDictInterface* dict, _dicts.keys()){
485         if(!dict || !dict->settings())
486             continue;
487         saveState(&set, dict->settings(), _dicts[dict], dict->hash());
488     }
489 }
490
491
492
493 void Backbone::saveState(QSettings* set, Settings* plugSet, bool active
494                          , uint hash) {
495     if(dryRun)
496         return;
497     if(!set || !plugSet)
498         return;
499
500     QString section;
501     section.append(QString("dictionary_%1").arg(hash));
502     QList<QString> keys = plugSet->keys();
503     foreach(QString key, keys)
504         set->setValue(section + "/" + key, plugSet->value(key));
505     set->setValue(section + "/active", active);
506 }
507
508
509
510 QStringList Backbone::htmls() {
511     return _htmlResult;
512 }
513
514
515
516 void Backbone::searchHtml(QList<Translation *> translations) {
517     _htmlResult.clear();
518
519     QList<TranslationPtr> dummy;
520     stopped = false;
521     foreach(Translation* tr, translations) {
522          if(containsDict(tr->dict()) || !tr->dict())
523             dummy.append(TranslationPtr(tr));
524   /*      foreach(CommonDictInterface* dict, activeDicts()) {
525             Translation* trans = dict->getTranslationFor(tr->key());
526             if(trans)
527                 dummy.append(TranslationPtr(trans));
528         } */
529     }
530     if(translations.size()>0) {
531         Translation *tr = translations.at(0);
532         foreach(CommonDictInterface* dict, activeDicts()) {
533             Translation* trans = dict->getTranslationFor(tr->key());
534             if(trans)
535                 dummy.append(TranslationPtr(trans));
536         }
537     }
538
539    _innerHtmlResult = QtConcurrent::mapped(dummy,
540                                             &TranslationPtr::toHtml);
541    _htmlResultWatcher.setFuture(_innerHtmlResult);
542 }
543
544
545
546 void Backbone::htmlTranslationReady() {
547
548     QFutureIterator<QString> it(_innerHtmlResult);
549     QSet<QString> uniqe;
550     while(it.hasNext())
551         uniqe.insert(it.next());
552     _htmlResult.clear();
553     _htmlResult = uniqe.toList();
554
555     if(!stopped)
556         Q_EMIT htmlReady();
557
558 }
559
560
561 QList<CommonDictInterface*> Backbone::activeDicts() {
562     QList<CommonDictInterface*>res;
563     foreach(CommonDictInterface* dict, _dicts.keys())
564         if(_dicts[dict])
565             res.append(dict);
566     return res;
567
568 }
569
570
571
572 void Backbone::bookmarksListReady() {
573    _bookmarksResult = _innerBookmarks.result();
574    Q_EMIT bookmarksReady();
575 }
576
577
578
579
580 void Backbone::setSettings(Settings *settings) {
581     _historyLen = settings->value("history_size").toInt();
582     _searchLimit = settings->value("search_limit").toInt();
583     if(settings->value("search_dictionaries") == "true")
584         _searchDicts = 1;
585     else
586         _searchDicts = 0;
587     if(settings->value("search_bookmarks") == "true")
588         _searchBookmarks = 1;
589     else
590         _searchBookmarks = 0;
591     _zoom = settings->value("zoom").toFloat();
592     if(!_zoom)
593         _zoom ++;
594
595     dictUpdated();
596     if(settings)
597         delete settings;
598 }
599
600
601
602
603 Settings* Backbone::settings() {
604     Settings * settings = new Settings();
605     settings->setValue("history_size", QString("%1").arg(_historyLen));
606     settings->setValue("search_limit", QString("%1").arg(_searchLimit));
607     settings->setValue("zoom", QString("%1").arg(_zoom));
608     if(_searchBookmarks)
609         settings->setValue("search_bookmarks", "true");
610     else
611         settings->setValue("search_bookmarks", "false");
612
613     if(_searchDicts)
614         settings->setValue("search_dictionaries", "true");
615     else
616         settings->setValue("search_dictionaries", "false");
617     return settings;
618 }
619
620
621 bool Backbone::containsDict(uint hash) const {
622     QHashIterator<CommonDictInterface*, bool> it(_dicts);
623     if (!hash)
624         return false;
625     while(it.hasNext())
626         if(it.next().key()->hash() == hash)
627             return true;
628     return false;
629 }