# include # include # include # include # include # include /* program options */ # include # include /* log */ # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include "astroid.hh" # include "build_config.hh" # include "db.hh" # include "config.hh" # include "account_manager.hh" # include "actions/action_manager.hh" # include "actions/action.hh" # include "utils/date_utils.hh" # include "utils/utils.hh" # include "utils/resource.hh" # ifndef DISABLE_PLUGINS # include "plugin/manager.hh" # endif # include "poll.hh" /* UI */ # include "main_window.hh" # include "modes/thread_index/thread_index.hh" # include "modes/edit_message.hh" # include "modes/saved_searches.hh" /* gmime */ # include # include # include using namespace std; using namespace boost::filesystem; namespace logging = boost::log; namespace keywords = boost::log::keywords; namespace expr = boost::log::expressions; /* globally available static instance of the Astroid */ refptr Astroid::astroid; const char* const Astroid::Astroid::version = GIT_DESC; namespace Astroid { // Initialization and creation {{{ refptr Astroid::create () { return refptr (new Astroid ()); } void Astroid::init_console_log () { /* log to console */ logging::formatter format = expr::stream << "[" << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%H:%M:%S") << "] [" << expr::attr ("ThreadID") << "] [M] [" << logging::trivial::severity << "] " << expr::smessage ; logging::add_console_log ()->set_formatter (format); } void Astroid::init_sys_log () { typedef logging::sinks::synchronous_sink< logging::sinks::syslog_backend > sink_t; // Create a backend boost::shared_ptr< logging::sinks::syslog_backend > backend(new logging::sinks::syslog_backend( keywords::facility = logging::sinks::syslog::user, keywords::use_impl = logging::sinks::syslog::native, keywords::ident = log_ident )); // Set the straightforward level translator for the "Severity" attribute of type int backend->set_severity_mapper( logging::sinks::syslog::direct_severity_mapping< int >("Severity")); // Wrap it into the frontend and register in the core. // The backend requires synchronization in the frontend. logging::core::get()->add_sink(boost::make_shared< sink_t >(backend)); } Astroid::Astroid () : Gtk::Application("org.astroid", Gio::APPLICATION_HANDLES_OPEN | Gio::APPLICATION_HANDLES_COMMAND_LINE) { setlocale (LC_ALL, ""); Glib::init (); /* user agent */ user_agent = ustring::compose ("astroid/%1 (https://github.com/astroidmail/astroid)", Astroid::version); /* gmime settings */ g_mime_init (); /* options */ desc.add_options () ( "help,h", "print this help message") ( "config,c", po::value(), "config file, default: $XDG_CONFIG_HOME/astroid/config") ( "new-config,n", "make new default config, then exit") # ifdef DEBUG ( "test-config,t", "use test config (same as used when tests are run), only makes sense from the source root") # endif ( "mailto,m", po::value(), "compose mail with mailto url or address") ( "no-auto-poll", "do not poll automatically") ( "disable-log", "disable logging") ( "log-stdout", "log to stdout regardless of configuration") ( "log-level", po::value(), "override configured logging level (trace, debug, info, warning, error, fatal)") ( "start-polling", "indicate that external polling (external notmuch db R/W operations) starts") ( "stop-polling", "indicate that external polling stops") ( "refresh", po::value(), "refresh messages changed since lastmod") # ifndef DISABLE_PLUGINS ( "disable-plugins", "disable plugins"); # else ; # endif } // }}} int Astroid::run (int argc, char **argv) { // {{{ LOG (info) << "welcome to astroid! - " << Astroid::version; register_application (); Resource::init (argv[0]); po::variables_map vm; bool show_help = false; try { po::store ( po::parse_command_line (argc, argv, desc), vm ); } catch (po::unknown_option &ex) { LOG (error) << "unknown option" << endl; LOG (error) << ex.what() << endl; show_help = true; } show_help |= vm.count("help"); bool test_config = vm.count("test-config"); if (show_help) { cout << desc << endl; exit (0); } logging::add_common_attributes (); if (vm.count ("log-stdout")) { log_stdout = true; init_console_log (); } if (vm.count ("disable-log")) { logging::core::get()->set_logging_enabled (false); disable_log = true; } /* make new config {{{ */ if (vm.count("new-config")) { if (test_config) { LOG (error) << "--new-config cannot be specified together with --test-config."; exit (1); } LOG (info) << "creating new config.."; ustring cnf; Config ncnf (false, true); if (vm.count("config")) { cnf = vm["config"].as().c_str(); if (exists (path(cnf))) { LOG (error) << "the config file: " << cnf << " already exists."; exit (1); } ncnf.std_paths.config_file = path(cnf); } else { /* use default */ if (exists(ncnf.std_paths.config_file)) { LOG (error) << "the config file: " << ncnf.std_paths.config_file.c_str() << " already exists."; exit (1); } } LOG (info) << "writing default config to: " << ncnf.std_paths.config_file.c_str(); ncnf.load_config (true); ncnf.write_back_config (); exit (0); } // }}} if (!is_remote ()) { /* load config */ if (vm.count("config")) { if (test_config) { LOG (error) << "--config cannot be specified together with --test-config."; exit (1); } LOG (info) << "astroid: loading config: " << vm["config"].as().c_str(); m_config = new Config (vm["config"].as().c_str()); } else { if (test_config) { m_config = new Config (true); } else { m_config = new Config (); } } /* setting up loggers */ if (config ("astroid.log").get("stdout") && !vm.count ("log-stdout")) { init_console_log (); log_stdout = true; LOG (debug) << "log: stdout: yes"; } if (config ("astroid.log").get ("syslog")) { init_sys_log (); log_syslog = true; LOG (debug) << "log: syslog: yes"; } if (vm.count ("log-level")) { log_level = vm["log-level"].as (); } else { log_level = config ("astroid.log").get ("level"); } /* Non existing llevel in map will be silently ignored */ logging::core::get()->set_filter (logging::trivial::severity >= sevmap[log_level]); string charset; Glib::get_charset(charset); LOG (debug) << "utf8: " << Glib::get_charset () << ", " << charset; LOG (info) << "log: level: " << log_level; if (!Glib::get_charset()) { LOG (error) << "astroid needs an UTF-8 locale! this is probably not going to work."; } if (vm.count("start-polling") || vm.count("stop-polling") || vm.count ("refresh")) { LOG (error) << "--start-polling, --stop-polling or --refresh can only be specifed when there is already a running astroid instance."; exit (1); } _hint_level = config ("astroid.hints").get ("level"); /* set up classes */ Date::init (); Utils::init (); /* Initialize Db and check if it has been set up */ try { Db::init (); Db d; d.get_revision (); } catch (database_error &ex) { LOG (error) << "db: failed to open database, please check the manual if everything is set up correctly: " << ex.what (); Gtk::MessageDialog md ("Astroid: Failed to open database", false, Gtk::MESSAGE_ERROR); md.set_secondary_text (ustring::compose ("Please check the documentation if you have set up astroid and notmuch correctly.\n Error: %1", ex.what ()), true); md.run (); on_quit (); return 1; } Keybindings::init (); SavedSearches::init (); /* set up accounts */ accounts = new AccountManager (); # ifndef DISABLE_PLUGINS /* set up plugins */ bool disable_plugins = vm.count ("disable-plugins"); plugin_manager = new PluginManager (disable_plugins, in_test ()); plugin_manager->astroid_extension = new PluginManager::AstroidExtension (this); # endif /* set up global actions */ actions = new ActionManager (); /* set up poller */ bool no_auto_poll = false; if (vm.count ("no-auto-poll")) { LOG (info) << "astroid: automatic polling is off."; no_auto_poll = true; } poll = new Poll (!no_auto_poll); Gtk::Application::run (argc, argv); on_quit (); } else { Gtk::Application::run (argc, argv); } return 0; } // }}} const boost::property_tree::ptree& Astroid::config (const std::string& id) const { return m_config->config.get_child(id); } const boost::property_tree::ptree& Astroid::notmuch_config () const { return m_config->notmuch_config; } const StandardPaths& Astroid::standard_paths() const { return m_config->std_paths; } RuntimePaths& Astroid::runtime_paths() const { return m_config->run_paths; } bool Astroid::has_notmuch_config () { return m_config->has_notmuch_config; } void Astroid::main_test () { // {{{ init_console_log (); m_config = new Config (true); /* set up static classes */ Date::init (); Utils::init (); Db::init (); SavedSearches::init (); /* set up accounts */ accounts = new AccountManager (); # ifndef DISABLE_PLUGINS /* set up plugins */ plugin_manager = new PluginManager (false, true); plugin_manager->astroid_extension = new PluginManager::AstroidExtension (this); # endif /* set up contacts */ //contacts = new Contacts (); /* set up global actions */ actions = new ActionManager (); /* set up poller */ poll = new Poll (false); } // }}} bool Astroid::in_test () { return m_config->test; } void Astroid::on_quit () { LOG (debug) << "astroid: quitting.."; if (poll && poll->get_auto_poll ()) poll->toggle_auto_poll (); if (poll) poll->close (); if (actions) actions->close (); SavedSearches::destruct (); # ifndef DISABLE_PLUGINS if (plugin_manager && plugin_manager->astroid_extension) delete plugin_manager->astroid_extension; if (plugin_manager) delete plugin_manager; # endif LOG (info) << "astroid: goodbye!"; } Astroid::~Astroid () { if (accounts) delete accounts; if (m_config) delete m_config; if (poll) { poll->close (); delete poll; } if (actions) { actions->close (); delete actions; } } int Astroid::on_command_line (const refptr & cmd) { char ** argv; int argc; bool new_window = true; argv = cmd->get_arguments (argc); if (get_windows().empty ()) { activate (); new_window = false; } /* handling command line arguments that may be passed from secondary * instances as well */ if (argc > 0) { po::variables_map vm; try { po::store ( po::parse_command_line (argc, argv, desc), vm ); } catch (po::unknown_option &ex) { LOG (error) << "unknown option" << endl; LOG (error) << ex.what() << endl; return 1; } if (vm.count("mailto")) { ustring mailtourl = vm["mailto"].as(); send_mailto (mailtourl); new_window = false; } if ((vm.count ("start-polling") ? 1:0) + (vm.count ("stop-polling") ? 1:0) + (vm.count("refresh") ? 1:0) > 1) { LOG (error) << "only one of --start-polling, --stop-polling and --refresh should be specified"; return 1; } if (vm.count ("start-polling")) { poll->start_polling (); new_window = false; } else if (vm.count ("stop-polling")) { poll->stop_polling (); new_window = false; } else if (vm.count ("refresh")) { unsigned long last = vm["refresh"].as (); poll->refresh ( last ); new_window = false; } } if (new_window) activate (); return 0; } MainWindow * Astroid::open_new_window (bool open_defaults) { LOG (info) << "astroid: starting a new window.."; /* set up a new main window */ /* start up default window with default buffers */ MainWindow * mw = new MainWindow (); // is freed / destroyed by application if (open_defaults) { if (config ("saved_searches").get("show_on_startup")) { Mode * s = (Mode *) new SavedSearches (mw); s->invincible = true; mw->add_mode (s); } # ifndef DISABLE_PLUGINS auto queries = plugin_manager->astroid_extension->get_queries (); LOG (info) << "extension queries: " << queries.size(); if (queries.size() > 0) { for (auto query : queries) { Mode * ti = new ThreadIndex(mw, query.second, query.first); ti->invincible = true; mw->add_mode(ti); } } else { # endif ptree qpt = config ("startup.queries"); for (const auto &kv : qpt) { ustring name = kv.first; ustring query = kv.second.data(); LOG (info) << "got query: " << name << ": " << query; Mode * ti = new ThreadIndex (mw, query, name); ti->invincible = true; // set startup queries to be invincible mw->add_mode (ti); } # ifndef DISABLE_PLUGINS } # endif mw->set_active (0); } mw->signal_delete_event ().connect ( sigc::bind ( sigc::mem_fun(*this, &Astroid::on_window_close), mw)); add_window (*mw); mw->show_all (); return mw; } bool Astroid::on_window_close (GdkEventAny *, MainWindow * mw) { // application automatically removes window // https://mail.gnome.org/archives/gtkmm-list/2004-March/msg00282.html delete mw; return false; // default Gtk handler destroys window } void Astroid::on_activate () { open_new_window (); } void Astroid::send_mailto (ustring url) { LOG (info) << "astroid: mailto: " << url; MainWindow * mw = (MainWindow*) get_windows ()[0]; SoupURI *uri = soup_uri_new(url.c_str()); if (SOUP_URI_IS_VALID(uri)) { /* we got an mailto url */ ustring from, to, cc, bcc, subject, body; to = soup_uri_decode (soup_uri_get_path (uri)); const char * soup_query = soup_uri_get_query (uri); if (soup_query) { std::istringstream query_string (soup_query); std::string keyval; while (std::getline(query_string, keyval, '&')) { ustring::size_type pos = keyval.find ("="); ustring key = keyval.substr (0, pos); key = key.lowercase (); ustring val = soup_uri_decode (keyval.substr (pos+1).c_str()); if (key == "from") { from = ustring (val); } else if (key == "cc") { cc = ustring (val); } else if (key == "bcc") { bcc = ustring (val); } else if (key == "subject" ) { subject = ustring (val); } else if (key == "body") { body = ustring (val); } } } mw->add_mode (new EditMessage (mw, to, from, cc, bcc, subject, body)); } else { /* we probably just got the address on the cmd line */ mw->add_mode (new EditMessage (mw, url)); } soup_uri_free (uri); } int Astroid::hint_level () { return _hint_level; } }