1 /*
2  * gnote
3  *
4  * Copyright (C) 2010-2017,2019-2021 Aurimas Cernius
5  * Copyright (C) 2009, 2010 Debarshi Ray
6  * Copyright (C) 2009 Hubert Figuiere
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 
23 #include <config.h>
24 
25 #include <glib.h>
26 #include <glibmm/i18n.h>
27 #include <glibmm/miscutils.h>
28 
29 #include "sharp/map.hpp"
30 #include "sharp/directory.hpp"
31 #include "sharp/dynamicmodule.hpp"
32 #include "sharp/files.hpp"
33 
34 #include "addinmanager.hpp"
35 #include "addinpreferencefactory.hpp"
36 #include "debug.hpp"
37 #include "iactionmanager.hpp"
38 #include "ignote.hpp"
39 #include "preferencetabaddin.hpp"
40 #include "watchers.hpp"
41 #include "notebooks/notebookapplicationaddin.hpp"
42 #include "notebooks/notebooknoteaddin.hpp"
43 #include "synchronization/syncserviceaddin.hpp"
44 
45 
46 
47 
48 namespace gnote {
49 
50 #define REGISTER_BUILTIN_NOTE_ADDIN(klass) \
51   do { sharp::IfaceFactoryBase *iface = new sharp::IfaceFactory<klass>; \
52   m_builtin_ifaces.push_back(iface); \
53   m_note_addin_infos.insert(std::make_pair(typeid(klass).name(),iface)); } while(0)
54 
55 #define REGISTER_APP_ADDIN(klass) \
56   m_app_addins.insert(std::make_pair(typeid(klass).name(),        \
57                                      klass::create()))
58 
59 #define SETUP_NOTE_ADDIN(key, klass) \
60   m_preferences.signal_##key##_changed.connect([this]() { \
61     if(m_preferences.key()) { \
62       sharp::IfaceFactoryBase *iface = new sharp::IfaceFactory<klass>; \
63       m_builtin_ifaces.push_back(iface); \
64       load_note_addin(typeid(klass).name(), iface); \
65     } \
66     else { \
67       erase_note_addin_info(typeid(klass).name()); \
68     } \
69   })
70 
71 #define SETUP_APP_ADDIN(key, klass) \
72   m_preferences.signal_##key##_changed.connect([this]() { \
73       if(m_preferences.key()) { \
74         auto iter = m_app_addins.find(typeid(klass).name()); \
75         if(iter != m_app_addins.end()) { \
76           iter->second->initialize(); \
77         } \
78         else { \
79           auto addin = klass::create(); \
80           m_app_addins.insert(std::make_pair(typeid(klass).name(), addin)); \
81           addin->initialize(m_gnote, m_note_manager); \
82         } \
83       } \
84       else { \
85         auto addin = m_app_addins.find(typeid(klass).name()); \
86         if(addin != m_app_addins.end()) { \
87           addin->second->shutdown(); \
88         } \
89       } \
90   })
91 
92 namespace {
93   template <typename AddinType>
get_id_for_addin(const AbstractAddin & addin,const std::map<Glib::ustring,AddinType * > & addins)94   Glib::ustring get_id_for_addin(const AbstractAddin & addin, const std::map<Glib::ustring, AddinType*> & addins)
95   {
96     const AddinType *plugin = dynamic_cast<const AddinType*>(&addin);
97     if(plugin != NULL) {
98       for(auto iter : addins) {
99         if(iter.second == plugin) {
100           return iter.first;
101         }
102       }
103     }
104     return "";
105   }
106 }
107 
108 
AddinManager(IGnote & g,NoteManager & note_manager,Preferences & preferences,const Glib::ustring & conf_dir)109   AddinManager::AddinManager(IGnote & g, NoteManager & note_manager, Preferences & preferences, const Glib::ustring & conf_dir)
110     : m_gnote(g)
111     , m_note_manager(note_manager)
112     , m_preferences(preferences)
113     , m_gnote_conf_dir(conf_dir)
114   {
115     m_addins_prefs_dir = Glib::build_filename(conf_dir, "addins");
116     m_addins_prefs_file = Glib::build_filename(m_addins_prefs_dir,
117                                                "global.ini");
118 
119     const bool is_first_run
120                  = !sharp::directory_exists(m_addins_prefs_dir);
121 
122     if (is_first_run)
123       g_mkdir_with_parents(m_addins_prefs_dir.c_str(), S_IRWXU);
124 
125     initialize_sharp_addins();
126   }
127 
~AddinManager()128   AddinManager::~AddinManager()
129   {
130     sharp::map_delete_all_second(m_app_addins);
131     for(NoteAddinMap::const_iterator iter = m_note_addins.begin();
132         iter != m_note_addins.end(); ++iter) {
133       sharp::map_delete_all_second(iter->second);
134     }
135     sharp::map_delete_all_second(m_addin_prefs);
136     sharp::map_delete_all_second(m_import_addins);
137     for(auto iter : m_builtin_ifaces) {
138       delete iter;
139     }
140   }
141 
add_note_addin_info(const Glib::ustring & id,const sharp::DynamicModule * dmod)142   void AddinManager::add_note_addin_info(const Glib::ustring & id,
143                                          const sharp::DynamicModule * dmod)
144   {
145     {
146       const IdInfoMap::const_iterator iter
147                                         = m_note_addin_infos.find(id);
148       if (m_note_addin_infos.end() != iter) {
149         ERR_OUT(_("Note plugin info %s already present"), id.c_str());
150         return;
151       }
152     }
153 
154     sharp::IfaceFactoryBase * const f = dmod->query_interface(
155                                           NoteAddin::IFACE_NAME);
156     if(!f) {
157       ERR_OUT(_("%s does not implement %s"), id.c_str(), NoteAddin::IFACE_NAME);
158       return;
159     }
160 
161     load_note_addin(id, f);
162   }
163 
load_note_addin(const Glib::ustring & id,sharp::IfaceFactoryBase * const f)164   void AddinManager::load_note_addin(const Glib::ustring & id, sharp::IfaceFactoryBase *const f)
165   {
166     m_note_addin_infos.insert(std::make_pair(id, f));
167     for(NoteAddinMap::iterator iter = m_note_addins.begin();
168         iter != m_note_addins.end(); ++iter) {
169       IdAddinMap & id_addin_map = iter->second;
170       IdAddinMap::const_iterator it = id_addin_map.find(id);
171       if(id_addin_map.end() != it) {
172         ERR_OUT(_("Note plugin %s already present"), id.c_str());
173         continue;
174       }
175 
176       const Note::Ptr & note = iter->first;
177       NoteAddin *const addin = dynamic_cast<NoteAddin *>((*f)());
178       if(addin) {
179        addin->initialize(m_gnote, note);
180        id_addin_map.insert(std::make_pair(id, addin));
181       }
182     }
183   }
184 
erase_note_addin_info(const Glib::ustring & id)185   void AddinManager::erase_note_addin_info(const Glib::ustring & id)
186   {
187     {
188       const IdInfoMap::iterator iter = m_note_addin_infos.find(id);
189       if (m_note_addin_infos.end() == iter) {
190         ERR_OUT(_("Note plugin info %s is absent"), id.c_str());
191         return;
192       }
193 
194       m_note_addin_infos.erase(iter);
195     }
196 
197     {
198       for(NoteAddinMap::iterator iter = m_note_addins.begin();
199           iter != m_note_addins.end(); ++iter) {
200         IdAddinMap & id_addin_map = iter->second;
201         IdAddinMap::iterator it = id_addin_map.find(id);
202         if (id_addin_map.end() == it) {
203           ERR_OUT(_("Note plugin %s is absent"), id.c_str());
204           continue;
205         }
206 
207         NoteAddin * const addin = it->second;
208         if (addin) {
209           addin->dispose(true);
210           delete addin;
211           id_addin_map.erase(it);
212         }
213       }
214     }
215   }
216 
load_addin_infos(const Glib::ustring & global_path,const Glib::ustring & local_path)217   void AddinManager::load_addin_infos(const Glib::ustring & global_path,
218                                       const Glib::ustring & local_path)
219   {
220     load_addin_infos(global_path);
221     load_addin_infos(local_path);
222   }
223 
load_addin_infos(const Glib::ustring & path)224   void AddinManager::load_addin_infos(const Glib::ustring & path)
225   {
226     std::vector<Glib::ustring> files = sharp::directory_get_files_with_ext(path, ".desktop");
227     for(auto file : files) {
228       try {
229         AddinInfo addin_info(file);
230         if(!addin_info.validate(LIBGNOTE_RELEASE, LIBGNOTE_VERSION_INFO)) {
231           continue;
232         }
233         Glib::ustring module = Glib::build_filename(path, addin_info.addin_module());
234         if(sharp::file_exists(module + "." + G_MODULE_SUFFIX)) {
235           addin_info.addin_module(module);
236           m_addin_infos[addin_info.id()] = addin_info;
237         }
238         else {
239           ERR_OUT(_("Failed to find module %s for addin %s"), addin_info.id().c_str(), module.c_str());
240         }
241       }
242       catch(std::exception & e) {
243         ERR_OUT(_("Failed to load addin info for %s: %s"), file.c_str(), e.what());
244       }
245     }
246   }
247 
get_enabled_addins() const248   std::vector<Glib::ustring> AddinManager::get_enabled_addins() const
249   {
250     std::vector<Glib::ustring> addins;
251     bool global_addins_prefs_loaded = true;
252     Glib::KeyFile global_addins_prefs;
253     try {
254       global_addins_prefs.load_from_file(m_addins_prefs_file);
255     }
256     catch(Glib::Error & not_loaded) {
257       global_addins_prefs_loaded = false;
258     }
259 
260     for(AddinInfoMap::const_iterator iter = m_addin_infos.begin(); iter != m_addin_infos.end(); ++iter) {
261       if(global_addins_prefs_loaded && global_addins_prefs.has_key("Enabled", iter->first)) {
262         if(global_addins_prefs.get_boolean("Enabled", iter->first)) {
263           addins.push_back(iter->second.addin_module());
264         }
265       }
266       else if(iter->second.default_enabled()) {
267           addins.push_back(iter->second.addin_module());
268       }
269     }
270 
271     return addins;
272   }
273 
initialize_sharp_addins()274   void AddinManager::initialize_sharp_addins()
275   {
276     if (!sharp::directory_exists (m_addins_prefs_dir))
277       g_mkdir_with_parents(m_addins_prefs_dir.c_str(), S_IRWXU);
278 
279     SETUP_NOTE_ADDIN(enable_url_links, NoteUrlWatcher);
280     SETUP_NOTE_ADDIN(enable_auto_links, NoteLinkWatcher);
281     SETUP_APP_ADDIN(enable_auto_links, AppLinkWatcher);
282     SETUP_NOTE_ADDIN(enable_wikiwords, NoteWikiWatcher);
283 
284     REGISTER_BUILTIN_NOTE_ADDIN(NoteRenameWatcher);
285     REGISTER_BUILTIN_NOTE_ADDIN(NoteSpellChecker);
286     if(m_preferences.enable_url_links()) {
287       REGISTER_BUILTIN_NOTE_ADDIN(NoteUrlWatcher);
288     }
289     if(m_preferences.enable_auto_links()) {
290       REGISTER_APP_ADDIN(AppLinkWatcher);
291       REGISTER_BUILTIN_NOTE_ADDIN(NoteLinkWatcher);
292     }
293     if(m_preferences.enable_wikiwords()) {
294       REGISTER_BUILTIN_NOTE_ADDIN(NoteWikiWatcher);
295     }
296     REGISTER_BUILTIN_NOTE_ADDIN(MouseHandWatcher);
297     REGISTER_BUILTIN_NOTE_ADDIN(NoteTagsWatcher);
298     REGISTER_BUILTIN_NOTE_ADDIN(notebooks::NotebookNoteAddin);
299 
300     REGISTER_APP_ADDIN(notebooks::NotebookApplicationAddin);
301 
302     Glib::ustring global_path = LIBDIR "/" PACKAGE_NAME "/plugins/" PACKAGE_VERSION;
303     Glib::ustring local_path = m_gnote_conf_dir + "/plugins";
304 
305     load_addin_infos(global_path, local_path);
306     std::vector<Glib::ustring> enabled_addins = get_enabled_addins();
307     m_module_manager.load_modules(enabled_addins);
308 
309     const sharp::ModuleMap & modules = m_module_manager.get_modules();
310     for(sharp::ModuleMap::const_iterator iter = modules.begin();
311         iter != modules.end(); ++iter) {
312 
313       Glib::ustring mod_id = get_info_for_module(iter->first).id();
314       sharp::DynamicModule* dmod = iter->second;
315       if(!dmod) {
316         continue;
317       }
318 
319       dmod->enabled(true); // enable all loaded modules on startup
320       add_module_addins(mod_id, dmod);
321     }
322   }
323 
add_module_addins(const Glib::ustring & mod_id,sharp::DynamicModule * dmod)324   void AddinManager::add_module_addins(const Glib::ustring & mod_id, sharp::DynamicModule * dmod)
325   {
326     sharp::IfaceFactoryBase * f = dmod->query_interface(NoteAddin::IFACE_NAME);
327     if(f && dmod->is_enabled()) {
328       m_note_addin_infos.insert(std::make_pair(mod_id, f));
329     }
330 
331     f = dmod->query_interface(AddinPreferenceFactoryBase::IFACE_NAME);
332     if(f) {
333       AddinPreferenceFactoryBase * factory = dynamic_cast<AddinPreferenceFactoryBase*>((*f)());
334       m_addin_prefs.insert(std::make_pair(mod_id, factory));
335     }
336 
337     f = dmod->query_interface(ImportAddin::IFACE_NAME);
338     if(f) {
339       ImportAddin * addin = dynamic_cast<ImportAddin*>((*f)());
340       m_import_addins.insert(std::make_pair(mod_id, addin));
341     }
342 
343     f = dmod->query_interface(ApplicationAddin::IFACE_NAME);
344     if(f) {
345       ApplicationAddin * addin = dynamic_cast<ApplicationAddin*>((*f)());
346       m_app_addins.insert(std::make_pair(mod_id, addin));
347     }
348     f = dmod->query_interface(sync::SyncServiceAddin::IFACE_NAME);
349     if(f) {
350       sync::SyncServiceAddin * addin = dynamic_cast<sync::SyncServiceAddin*>((*f)());
351       m_sync_service_addins.insert(std::make_pair(mod_id, addin));
352     }
353   }
354 
get_info_for_module(const Glib::ustring & module) const355   AddinInfo AddinManager::get_info_for_module(const Glib::ustring & module) const
356   {
357     for(AddinInfoMap::const_iterator iter = m_addin_infos.begin();
358         iter != m_addin_infos.end(); ++iter) {
359       if(iter->second.addin_module() == module) {
360         return iter->second;
361       }
362     }
363     return AddinInfo();
364   }
365 
load_addins_for_note(const Note::Ptr & note)366   void AddinManager::load_addins_for_note(const Note::Ptr & note)
367   {
368     if(m_note_addins.find(note) != m_note_addins.end()) {
369       ERR_OUT(_("Trying to load addins when they are already loaded"));
370       return;
371     }
372     IdAddinMap loaded_addins;
373     m_note_addins[note] = loaded_addins;
374 
375     IdAddinMap & loaded(m_note_addins[note]); // avoid copying the whole map
376     for(IdInfoMap::const_iterator iter = m_note_addin_infos.begin();
377         iter != m_note_addin_infos.end(); ++iter) {
378 
379       const IdInfoMap::value_type & addin_info(*iter);
380       sharp::IInterface* iface = (*addin_info.second)();
381       NoteAddin * addin = dynamic_cast<NoteAddin *>(iface);
382       if(addin) {
383         addin->initialize(m_gnote, note);
384         loaded.insert(std::make_pair(addin_info.first, addin));
385       }
386       else {
387         DBG_OUT("wrong type for the interface: %s", typeid(*iface).name());
388         delete iface;
389       }
390     }
391   }
392 
get_note_addins(const Note::Ptr & note) const393   std::vector<NoteAddin*> AddinManager::get_note_addins(const Note::Ptr & note) const
394   {
395     std::vector<NoteAddin*> addins;
396     NoteAddinMap::const_iterator iter = m_note_addins.find(note);
397     if(iter != m_note_addins.end()) {
398       for(IdAddinMap::const_iterator it = iter->second.begin(); it != iter->second.end(); ++it) {
399         addins.push_back(it->second);
400       }
401     }
402 
403     return addins;
404   }
405 
get_application_addin(const Glib::ustring & id) const406   ApplicationAddin * AddinManager::get_application_addin(
407                                      const Glib::ustring & id) const
408   {
409     const IdImportAddinMap::const_iterator import_iter
410       = m_import_addins.find(id);
411 
412     if (m_import_addins.end() != import_iter)
413       return import_iter->second;
414 
415     const AppAddinMap::const_iterator app_iter
416       = m_app_addins.find(id);
417 
418     if (m_app_addins.end() != app_iter)
419       return app_iter->second;
420 
421     return 0;
422   }
423 
get_sync_service_addin(const Glib::ustring & id) const424   sync::SyncServiceAddin *AddinManager::get_sync_service_addin(const Glib::ustring & id) const
425   {
426     const IdSyncServiceAddinMap::const_iterator iter = m_sync_service_addins.find(id);
427     if(iter != m_sync_service_addins.end()) {
428       return iter->second;
429     }
430 
431     return NULL;
432   }
433 
get_preference_tab_addins() const434   std::vector<PreferenceTabAddin*> AddinManager::get_preference_tab_addins() const
435   {
436     return sharp::map_get_values(m_pref_tab_addins);
437   }
438 
439 
get_sync_service_addins() const440   std::vector<sync::SyncServiceAddin*> AddinManager::get_sync_service_addins() const
441   {
442     return sharp::map_get_values(m_sync_service_addins);
443   }
444 
445 
get_import_addins() const446   std::vector<ImportAddin*> AddinManager::get_import_addins() const
447   {
448     return sharp::map_get_values(m_import_addins);
449   }
450 
initialize_application_addins() const451   void AddinManager::initialize_application_addins() const
452   {
453     register_addin_actions();
454     for(AppAddinMap::const_iterator iter = m_app_addins.begin();
455         iter != m_app_addins.end(); ++iter) {
456       ApplicationAddin * addin = iter->second;
457       const sharp::DynamicModule * dmod
458         = m_module_manager.get_module(iter->first);
459       if (!dmod || dmod->is_enabled()) {
460         addin->initialize(m_gnote, m_note_manager);
461       }
462     }
463   }
464 
initialize_sync_service_addins() const465   void AddinManager::initialize_sync_service_addins() const
466   {
467     for(IdSyncServiceAddinMap::const_iterator iter = m_sync_service_addins.begin();
468         iter != m_sync_service_addins.end(); ++iter) {
469       sync::SyncServiceAddin *addin = NULL;
470       try {
471         addin = iter->second;
472         const sharp::DynamicModule *dmod = m_module_manager.get_module(iter->first);
473         if(!dmod || dmod->is_enabled()) {
474           addin->initialize(m_gnote, m_gnote.sync_manager());
475         }
476       }
477       catch(std::exception & e) {
478         DBG_OUT("Error calling %s.initialize (): %s", addin->id().c_str(), e.what());
479 
480         // TODO: Call something like AddinManager.Disable (addin)
481       }
482     }
483   }
484 
shutdown_application_addins() const485   void AddinManager::shutdown_application_addins() const
486   {
487     for(AppAddinMap::const_iterator iter = m_app_addins.begin();
488         iter != m_app_addins.end(); ++iter) {
489       ApplicationAddin * addin = iter->second;
490       const sharp::DynamicModule * dmod
491         = m_module_manager.get_module(iter->first);
492       if (!dmod || dmod->is_enabled()) {
493         try {
494           addin->shutdown();
495         }
496         catch (const sharp::Exception & e) {
497           DBG_OUT("Error calling %s.Shutdown (): %s",
498                   typeid(*addin).name(), e.what());
499         }
500       }
501     }
502   }
503 
save_addins_prefs() const504   void AddinManager::save_addins_prefs() const
505   {
506     Glib::KeyFile global_addins_prefs;
507     try {
508       global_addins_prefs.load_from_file(m_addins_prefs_file);
509     }
510     catch (Glib::Error & not_loaded_ignored) {
511     }
512 
513     const sharp::ModuleMap & modules = m_module_manager.get_modules();
514     for(AddinInfoMap::const_iterator iter = m_addin_infos.begin();
515         iter != m_addin_infos.end(); ++iter) {
516       const Glib::ustring & mod_id = iter->first;
517       sharp::ModuleMap::const_iterator mod_iter = modules.find(iter->second.addin_module());
518       bool enabled = mod_iter != modules.end() && mod_iter->second->is_enabled();
519       global_addins_prefs.set_boolean("Enabled", mod_id, enabled);
520     }
521 
522     Glib::RefPtr<Gio::File> prefs_file = Gio::File::create_for_path(
523                                            m_addins_prefs_file);
524     Glib::RefPtr<Gio::FileOutputStream> prefs_file_stream
525                                           = prefs_file->append_to();
526     prefs_file_stream->truncate(0);
527     prefs_file_stream->write(global_addins_prefs.to_data());
528   }
529 
get_addin_info(const Glib::ustring & id) const530   AddinInfo AddinManager::get_addin_info(const Glib::ustring & id) const
531   {
532     AddinInfoMap::const_iterator iter = m_addin_infos.find(id);
533     if(iter != m_addin_infos.end()) {
534       return iter->second;
535     }
536     return AddinInfo();
537   }
538 
get_addin_info(const AbstractAddin & addin) const539   AddinInfo AddinManager::get_addin_info(const AbstractAddin & addin) const
540   {
541     Glib::ustring id;
542     id = get_id_for_addin(addin, m_app_addins);
543     if(id.empty()) {
544       id = get_id_for_addin(addin, m_pref_tab_addins);
545     }
546     if(id.empty()) {
547       id = get_id_for_addin(addin, m_sync_service_addins);
548     }
549     if(id.empty()) {
550       id = get_id_for_addin(addin, m_import_addins);
551     }
552     for(NoteAddinMap::const_iterator iter = m_note_addins.begin();
553         id.empty() && iter != m_note_addins.end(); ++iter) {
554       id = get_id_for_addin(addin, iter->second);
555     }
556     if(id.empty()) {
557       return AddinInfo();
558     }
559     return get_addin_info(id);
560   }
561 
is_module_loaded(const Glib::ustring & id) const562   bool AddinManager::is_module_loaded(const Glib::ustring & id) const
563   {
564     AddinInfo info = get_addin_info(id);
565     return m_module_manager.get_module(info.addin_module());
566   }
567 
get_module(const Glib::ustring & id)568   sharp::DynamicModule *AddinManager::get_module(const Glib::ustring & id)
569   {
570     AddinInfo info = get_addin_info(id);
571     sharp::DynamicModule *module = m_module_manager.get_module(info.addin_module());
572     if(!module) {
573       module = m_module_manager.load_module(info.addin_module());
574       if(module) {
575         add_module_addins(id, module);
576       }
577     }
578     return module;
579   }
580 
create_addin_preference_widget(const Glib::ustring & id)581   Gtk::Widget * AddinManager::create_addin_preference_widget(const Glib::ustring & id)
582   {
583     IdAddinPrefsMap::const_iterator iter = m_addin_prefs.find(id);
584     if(iter != m_addin_prefs.end()) {
585       return iter->second->create_preference_widget(m_gnote, m_gnote.preferences(), m_note_manager);
586     }
587     return NULL;
588   }
589 
register_addin_actions() const590   void AddinManager::register_addin_actions() const
591   {
592     auto & manager(m_gnote.action_manager());
593     for(auto & info : m_addin_infos) {
594       auto & non_modifying = info.second.non_modifying_actions();
595       for(auto & action : info.second.actions()) {
596         manager.register_main_window_action(action.first, action.second,
597           std::find(non_modifying.begin(), non_modifying.end(), action.first) == non_modifying.end());
598       }
599     }
600   }
601 }
602