Skip to content

Commit

Permalink
A bit of main.rs cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
zkxs committed Jul 5, 2024
1 parent d6591f6 commit 60c75f2
Showing 1 changed file with 153 additions and 139 deletions.
292 changes: 153 additions & 139 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use std::{env, io};
use std::ffi::OsString;
use std::fs::{self, File, OpenOptions};
use std::fs::{self, DirEntry, File, OpenOptions};
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::num::TryFromIntError;
use std::path::PathBuf;
Expand Down Expand Up @@ -72,139 +72,163 @@ impl Hooligan {
let read_dir = fs::read_dir(get_local_player_moderations_path()?).map_err(Error::Io)?;
for dir_entry in read_dir {
let dir_entry = dir_entry.map_err(Error::Io)?;
if dir_entry.file_name().as_encoded_bytes().ends_with(b".vrcset") {
let vrcset_path = dir_entry.path();
if vrcset_path.is_file() {
// calculate some paths and filenames
let mut transaction_log_path = self.project_dirs.data_local_dir().join("history");
fs::create_dir_all(transaction_log_path.as_path()).map_err(Error::Io)?;
let vrcset_os_filename = vrcset_path.file_name().unwrap();
let vrcset_filename = vrcset_os_filename.to_str().ok_or_else(|| Error::BadFilename(vrcset_os_filename.to_owned()))?;
let transaction_log_filename = vrcset_filename
.split_once('.')
.ok_or_else(|| Error::BadFilename(vrcset_os_filename.to_owned()))?
.0.to_string() + ".history";
transaction_log_path.push(transaction_log_filename);
self.process_file(dir_entry, &config)?;
}

// read ordered transaction log counting shows since last hide into a map
let transaction_log_file = {
let mut open_options = OpenOptions::new();
open_options.read(true);
open_options.append(true);
open_options.create(true);
open_options.open(transaction_log_path.as_path()).map_err(Error::Io)?
};
let mut shows_since_last_hide = if transaction_log_path.is_file() {
Some(transaction::read_log(&transaction_log_file)?)
} else {
None
};
// launch the VRChat process
self.spawn_process()?;

// stream changes to vrcset file
let vrcset_file = {
let mut open_options = OpenOptions::new();
open_options.read(true);
open_options.write(true);
open_options.open(vrcset_path.as_path()).map_err(Error::Io)?
};
let mut removed: u32 = 0; // track removed lines
let mut retained: u32 = 0; // track retained lines that we would have normally removed, if not for the threshold
let mut pending_transactions: Vec<Transaction> = Vec::new(); // track difference between previous data and current data
let lines_to_remove = {
let line_reader = BufReader::new(&vrcset_file).lines();
line_reader.map(|maybe_line| { // parse the lines handling errors
match maybe_line {
Ok(line) => moderation::Line::parse(&line).map_err(Error::ShowHideParse),
Err(e) => Err(Error::Io(e)),
}
})
}.filter(|line| {
line.as_ref().map_or(true, |line| { // retain errors
Ok(())
}

/// Load config from disk
fn load_config(&mut self) -> Config {
let config_dir = self.project_dirs.config_local_dir();
let config_path = config_dir.join("config.props");
if config_path.is_file() {
match Config::load(config_path.as_path()).map_err(Error::ConfigLoad) {
Ok(config) => config,
Err(e) => {
writeln!(self.log, "failed to load config and falling back to default: {e:?}");
Config::default()
}
}
} else {
let config = Config::default();
if let Err(e) = fs::create_dir_all(config_dir) {
writeln!(self.log, "error creating config directory: {e:?}");
}
if let Err(e) = config.serialize(config_path.as_path()) {
writeln!(self.log, "error saving default config: {e:?}");
}
config
}
}

/// process a *.vrcset file
fn process_file(&mut self, dir_entry: DirEntry, config: &Config) -> Result<(), Error> {
if dir_entry.file_name().as_encoded_bytes().ends_with(b".vrcset") {
let vrcset_path = dir_entry.path();
if vrcset_path.is_file() {
// calculate some paths and filenames
let mut transaction_log_path = self.project_dirs.data_local_dir().join("history");
fs::create_dir_all(transaction_log_path.as_path()).map_err(Error::Io)?;
let vrcset_os_filename = vrcset_path.file_name().unwrap();
let vrcset_filename = vrcset_os_filename.to_str().ok_or_else(|| Error::BadFilename(vrcset_os_filename.to_owned()))?;
let transaction_log_filename = vrcset_filename
.split_once('.')
.ok_or_else(|| Error::BadFilename(vrcset_os_filename.to_owned()))?
.0.to_string() + ".history";
transaction_log_path.push(transaction_log_filename);

// read ordered transaction log counting shows since last hide into a map
let transaction_log_file = {
let mut open_options = OpenOptions::new();
open_options.read(true);
open_options.append(true);
open_options.create(true);
open_options.open(transaction_log_path.as_path()).map_err(Error::Io)?
};
let mut shows_since_last_hide = if transaction_log_path.is_file() {
Some(transaction::read_log(&transaction_log_file)?)
} else {
None
};

// stream changes to vrcset file
let vrcset_file = {
let mut open_options = OpenOptions::new();
open_options.read(true);
open_options.write(true);
open_options.open(vrcset_path.as_path()).map_err(Error::Io)?
};
let mut removed: u32 = 0; // track removed lines
let mut retained: u32 = 0; // track retained lines that we would have normally removed, if not for the threshold
let mut pending_transactions: Vec<Transaction> = Vec::new(); // track difference between previous data and current data
let lines_to_remove = {
let line_reader = BufReader::new(&vrcset_file).lines();
line_reader.map(|maybe_line| { // parse the lines handling errors
match maybe_line {
Ok(line) => moderation::Line::parse(&line).map_err(Error::ShowHideParse),
Err(e) => Err(Error::Io(e)),
}
})
}.filter(|line| {
line.as_ref().map_or(true, |line| { // retain errors

// number of times user was shown since last hide OR None if there is no data
let shows = shows_since_last_hide.as_mut()
.and_then(|map| map.remove(&line.key));
// number of times user was shown since last hide OR None if there is no data
let shows = shows_since_last_hide.as_mut()
.and_then(|map| map.remove(&line.key));

match line.value {
moderation::Value::Hide => { // we read a Hide from the vrcset file
if shows.map(|shows| !shows.is_hidden()).unwrap_or(true) {
// if user was NOT last known to be hidden, record this manual hide
pending_transactions.push(Transaction::new(line.key.to_owned(), TransactionValue::ManualHide));
}
true // retain hidden user entries
match line.value {
moderation::Value::Hide => { // we read a Hide from the vrcset file
if shows.map(|shows| !shows.is_hidden()).unwrap_or(true) {
// if user was NOT last known to be hidden, record this manual hide
pending_transactions.push(Transaction::new(line.key.to_owned(), TransactionValue::ManualHide));
}
moderation::Value::Show => { // we read a Show from the vrcset file
// if we see a manual show in this block we need to consider it in the total show count
let extra_shows = if shows.as_ref().map(|shows| !shows.is_shown()).unwrap_or(true) {
// if user was NOT last known to be shown, record this manual show
pending_transactions.push(Transaction::new(line.key.to_owned(), TransactionValue::ManualShow));
1
} else {
0
};
true // retain hidden user entries
}
moderation::Value::Show => { // we read a Show from the vrcset file
// if we see a manual show in this block we need to consider it in the total show count
let extra_shows = if shows.as_ref().map(|shows| !shows.is_shown()).unwrap_or(true) {
// if user was NOT last known to be shown, record this manual show
pending_transactions.push(Transaction::new(line.key.to_owned(), TransactionValue::ManualShow));
1
} else {
0
};

// check if we've shown this user enough times that the show should stick
if shows.map(|shows| shows.count() + extra_shows < config.auto_hide_threshold).unwrap_or(true) {
// not enough shows; reset the user
pending_transactions.push(Transaction::new(line.key.to_owned(), TransactionValue::AutoReset));
removed += 1;
false // remove entry
} else {
// enough shows; retain the user
retained += 1;
true // retain entry
}
// check if we've shown this user enough times that the show should stick
if shows.map(|shows| shows.count() + extra_shows < config.auto_hide_threshold).unwrap_or(true) {
// not enough shows; reset the user
pending_transactions.push(Transaction::new(line.key.to_owned(), TransactionValue::AutoReset));
removed += 1;
false // remove entry
} else {
// enough shows; retain the user
retained += 1;
true // retain entry
}
}
})
});
self.write_lines(&vrcset_file, lines_to_remove, true)?; // overwrite the vrcset file
writeln!(self.log, "removed {removed} and retained {retained} shown user entries from {vrcset_filename}");
}
})
});
self.write_lines(&vrcset_file, lines_to_remove, true)?; // overwrite the vrcset file
writeln!(self.log, "removed {removed} and retained {retained} shown user entries from {vrcset_filename}");

// handle any remaining entries in the map
if let Some(shows_since_last_hide) = shows_since_last_hide {
let mut shown: u32 = 0;
// handle any remaining entries in the map
if let Some(shows_since_last_hide) = shows_since_last_hide {
let mut shown: u32 = 0;

let (default_lines, non_default_lines): (Vec<_>, Vec<_>) = shows_since_last_hide.into_iter()
.partition(|(_, state)| state.is_default());
let (default_lines, non_default_lines): (Vec<_>, Vec<_>) = shows_since_last_hide.into_iter()
.partition(|(_, state)| state.is_default());

// handle manual non-default -> default transitions
non_default_lines.into_iter()
.for_each(|(key, _)| pending_transactions.push(Transaction::new(key, TransactionValue::ManualReset)));
// handle manual non-default -> default transitions
non_default_lines.into_iter()
.for_each(|(key, _)| pending_transactions.push(Transaction::new(key, TransactionValue::ManualReset)));

// handle case where the show threshold has lowered: we need to go back and re-show previously reset users
let mut lines_to_show = default_lines.into_iter()
.filter(|(_, show_hide_count)| show_hide_count.count() >= config.auto_hide_threshold)
.map(|(key, _)| {
shown += 1;
pending_transactions.push(Transaction::new(key.clone(), TransactionValue::AutoShow));
Ok(moderation::Line::new(key, moderation::Value::Show))
}).peekable();
if lines_to_show.peek().is_some() {
// reopen file in append mode and write these lines
let mut open_options = OpenOptions::new();
open_options.append(true);
let vrcset_file = open_options.open(vrcset_path.as_path()).map_err(Error::Io)?;
self.write_lines(&vrcset_file, lines_to_show, false)?;
writeln!(self.log, "added {shown} shown user entries to {vrcset_filename}");
}
// handle case where the show threshold has lowered: we need to go back and re-show previously reset users
let mut lines_to_show = default_lines.into_iter()
.filter(|(_, show_hide_count)| show_hide_count.count() >= config.auto_hide_threshold)
.map(|(key, _)| {
shown += 1;
pending_transactions.push(Transaction::new(key.clone(), TransactionValue::AutoShow));
Ok(moderation::Line::new(key, moderation::Value::Show))
}).peekable();
if lines_to_show.peek().is_some() {
// reopen file in append mode and write these lines
let mut open_options = OpenOptions::new();
open_options.append(true);
let vrcset_file = open_options.open(vrcset_path.as_path()).map_err(Error::Io)?;
self.write_lines(&vrcset_file, lines_to_show, false)?;
writeln!(self.log, "added {shown} shown user entries to {vrcset_filename}");
}

// persist changes to transaction log
writeln!(self.log, "about to record {} transactions", pending_transactions.len());
transaction::write_log(&transaction_log_file, pending_transactions)?;
}
}
}

// launch the VRChat process
let mut args = env::args().skip(1); // we skip the first arg because it's just a path to this executable
if let Some(command) = args.next() {
// we got args, blindly run them as a command
let mut command = Command::new(command);
command.args(args);
writeln!(self.log, "spawning {command:?}");
let _ = command.spawn().map_err(Error::Io)?;
// persist changes to transaction log
writeln!(self.log, "about to record {} transactions", pending_transactions.len());
transaction::write_log(&transaction_log_file, pending_transactions)?;
}
}

Ok(())
Expand Down Expand Up @@ -237,27 +261,17 @@ impl Hooligan {
Ok(())
}

fn load_config(&mut self) -> Config {
let config_dir = self.project_dirs.config_local_dir();
let config_path = config_dir.join("config.props");
if config_path.is_file() {
match Config::load(config_path.as_path()).map_err(Error::ConfigLoad) {
Ok(config) => config,
Err(e) => {
writeln!(self.log, "failed to load config and falling back to default: {e:?}");
Config::default()
}
}
} else {
let config = Config::default();
if let Err(e) = fs::create_dir_all(config_dir) {
writeln!(self.log, "error creating config directory: {e:?}");
}
if let Err(e) = config.serialize(config_path.as_path()) {
writeln!(self.log, "error saving default config: {e:?}");
}
config
/// launch the provided process
fn spawn_process(&mut self) -> Result<(), Error> {
let mut args = env::args().skip(1); // we skip the first arg because it's just a path to this executable
if let Some(command) = args.next() {
// we got args, blindly run them as a command
let mut command = Command::new(command);
command.args(args);
writeln!(self.log, "spawning {command:?}");
let _ = command.spawn().map_err(Error::Io)?;
}
Ok(())
}
}

Expand Down

0 comments on commit 60c75f2

Please sign in to comment.