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