<commit_before>/*
 * Copyright (c) 2016-2017 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/*
 * TODO: hint to inspect.
 * TODO: automatically detach the inspector when it is opened with "Inspect element".
 * TODO: support marks.
 *
 * TODO: private browsing.
 *
 * TODO: hide hovered link when the text entry is shown?
 *
 * FIXME: angular form needs the typing action to be done in order to submit: https://grafana.int.adgear.com/
 *
 * FIXME: seems slower when running as normal user (and faster as root), so perhaps the config slow
 * it down. Looks like it is slowed down by the hard drive.
 *
 * TODO: switch to relm-epoll, a crate that polls epoll from glib.
 *
 * TODO: downloading a non-existing file (http://download.microsoft.com/download/8/8/8/888f34b7-4f54-4f06-8dac-fa29b19f33dd/msxml3.msi) causes an error.
 * FIXME: cannot download this link: https://link.springer.com/content/pdf/10.1007%2F978-3-540-78800-3_24.pdf
 *
 * TODO: might not require the syntax with (relm) if we emit the signal normally.
 *
 * TODO: add a kill command to close a webview without try_close().
 *
 * TODO: remove dependencies like tokio.
 *
 * FIXME: windows opened by JavaScript cannot be claused: probably need to set the settings
 * javascript_can_close_windows when the window was opened by JS.
 *
 * FIXME: scroll on
 * https://tutorial.ponylang.org/getting-started/how-it-works.html
 * https://www.fstar-lang.org/tutorial/
 * https://www.cnet.com/special-reports/mozilla-firefox-fights-back-against-google-chrome/
 * FIXME: scroll percentage wrong on:
 * file:///home/bouanto
 * FIXME: scroll percentage is all on some pages.
 *
 * FIXME: focus does not go to first element (wiktionary). Compare the position instead of element
 * order.
 *
 * TODO: save the current URLs of every window in case of a crash.
 * TODO: command to restore the last closed window.
 *
 * TODO: add command (;f) to change the active element.
 *
 * TODO: use connect_load_failed_with_tls_errors() to show the URL in red in case there's a TLS
 * error (test with https://www.pcwebshop.co.uk/)
 *
 * FIXME: using Escape in insert mode triggers Escape in the web page (in Scala doc: http://www.scala-lang.org/api/current/).
 * FIXME: sometimes, the page load percentage stays shown after the load is finished.
 *
 * TODO: remove unwrap() and expect() in dependencies (relm, mg).
 * TODO: remove every unwrap().
 *
 * FIXME: on http://ticki.github.io/blog/how-lz4-works/, clicking on the other article links at the
 * bottom redirect to a blank page.
 *
 * TODO: attempt to migrate to gecko.
 *
 * FIXME: file download should not navigate to a new page (see duckduckgo.com, because it uses a
 * redirection, this was working fine before).
 *
 * FIXME Error on exit: Io(Error { repr: Custom(Custom { kind: Other, error: Error { domain: 1537, code: 8, message: "Connexion ré-initialisée par le correspondant" } }) })
 *
 * TODO: method called by message should not take ownership of values.
 * TODO: add documentation for every method.
 * TODO: refactor to remove every use of callbacks in relm widgets.
 * FIXME: wrong scroll percentage on https://mail.gnome.org/archives/gtk-devel-list/2001-November/msg00204.html
 * FIXME: Invalid read of size 8 (see valgrind).
 *
 * TODO: use asynchronous communication with pass?
 *
 * FIXME: cannot scroll on https://translate.google.com/translate?hl=fr&sl=es&tl=en&u=http%3A%2F%2Fblog.bltavares.com%2F2017%2F01%2F18%2Fexpressando_o_dominio_atraves_do_sistema_de_tipos%2F (find the closest node which can scroll: if more than one are found at the same level, use the largest)
 * TODO: add tests for the scrolling element.
 *
 * TODO: cli argument for the abstract namespace of the unix domain socket.
 * TODO: cli argument for minimal log level.
 * TODO: plugin for a read mode (remove all useless stuff in the page, only keep the text).
 * FIXME: negative zoom level.
 * FIXME: scrolling hides the info message.
 * FIXME: scrolling goes too far when zoomed in.
 * FIXME: hint not working on http://bibliotheque.ville.brossard.qc.ca/
 * TODO: modal dialog for authentication.
 *
 * TODO: add a --redirect option or redirect command to bypass the adblocker.
 * TODO: add a command to do the redirections to avoid being blocked by the ad blocker.
 *
 * TODO: show an error for request blocked by host blocker (instead of a white page).
 * TODO: block cookie banner.
 * FIXME: the insert mode sometimes disable itself (using rofi-pass). For instance, on https://courrielweb.videotron.com/cw/legacyLoginResidentiel.action
 *
 * FIXME: hint on wrong location on the warning of https://zestedesavoir.com/tutoriels/1642/les-soins-non-urgents/#2-traiter-une-plaie
 * FIXME: hints on wrong locations on http://www.mensacanada.org/contact/ and on https://www.ralfj.de/blog/2017/06/06/MIR-semantics.html
 * FIXME: crash when attempting to open a PDF on Air Transat, Mon Dossier.
 * TODO: auto-delete tags.
 * FIXME: hitting the 's' key on https://developer.github.com/ scroll to the search bar.
 * FIXME: scrolling not working on http://www.freenom.com/en/termsandconditions.html
 * TODO: auto-detect static bars at the bottom/top of webpages to scroll less when one is present.
 *
 * FIXME: saving empty credentials on https://lichess4545.slack.com/
 *
 * TODO: sort bookmark completion with number of access (the most accessed URLs come first, then by
 * alphebetical order) and perhaps also by relevance (like the percentage of tags/words that
 * matches).
 * TODO: automatically propose tags when editting bookmark tags (fetch them from the webpage, <meta
 * property="og:article:tag"/> is a start).
 *
 * TODO: hint file input.
 * TODO: find a way to avoid having hints on top of each other.
 *
 * TODO: check if an extension process crashing causes issues in other extension process.
 * FIXME: missing hints on duckduckgo.com menu (caused by CSS3 transform).
 *
 * TODO: show an error when there are no hints.
 * FIXME: show hints for element with click event.
 *
 * TODO: #[default(value)] attribute for settings.
 *
 * FIXME: ctrl-/ should not trigger the mapping for /.
 * TODO: allow using Backspace to remove the last hint character.
 * TODO: hide HTML in title/bookmarks?
 * FIXME: the window sometimes does not hide when quitting: it hides when a new window is shown.
 * FIXME: hover does not always work (usherbrooke.ca) (perhaps trigger real click/hover mouse events in GTK+ instead of using DOM while still using the DOM focus function).
 * FIXME: an element visible but whose top-left corner is not shown wont get an hint.
 * FIXME: sometimes does not go to insert mode after focusing first input (youtube.com).
 * TODO: generate the default files from the code (for instance, from default settings) instead of
 * copying predefined files.
 * TODO: shortcut to open the selected (searched) word.
 * TODO: support CTRL-Z in input elements.
 * TODO: allow paste from selection clipboard (if the other is empty or with another shortcut?).
 * TODO: message when search fails (and when it wraps to the start/end).
 * TODO: hide the scrollbars?
 * FIXME: select dropdown can open in the other screen (webkit2gtk bug, move the cursor before clicking?).
 * TODO: unselect text when focusing a field.
 * TODO: add a passthrough mode.
 * TODO: add help text for commands and settings.
 * TODO: handle network errors.
 * TODO: show a star next to the url of a bookmarked site.
 * TODO: find a way to recover accidently removed bookmarks (shortcut to read bookmarks from a
 * stack of removed bookmarks? another shortcut which is harder to do C-S-d?).
 * TODO: warn when adding a bookmarks that has the same URL as another one, except with(out) a /.
 * TODO: preferred languages.
 * TODO: store cache.
 * TODO: add a command to delete history, …
 * TODO: NoScript.
 * TODO: open textarea in text editor.
 * TODO: add option to use light theme variant instead of dark variant.
 * TODO: soft scrolling (to avoid flickering for fixed elements, set_enable_smooth_scrolling).
 * TODO: do not search for the empty string, only disable the current search to allow continuing
 * the search on another page.
 * FIXME: do not show (or move) hints hidden by another element (branch button on GitHub).
 * TODO: show source.
 * TODO: prevent videos from autoplaying.
 * TODO: copier plugin (word, line, sentense, block, links…).
 * TODO: separate config options in section (like webkit.enable-java instead of webkit-enable-java).
 * TODO: i18n.
 * TODO: plugin to block modal JavaScript dialog (https://www.sitepoint.com/community/t/ie-hover-trigger/69968).
 * TODO: plugin to prevent a menu bar in a website to appear from scrolling up (ou keep fixed elements at the top)
 * (example: https://www.fastcoexist.com/3027876/millennials-dont-care-about-owning-cars-and-car-makers-cant-figure-out-why).
 * FIXME: popup not blocked on bnc.ca.
 * TODO: do not consider right-click open in new window as a popup.
 * TODO: delete the files opened (perhaps by placing them in a temporary directory).
 *
 * TODO: Rust-based plugin architecture (based on webkit web extensions).
 * TODO: command to update adblocker hosts file.
 * TODO: option to disable the adblocker.
 * TODO: block ads coming from websocket.
 * TODO: create a whitelist-based adblocker.
 * TODO: remove the title bar of the inspector (window decorated property).
 * TODO: hide the hints when activating a hint.
 * TODO: in command and input mode, put the messages into a queue.
 * TODO: ask confirmation before submitting again the same form.
 * TODO: do not hard-code the extension directory: use the one provided by cargo.
 * TODO: find a way to install the titanium web extension library on cargo install.
 * TODO: activate insert mode after focusing a text element (disable insert mode when focus is lost).
 * TODO: show URL in title when the title is not available.
 * TODO: add a validator for the file input (browse): check that a file is selected (and not a
 * directory), check that the input file exists.
 * FIXME: prompt slow to show (it seems to slow down when there are other events waiting: try
 * starting a download when the page is still loading).
 * FIXME: issues when multiple input are shown (they must be inserted in a queue and shown one at a
 * time, or perhaps just using a blocking input for popups will do it).
 *
 * TODO: add tests.
 */

//! Titanium is a webkit2 keyboard-driven web browser.

#![feature(proc_macro)]

#![warn(
    missing_docs,
    trivial_casts,
    trivial_numeric_casts,
    unused_extern_crates,
    unused_import_braces,
    unused_qualifications,
)]

extern crate cairo;
extern crate fg_uds;
extern crate futures;
extern crate futures_glib;
extern crate gdk;
extern crate glib;
extern crate gtk;
extern crate gumdrop;
#[macro_use]
extern crate gumdrop_derive;
#[cfg(test)]
extern crate libxdo;
#[macro_use]
extern crate log;
extern crate log_panics;
#[macro_use]
extern crate mg;
extern crate mg_settings;
#[macro_use]
extern crate mg_settings_macros;
extern crate nix;
extern crate number_prefix;
extern crate open;
extern crate password_store;
#[macro_use]
extern crate relm;
extern crate relm_attributes;
#[macro_use]
extern crate relm_derive;
extern crate rusqlite;
extern crate simplelog;
extern crate syslog;
#[cfg(test)]
extern crate tempdir;
extern crate tempfile;
extern crate titanium_common;
extern crate tokio_io;
extern crate tokio_serde_bincode;
extern crate url;
extern crate webkit2gtk;
extern crate xdg;

mod app;
mod bookmarks;
mod clipboard;
mod commands;
mod completers;
mod config_dir;
mod download;
mod download_view;
mod download_list_view;
mod errors;
mod file;
mod message_server;
mod pass_manager;
mod popup_manager;
mod settings;
mod stylesheet;
mod urls;
mod webview;

use std::env::args;

use gumdrop::Options;
use log::LogLevel::Error;
use simplelog::{Config, LogLevelFilter, TermLogger};
use syslog::Facility;

use app::APP_NAME;
use message_server::create_message_server;

const INVALID_UTF8_ERROR: &str = "invalid utf-8 string";

#[derive(Debug, Default, Options)]
struct Args {
    #[options(help="The configuration directory.")]
    config: Option<String>,
    #[options(help="Print help message.")]
    help: bool,
    #[options(help="Show the log messages.")]
    log: bool,
    #[options(free)]
    url: Vec<String>,
}

fn main() {
    futures_glib::init();
    gtk::init().unwrap(); // TODO: this is called twice. Remove?

    let args: Vec<_> = args().collect();

    let args = match Args::parse_args_default(&args[1..]) {
        Ok(options) => options,
        Err(error) => {
            println!("{}: {}", APP_NAME, error);
            println!();
            println!("{}", Args::usage());
            return;
        },
    };

    if args.help {
        println!("Usage: {} [OPTIONS] [ARGUMENTS]", APP_NAME);
        println!();
        println!("{}", Args::usage());
    }
    else {
        init_logging(args.log);

        let _message_server = create_message_server(args.url, args.config);
        gtk::main();
    }
}

fn init_logging(log_to_term: bool) {
    if log_to_term {
        let config = Config {
            time: Some(Error),
            level: Some(Error),
            target: None,
            location: None,
        };
        TermLogger::init(LogLevelFilter::max(), config).unwrap();
    }
    else {
        syslog::init_unix(Facility::LOG_USER, LogLevelFilter::max()).unwrap();
    }
    log_panics::init();
}
<commit_msg>Remove comment<commit_after>/*
 * Copyright (c) 2016-2017 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/*
 * TODO: automatically detach the inspector when it is opened with "Inspect element".
 * TODO: support marks.
 *
 * TODO: private browsing.
 *
 * TODO: hide hovered link when the text entry is shown?
 *
 * FIXME: angular form needs the typing action to be done in order to submit: https://grafana.int.adgear.com/
 *
 * FIXME: seems slower when running as normal user (and faster as root), so perhaps the config slow
 * it down. Looks like it is slowed down by the hard drive.
 *
 * TODO: switch to relm-epoll, a crate that polls epoll from glib.
 *
 * TODO: downloading a non-existing file (http://download.microsoft.com/download/8/8/8/888f34b7-4f54-4f06-8dac-fa29b19f33dd/msxml3.msi) causes an error.
 * FIXME: cannot download this link: https://link.springer.com/content/pdf/10.1007%2F978-3-540-78800-3_24.pdf
 *
 * TODO: might not require the syntax with (relm) if we emit the signal normally.
 *
 * TODO: add a kill command to close a webview without try_close().
 *
 * TODO: remove dependencies like tokio.
 *
 * FIXME: windows opened by JavaScript cannot be claused: probably need to set the settings
 * javascript_can_close_windows when the window was opened by JS.
 *
 * FIXME: scroll on
 * https://tutorial.ponylang.org/getting-started/how-it-works.html
 * https://www.fstar-lang.org/tutorial/
 * https://www.cnet.com/special-reports/mozilla-firefox-fights-back-against-google-chrome/
 * FIXME: scroll percentage wrong on:
 * file:///home/bouanto
 * FIXME: scroll percentage is all on some pages.
 *
 * FIXME: focus does not go to first element (wiktionary). Compare the position instead of element
 * order.
 *
 * TODO: save the current URLs of every window in case of a crash.
 * TODO: command to restore the last closed window.
 *
 * TODO: add command (;f) to change the active element.
 *
 * TODO: use connect_load_failed_with_tls_errors() to show the URL in red in case there's a TLS
 * error (test with https://www.pcwebshop.co.uk/)
 *
 * FIXME: using Escape in insert mode triggers Escape in the web page (in Scala doc: http://www.scala-lang.org/api/current/).
 * FIXME: sometimes, the page load percentage stays shown after the load is finished.
 *
 * TODO: remove unwrap() and expect() in dependencies (relm, mg).
 * TODO: remove every unwrap().
 *
 * FIXME: on http://ticki.github.io/blog/how-lz4-works/, clicking on the other article links at the
 * bottom redirect to a blank page.
 *
 * TODO: attempt to migrate to gecko.
 *
 * FIXME: file download should not navigate to a new page (see duckduckgo.com, because it uses a
 * redirection, this was working fine before).
 *
 * FIXME Error on exit: Io(Error { repr: Custom(Custom { kind: Other, error: Error { domain: 1537, code: 8, message: "Connexion ré-initialisée par le correspondant" } }) })
 *
 * TODO: method called by message should not take ownership of values.
 * TODO: add documentation for every method.
 * TODO: refactor to remove every use of callbacks in relm widgets.
 * FIXME: wrong scroll percentage on https://mail.gnome.org/archives/gtk-devel-list/2001-November/msg00204.html
 * FIXME: Invalid read of size 8 (see valgrind).
 *
 * TODO: use asynchronous communication with pass?
 *
 * FIXME: cannot scroll on https://translate.google.com/translate?hl=fr&sl=es&tl=en&u=http%3A%2F%2Fblog.bltavares.com%2F2017%2F01%2F18%2Fexpressando_o_dominio_atraves_do_sistema_de_tipos%2F (find the closest node which can scroll: if more than one are found at the same level, use the largest)
 * TODO: add tests for the scrolling element.
 *
 * TODO: cli argument for the abstract namespace of the unix domain socket.
 * TODO: cli argument for minimal log level.
 * TODO: plugin for a read mode (remove all useless stuff in the page, only keep the text).
 * FIXME: negative zoom level.
 * FIXME: scrolling hides the info message.
 * FIXME: scrolling goes too far when zoomed in.
 * FIXME: hint not working on http://bibliotheque.ville.brossard.qc.ca/
 * TODO: modal dialog for authentication.
 *
 * TODO: add a --redirect option or redirect command to bypass the adblocker.
 * TODO: add a command to do the redirections to avoid being blocked by the ad blocker.
 *
 * TODO: show an error for request blocked by host blocker (instead of a white page).
 * TODO: block cookie banner.
 * FIXME: the insert mode sometimes disable itself (using rofi-pass). For instance, on https://courrielweb.videotron.com/cw/legacyLoginResidentiel.action
 *
 * FIXME: hint on wrong location on the warning of https://zestedesavoir.com/tutoriels/1642/les-soins-non-urgents/#2-traiter-une-plaie
 * FIXME: hints on wrong locations on http://www.mensacanada.org/contact/ and on https://www.ralfj.de/blog/2017/06/06/MIR-semantics.html
 * FIXME: crash when attempting to open a PDF on Air Transat, Mon Dossier.
 * TODO: auto-delete tags.
 * FIXME: hitting the 's' key on https://developer.github.com/ scroll to the search bar.
 * FIXME: scrolling not working on http://www.freenom.com/en/termsandconditions.html
 * TODO: auto-detect static bars at the bottom/top of webpages to scroll less when one is present.
 *
 * FIXME: saving empty credentials on https://lichess4545.slack.com/
 *
 * TODO: sort bookmark completion with number of access (the most accessed URLs come first, then by
 * alphebetical order) and perhaps also by relevance (like the percentage of tags/words that
 * matches).
 * TODO: automatically propose tags when editting bookmark tags (fetch them from the webpage, <meta
 * property="og:article:tag"/> is a start).
 *
 * TODO: hint file input.
 * TODO: find a way to avoid having hints on top of each other.
 *
 * TODO: check if an extension process crashing causes issues in other extension process.
 * FIXME: missing hints on duckduckgo.com menu (caused by CSS3 transform).
 *
 * TODO: show an error when there are no hints.
 * FIXME: show hints for element with click event.
 *
 * TODO: #[default(value)] attribute for settings.
 *
 * FIXME: ctrl-/ should not trigger the mapping for /.
 * TODO: allow using Backspace to remove the last hint character.
 * TODO: hide HTML in title/bookmarks?
 * FIXME: the window sometimes does not hide when quitting: it hides when a new window is shown.
 * FIXME: hover does not always work (usherbrooke.ca) (perhaps trigger real click/hover mouse events in GTK+ instead of using DOM while still using the DOM focus function).
 * FIXME: an element visible but whose top-left corner is not shown wont get an hint.
 * FIXME: sometimes does not go to insert mode after focusing first input (youtube.com).
 * TODO: generate the default files from the code (for instance, from default settings) instead of
 * copying predefined files.
 * TODO: shortcut to open the selected (searched) word.
 * TODO: support CTRL-Z in input elements.
 * TODO: allow paste from selection clipboard (if the other is empty or with another shortcut?).
 * TODO: message when search fails (and when it wraps to the start/end).
 * TODO: hide the scrollbars?
 * FIXME: select dropdown can open in the other screen (webkit2gtk bug, move the cursor before clicking?).
 * TODO: unselect text when focusing a field.
 * TODO: add a passthrough mode.
 * TODO: add help text for commands and settings.
 * TODO: handle network errors.
 * TODO: show a star next to the url of a bookmarked site.
 * TODO: find a way to recover accidently removed bookmarks (shortcut to read bookmarks from a
 * stack of removed bookmarks? another shortcut which is harder to do C-S-d?).
 * TODO: warn when adding a bookmarks that has the same URL as another one, except with(out) a /.
 * TODO: preferred languages.
 * TODO: store cache.
 * TODO: add a command to delete history, …
 * TODO: NoScript.
 * TODO: open textarea in text editor.
 * TODO: add option to use light theme variant instead of dark variant.
 * TODO: soft scrolling (to avoid flickering for fixed elements, set_enable_smooth_scrolling).
 * TODO: do not search for the empty string, only disable the current search to allow continuing
 * the search on another page.
 * FIXME: do not show (or move) hints hidden by another element (branch button on GitHub).
 * TODO: show source.
 * TODO: prevent videos from autoplaying.
 * TODO: copier plugin (word, line, sentense, block, links…).
 * TODO: separate config options in section (like webkit.enable-java instead of webkit-enable-java).
 * TODO: i18n.
 * TODO: plugin to block modal JavaScript dialog (https://www.sitepoint.com/community/t/ie-hover-trigger/69968).
 * TODO: plugin to prevent a menu bar in a website to appear from scrolling up (ou keep fixed elements at the top)
 * (example: https://www.fastcoexist.com/3027876/millennials-dont-care-about-owning-cars-and-car-makers-cant-figure-out-why).
 * FIXME: popup not blocked on bnc.ca.
 * TODO: do not consider right-click open in new window as a popup.
 * TODO: delete the files opened (perhaps by placing them in a temporary directory).
 *
 * TODO: Rust-based plugin architecture (based on webkit web extensions).
 * TODO: command to update adblocker hosts file.
 * TODO: option to disable the adblocker.
 * TODO: block ads coming from websocket.
 * TODO: create a whitelist-based adblocker.
 * TODO: remove the title bar of the inspector (window decorated property).
 * TODO: hide the hints when activating a hint.
 * TODO: in command and input mode, put the messages into a queue.
 * TODO: ask confirmation before submitting again the same form.
 * TODO: do not hard-code the extension directory: use the one provided by cargo.
 * TODO: find a way to install the titanium web extension library on cargo install.
 * TODO: activate insert mode after focusing a text element (disable insert mode when focus is lost).
 * TODO: show URL in title when the title is not available.
 * TODO: add a validator for the file input (browse): check that a file is selected (and not a
 * directory), check that the input file exists.
 * FIXME: prompt slow to show (it seems to slow down when there are other events waiting: try
 * starting a download when the page is still loading).
 * FIXME: issues when multiple input are shown (they must be inserted in a queue and shown one at a
 * time, or perhaps just using a blocking input for popups will do it).
 *
 * TODO: add tests.
 */

//! Titanium is a webkit2 keyboard-driven web browser.

#![feature(proc_macro)]

#![warn(
    missing_docs,
    trivial_casts,
    trivial_numeric_casts,
    unused_extern_crates,
    unused_import_braces,
    unused_qualifications,
)]

extern crate cairo;
extern crate fg_uds;
extern crate futures;
extern crate futures_glib;
extern crate gdk;
extern crate glib;
extern crate gtk;
extern crate gumdrop;
#[macro_use]
extern crate gumdrop_derive;
#[cfg(test)]
extern crate libxdo;
#[macro_use]
extern crate log;
extern crate log_panics;
#[macro_use]
extern crate mg;
extern crate mg_settings;
#[macro_use]
extern crate mg_settings_macros;
extern crate nix;
extern crate number_prefix;
extern crate open;
extern crate password_store;
#[macro_use]
extern crate relm;
extern crate relm_attributes;
#[macro_use]
extern crate relm_derive;
extern crate rusqlite;
extern crate simplelog;
extern crate syslog;
#[cfg(test)]
extern crate tempdir;
extern crate tempfile;
extern crate titanium_common;
extern crate tokio_io;
extern crate tokio_serde_bincode;
extern crate url;
extern crate webkit2gtk;
extern crate xdg;

mod app;
mod bookmarks;
mod clipboard;
mod commands;
mod completers;
mod config_dir;
mod download;
mod download_view;
mod download_list_view;
mod errors;
mod file;
mod message_server;
mod pass_manager;
mod popup_manager;
mod settings;
mod stylesheet;
mod urls;
mod webview;

use std::env::args;

use gumdrop::Options;
use log::LogLevel::Error;
use simplelog::{Config, LogLevelFilter, TermLogger};
use syslog::Facility;

use app::APP_NAME;
use message_server::create_message_server;

const INVALID_UTF8_ERROR: &str = "invalid utf-8 string";

#[derive(Debug, Default, Options)]
struct Args {
    #[options(help="The configuration directory.")]
    config: Option<String>,
    #[options(help="Print help message.")]
    help: bool,
    #[options(help="Show the log messages.")]
    log: bool,
    #[options(free)]
    url: Vec<String>,
}

fn main() {
    futures_glib::init();
    gtk::init().unwrap(); // TODO: this is called twice. Remove?

    let args: Vec<_> = args().collect();

    let args = match Args::parse_args_default(&args[1..]) {
        Ok(options) => options,
        Err(error) => {
            println!("{}: {}", APP_NAME, error);
            println!();
            println!("{}", Args::usage());
            return;
        },
    };

    if args.help {
        println!("Usage: {} [OPTIONS] [ARGUMENTS]", APP_NAME);
        println!();
        println!("{}", Args::usage());
    }
    else {
        init_logging(args.log);

        let _message_server = create_message_server(args.url, args.config);
        gtk::main();
    }
}

fn init_logging(log_to_term: bool) {
    if log_to_term {
        let config = Config {
            time: Some(Error),
            level: Some(Error),
            target: None,
            location: None,
        };
        TermLogger::init(LogLevelFilter::max(), config).unwrap();
    }
    else {
        syslog::init_unix(Facility::LOG_USER, LogLevelFilter::max()).unwrap();
    }
    log_panics::init();
}
<|endoftext|>


<commit_before>extern crate iron;
#[macro_use]
extern crate router;
extern crate staticfile;
extern crate mount;
extern crate urlencoded;
extern crate rustc_serialize;
extern crate html5ever;
extern crate regex;
extern crate uuid;

use std::net::SocketAddrV4;
use std::net::Ipv4Addr;
use std::path::Path;
use iron::prelude::*;
use iron::status;
use iron::headers::{ContentType};
use iron::modifiers::Header;
use iron::mime::Mime;
use staticfile::Static;
use mount::Mount;
use router::{Router};
use urlencoded::UrlEncodedQuery;
use urlencoded::UrlEncodedBody;
use std::str::FromStr;

#[macro_use]
extern crate string_cache;

extern crate pink_spider;

use pink_spider::error::Error;
use pink_spider::scraper::extract;
use pink_spider::model::{Track, Entry, Provider};
use rustc_serialize::json::{ToJson, Json};

static JSON: &'static str = "application/json";

pub fn index_entries(req: &mut Request) -> IronResult<Response> {
    let json_type = Header(ContentType(Mime::from_str(JSON).ok().unwrap()));
    let entries = Entry::find();
    let json_obj: Json   = entries.to_json();
    let json_str: String = json_obj.to_string();
    Ok(Response::with((status::Ok, json_type, json_str)))
}

pub fn playlistify(req: &mut Request) -> IronResult<Response> {
    let json_type = Header(ContentType(Mime::from_str("application/json").ok().unwrap()));
    match req.get_ref::<UrlEncodedQuery>() {
        Ok(ref params) => {
            match params.get("url") {
                Some(url) => {
                    let defaults = &vec!("false".to_string());
                    let force    = params.get("force").unwrap_or(defaults);
                    match find_or_playlistify_entry(&url[0], force.len() > 0 && &force[0] == "true") {
                        Ok(entry) => {
                            let json_obj: Json   = entry.to_json();
                            let json_str: String = json_obj.to_string();
                            Ok(Response::with((status::Ok, json_type, json_str)))
                        },
                        Err(e) => Ok(e.as_response())
                    }
                },
                None => Ok(Error::BadRequest.as_response())
            }
        }
        Err(_) => Ok(Error::BadRequest.as_response())
    }
}

pub fn find_or_playlistify_entry(url: &str, force: bool) -> Result<Entry, Error> {
    match Entry::find_by_url(url) {
        Ok(entry) => {
            println!("Get entry from database cache: {}", url);
            if force {
                println!("Update entry: {}", url);
                let entry = try!(playlistify_entry(entry));
                Ok(entry)
            } else {
                Ok(entry)
            }
        },
        Err(_) => {
            let entry = try!(Entry::create_by_url(url.to_string()));
            let entry = try!(playlistify_entry(entry));
            println!("Create new entry to database cache: {}", url);
            Ok(entry)
        },
    }
}

pub fn playlistify_entry(entry: Entry) -> Result<Entry, Error> {
    let mut e = entry.clone();
    let product = try!(extract(&entry.url));
    match product.og_obj {
        Some(og_obj) => {
            e.title       = Some(og_obj.title);
            e.description = og_obj.description;
            e.locale      = og_obj.locale;
            e.visual_url  = og_obj.images.first().map(|i| i.url.clone());
        },
        None => (),
    }
    for t in product.tracks {
        let track = try!(Track::find_or_create(t.provider,
                                               t.title,
                                               t.url,
                                               t.identifier));
        match entry.tracks.iter().find(|&t| t.id == track.id) {
            Some(_) => (),
            None    => e.add_track(track.clone())
        }
    }
    try!(e.save());
    Ok(e)
}

pub fn show_track(req: &mut Request) -> IronResult<Response> {
    let ref track_id = req.extensions.get::<Router>().unwrap()
                          .find("track_id").unwrap();
    let json_type = Header(ContentType(Mime::from_str("application/json").ok().unwrap()));
    match Track::find_by_id(track_id) {
        Ok(track) => {
            Ok(Response::with((status::Ok,
                               json_type,
                               track.to_json().to_string())))
        },
        Err(e) => Ok(e.as_response())
    }
}

pub fn show_track_by_provider_id(req: &mut Request) -> IronResult<Response> {
    let provider   = req.extensions.get::<Router>().unwrap().find("provider").unwrap();
    let identifier = req.extensions.get::<Router>().unwrap().find("id").unwrap();
    let json_type  = Header(ContentType(Mime::from_str("application/json").ok().unwrap()));
    let p = &Provider::new(provider.to_string());
    match Track::find_by(p, identifier) {
        Ok(track) => {
            Ok(Response::with((status::Ok,
                               json_type,
                               track.to_json().to_string())))
        },
        Err(e) => Ok(e.as_response())
    }
}

pub fn update_track(req: &mut Request) -> IronResult<Response> {
    let json_type = Header(ContentType(Mime::from_str("application/json").ok().unwrap()));
    fn param_as_string(req: &mut Request, key: &str) -> Option<String> {
        match req.get_ref::<UrlEncodedBody>() {
            Ok(ref params) => match params.get(key) {
                Some(val) => Some(val[0].clone()),
                None      => None
            },
            Err(_) => None
        }
    }

    fn query_as_string(req: &mut Request, key: &str) -> String {
        return req.extensions.get::<Router>().unwrap()
                  .find(key).unwrap().to_string();
    }

    match Track::find_by_id(&query_as_string(req, "track_id")) {
        Ok(mut track) => {
            match param_as_string(req, "title") {
                Some(title) => track.title = title,
                None        => println!("no title")
            }
            match param_as_string(req, "url") {
                Some(url) => track.url = url,
                None      => println!("no url")
            }
            match track.save() {
                Ok(_) => Ok(Response::with((status::Ok,
                                         json_type,
                                         track.to_json().to_string()))),
                Err(e) =>  Ok(e.as_response())
            }
        },
        Err(e) =>  {
            Ok(e.as_response())
        }
    }
}

pub fn main() {
    let path = Path::new("public");
    let mut mount = Mount::new();
    mount.mount("/web/", Static::new(Path::new(path)));
    let router = router!(
                         playlistify: get  "/playlistify"          => playlistify,
                          show_track: get  "/tracks/:track_id"     => show_track,
                        update_track: post "/tracks/:track_id"     => update_track,
           show_track_by_provider_id: get  "/tracks/:provider/:id" => show_track_by_provider_id,
                       index_entries: get  "/entries"              => index_entries,
                                 web: get  "/*"                    => mount,
    );
    let port_str = match std::env::var("PORT") {
        Ok(n)    => n,
        Err(_) => "8080".to_string()
    };
    let port: u16 = match port_str.trim().parse() {
        Ok(n) => n,
        Err(_) => {
            println!("Faild to parse port");
            return;
        }
    };
    println!("PORT {}", port_str);
    let ip = Ipv4Addr::new(0, 0, 0, 0);
    Iron::new(router).http(SocketAddrV4::new(ip, port)).unwrap();
}
<commit_msg>Implement GET `/tracks` api that returns tracks<commit_after>extern crate iron;
#[macro_use]
extern crate router;
extern crate staticfile;
extern crate mount;
extern crate urlencoded;
extern crate rustc_serialize;
extern crate html5ever;
extern crate regex;
extern crate uuid;

use std::net::SocketAddrV4;
use std::net::Ipv4Addr;
use std::path::Path;
use iron::prelude::*;
use iron::status;
use iron::headers::{ContentType};
use iron::modifiers::Header;
use iron::mime::Mime;
use staticfile::Static;
use mount::Mount;
use router::{Router};
use urlencoded::UrlEncodedQuery;
use urlencoded::UrlEncodedBody;
use std::str::FromStr;

#[macro_use]
extern crate string_cache;

extern crate pink_spider;

use pink_spider::error::Error;
use pink_spider::scraper::extract;
use pink_spider::model::{Track, Entry, Provider};
use rustc_serialize::json::{ToJson, Json};

static JSON: &'static str = "application/json";

pub fn index_entries(req: &mut Request) -> IronResult<Response> {
    let json_type = Header(ContentType(Mime::from_str(JSON).ok().unwrap()));
    let entries = Entry::find();
    let json_obj: Json   = entries.to_json();
    let json_str: String = json_obj.to_string();
    Ok(Response::with((status::Ok, json_type, json_str)))
}

pub fn index_tracks(req: &mut Request) -> IronResult<Response> {
    let json_type = Header(ContentType(Mime::from_str(JSON).ok().unwrap()));
    let tracks = Track::find();
    let json_obj: Json   = tracks.to_json();
    let json_str: String = json_obj.to_string();
    Ok(Response::with((status::Ok, json_type, json_str)))
}

pub fn playlistify(req: &mut Request) -> IronResult<Response> {
    let json_type = Header(ContentType(Mime::from_str("application/json").ok().unwrap()));
    match req.get_ref::<UrlEncodedQuery>() {
        Ok(ref params) => {
            match params.get("url") {
                Some(url) => {
                    let defaults = &vec!("false".to_string());
                    let force    = params.get("force").unwrap_or(defaults);
                    match find_or_playlistify_entry(&url[0], force.len() > 0 && &force[0] == "true") {
                        Ok(entry) => {
                            let json_obj: Json   = entry.to_json();
                            let json_str: String = json_obj.to_string();
                            Ok(Response::with((status::Ok, json_type, json_str)))
                        },
                        Err(e) => Ok(e.as_response())
                    }
                },
                None => Ok(Error::BadRequest.as_response())
            }
        }
        Err(_) => Ok(Error::BadRequest.as_response())
    }
}

pub fn find_or_playlistify_entry(url: &str, force: bool) -> Result<Entry, Error> {
    match Entry::find_by_url(url) {
        Ok(entry) => {
            println!("Get entry from database cache: {}", url);
            if force {
                println!("Update entry: {}", url);
                let entry = try!(playlistify_entry(entry));
                Ok(entry)
            } else {
                Ok(entry)
            }
        },
        Err(_) => {
            let entry = try!(Entry::create_by_url(url.to_string()));
            let entry = try!(playlistify_entry(entry));
            println!("Create new entry to database cache: {}", url);
            Ok(entry)
        },
    }
}

pub fn playlistify_entry(entry: Entry) -> Result<Entry, Error> {
    let mut e = entry.clone();
    let product = try!(extract(&entry.url));
    match product.og_obj {
        Some(og_obj) => {
            e.title       = Some(og_obj.title);
            e.description = og_obj.description;
            e.locale      = og_obj.locale;
            e.visual_url  = og_obj.images.first().map(|i| i.url.clone());
        },
        None => (),
    }
    for t in product.tracks {
        let track = try!(Track::find_or_create(t.provider,
                                               t.title,
                                               t.url,
                                               t.identifier));
        match entry.tracks.iter().find(|&t| t.id == track.id) {
            Some(_) => (),
            None    => e.add_track(track.clone())
        }
    }
    try!(e.save());
    Ok(e)
}

pub fn show_track(req: &mut Request) -> IronResult<Response> {
    let ref track_id = req.extensions.get::<Router>().unwrap()
                          .find("track_id").unwrap();
    let json_type = Header(ContentType(Mime::from_str("application/json").ok().unwrap()));
    match Track::find_by_id(track_id) {
        Ok(track) => {
            Ok(Response::with((status::Ok,
                               json_type,
                               track.to_json().to_string())))
        },
        Err(e) => Ok(e.as_response())
    }
}

pub fn show_track_by_provider_id(req: &mut Request) -> IronResult<Response> {
    let provider   = req.extensions.get::<Router>().unwrap().find("provider").unwrap();
    let identifier = req.extensions.get::<Router>().unwrap().find("id").unwrap();
    let json_type  = Header(ContentType(Mime::from_str("application/json").ok().unwrap()));
    let p = &Provider::new(provider.to_string());
    match Track::find_by(p, identifier) {
        Ok(track) => {
            Ok(Response::with((status::Ok,
                               json_type,
                               track.to_json().to_string())))
        },
        Err(e) => Ok(e.as_response())
    }
}

pub fn update_track(req: &mut Request) -> IronResult<Response> {
    let json_type = Header(ContentType(Mime::from_str("application/json").ok().unwrap()));
    fn param_as_string(req: &mut Request, key: &str) -> Option<String> {
        match req.get_ref::<UrlEncodedBody>() {
            Ok(ref params) => match params.get(key) {
                Some(val) => Some(val[0].clone()),
                None      => None
            },
            Err(_) => None
        }
    }

    fn query_as_string(req: &mut Request, key: &str) -> String {
        return req.extensions.get::<Router>().unwrap()
                  .find(key).unwrap().to_string();
    }

    match Track::find_by_id(&query_as_string(req, "track_id")) {
        Ok(mut track) => {
            match param_as_string(req, "title") {
                Some(title) => track.title = title,
                None        => println!("no title")
            }
            match param_as_string(req, "url") {
                Some(url) => track.url = url,
                None      => println!("no url")
            }
            match track.save() {
                Ok(_) => Ok(Response::with((status::Ok,
                                         json_type,
                                         track.to_json().to_string()))),
                Err(e) =>  Ok(e.as_response())
            }
        },
        Err(e) =>  {
            Ok(e.as_response())
        }
    }
}

pub fn main() {
    let path = Path::new("public");
    let mut mount = Mount::new();
    mount.mount("/web/", Static::new(Path::new(path)));
    let router = router!(
                         playlistify: get  "/playlistify"          => playlistify,
                          show_track: get  "/tracks/:track_id"     => show_track,
                        update_track: post "/tracks/:track_id"     => update_track,
           show_track_by_provider_id: get  "/tracks/:provider/:id" => show_track_by_provider_id,
                       index_entries: get  "/entries"              => index_entries,
                        index_tracks: get  "/tracks"               => index_tracks,
                                 web: get  "/*"                    => mount,
    );
    let port_str = match std::env::var("PORT") {
        Ok(n)    => n,
        Err(_) => "8080".to_string()
    };
    let port: u16 = match port_str.trim().parse() {
        Ok(n) => n,
        Err(_) => {
            println!("Faild to parse port");
            return;
        }
    };
    println!("PORT {}", port_str);
    let ip = Ipv4Addr::new(0, 0, 0, 0);
    Iron::new(router).http(SocketAddrV4::new(ip, port)).unwrap();
}
<|endoftext|>


<commit_before>extern crate toml;
extern crate glob;

use std::io::fs::PathExtensions;
use std::io::fs;
use std::io;
use std::os;

use app_result::{AppResult, app_err};
use dependencies::read_dependencies;
use types::{TagsRoot, TagsKind};

use tags::{
   update_tags,
   update_tags_and_check_for_reexports,
   create_tags,
   merge_tags
};

use dirs::rusty_tags_dir;

mod app_result;
mod dependencies;
mod dirs;
mod tags;
mod types;

fn main()
{
   let args = os::args();
   let tags_kind =
      if args.len() == 2 {
         match args[1].as_slice() {
            "vi"    => Some(TagsKind::Vi),
            "emacs" => Some(TagsKind::Emacs),
            _       => None
         }
      }
      else {
         None
      };

   if let Some(tkind) = tags_kind {
      update_all_tags(&tkind).unwrap_or_else(|err| {
         let stderr = &mut io::stderr();
         let _ = writeln!(stderr, "rusty-tags: {}", err);
         os::set_exit_status(1);
      });
   }
   else {
      println!("Usage:
   rusty-tags vi
   rusty-tags emacs");
   }
}

fn update_all_tags(tags_kind: &TagsKind) -> AppResult<()>
{
   let cwd = try!(os::getcwd());
   let cargo_dir = try!(find_cargo_toml_dir(&cwd));
   let tags_roots = try!(read_dependencies(&cargo_dir));

   let rust_std_lib_tags_file = try!(rust_std_lib_tags_file(tags_kind));

   for tags_root in tags_roots.iter() {
      let mut tag_files: Vec<Path> = Vec::new();
      let mut tag_dir: Option<Path> = None;

      match *tags_root {
         TagsRoot::Src { ref src_dir, ref dependencies } => {
            let mut src_tags = src_dir.clone();
            src_tags.push(tags_kind.tags_file_name());
            try!(create_tags(tags_kind, src_dir, &src_tags));
            tag_files.push(src_tags);

            for dep in dependencies.iter() {
               tag_files.push(try!(update_tags(tags_kind, dep)).tags_file);
            }

            tag_dir = Some(src_dir.clone());
         },

         TagsRoot::Lib { ref src_kind, ref dependencies } => {
            let lib_tags = try!(update_tags_and_check_for_reexports(tags_kind, src_kind, dependencies));
            if lib_tags.is_up_to_date(tags_kind) {
               continue;
            }

            tag_files.push(lib_tags.tags_file);

            for dep in dependencies.iter() {
               tag_files.push(try!(update_tags(tags_kind, dep)).tags_file);
            }

            tag_dir = Some(lib_tags.src_dir.clone());
         }
      }

      if tag_files.is_empty() || tag_dir.is_none() {
         continue;
      }

      if rust_std_lib_tags_file.is_file() {
         tag_files.push(rust_std_lib_tags_file.clone());
      }

      let mut tags_file = tag_dir.unwrap();
      tags_file.push(tags_kind.tags_file_name());

      try!(merge_tags(tags_kind, &tag_files, &tags_file));
   }

   Ok(())
}

/// Searches for a directory containing a `Cargo.toml` file starting at
/// `start_dir` and continuing the search upwards the directory tree
/// until a directory is found.
fn find_cargo_toml_dir(start_dir: &Path) -> AppResult<Path>
{
   let mut dir = start_dir.clone();
   loop {
      if let Ok(files) = fs::readdir(&dir) {
         for file in files.iter() {
            if file.is_file() {
               if let Some("Cargo.toml") = file.filename_str() {
                  return Ok(dir);
               }
            }
         }
      }

      if ! dir.pop() {
         return Err(app_err(format!("Couldn't find 'Cargo.toml' starting at directory '{}'!", start_dir.display())));
      }
   }
}

/// the tags file containing the tags for the rust standard library
fn rust_std_lib_tags_file(tags_kind: &TagsKind) -> AppResult<Path>
{
   let mut tags_file = try!(rusty_tags_dir());
   tags_file.push(format!("rust-std-lib.{}", tags_kind.tags_file_extension()));

   Ok(tags_file)
}
<commit_msg>Get rid of invalid warning<commit_after>#![allow(unused_assignments)]

extern crate toml;
extern crate glob;

use std::io::fs::PathExtensions;
use std::io::fs;
use std::io;
use std::os;

use app_result::{AppResult, app_err};
use dependencies::read_dependencies;
use types::{TagsRoot, TagsKind};

use tags::{
   update_tags,
   update_tags_and_check_for_reexports,
   create_tags,
   merge_tags
};

use dirs::rusty_tags_dir;

mod app_result;
mod dependencies;
mod dirs;
mod tags;
mod types;

fn main()
{
   let args = os::args();
   let tags_kind =
      if args.len() == 2 {
         match args[1].as_slice() {
            "vi"    => Some(TagsKind::Vi),
            "emacs" => Some(TagsKind::Emacs),
            _       => None
         }
      }
      else {
         None
      };

   if let Some(tkind) = tags_kind {
      update_all_tags(&tkind).unwrap_or_else(|err| {
         let stderr = &mut io::stderr();
         let _ = writeln!(stderr, "rusty-tags: {}", err);
         os::set_exit_status(1);
      });
   }
   else {
      println!("Usage:
   rusty-tags vi
   rusty-tags emacs");
   }
}

fn update_all_tags(tags_kind: &TagsKind) -> AppResult<()>
{
   let cwd = try!(os::getcwd());
   let cargo_dir = try!(find_cargo_toml_dir(&cwd));
   let tags_roots = try!(read_dependencies(&cargo_dir));

   let rust_std_lib_tags_file = try!(rust_std_lib_tags_file(tags_kind));

   for tags_root in tags_roots.iter() {
      let mut tag_files: Vec<Path> = Vec::new();
      let mut tag_dir: Option<Path> = None;

      match *tags_root {
         TagsRoot::Src { ref src_dir, ref dependencies } => {
            let mut src_tags = src_dir.clone();
            src_tags.push(tags_kind.tags_file_name());
            try!(create_tags(tags_kind, src_dir, &src_tags));
            tag_files.push(src_tags);

            for dep in dependencies.iter() {
               tag_files.push(try!(update_tags(tags_kind, dep)).tags_file);
            }

            tag_dir = Some(src_dir.clone());
         },

         TagsRoot::Lib { ref src_kind, ref dependencies } => {
            let lib_tags = try!(update_tags_and_check_for_reexports(tags_kind, src_kind, dependencies));
            if lib_tags.is_up_to_date(tags_kind) {
               continue;
            }

            tag_files.push(lib_tags.tags_file);

            for dep in dependencies.iter() {
               tag_files.push(try!(update_tags(tags_kind, dep)).tags_file);
            }

            tag_dir = Some(lib_tags.src_dir.clone());
         }
      }

      if tag_files.is_empty() || tag_dir.is_none() {
         continue;
      }

      if rust_std_lib_tags_file.is_file() {
         tag_files.push(rust_std_lib_tags_file.clone());
      }

      let mut tags_file = tag_dir.unwrap();
      tags_file.push(tags_kind.tags_file_name());

      try!(merge_tags(tags_kind, &tag_files, &tags_file));
   }

   Ok(())
}

/// Searches for a directory containing a `Cargo.toml` file starting at
/// `start_dir` and continuing the search upwards the directory tree
/// until a directory is found.
fn find_cargo_toml_dir(start_dir: &Path) -> AppResult<Path>
{
   let mut dir = start_dir.clone();
   loop {
      if let Ok(files) = fs::readdir(&dir) {
         for file in files.iter() {
            if file.is_file() {
               if let Some("Cargo.toml") = file.filename_str() {
                  return Ok(dir);
               }
            }
         }
      }

      if ! dir.pop() {
         return Err(app_err(format!("Couldn't find 'Cargo.toml' starting at directory '{}'!", start_dir.display())));
      }
   }
}

/// the tags file containing the tags for the rust standard library
fn rust_std_lib_tags_file(tags_kind: &TagsKind) -> AppResult<Path>
{
   let mut tags_file = try!(rusty_tags_dir());
   tags_file.push(format!("rust-std-lib.{}", tags_kind.tags_file_extension()));

   Ok(tags_file)
}
<|endoftext|>


<commit_before>#![windows_subsystem(windows)]

#[cfg(windows)] extern crate winrt;
#[macro_use]    extern crate error_chain;
extern crate chrono;

#[macro_use] mod util;
mod config;
mod error;
mod feed;
mod math;
mod notify;

use config::Config;
use chrono::{Utc, Timelike};
use feed::Feed;
use feed::statistics::{AverageData, ListenerStats};
use std::time::Duration;
use std::path::PathBuf;

error_chain! {
    links {
        Config(config::Error, config::ErrorKind);
        Feed(feed::Error, feed::ErrorKind);
        Statistics(feed::statistics::Error, feed::statistics::ErrorKind);
        Notify(notify::Error, notify::ErrorKind);
    }

    foreign_links {
        Io(std::io::Error);
    }
}

fn main() {
    #[cfg(windows)]
    let rt = winrt::RuntimeContext::init();

    match start() {
        Ok(_) => (),
        Err(err) => eprintln!("fatal error: {:?}", err),
    }

    #[cfg(windows)]
    rt.uninit();
}

fn start() -> Result<()> {
    let exe_dir = get_exe_directory()?;
    let config_path = exe_dir.clone().join("config.yaml");

    let mut averages = AverageData::new(exe_dir.join("averages.csv"));

    if averages.path.exists() {
        averages.load()?;
    }

    loop {
        let config = Config::from_file(&config_path)?;

        match perform_update(&mut averages, &config) {
            Ok(_) => (),
            Err(err) => error::display(&err),
        }

        std::thread::sleep(Duration::from_secs((config.misc.update_time * 60.0) as u64));
    }
}

fn perform_update(averages: &mut AverageData, config: &Config) -> Result<()> {
    let hour = Utc::now().hour();
    let mut display_feeds = Vec::new();

    for feed in Feed::download_and_scrape(&config)? {
        if feed.listeners < config.misc.minimum_listeners {
            continue
        }

        let stats = update_feed_stats(hour, &feed, &config, averages);

        if cfg!(feature = "print-feed-data") {
            print_info(&feed, &stats);
        }

        if stats.has_spiked || feed.alert.is_some() {
            display_feeds.push((feed, stats.clone()));
        }
    }

    show_feeds(display_feeds)?;

    averages.save()?;
    Ok(())
}

fn update_feed_stats<'a>(hour: u32, feed: &Feed, config: &Config, averages: &'a mut AverageData)
    -> &'a ListenerStats {

    let stats = averages.data
        .entry(feed.id)
        .or_insert(ListenerStats::new());

    stats.update(hour as usize, feed, &config);
    stats
}

fn show_feeds(mut feeds: Vec<(Feed, ListenerStats)>) -> Result<()> {
    feeds.sort_by_key(|&(ref f, _)| f.listeners);

    let total = feeds.len() as i32;

    for (i, (feed, stats)) in feeds.into_iter().enumerate() {
        notify::create_update(1 + i as i32, total, &feed, &stats)?;
    }

    Ok(())
}

fn print_info(feed: &Feed, stats: &ListenerStats) {
    println!("[{}] {}", feed.id, feed.name);
    println!("\tlisteners    | {}", feed.listeners);

    println!("\taverage lis. | cur: {} last: {} samples: {:?}",
        stats.average.current,
        stats.average.last,
        stats.average.data);

    println!("\tunskewed avg | {:?}", stats.unskewed_average);
    println!("\thas spiked   | {}", stats.has_spiked);
    println!("\ttimes spiked | {}", stats.spike_count);
}

fn get_exe_directory() -> Result<PathBuf> {
    let mut path = std::env::current_exe()?;
    path.pop();
    Ok(path)
}<commit_msg>Fix broken windows_subsystem attribute<commit_after>#![windows_subsystem = "windows"]

#[cfg(windows)] extern crate winrt;
#[macro_use]    extern crate error_chain;
extern crate chrono;

#[macro_use] mod util;
mod config;
mod error;
mod feed;
mod math;
mod notify;

use config::Config;
use chrono::{Utc, Timelike};
use feed::Feed;
use feed::statistics::{AverageData, ListenerStats};
use std::time::Duration;
use std::path::PathBuf;

error_chain! {
    links {
        Config(config::Error, config::ErrorKind);
        Feed(feed::Error, feed::ErrorKind);
        Statistics(feed::statistics::Error, feed::statistics::ErrorKind);
        Notify(notify::Error, notify::ErrorKind);
    }

    foreign_links {
        Io(std::io::Error);
    }
}

fn main() {
    #[cfg(windows)]
    let rt = winrt::RuntimeContext::init();

    match start() {
        Ok(_) => (),
        Err(err) => eprintln!("fatal error: {:?}", err),
    }

    #[cfg(windows)]
    rt.uninit();
}

fn start() -> Result<()> {
    let exe_dir = get_exe_directory()?;
    let config_path = exe_dir.clone().join("config.yaml");

    let mut averages = AverageData::new(exe_dir.join("averages.csv"));

    if averages.path.exists() {
        averages.load()?;
    }

    loop {
        let config = Config::from_file(&config_path)?;

        match perform_update(&mut averages, &config) {
            Ok(_) => (),
            Err(err) => error::display(&err),
        }

        std::thread::sleep(Duration::from_secs((config.misc.update_time * 60.0) as u64));
    }
}

fn perform_update(averages: &mut AverageData, config: &Config) -> Result<()> {
    let hour = Utc::now().hour();
    let mut display_feeds = Vec::new();

    for feed in Feed::download_and_scrape(&config)? {
        if feed.listeners < config.misc.minimum_listeners {
            continue
        }

        let stats = update_feed_stats(hour, &feed, &config, averages);

        if cfg!(feature = "print-feed-data") {
            print_info(&feed, &stats);
        }

        if stats.has_spiked || feed.alert.is_some() {
            display_feeds.push((feed, stats.clone()));
        }
    }

    show_feeds(display_feeds)?;

    averages.save()?;
    Ok(())
}

fn update_feed_stats<'a>(hour: u32, feed: &Feed, config: &Config, averages: &'a mut AverageData)
    -> &'a ListenerStats {

    let stats = averages.data
        .entry(feed.id)
        .or_insert(ListenerStats::new());

    stats.update(hour as usize, feed, &config);
    stats
}

fn show_feeds(mut feeds: Vec<(Feed, ListenerStats)>) -> Result<()> {
    feeds.sort_by_key(|&(ref f, _)| f.listeners);

    let total = feeds.len() as i32;

    for (i, (feed, stats)) in feeds.into_iter().enumerate() {
        notify::create_update(1 + i as i32, total, &feed, &stats)?;
    }

    Ok(())
}

fn print_info(feed: &Feed, stats: &ListenerStats) {
    println!("[{}] {}", feed.id, feed.name);
    println!("\tlisteners    | {}", feed.listeners);

    println!("\taverage lis. | cur: {} last: {} samples: {:?}",
        stats.average.current,
        stats.average.last,
        stats.average.data);

    println!("\tunskewed avg | {:?}", stats.unskewed_average);
    println!("\thas spiked   | {}", stats.has_spiked);
    println!("\ttimes spiked | {}", stats.spike_count);
}

fn get_exe_directory() -> Result<PathBuf> {
    let mut path = std::env::current_exe()?;
    path.pop();
    Ok(path)
}<|endoftext|>


<commit_before>// see 'rustc -W help'
#![warn(missing_docs, unused_extern_crates, unused_results)]

//! An IRC bot that posts comments to github when W3C-style IRC minuting is
//! combined with "Github topic:" or "Github issue:" lines that give the
//! github issue to comment in.

use futures::prelude::*;
use futures::stream::StreamExt;
use irc::client::prelude::{Client as IrcClient, Config as IrcConfig};
use serde::Deserialize;
use std::collections::HashMap;
use std::env;
use std::fs;
use tokio::runtime::Runtime;
use wgmeeting_github_ircbot::*;

fn read_config() -> (IrcConfig, BotConfig) {
    let mut args = env::args_os();
    if args.len() != 3 {
        eprintln!(
            "syntax: {} <config file> <github access token file>\n",
            env::args().next().unwrap()
        );
        ::std::process::exit(1);
    }
    let (_, config_file, token_file) = (
        args.next().unwrap(),
        args.next().unwrap(),
        args.next().unwrap(),
    );

    #[derive(Deserialize)]
    struct Config {
        irc: IrcConfig,
        bot: BotConfig,
        channels: HashMap<String, ChannelConfig>,
    }
    let file = fs::read(config_file).expect("couldn't load configuration file");
    let mut config: Config = toml::from_slice(&file).expect("couldn't parse configuration file");
    config.bot.github_access_token =
        fs::read_to_string(token_file).expect("couldn't read github access token file");
    config.irc.channels = config.channels.keys().cloned().collect();
    config.bot.channels = config.channels;
    (config.irc, config.bot)
}

fn main() -> Result<(), failure::Error> {
    env_logger::init();
    let (irc_config, bot_config) = read_config();
    let bot_config: &'static _ = Box::leak(Box::new(bot_config));

    // FIXME: Add a way to ask the bot to reboot itself?

    let mut rt = Runtime::new()?;
    let mut irc_state = IRCState::new(GithubType::RealGithubConnection);

    // FIXME: Convert this to Rust 2018 in the style of https://docs.rs/crate/irc/0.14.0 ,
    // using async fn and #[tokio::main].

    let irc_client: &'static mut _ =
        Box::leak(Box::new(rt.block_on(IrcClient::from_config(irc_config))?));
    irc_client.identify()?;

    let irc_stream = irc_client.stream()?;

    // Work around https://github.com/rust-lang/rust/issues/42574 ?
    let irc_client_: &'static _ = irc_client;

    let irc_future = irc_stream.for_each(move |message_result| {
        if let Ok(message) = message_result {
            process_irc_message(irc_client_, &mut irc_state, bot_config, message);
        }
        future::ready(())
    });

    let _ = rt.block_on(irc_future);

    Ok(())
}
<commit_msg>Convert main() to async fn with #[tokio::main].<commit_after>// see 'rustc -W help'
#![warn(missing_docs, unused_extern_crates, unused_results)]

//! An IRC bot that posts comments to github when W3C-style IRC minuting is
//! combined with "Github topic:" or "Github issue:" lines that give the
//! github issue to comment in.

use futures::prelude::*;
use irc::client::prelude::{Client as IrcClient, Config as IrcConfig};
use serde::Deserialize;
use std::collections::HashMap;
use std::env;
use std::fs;
use wgmeeting_github_ircbot::*;

fn read_config() -> (IrcConfig, BotConfig) {
    let mut args = env::args_os();
    if args.len() != 3 {
        eprintln!(
            "syntax: {} <config file> <github access token file>\n",
            env::args().next().unwrap()
        );
        ::std::process::exit(1);
    }
    let (_, config_file, token_file) = (
        args.next().unwrap(),
        args.next().unwrap(),
        args.next().unwrap(),
    );

    #[derive(Deserialize)]
    struct Config {
        irc: IrcConfig,
        bot: BotConfig,
        channels: HashMap<String, ChannelConfig>,
    }
    let file = fs::read(config_file).expect("couldn't load configuration file");
    let mut config: Config = toml::from_slice(&file).expect("couldn't parse configuration file");
    config.bot.github_access_token =
        fs::read_to_string(token_file).expect("couldn't read github access token file");
    config.irc.channels = config.channels.keys().cloned().collect();
    config.bot.channels = config.channels;
    (config.irc, config.bot)
}

#[tokio::main]
async fn main() -> Result<(), failure::Error> {
    env_logger::init();
    let (irc_config, bot_config) = read_config();
    let bot_config: &'static _ = Box::leak(Box::new(bot_config));

    // FIXME: Add a way to ask the bot to reboot itself?

    let mut irc_state = IRCState::new(GithubType::RealGithubConnection);

    let irc_client: &'static mut _ =
        Box::leak(Box::new(IrcClient::from_config(irc_config).await?));
    irc_client.identify()?;

    let mut irc_stream = irc_client.stream()?;

    while let Some(message) = irc_stream.next().await.transpose()? {
        process_irc_message(irc_client, &mut irc_state, bot_config, message);
    }

    Ok(())
}
<|endoftext|>


<commit_before>extern crate rustwlc;

fn main() {
    println!("Hello, world!");
    //rustwlc::init();
}
<commit_msg>Added example translation from wlc example, does not compile<commit_after>extern crate rustwlc;

struct Data {
    handle: rustwlc::WLCHandle,
    grab: rustwlc::Point,
    drag_edge: rustwlc::ResizeEdge
}

pub data: mut 'static Data;

fn start_interactive_action(view: rustwlc::WLCHandle, origin: rustwlc::Point) -> bool
{
    data.handle = view;
    data.grab = origin;
    view.bring_to_front();
    return true;
}

fn main() {
    println!("Hello, world!");

}
<|endoftext|>


<commit_before>use std::collections::BTreeMap;
use std::fs;
use std::io;
use std::path::Path;

use handlebars::{no_escape, Handlebars};

const SITE_TITLE: &str = "Grauwoelfchen's Workroom";
const DST_DIR: &str = "./dst";

const LINKS: [Link; 4] = [
    Link {
        title: "Home",
        href: "/",
    },
    Link {
        title: "About",
        href: "/about.html",
    },
    Link {
        title: "Software",
        href: "/software.html",
    },
    Link {
        title: "Link",
        href: "/link.html",
    },
];

#[derive(Debug)]
enum Error {
    Io(io::Error),
    Parse(handlebars::TemplateError),
    Render(handlebars::RenderError),
}

impl From<io::Error> for Error {
    fn from(error: io::Error) -> Self {
        Self::Io(error)
    }
}

impl From<handlebars::TemplateError> for Error {
    fn from(error: handlebars::TemplateError) -> Self {
        Self::Parse(error)
    }
}

impl From<handlebars::RenderError> for Error {
    fn from(error: handlebars::RenderError) -> Self {
        Self::Render(error)
    }
}

struct Link {
    pub title: &'static str,
    pub href: &'static str,
}

fn main() -> Result<(), Error> {
    let mut reg = Handlebars::new();

    let layout = include_str!("./layout.hbs");
    reg.register_template_string("layout", layout)?;
    reg.register_escape_fn(no_escape);

    let dst = Path::new(&DST_DIR);

    // error
    let dat = include_str!("./error.hbs");
    fs::write(dst.join("error.html"), &dat.as_bytes())?;

    // index
    let data = load("Home", include_str!("./index.hbs"));
    let result = reg.render("layout", &data)?;
    fs::write(dst.join("index.html"), result)?;

    // about
    let data = load("About", include_str!("./about.hbs"));
    let result = reg.render("layout", &data).unwrap();
    fs::write(dst.join("about.html"), result)?;

    // software
    let data = load("Software", include_str!("./software.hbs"));
    let result = reg.render("layout", &data).unwrap();
    fs::write(dst.join("software.html"), result)?;

    // link
    let data = load("Link", include_str!("./link.hbs"));
    let result = reg.render("layout", &data).unwrap();
    fs::write(dst.join("link.html"), result)?;

    reg.unregister_escape_fn();

    Ok(())
}

/// Returns built an entry for navigations list.
fn build_nav(heading: &str, link: &Link) -> String {
    let class = if link.title == heading {
        " class=\"active\""
    } else {
        ""
    };
    format!(
        r#"

  {}

"#,
        class, link.href, link.title,
    )
}

/// Loads the content as a BTreeMap object that has navi, content
/// {page,site}_title as attributes.
fn load(heading: &str, content: &str) -> BTreeMap<String, String> {
    let mut data = BTreeMap::new();

    let mut nav = "".to_string();
    for link in LINKS {
        nav.push_str(&build_nav(heading, &link));
    }
    data.insert("navi".to_string(), nav);
    data.insert("content".to_string(), content.to_string());

    // meta
    if heading != "Home" {
        data.insert("page_title".to_string(), heading.to_string());
    }
    data.insert("site_title".to_string(), SITE_TITLE.to_string());
    data
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_build_nav() {
        let link = Link {
            title: "Foo",
            href: "/foo.html",
        };
        let nav = build_nav("Foo", &link);
        assert_eq!(
            nav,
            r#"

  Foo

"#
        );

        let link = Link {
            title: "Bar",
            href: "/foo.html",
        };
        let nav = build_nav("Bar", &link);
        assert_eq!(
            nav,
            r#"

  Foo

"#
        );
    }
}
<commit_msg>Fix a test for inactive link<commit_after>use std::collections::BTreeMap;
use std::fs;
use std::io;
use std::path::Path;

use handlebars::{no_escape, Handlebars};

const SITE_TITLE: &str = "Grauwoelfchen's Workroom";
const DST_DIR: &str = "./dst";

const LINKS: [Link; 4] = [
    Link {
        title: "Home",
        href: "/",
    },
    Link {
        title: "About",
        href: "/about.html",
    },
    Link {
        title: "Software",
        href: "/software.html",
    },
    Link {
        title: "Link",
        href: "/link.html",
    },
];

#[derive(Debug)]
enum Error {
    Io(io::Error),
    Parse(handlebars::TemplateError),
    Render(handlebars::RenderError),
}

impl From<io::Error> for Error {
    fn from(error: io::Error) -> Self {
        Self::Io(error)
    }
}

impl From<handlebars::TemplateError> for Error {
    fn from(error: handlebars::TemplateError) -> Self {
        Self::Parse(error)
    }
}

impl From<handlebars::RenderError> for Error {
    fn from(error: handlebars::RenderError) -> Self {
        Self::Render(error)
    }
}

struct Link {
    pub title: &'static str,
    pub href: &'static str,
}

fn main() -> Result<(), Error> {
    let mut reg = Handlebars::new();

    let layout = include_str!("./layout.hbs");
    reg.register_template_string("layout", layout)?;
    reg.register_escape_fn(no_escape);

    let dst = Path::new(&DST_DIR);

    // error
    let dat = include_str!("./error.hbs");
    fs::write(dst.join("error.html"), &dat.as_bytes())?;

    // index
    let data = load("Home", include_str!("./index.hbs"));
    let result = reg.render("layout", &data)?;
    fs::write(dst.join("index.html"), result)?;

    // about
    let data = load("About", include_str!("./about.hbs"));
    let result = reg.render("layout", &data).unwrap();
    fs::write(dst.join("about.html"), result)?;

    // software
    let data = load("Software", include_str!("./software.hbs"));
    let result = reg.render("layout", &data).unwrap();
    fs::write(dst.join("software.html"), result)?;

    // link
    let data = load("Link", include_str!("./link.hbs"));
    let result = reg.render("layout", &data).unwrap();
    fs::write(dst.join("link.html"), result)?;

    reg.unregister_escape_fn();

    Ok(())
}

/// Returns built an entry for navigations list.
fn build_nav(heading: &str, link: &Link) -> String {
    let class = if link.title == heading {
        " class=\"active\""
    } else {
        ""
    };
    format!(
        r#"

  {}

"#,
        class, link.href, link.title,
    )
}

/// Loads the content as a BTreeMap object that has navi, content
/// {page,site}_title as attributes.
fn load(heading: &str, content: &str) -> BTreeMap<String, String> {
    let mut data = BTreeMap::new();

    let mut nav = "".to_string();
    for link in LINKS {
        nav.push_str(&build_nav(heading, &link));
    }
    data.insert("navi".to_string(), nav);
    data.insert("content".to_string(), content.to_string());

    // meta
    if heading != "Home" {
        data.insert("page_title".to_string(), heading.to_string());
    }
    data.insert("site_title".to_string(), SITE_TITLE.to_string());
    data
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_build_nav() {
        let link = Link {
            title: "Foo",
            href: "/foo.html",
        };
        let nav = build_nav("Foo", &link);
        assert_eq!(
            nav,
            r#"

  Foo

"#
        );

        let link = Link {
            title: "Foo",
            href: "/foo.html",
        };
        let nav = build_nav("Bar", &link);
        assert_eq!(
            nav,
            r#"

  Foo

"#
        );
    }
}
<|endoftext|>


<commit_before>extern crate bytes;
extern crate env_logger;
extern crate mio;
#[macro_use]
extern crate log;
#[macro_use]
extern crate clap;
extern crate failure;
extern crate slab;

use clap::{App, Arg};
use failure::Error;
use failure::ResultExt;
use mio::net::{TcpListener, TcpStream};
use slab::Slab;
use std::collections::VecDeque;
use std::io::{Read, Write};
use std::ops;

#[derive(Debug)]
enum MiChatCommand {
    Broadcast(String),
    NewConnection(TcpStream),
    CloseConnection,
}

struct MiChat {
    connections: Slab<EventHandler>,
}

trait RuleHandler {
    fn handle_event(&mut self, _event_loop: &mut mio::Poll, events: mio::Ready);
    fn register(&self, event_loop: &mut mio::Poll, token: mio::Token) -> Result<(), Error>;
    fn deregister(&self, event_loop: &mut mio::Poll) -> Result<(), Error>;
    fn process_rules(
        &mut self,
        event_loop: &mut mio::Poll,
        to_parent: &mut FnMut(MiChatCommand),
    ) -> Result<(), Error>;
}

impl MiChat {
    fn new() -> MiChat {
        MiChat {
            connections: Slab::new(),
        }
    }

    fn listen(&mut self, event_loop: &mut mio::Poll, listener: TcpListener) -> Result<(), Error> {
        let token = {
            let e = self.connections.vacant_entry();
            let token = mio::Token(e.key());
            let l = EventHandler::Listener(Listener::new(listener, token));
            e.insert(l);
            token
        };
        &self.connections[token.into()].register(event_loop, token)?;
        Ok(())
    }

    fn process_action(
        &mut self,
        src: usize,
        msg: MiChatCommand,
        event_loop: &mut mio::Poll,
    ) -> Result<(), Error> {
        trace!("{:p}; from {:?}; got {:?}", self, src, msg);
        match msg {
            MiChatCommand::Broadcast(s) => self.process_broadcast(s)?,
            MiChatCommand::NewConnection(socket) => {
                self.process_new_connection(event_loop, socket)?
            }
            MiChatCommand::CloseConnection => self.process_close(src, event_loop)?,
        }
        Ok(())
    }

    fn process_broadcast(&mut self, s: String) -> Result<(), Error> {
        for c in self.client_connections_mut() {
            c.enqueue(&s);
        }
        Ok(())
    }

    fn process_new_connection(
        &mut self,
        event_loop: &mut mio::Poll,
        socket: mio::tcp::TcpStream,
    ) -> Result<(), Error> {
        let token = {
            let e = self.connections.vacant_entry();
            let token = mio::Token(e.key());
            let l = EventHandler::Conn(Connection::new(socket, token)?);
            e.insert(l);
            token
        };
        &self.connections[token.into()].register(event_loop, token)?;
        Ok(())
    }

    fn process_close(&mut self, src: usize, event_loop: &mut mio::Poll) -> Result<(), Error> {
        debug!("Processing close: {:?}", src);
        let conn = self.connections.remove(src);
        conn.deregister(event_loop)?;
        Ok(())
    }

    fn client_connections_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut Connection> + 'a {
        self.connections.iter_mut().filter_map(|(_, it)| match it {
            &mut EventHandler::Conn(ref mut c) => Some(c),
            _ => None,
        })
    }
}

#[derive(Debug)]
struct Connection {
    socket: TcpStream,
    sock_status: mio::Ready,
    token: mio::Token,
    read_buf: Vec<u8>,
    read_eof: bool,
    write_buf: Vec<u8>,
}

impl RuleHandler for Connection {
    fn register(&self, event_loop: &mut mio::Poll, token: mio::Token) -> Result<(), Error> {
        event_loop
            .register(
                &self.socket,
                token,
                mio::Ready::readable(),
                mio::PollOpt::edge() | mio::PollOpt::oneshot(),
            ).context("event loop register")?;
        Ok(())
    }
    fn deregister(&self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        event_loop.deregister(&self.socket)?;
        Ok(())
    }

    // Event updates arrive here
    fn handle_event(&mut self, _event_loop: &mut mio::Poll, events: mio::Ready) {
        self.sock_status.insert(events);
        info!(
            "Connection::handle_event: {:?}; this time: {:?}; now: {:?}",
            self.socket.peer_addr(),
            events,
            self.sock_status
        );
    }

    // actions are processed here on down.

    fn process_rules(
        &mut self,
        event_loop: &mut mio::Poll,
        to_parent: &mut FnMut(MiChatCommand),
    ) -> Result<(), Error> {
        if self.sock_status.is_readable() {
            self.sock_status.remove(mio::Ready::readable());
            self.read()?;
        }

        self.process_buffer(to_parent);

        if self.sock_status.is_writable() {
            self.sock_status.remove(mio::Ready::writable());
            self.write()?;
        }

        if self.should_close() {
            to_parent(MiChatCommand::CloseConnection)
        } else {
            self.reregister(event_loop)?;
        }

        Ok(())
    }
}

impl Connection {
    fn new(socket: TcpStream, token: mio::Token) -> Result<Connection, Error> {
        Ok(Connection {
            socket: socket,
            sock_status: mio::Ready::empty(),
            token: token,
            read_buf: Vec::with_capacity(1024),
            write_buf: Vec::new(),
            read_eof: false,
        })
    }
    fn process_buffer(&mut self, to_parent: &mut FnMut(MiChatCommand)) {
        let mut prev = 0;
        info!(
            "{:?}: Read buffer: {:?}",
            self.socket.peer_addr(),
            self.read_buf
        );
        for n in self.read_buf.iter().enumerate().filter_map(|(i, e)| {
            if *e == '\n' as u8 {
                Some(i)
            } else {
                None
            }
        }) {
            info!(
                "{:?}: Pos: {:?}; chunk: {:?}",
                self.socket.peer_addr(),
                n,
                &self.read_buf[prev..n]
            );
            let s = String::from_utf8_lossy(&self.read_buf[prev..n]).to_string();
            let cmd = MiChatCommand::Broadcast(s);
            info!("Send! {:?}", cmd);
            to_parent(cmd);
            prev = n + 1;
        }
        let remainder = self.read_buf[prev..].to_vec();
        info!(
            "{:?}: read Remainder: {}",
            self.socket.peer_addr(),
            remainder.len()
        );
        self.read_buf = remainder;
    }

    fn read(&mut self) -> Result<(), Error> {
        let mut abuf = vec![0; 1024];
        match self.socket.read(&mut abuf).context("Reading socket")? {
            0 => {
                info!("{:?}: EOF!", self.socket.peer_addr());
                self.read_eof = true;
                Ok(())
            }
            n => {
                info!("{:?}: Read {}bytes", self.socket.peer_addr(), n);
                self.read_buf.extend(&abuf[0..n]);
                Ok(())
            }
        }
    }

    fn write(&mut self) -> Result<(), Error> {
        let n = self
            .socket
            .write(&mut self.write_buf)
            .context("writing buffer")?;
        info!(
            "{:?}: Wrote {} of {} in buffer",
            self.socket.peer_addr(),
            n,
            self.write_buf.len()
        );
        self.write_buf = self.write_buf[n..].to_vec();
        info!(
            "{:?}: Now {:?}b",
            self.socket.peer_addr(),
            self.write_buf.len()
        );
        Ok(())
    }

    fn should_close(&self) -> bool {
        self.read_eof && self.write_buf.is_empty()
    }

    fn reregister(&mut self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        let mut flags = mio::Ready::readable();
        if !self.write_buf.is_empty() {
            flags.insert(mio::Ready::writable());
        }
        info!("Registering {:?} with {:?}", self, flags);

        event_loop
            .reregister(&self.socket, self.token, flags, mio::PollOpt::oneshot())
            .context("EventLoop#reregister")?;

        Ok(())
    }

    fn enqueue(&mut self, s: &str) {
        self.write_buf.extend(s.as_bytes());
        self.write_buf.push('\n' as u8);
    }
}

#[derive(Debug)]
struct Listener {
    listener: TcpListener,
    sock_status: mio::Ready,
    token: mio::Token,
}

impl Listener {
    fn new(listener: TcpListener, token: mio::Token) -> Listener {
        Listener {
            listener: listener,
            sock_status: mio::Ready::empty(),
            token: token,
        }
    }
    fn reregister(&self, event_loop: &mut mio::Poll, token: mio::Token) -> Result<(), Error> {
        event_loop
            .reregister(
                &self.listener,
                token,
                mio::Ready::readable(),
                mio::PollOpt::edge() | mio::PollOpt::oneshot(),
            ).context("Register listener")?;
        Ok(())
    }
}

impl RuleHandler for Listener {
    fn register(&self, event_loop: &mut mio::Poll, token: mio::Token) -> Result<(), Error> {
        event_loop
            .register(
                &self.listener,
                token,
                mio::Ready::readable(),
                mio::PollOpt::edge() | mio::PollOpt::oneshot(),
            ).context("Register listener")?;
        Ok(())
    }
    fn deregister(&self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        event_loop.deregister(&self.listener)?;
        Ok(())
    }

    fn handle_event(&mut self, _event_loop: &mut mio::Poll, events: mio::Ready) {
        assert!(events.is_readable());
        self.sock_status.insert(events);
        info!(
            "Listener::handle_event: {:?}; this time: {:?}; now: {:?}",
            self.listener.local_addr(),
            events,
            self.sock_status
        );
    }
    fn process_rules(
        &mut self,
        event_loop: &mut mio::Poll,
        to_parent: &mut FnMut(MiChatCommand),
    ) -> Result<(), Error> {
        if self.sock_status.is_readable() {
            info!("the listener socket is ready to accept a connection");
            self.sock_status.remove(mio::Ready::readable());
            let (socket, _client) = self.listener.accept().context("Accept listening socket")?;
            let cmd = MiChatCommand::NewConnection(socket);
            to_parent(cmd);

            self.reregister(event_loop, self.token)?;
        }
        Ok(())
    }
}

#[derive(Debug)]
enum EventHandler {
    Listener(Listener),
    Conn(Connection),
}

impl ops::Deref for EventHandler {
    type Target = RuleHandler;
    fn deref(&self) -> &Self::Target {
        match self {
            &EventHandler::Conn(ref conn) => conn,
            &EventHandler::Listener(ref listener) => listener,
        }
    }
}
impl ops::DerefMut for EventHandler {
    fn deref_mut(&mut self) -> &mut Self::Target {
        match self {
            &mut EventHandler::Conn(ref mut conn) => conn,
            &mut EventHandler::Listener(ref mut listener) => listener,
        }
    }
}

impl MiChat {
    fn ready(&mut self, event_loop: &mut mio::Poll, token: mio::Token, events: mio::Ready) {
        info!("{:?}: {:?}", token, events);
        self.connections[token.into()].handle_event(event_loop, events);
    }

    fn process(&mut self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        let mut parent_actions = VecDeque::new();
        let mut failed = Vec::new();
        loop {
            let mut something_done = false;
            for (idx, conn) in self.connections.iter_mut() {
                if let Err(e) = conn.process_rules(event_loop, &mut |action| {
                    parent_actions.push_back((idx, action))
                }) {
                    failed.push(idx);
                    error!("Error on connection {:?}; Dropping: {:?}", idx, e);
                }
            }
            for idx in failed.drain(..) {
                debug!("Remove failed; {:?}", idx);
                let conn = self.connections.remove(idx);
                conn.deregister(event_loop)?;
            }
            for (src, action) in parent_actions.drain(..) {
                something_done = true;
                if let Err(e) = self.process_action(src, action, event_loop) {
                    failed.push(src);
                    error!("Error on action for {:?}; Dropping: {:?}", src, e);
                }
            }
            // If we didn't do anything, we're done.
            if !something_done {
                break;
            }
        }

        Ok(())
    }

    fn run(&mut self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        loop {
            self.run_once(event_loop)?;
        }
    }
    fn run_once(&mut self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        let mut events = mio::Events::with_capacity(1024);
        event_loop.poll(&mut events, None)?;
        for ev in &events {
            self.ready(event_loop, ev.token(), ev.readiness())
        }

        self.process(event_loop)
    }
}

fn main() {
    env_logger::init();
    let matches = App::new("michat")
        .arg(
            Arg::with_name("bind")
                .short("l")
                .takes_value(true)
                .required(true),
        ).get_matches();

    let address = value_t_or_exit!(matches.value_of("bind"), std::net::SocketAddr);
    let listener = TcpListener::bind(&address).expect("bind");

    let mut event_loop = mio::Poll::new().expect("Create event loop");
    let mut service = MiChat::new();

    service
        .listen(&mut event_loop, listener)
        .expect("Starting listener");
    info!("running michat listener at: {:?}", address);
    service.run(&mut event_loop).expect("run");
}
<commit_msg>Cache peer address.<commit_after>extern crate bytes;
extern crate env_logger;
extern crate mio;
#[macro_use]
extern crate log;
#[macro_use]
extern crate clap;
extern crate failure;
extern crate slab;

use clap::{App, Arg};
use failure::Error;
use failure::ResultExt;
use mio::net::{TcpListener, TcpStream};
use slab::Slab;
use std::collections::VecDeque;
use std::io::{Read, Write};
use std::ops;

#[derive(Debug)]
enum MiChatCommand {
    Broadcast(String),
    NewConnection(TcpStream),
    CloseConnection,
}

struct MiChat {
    connections: Slab<EventHandler>,
}

trait RuleHandler {
    fn handle_event(&mut self, _event_loop: &mut mio::Poll, events: mio::Ready);
    fn register(&self, event_loop: &mut mio::Poll, token: mio::Token) -> Result<(), Error>;
    fn deregister(&self, event_loop: &mut mio::Poll) -> Result<(), Error>;
    fn process_rules(
        &mut self,
        event_loop: &mut mio::Poll,
        to_parent: &mut FnMut(MiChatCommand),
    ) -> Result<(), Error>;
}

impl MiChat {
    fn new() -> MiChat {
        MiChat {
            connections: Slab::new(),
        }
    }

    fn listen(&mut self, event_loop: &mut mio::Poll, listener: TcpListener) -> Result<(), Error> {
        let token = {
            let e = self.connections.vacant_entry();
            let token = mio::Token(e.key());
            let l = EventHandler::Listener(Listener::new(listener, token));
            e.insert(l);
            token
        };
        &self.connections[token.into()].register(event_loop, token)?;
        Ok(())
    }

    fn process_action(
        &mut self,
        src: usize,
        msg: MiChatCommand,
        event_loop: &mut mio::Poll,
    ) -> Result<(), Error> {
        trace!("{:p}; from {:?}; got {:?}", self, src, msg);
        match msg {
            MiChatCommand::Broadcast(s) => self.process_broadcast(s)?,
            MiChatCommand::NewConnection(socket) => {
                self.process_new_connection(event_loop, socket)?
            }
            MiChatCommand::CloseConnection => self.process_close(src, event_loop)?,
        }
        Ok(())
    }

    fn process_broadcast(&mut self, s: String) -> Result<(), Error> {
        for c in self.client_connections_mut() {
            c.enqueue(&s);
        }
        Ok(())
    }

    fn process_new_connection(
        &mut self,
        event_loop: &mut mio::Poll,
        socket: mio::tcp::TcpStream,
    ) -> Result<(), Error> {
        let token = {
            let e = self.connections.vacant_entry();
            let token = mio::Token(e.key());
            let l = EventHandler::Conn(Connection::new(socket, token)?);
            e.insert(l);
            token
        };
        &self.connections[token.into()].register(event_loop, token)?;
        Ok(())
    }

    fn process_close(&mut self, src: usize, event_loop: &mut mio::Poll) -> Result<(), Error> {
        debug!("Processing close: {:?}", src);
        let conn = self.connections.remove(src);
        conn.deregister(event_loop)?;
        Ok(())
    }

    fn client_connections_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut Connection> + 'a {
        self.connections.iter_mut().filter_map(|(_, it)| match it {
            &mut EventHandler::Conn(ref mut c) => Some(c),
            _ => None,
        })
    }
}

#[derive(Debug)]
struct Connection {
    socket: TcpStream,
    peer: std::net::SocketAddr,
    sock_status: mio::Ready,
    token: mio::Token,
    read_buf: Vec<u8>,
    read_eof: bool,
    write_buf: Vec<u8>,
}

impl RuleHandler for Connection {
    fn register(&self, event_loop: &mut mio::Poll, token: mio::Token) -> Result<(), Error> {
        event_loop
            .register(
                &self.socket,
                token,
                mio::Ready::readable(),
                mio::PollOpt::edge() | mio::PollOpt::oneshot(),
            ).context("event loop register")?;
        Ok(())
    }
    fn deregister(&self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        event_loop.deregister(&self.socket)?;
        Ok(())
    }

    // Event updates arrive here
    fn handle_event(&mut self, _event_loop: &mut mio::Poll, events: mio::Ready) {
        self.sock_status.insert(events);
        info!(
            "Connection::handle_event: {:?}; this time: {:?}; now: {:?}",
            self.peer, events, self.sock_status
        );
    }

    // actions are processed here on down.

    fn process_rules(
        &mut self,
        event_loop: &mut mio::Poll,
        to_parent: &mut FnMut(MiChatCommand),
    ) -> Result<(), Error> {
        if self.sock_status.is_readable() {
            self.sock_status.remove(mio::Ready::readable());
            self.read()?;
        }

        self.process_buffer(to_parent);

        if self.sock_status.is_writable() {
            self.sock_status.remove(mio::Ready::writable());
            self.write()?;
        }

        if self.should_close() {
            to_parent(MiChatCommand::CloseConnection)
        } else {
            self.reregister(event_loop)?;
        }

        Ok(())
    }
}

impl Connection {
    fn new(socket: TcpStream, token: mio::Token) -> Result<Connection, Error> {
        let peer = socket.peer_addr()?;
        Ok(Connection {
            socket: socket,
            peer: peer,
            sock_status: mio::Ready::empty(),
            token: token,
            read_buf: Vec::with_capacity(1024),
            write_buf: Vec::new(),
            read_eof: false,
        })
    }
    fn process_buffer(&mut self, to_parent: &mut FnMut(MiChatCommand)) {
        let mut prev = 0;
        info!("{:?}: Read buffer: {:?}", self.peer, self.read_buf);
        for n in self.read_buf.iter().enumerate().filter_map(|(i, e)| {
            if *e == '\n' as u8 {
                Some(i)
            } else {
                None
            }
        }) {
            info!(
                "{:?}: Pos: {:?}; chunk: {:?}",
                self.peer,
                n,
                &self.read_buf[prev..n]
            );
            let s = String::from_utf8_lossy(&self.read_buf[prev..n]).to_string();
            let cmd = MiChatCommand::Broadcast(s);
            info!("Send! {:?}", cmd);
            to_parent(cmd);
            prev = n + 1;
        }
        let remainder = self.read_buf[prev..].to_vec();
        info!("{:?}: read Remainder: {}", self.peer, remainder.len());
        self.read_buf = remainder;
    }

    fn read(&mut self) -> Result<(), Error> {
        let mut abuf = vec![0; 1024];
        match self.socket.read(&mut abuf).context("Reading socket")? {
            0 => {
                info!("{:?}: EOF!", self.peer);
                self.read_eof = true;
                Ok(())
            }
            n => {
                info!("{:?}: Read {}bytes", self.peer, n);
                self.read_buf.extend(&abuf[0..n]);
                Ok(())
            }
        }
    }

    fn write(&mut self) -> Result<(), Error> {
        let n = self
            .socket
            .write(&mut self.write_buf)
            .context("writing buffer")?;
        info!(
            "{:?}: Wrote {} of {} in buffer",
            self.peer,
            n,
            self.write_buf.len()
        );
        self.write_buf = self.write_buf[n..].to_vec();
        info!("{:?}: Now {:?}b", self.peer, self.write_buf.len());
        Ok(())
    }

    fn should_close(&self) -> bool {
        self.read_eof && self.write_buf.is_empty()
    }

    fn reregister(&mut self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        let mut flags = mio::Ready::readable();
        if !self.write_buf.is_empty() {
            flags.insert(mio::Ready::writable());
        }
        info!("Registering {:?} with {:?}", self, flags);

        event_loop
            .reregister(&self.socket, self.token, flags, mio::PollOpt::oneshot())
            .context("EventLoop#reregister")?;

        Ok(())
    }

    fn enqueue(&mut self, s: &str) {
        self.write_buf.extend(s.as_bytes());
        self.write_buf.push('\n' as u8);
    }
}

#[derive(Debug)]
struct Listener {
    listener: TcpListener,
    sock_status: mio::Ready,
    token: mio::Token,
}

impl Listener {
    fn new(listener: TcpListener, token: mio::Token) -> Listener {
        Listener {
            listener: listener,
            sock_status: mio::Ready::empty(),
            token: token,
        }
    }
    fn reregister(&self, event_loop: &mut mio::Poll, token: mio::Token) -> Result<(), Error> {
        event_loop
            .reregister(
                &self.listener,
                token,
                mio::Ready::readable(),
                mio::PollOpt::edge() | mio::PollOpt::oneshot(),
            ).context("Register listener")?;
        Ok(())
    }
}

impl RuleHandler for Listener {
    fn register(&self, event_loop: &mut mio::Poll, token: mio::Token) -> Result<(), Error> {
        event_loop
            .register(
                &self.listener,
                token,
                mio::Ready::readable(),
                mio::PollOpt::edge() | mio::PollOpt::oneshot(),
            ).context("Register listener")?;
        Ok(())
    }
    fn deregister(&self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        event_loop.deregister(&self.listener)?;
        Ok(())
    }

    fn handle_event(&mut self, _event_loop: &mut mio::Poll, events: mio::Ready) {
        assert!(events.is_readable());
        self.sock_status.insert(events);
        info!(
            "Listener::handle_event: {:?}; this time: {:?}; now: {:?}",
            self.listener.local_addr(),
            events,
            self.sock_status
        );
    }
    fn process_rules(
        &mut self,
        event_loop: &mut mio::Poll,
        to_parent: &mut FnMut(MiChatCommand),
    ) -> Result<(), Error> {
        if self.sock_status.is_readable() {
            info!("the listener socket is ready to accept a connection");
            self.sock_status.remove(mio::Ready::readable());
            let (socket, _client) = self.listener.accept().context("Accept listening socket")?;
            let cmd = MiChatCommand::NewConnection(socket);
            to_parent(cmd);

            self.reregister(event_loop, self.token)?;
        }
        Ok(())
    }
}

#[derive(Debug)]
enum EventHandler {
    Listener(Listener),
    Conn(Connection),
}

impl ops::Deref for EventHandler {
    type Target = RuleHandler;
    fn deref(&self) -> &Self::Target {
        match self {
            &EventHandler::Conn(ref conn) => conn,
            &EventHandler::Listener(ref listener) => listener,
        }
    }
}
impl ops::DerefMut for EventHandler {
    fn deref_mut(&mut self) -> &mut Self::Target {
        match self {
            &mut EventHandler::Conn(ref mut conn) => conn,
            &mut EventHandler::Listener(ref mut listener) => listener,
        }
    }
}

impl MiChat {
    fn ready(&mut self, event_loop: &mut mio::Poll, token: mio::Token, events: mio::Ready) {
        info!("{:?}: {:?}", token, events);
        self.connections[token.into()].handle_event(event_loop, events);
    }

    fn process(&mut self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        let mut parent_actions = VecDeque::new();
        let mut failed = Vec::new();
        loop {
            let mut something_done = false;
            for (idx, conn) in self.connections.iter_mut() {
                if let Err(e) = conn.process_rules(event_loop, &mut |action| {
                    parent_actions.push_back((idx, action))
                }) {
                    failed.push(idx);
                    error!("Error on connection {:?}; Dropping: {:?}", idx, e);
                }
            }
            for idx in failed.drain(..) {
                debug!("Remove failed; {:?}", idx);
                let conn = self.connections.remove(idx);
                conn.deregister(event_loop)?;
            }
            for (src, action) in parent_actions.drain(..) {
                something_done = true;
                if let Err(e) = self.process_action(src, action, event_loop) {
                    failed.push(src);
                    error!("Error on action for {:?}; Dropping: {:?}", src, e);
                }
            }
            // If we didn't do anything, we're done.
            if !something_done {
                break;
            }
        }

        Ok(())
    }

    fn run(&mut self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        loop {
            self.run_once(event_loop)?;
        }
    }
    fn run_once(&mut self, event_loop: &mut mio::Poll) -> Result<(), Error> {
        let mut events = mio::Events::with_capacity(1024);
        event_loop.poll(&mut events, None)?;
        for ev in &events {
            self.ready(event_loop, ev.token(), ev.readiness())
        }

        self.process(event_loop)
    }
}

fn main() {
    env_logger::init();
    let matches = App::new("michat")
        .arg(
            Arg::with_name("bind")
                .short("l")
                .takes_value(true)
                .required(true),
        ).get_matches();

    let address = value_t_or_exit!(matches.value_of("bind"), std::net::SocketAddr);
    let listener = TcpListener::bind(&address).expect("bind");

    let mut event_loop = mio::Poll::new().expect("Create event loop");
    let mut service = MiChat::new();

    service
        .listen(&mut event_loop, listener)
        .expect("Starting listener");
    info!("running michat listener at: {:?}", address);
    service.run(&mut event_loop).expect("run");
}
<|endoftext|>