use reqwest::Url;
use std::collections::{HashMap, HashSet, VecDeque};
-use std::io::Read;
+use std::io::{Read, Write, IoSlice};
+use std::fs::File;
use super::auth::{AuthConfig,AuthError};
use super::config::ConfigLocation;
feeds: HashMap<FeedId, Feed>,
instance: Option<Instance>,
permit_write: bool,
+ logfile: Option<File>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
lines
}
+ pub fn write_to(&self, logfile: &mut Option<File>) {
+ if let Some(ref mut file) = logfile {
+ for line in self.format_full() {
+ // Deliberately squash any error from writing to the
+ // log file. If the disk fills up, it's a shame to
+ // lose the logs, but we don't _also_ want to
+ // terminate the client in a panic.
+ let _ = file.write_vectored(&[IoSlice::new(line.as_bytes()),
+ IoSlice::new(b"\n")]);
+ }
+ }
+ }
+}
+
pub fn execute_and_log_request(client: &reqwest::blocking::Client,
req: reqwest::blocking::Request) ->
Result<(reqwest::blocking::Response, TransactionLogEntry), ClientError>
feeds: HashMap::new(),
instance: None,
permit_write: false,
+ logfile: None,
})
}
self.permit_write = permit;
}
+ pub fn set_logfile(&mut self, file: Option<File>) {
+ self.logfile = file;
+ }
+
pub fn fq(&self, acct: &str) -> String {
match acct.contains('@') {
true => acct.to_owned(),
}
fn consume_transaction_log(&mut self, log: TransactionLogEntry) {
+ log.write_to(&mut self.logfile)
}
fn api_request_cl(&self, client: &reqwest::blocking::Client, req: Req) ->
use reqwest::Url;
use std::io::Write;
+use std::fs::File;
use super::TopLevelError;
use super::auth::{AuthConfig, AuthError};
struct Login {
instance_url: String,
client: reqwest::blocking::Client,
+ logfile: Option<File>,
}
enum AppTokenType<'a> {
const REDIRECT_MAGIC_STRING: &'static str = "urn:ietf:wg:oauth:2.0:oob";
impl Login {
- fn new(instance_url: &str) -> Result<Self, ClientError> {
+ fn new(instance_url: &str, logfile: Option<File>)
+ -> Result<Self, ClientError>
+ {
Ok(Login {
instance_url: instance_url.to_owned(),
client: reqwest_client()?,
+ logfile,
})
}
- fn execute_request(&self, req: reqwest::blocking::RequestBuilder)
+ fn execute_request(&mut self, req: reqwest::blocking::RequestBuilder)
-> Result<reqwest::blocking::Response, ClientError>
{
- let (rsp, _log) = execute_and_log_request(
+ let (rsp, log) = execute_and_log_request(
&self.client, req.build()?)?;
+ log.write_to(&mut self.logfile);
Ok(rsp)
}
- fn register_client(&self) -> Result<Application, ClientError> {
+ fn register_client(&mut self) -> Result<Application, ClientError> {
let (url, req) = Req::post("/api/v1/apps")
.param("redirect_uris", REDIRECT_MAGIC_STRING)
.param("client_name", "Mastodonochrome")
}
}
- fn get_token(&self, app: &Application, toktype: AppTokenType) ->
+ fn get_token(&mut self, app: &Application, toktype: AppTokenType) ->
Result<Token, ClientError>
{
let client_id = match &app.client_id {
}
}
- fn get<T: for<'a> serde::Deserialize<'a>>(&self, path: &str, token: &str) ->
- Result<T, ClientError>
+ fn get<T: for<'a> serde::Deserialize<'a>>(
+ &mut self, path: &str, token: &str) -> Result<T, ClientError>
{
let (url, req) = Req::get(path)
.build(&self.instance_url, &self.client, Some(token))?;
}
}
-pub fn login(cfgloc: &ConfigLocation, instance_url: &str) ->
+pub fn login(cfgloc: &ConfigLocation, instance_url: &str,
+ logfile: Option<File>) ->
Result<(), TopLevelError>
{
// First, check we aren't logged in already, and give some
}?;
let instance_url = url.as_str().trim_end_matches('/');
- let login = Login::new(instance_url)?;
+ let mut login = Login::new(instance_url, logfile)?;
// Register the client and get its details
let app = login.register_client()?;
#[arg(short, long, action(clap::ArgAction::SetTrue))]
readonly: bool,
+ /// HTTP logging mode: the client logs all its transactions to a file.
+ #[arg(long)]
+ loghttp: Option<std::path::PathBuf>,
+
/// Log in to a server, instead of running the main user interface.
/// Provide the top-level URL of the instance website.
#[arg(long, conflicts_with("readonly"))]
None => ConfigLocation::default()?,
Some(dir) => ConfigLocation::from_pathbuf(dir),
};
+ let httplogfile = match cli.loghttp {
+ None => None,
+ Some(path) => Some(std::fs::File::create(path)?),
+ };
match cli.login {
- None => Tui::run(&cfgloc, cli.readonly)?,
- Some(ref server) => login(&cfgloc, server)?,
+ None => Tui::run(&cfgloc, cli.readonly, httplogfile)?,
+ Some(ref server) => login(&cfgloc, server, httplogfile)?,
}
Ok(())
}
use std::cmp::min;
use std::collections::HashSet;
use std::io::{Stdout, Write, stdout};
+use std::fs::File;
use unicode_width::UnicodeWidthStr;
use super::activity_stack::*;
}
impl Tui {
- pub fn run(cfgloc: &ConfigLocation, readonly: bool) ->
+ pub fn run(cfgloc: &ConfigLocation, readonly: bool,
+ logfile: Option<File>) ->
Result<(), TuiError>
{
let (sender, receiver) = std::sync::mpsc::sync_channel(1);
let mut client = Client::new(cfgloc)?;
client.set_writable(!readonly);
+ client.set_logfile(logfile);
stdout().execute(EnterAlternateScreen)?;
enable_raw_mode()?;