Small changes in building system
[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";
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         CommonDictInterface *plugin = qobject_cast<CommonDictInterface*>(pl);
350         _plugins.append(plugin);
351     }
352 }
353
354
355
356 CommonDictInterface* Backbone::plugin(QString type) {
357     foreach(CommonDictInterface* plugin, _plugins)
358         if(plugin->type() == type)
359             return plugin;
360     return 0;
361 }
362
363
364
365 void Backbone::loadPrefs(QString fileName) {
366     if(dryRun)
367         return;
368     QFileInfo file(QDir::toNativeSeparators(fileName));
369     QDir confDir(file.dir());
370     if(!confDir.exists()){
371         qDebug() << "Configuration file doesn't exist ("
372                 << file.filePath() << ")";
373         Q_EMIT notify(Notify::Warning,
374                 QString("%1 configuration file doesn't exist.")
375                 .arg(file.filePath()));
376         return;
377     }
378     QSettings set(file.filePath(), QSettings::IniFormat);
379     _pluginPath = set.value("general/plugin_path", _pluginPath).toString();
380     _historyLen = set.value("general/history_size", 10).toInt();
381     _searchLimit = set.value("general/search_limit", 15).toInt();
382     _searchBookmarks = set.value("general/search_bookmarks",1).toBool();
383     _searchDicts = set.value("general/search_dictionaries",1).toBool();
384 }
385
386
387
388 void Backbone::savePrefs(QSettings *set) {
389     if(dryRun)
390         return;
391     set->setValue("general/plugin_path", _pluginPath);
392     set->setValue("general/history_size", _historyLen);
393     set->setValue("general/search_limit", _searchLimit);
394     set->setValue("general/search_bookmarks", _searchBookmarks);
395     set->setValue("general/search_dictionaries", _searchDicts);
396 }
397
398
399
400 void Backbone::saveDefaultPrefs(QSettings *set) {
401     if(dryRun)
402         return;
403     set->setValue("general/plugin_path", _defaultPluginPath);
404     set->setValue("general/history_size", _defaultHistoryLen);
405     set->setValue("general/search_limit", _defaultSearchLimit);
406 }
407
408
409
410 void Backbone::loadDicts(QString fileName, bool _default) {
411     if(dryRun)
412         return;
413
414     QFileInfo file(QDir::toNativeSeparators(fileName));
415     QDir confDir(file.dir());
416     if(!confDir.exists()){
417         qDebug() << "Configuration file doesn't exist ("
418                 << file.filePath() << ")";
419         Q_EMIT notify(Notify::Warning,
420                 QString("%1 configurationfile doesn't exist.")
421                 .arg(file.filePath()));
422         return;
423     }
424
425     QSettings set(file.filePath(), QSettings::IniFormat);
426     QStringList dicts = set.childGroups();
427     foreach(QString dict, dicts) {
428         if(!dict.contains("dictionary_"))
429             continue;
430         CommonDictInterface* plug = plugin
431                                     (set.value(dict + "/type", "").toString());
432         if(!plug) {
433             qDebug() << "Config file error: "
434                     << set.value(dict + "/type", "").toString()
435                     << " doesn't exist";
436             Q_EMIT notify(Notify::Warning,
437                     QString("Configuration file error. %2 plugin doesn't exist.")
438                     .arg(set.value(dict + "/type", "").toString()));
439             continue;
440         }
441         Settings* plugSet = new Settings();
442         set.beginGroup(dict);
443         QStringList items = set.childKeys();
444         foreach(QString item, items) {
445             plugSet->setValue(item, set.value(item, "").toString());
446         }
447         bool active = set.value("active",1).toBool();
448
449         if(_default)
450             plugSet->setValue("_default_", "true");
451
452         set.endGroup();
453         addInternalDictionary(plug->getNew(plugSet), active);
454     }
455 }
456
457
458
459 void Backbone::dictUpdated() {
460     if(dryRun)
461         return;
462
463     // For convienence this function is called for each change in dictionaries
464     // and each call dumps configuration for all dictionaries into file.
465     // Maybe better way would be to store new/changed configuration but
466     // parsing settings file and figuring out what was changed, in my opinion,
467     // would take more time
468     _history->setMaxSize(_historyLen);
469     QFileInfo file(QDir::toNativeSeparators(_configPath));
470     QDir confDir(file.dir());
471     if(!confDir.exists())
472         confDir.mkpath(file.dir().path());
473     QSettings set(file.filePath(), QSettings::IniFormat);
474     set.clear();
475
476     QFileInfo defFile(QDir::toNativeSeparators(_defaultConfigPath));
477     QDir defConfDir(defFile.dir());
478     if(!defConfDir.exists())
479         defConfDir.mkpath(defFile.dir().path());
480     QSettings defSet(defFile.filePath(), QSettings::IniFormat);
481     defSet.clear();
482     savePrefs(&set);
483     saveDefaultPrefs(&defSet);
484
485     foreach(CommonDictInterface* dict, _dicts.keys()){
486         if(!dict || !dict->settings())
487             continue;
488         if(!dict->settings()->keys().contains("_default_"))
489             saveState(&set, dict->settings(), _dicts[dict], dict->hash());
490         else
491             saveState(&defSet, dict->settings(), _dicts[dict], dict->hash());
492     }
493 }
494
495
496
497 void Backbone::saveState(QSettings* set, Settings* plugSet, bool active
498                          , uint hash) {
499     if(dryRun)
500         return;
501     if(!set || !plugSet)
502         return;
503
504     QString section;
505     section.append(QString("dictionary_%1").arg(hash));
506     QList<QString> keys = plugSet->keys();
507     foreach(QString key, keys)
508         set->setValue(section + "/" + key, plugSet->value(key));
509     set->setValue(section + "/active", active);
510 }
511
512
513
514 QStringList Backbone::htmls() {
515     return _htmlResult;
516 }
517
518
519
520 void Backbone::searchHtml(QList<Translation *> translations) {
521     _htmlResult.clear();
522
523     QList<TranslationPtr> dummy;
524     stopped = false;
525     foreach(Translation* tr, translations) {
526         if(containsDict(tr->dict()) || !tr->dict())
527             dummy.append(TranslationPtr(tr));
528         foreach(CommonDictInterface* dict, activeDicts()) {
529             Translation* trans = dict->getTranslationFor(tr->key());
530             if(trans)
531                 dummy.append(TranslationPtr(trans));
532         }
533   }
534
535    _innerHtmlResult = QtConcurrent::mapped(dummy,
536                                             &TranslationPtr::toHtml);
537    _htmlResultWatcher.setFuture(_innerHtmlResult);
538 }
539
540
541
542 void Backbone::htmlTranslationReady() {
543
544     QFutureIterator<QString> it(_innerHtmlResult);
545     QSet<QString> uniqe;
546     while(it.hasNext())
547         uniqe.insert(it.next());
548     _htmlResult.clear();
549     _htmlResult = uniqe.toList();
550
551     if(!stopped)
552         Q_EMIT htmlReady();
553
554 }
555
556
557 QList<CommonDictInterface*> Backbone::activeDicts() {
558     QList<CommonDictInterface*>res;
559     foreach(CommonDictInterface* dict, _dicts.keys())
560         if(_dicts[dict])
561             res.append(dict);
562     return res;
563
564 }
565
566
567
568 void Backbone::bookmarksListReady() {
569    _bookmarksResult = _innerBookmarks.result();
570    Q_EMIT bookmarksReady();
571 }
572
573
574
575
576 void Backbone::setSettings(Settings *settings) {
577     _historyLen = settings->value("history_size").toInt();
578     _searchLimit = settings->value("search_limit").toInt();
579     if(settings->value("search_dictionaries") == "true")
580         _searchDicts = 1;
581     else
582         _searchDicts = 0;
583     if(settings->value("search_bookmarks") == "true")
584         _searchBookmarks = 1;
585     else
586         _searchBookmarks = 0;
587     dictUpdated();
588     if(settings)
589         delete settings;
590 }
591
592
593
594
595 Settings* Backbone::settings() {
596     Settings * settings = new Settings();
597     settings->setValue("history_size", QString("%1").arg(_historyLen));
598     settings->setValue("search_limit", QString("%1").arg(_searchLimit));
599     if(_searchBookmarks)
600         settings->setValue("search_bookmarks", "true");
601     else
602         settings->setValue("search_bookmarks", "false");
603
604     if(_searchDicts)
605         settings->setValue("search_dictionaries", "true");
606     else
607         settings->setValue("search_dictionaries", "false");
608     return settings;
609 }
610
611
612 bool Backbone::containsDict(uint hash) const {
613     QHashIterator<CommonDictInterface*, bool> it(_dicts);
614     if (!hash)
615         return false;
616     while(it.hasNext())
617         if(it.next().key()->hash() == hash)
618             return true;
619     return false;
620 }