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