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