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