diff --git a/Cargo.toml b/Cargo.toml index cd02ca1c..41bd1921 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ authors = [ description = "A console XMPP client" [dependencies] -pyo3 = { version = "0.17", features = ["extension-module"] } +pyo3 = { version = "0.17", features = ["auto-initialize", "nightly"] } nom = "7.1" chrono = "0.4" ncurses = "5" @@ -20,6 +20,9 @@ clap = { version = "3.2.17", features = ["derive"] } directories = "4.0.1" configparser = "3.0.1" jid = "0.9.4" +libc = "0.2.132" +log = "0.4.17" +simplelog = "0.12.0" [lib] crate-type = ["cdylib"] diff --git a/src/error.rs b/src/error.rs index 84ed56d9..baddcd94 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,10 +21,14 @@ use std::io; use std::num::TryFromIntError; use std::path::PathBuf; +use pyo3::PyErr; + #[derive(Debug)] pub(crate) enum Error { IOError(io::Error), IntError(TryFromIntError), + PyError(PyErr), + StartupCheck(String), UnableToCreateConfigDir, InvalidConfigValueType(ConfigValue), InvalidValueType(String), @@ -36,6 +40,8 @@ impl fmt::Display for Error { match self { Error::IOError(e) => write!(f, "io error: {}", e), Error::IntError(e) => write!(f, "int error: {}", e), + Error::PyError(e) => write!(f, "python error: {}", e), + Error::StartupCheck(e) => write!(f, "Startup check error: {}", e), Error::UnableToCreateConfigDir => write!(f, "Unable to create config dir"), Error::InvalidConfigValueType(err) => write!(f, "Invalid ConfigValue type: {}", err), Error::InvalidValueType(err) => write!(f, "Invalid value type: {}", err), @@ -59,3 +65,9 @@ impl From for Error { Error::IntError(err) } } + +impl From for Error { + fn from(err: PyErr) -> Error { + Error::PyError(err) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..39164775 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,117 @@ +// Copyright (C) 2018-2099 The crate authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![feature(once_cell)] + +mod args; +mod config; +mod error; +mod xdg; + +use crate::args::Args; +use crate::config::Config; +use crate::error::Error; + +use std::fs; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; + +use clap::Parser; +use libc; +use log; +use simplelog::WriteLogger; +use pyo3::{Python, PyResult, py_run}; + + +fn main() -> Result<(), Error> { + if unsafe { libc::getuid() == 0 } { + return Err(Error::StartupCheck(String::from("Do not run as root"))); + } + + io::stdout().write_all(b"\x1b]0;poezio\x07")?; + io::stdout().flush()?; + + let args = Args::parse(); + + let firstrun = !args.filename.exists(); + if firstrun { + let parent = args + .filename + .parent() + .ok_or(Error::UnableToCreateConfigDir)?; + fs::create_dir_all(parent).map_err(|_| Error::UnableToCreateConfigDir)?; + let default = include_bytes!("../data/default_config.cfg"); + let mut file = fs::File::create::<&Path>(args.filename.as_ref())?; + file.write_all(default)?; + }; + + let config: Config = Config::builder::<&Path>(args.filename.as_ref()).build()?; + // println!("Config: {:?}", config); + config.setup_logging(None::)?; + + if args.check_config { + config.check_config(); + return Ok(()) + } + + let custom_version = args.custom_version; + + update_themes_dir()?; + + Python::with_gil(|py| -> PyResult<()> { + let pathlib = py.import("pathlib")?; + let path = pathlib.getattr("Path")?; + let configfile = path.call1((args.filename.clone(),))?; + + py_run!(py, configfile firstrun custom_version, r#" + import signal + + from poezio import config + from poezio.core.core import Core + + config.config = config.Config(configfile) # XXX: doesn't work. + + signal.signal(signal.SIGINT, signal.SIG_IGN) + cocore = Core(custom_version, firstrun) + signal.signal(signal.SIGUSR1, cocore.sigusr_handler) # reload the config + signal.signal(signal.SIGHUP, cocore.exit_from_signal) + signal.signal(signal.SIGTERM, cocore.exit_from_signal) + cocore.start() + + from slixmpp.exceptions import IqError, IqTimeout + + # Warning: asyncio must always be imported after the config. Otherwise + # the asyncio logger will not follow our configuration and won't write + # the tracebacks in the correct file, etc + import asyncio + loop = asyncio.get_event_loop() + loop.set_exception_handler(cocore.loop_exception_handler) + + loop.add_reader(sys.stdin, cocore.on_input_readable) + loop.add_signal_handler(signal.SIGWINCH, cocore.sigwinch_handler) + cocore.xmpp.start() + loop.run_forever() + # We reach this point only when loop.stop() is called + try: + cocore.reset_curses() + except: + pass + "#); + + Ok(()) + })?; + + Ok(()) +}