Compare commits

...

2 Commits

  1. 21
      src/bootstrap.rs
  2. 88
      src/ele.rs
  3. 127
      src/main.rs
  4. 18
      src/store.rs

@ -1,8 +1,9 @@
//! Application start-up code handling CLI args, logging, config file load etc
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
use failure::Fallible; use failure::Fallible;
use serde::Deserialize; use crate::Config;
use serde::Serialize;
const CONFIG_FILE: &str = "manabu.toml"; const CONFIG_FILE: &str = "manabu.toml";
@ -12,20 +13,11 @@ const SOFTWARE_NAME: &str = env!("CARGO_PKG_NAME");
/// to allow using e.g. TRACE without drowing our custom messages /// to allow using e.g. TRACE without drowing our custom messages
const SPAMMY_LIBS: [&str; 6] = ["tokio_reactor", "hyper", "reqwest", "mio", "want", "elefren"]; const SPAMMY_LIBS: [&str; 6] = ["tokio_reactor", "hyper", "reqwest", "mio", "want", "elefren"];
#[derive(SmartDefault,Serialize,Deserialize,Debug)]
#[serde(default)]
pub struct Config {
#[default="info"]
logging: String,
pub instance: String,
#[default="manabu_store.json"]
pub store: String,
}
const LOG_LEVELS: [&str; 5] = ["error", "warn", "info", "debug", "trace"]; const LOG_LEVELS: [&str; 5] = ["error", "warn", "info", "debug", "trace"];
/// Load the shared config file /// Load the shared config file
fn load_config(file: &str) -> Fallible<Config> { pub fn load_config(file: &str) -> Fallible<Config> {
let mut file = File::open(file)?; let mut file = File::open(file)?;
let mut buf = String::new(); let mut buf = String::new();
@ -41,7 +33,8 @@ fn load_config(file: &str) -> Fallible<Config> {
Ok(config) Ok(config)
} }
pub(crate) fn init() -> Fallible<Config> { /// Boot up the application. Initializes common stuff and returns the loaded config.
pub fn handle_cli_args_and_load_config() -> Fallible<Config> {
let version = format!("{}, built from {}", env!("CARGO_PKG_VERSION"), env!("GIT_REV")); let version = format!("{}, built from {}", env!("CARGO_PKG_VERSION"), env!("GIT_REV"));
let argv = let argv =
clap::App::new(SOFTWARE_NAME) clap::App::new(SOFTWARE_NAME)

@ -0,0 +1,88 @@
//! Mastodon API types and functions building on elefren
use serde::Deserialize;
use serde::Serialize;
use crate::store::Store;
use crate::Config;
use elefren::{Registration, Mastodon};
use elefren::http_send::HttpSender;
use elefren::helpers::cli;
use elefren::scopes::Scopes;
use std::str::FromStr;
use failure::Fallible;
const KEY_OAUTH_REGISTRATION: &str = "oauth.registration";
const KEY_OAUTH_SESSION: &str = "oauth.session";
pub type EleRegistratered = elefren::registration::Registered<HttpSender>;
pub type EleSession = elefren::Mastodon<HttpSender>;
pub type EleStreamSocket = websocket::sync::Client<native_tls::TlsStream<std::net::TcpStream>>;
/// Wrapper for the long tuple with Registration state
#[derive(Serialize,Deserialize,Debug)]
pub struct ElefrenRegistration {
pub parts: (String, String, String, String, Scopes, bool),
}
/// Register "app" in the server software
pub fn register(store : &mut Store, config : &Config) -> EleRegistratered {
match store.get::<ElefrenRegistration>(KEY_OAUTH_REGISTRATION) {
Some(reg) => {
info!("Loaded registration from store");
EleRegistratered::from_parts(
// this sucks
&reg.parts.0, &reg.parts.1, &reg.parts.2, &reg.parts.3, reg.parts.4, reg.parts.5
)
}
None => {
info!("Creating a new registration");
let registered = Registration::new(&format!("https://{}", config.instance))
.client_name("manabu")
.scopes(Scopes::from_str("read write").expect("err parse scopes"))
.build().expect("error register");
store.put(KEY_OAUTH_REGISTRATION, ElefrenRegistration { parts : registered.clone().into_parts() });
registered
}
}
}
/// Open mastodon API session (get access token)
pub fn open_session(store : &mut Store, registered: EleRegistratered) -> EleSession {
match store.get::<elefren::Data>(KEY_OAUTH_SESSION) {
Some(data) => {
info!("Reusing saved authorization.");
let cli = Mastodon::from(data);
// TODO check if the session is live, somehow
cli
}
None => {
info!("Creating new authorization.");
let cli = cli::authenticate(registered).expect("error auth");
store.put(KEY_OAUTH_SESSION, cli.data.clone());
cli
}
}
}
/// Open streaming api websocket
pub fn open_stream_websocket(session : &EleSession, stream_name : &str) -> Fallible<EleStreamSocket> {
let connector = native_tls::TlsConnector::new()?;
let hostname = &session.data.base[session.data.base.find("://").unwrap()+3..];
let url = format!("wss://{host}/api/v1/streaming/?stream={sname}&access_token={token}",
host=hostname,
sname=stream_name,
token=session.data.token);
debug!("WS url = {}", &url);
Ok(websocket::ClientBuilder::new(&url)
.expect("Error create ClientBuilder")
.connect_secure(Some(connector))?)
}

@ -6,120 +6,85 @@ extern crate failure;
extern crate smart_default; extern crate smart_default;
#[macro_use] #[macro_use]
extern crate serde; extern crate serde;
#[macro_use]
use elefren::{
helpers::cli,
prelude::*,
entities::prelude::*,
http_send::HttpSender,
};
use failure::Fallible;
use crate::bootstrap::Config;
use crate::store::Store;
use native_tls::TlsConnector; use websocket::{OwnedMessage};
use std::io::{Read, Write}; use std::thread;
use std::net::TcpStream;
use websocket::OwnedMessage;
mod bootstrap; mod bootstrap;
mod store; mod store;
mod ele;
type EleRegistratered = elefren::registration::Registered<HttpSender>; use crate::bootstrap::handle_cli_args_and_load_config;
/// Wrapper for the long tuple with Registration state use crate::store::Store;
#[derive(Serialize,Deserialize,Debug)] use crate::ele::{EleSession, EleStreamSocket};
struct ElefrenRegistration { use std::time::Duration;
parts: (String, String, String, String, Scopes, bool),
#[derive(SmartDefault,Serialize,Deserialize,Debug)]
#[serde(default)]
pub struct Config {
/// Logging level to use (can be increased using -v flags)
#[default="info"]
pub logging: String,
/// Instance domain name, e.g. example.com
pub instance: String,
/// File to use for session storage
#[default="manabu_store.json"]
pub store: String,
} }
const KEY_OAUTH_REGISTRATION: &str = "elefren.registration";
const KEY_OAUTH_SESSION: &str = "elefren.session";
fn main() { fn main() {
let config : Config = bootstrap::init().expect("error init config"); let config : Config = handle_cli_args_and_load_config().expect("error init");
debug!("Loaded config: {:#?}", config); debug!("Loaded config: {:#?}", config);
let mut store = Store::from_file(&config.store); let mut store = Store::from_file(&config.store);
store.set_autosave(true); store.set_autosave(true);
store.set_pretty_print(true);
let registered = register(&mut store, &config); let registered = ele::register(&mut store, &config);
let client = open_session(&mut store, registered); let session = ele::open_session(&mut store, registered);
debug!("Listening to events"); debug!("Listening to events");
let connector = TlsConnector::new().unwrap(); let m_session = session.clone();
let handle = thread::spawn(move || handle_stream(m_session));
let _ = handle.join();
}
fn handle_stream(session : EleSession) -> ! {
loop {
debug!("Trying to open websocket");
use websocket::ClientBuilder; match ele::open_stream_websocket(&session, "user") {
Ok(socket) => process_incoming_events(socket),
Err(e) => error!("Error opening socket: {}", e)
}
let url = format!("wss://{host}/api/v1/streaming/?stream=user&access_token={token}", host=config.instance, token=client.data.token); debug!("Delay before reconnecting...");
debug!("WS url = {}", &url); thread::sleep(Duration::from_secs(10));
let mut client = ClientBuilder::new(&url) }
.expect("Error create ClientBuilder") }
.connect_secure(Some(connector))
.expect("Error connect to wss");
for m in client.incoming_messages() { fn process_incoming_events(mut socket : EleStreamSocket) {
'listen: for m in socket.incoming_messages() {
match m { match m {
Ok(OwnedMessage::Text(text)) => { Ok(OwnedMessage::Text(text)) => {
debug!("Got msg: {}", text); debug!("Got msg: {}", text);
}, },
Ok(OwnedMessage::Close(text)) => { Ok(OwnedMessage::Close(_text)) => {
debug!("Close"); debug!("Close");
break; break 'listen;
}, },
Ok(any) => { Ok(any) => {
debug!("Unhandled msg: {:?}", any); debug!("Unhandled msg: {:?}", any);
} }
Err(e) => { Err(e) => {
error!("{}", e); error!("Error reading from socket: {}", e);
break; break 'listen;
}, },
} }
} }
info!("Exit.");
}
fn register(store : &mut Store, config : &Config) -> EleRegistratered {
match store.get::<ElefrenRegistration>(KEY_OAUTH_REGISTRATION) {
Some(reg) => {
info!("Loaded registration from store");
EleRegistratered::from_parts(
// this sucks
&reg.parts.0, &reg.parts.1, &reg.parts.2, &reg.parts.3, reg.parts.4, reg.parts.5
)
}
None => {
info!("Creating a new registration");
let registered = Registration::new(&format!("https://{}", config.instance))
.client_name("manabu")
.build().expect("error register");
store.put(KEY_OAUTH_REGISTRATION, ElefrenRegistration { parts : registered.clone().into_parts() });
registered
}
}
}
fn open_session(store : &mut Store, registered: EleRegistratered) -> Mastodon<HttpSender> {
match store.get::<elefren::Data>(KEY_OAUTH_SESSION) {
Some(data) => {
info!("Reusing saved authorization.");
let cli = Mastodon::from(data);
// TODO check if the session is live, somehow
cli
}
None => {
info!("Creating new authorization.");
let cli = cli::authenticate(registered).expect("error auth");
store.put(KEY_OAUTH_SESSION, cli.data.clone());
cli
}
}
} }

@ -1,3 +1,7 @@
#![allow(unused)]
//! JSON store
use std::borrow::Cow; use std::borrow::Cow;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
@ -18,6 +22,7 @@ use std::fmt::{self, Display, Formatter};
pub struct Store { pub struct Store {
path: Option<PathBuf>, path: Option<PathBuf>,
autosave: bool, autosave: bool,
prettysave: bool,
items: Map<String, serde_json::Value>, items: Map<String, serde_json::Value>,
} }
@ -26,6 +31,7 @@ impl Default for Store {
Self { Self {
path: None, path: None,
autosave: false, autosave: false,
prettysave: false,
items: Map::new() items: Map::new()
} }
} }
@ -82,6 +88,7 @@ impl Store {
let mut store = Store { let mut store = Store {
path: Some(path.as_ref().into()), path: Some(path.as_ref().into()),
autosave: false, autosave: false,
prettysave: false,
items: Map::new(), items: Map::new(),
}; };
@ -92,6 +99,11 @@ impl Store {
store store
} }
/// Set pretty print option to use when saved
pub fn set_pretty_print(&mut self, pretty : bool) {
self.prettysave = pretty;
}
/// Set auto-save option - save on each mutation. /// Set auto-save option - save on each mutation.
pub fn set_autosave(&mut self, autosave: bool) { pub fn set_autosave(&mut self, autosave: bool) {
self.autosave = autosave; self.autosave = autosave;
@ -142,7 +154,11 @@ impl Store {
/// Save the map to a custom file path. /// Save the map to a custom file path.
pub fn save_to<P: AsRef<Path>>(&self, path: P) -> Result<()> { pub fn save_to<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let as_str = serde_json::to_string(&self.items).unwrap(); let as_str = (if self.prettysave {
serde_json::to_string_pretty(&self.items)
} else {
serde_json::to_string(&self.items)
}).unwrap();
let mut file = File::create(path)?; let mut file = File::create(path)?;
file.write(as_str.as_bytes())?; file.write(as_str.as_bytes())?;

Loading…
Cancel
Save