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