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