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::io::Read;
use failure::Fallible;
use serde::Deserialize;
use serde::Serialize;
use crate::Config;
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
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"];
/// 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 buf = String::new();
@ -41,7 +33,8 @@ fn load_config(file: &str) -> Fallible<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 argv =
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;
#[macro_use]
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 std::io::{Read, Write};
use std::net::TcpStream;
use websocket::OwnedMessage;
use websocket::{OwnedMessage};
use std::thread;
mod bootstrap;
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
#[derive(Serialize,Deserialize,Debug)]
struct ElefrenRegistration {
parts: (String, String, String, String, Scopes, bool),
use crate::store::Store;
use crate::ele::{EleSession, EleStreamSocket};
use std::time::Duration;
#[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() {
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);
let mut store = Store::from_file(&config.store);
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");
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!("WS url = {}", &url);
let mut client = ClientBuilder::new(&url)
.expect("Error create ClientBuilder")
.connect_secure(Some(connector))
.expect("Error connect to wss");
debug!("Delay before reconnecting...");
thread::sleep(Duration::from_secs(10));
}
}
for m in client.incoming_messages() {
fn process_incoming_events(mut socket : EleStreamSocket) {
'listen: for m in socket.incoming_messages() {
match m {
Ok(OwnedMessage::Text(text)) => {
debug!("Got msg: {}", text);
},
Ok(OwnedMessage::Close(text)) => {
Ok(OwnedMessage::Close(_text)) => {
debug!("Close");
break;
break 'listen;
},
Ok(any) => {
debug!("Unhandled msg: {:?}", any);
}
Err(e) => {
error!("{}", e);
break;
error!("Error reading from socket: {}", e);
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 serde::{Serialize, Deserialize};
use serde::de::DeserializeOwned;
@ -18,6 +22,7 @@ use std::fmt::{self, Display, Formatter};
pub struct Store {
path: Option<PathBuf>,
autosave: bool,
prettysave: bool,
items: Map<String, serde_json::Value>,
}
@ -26,6 +31,7 @@ impl Default for Store {
Self {
path: None,
autosave: false,
prettysave: false,
items: Map::new()
}
}
@ -82,6 +88,7 @@ impl Store {
let mut store = Store {
path: Some(path.as_ref().into()),
autosave: false,
prettysave: false,
items: Map::new(),
};
@ -92,6 +99,11 @@ impl 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.
pub fn set_autosave(&mut self, autosave: bool) {
self.autosave = autosave;
@ -142,7 +154,11 @@ impl Store {
/// Save the map to a custom file path.
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)?;
file.write(as_str.as_bytes())?;

Loading…
Cancel
Save