Completion Part 2

master
random-geek 2021-03-20 23:44:06 -07:00
parent a593f141ff
commit b1f3f66006
12 changed files with 220 additions and 101 deletions

View File

@ -20,11 +20,12 @@ SQLite format maps are currently supported.
## General usage ## General usage
`mapeditr [-h] <map> <subcommand>` `mapeditr [-h] [-y] <map> <subcommand>`
Arguments: Arguments:
- `-h`: Show a help message and exit. - `-h, --help`: Print help information and exit.
- `-y, --yes`: Skip the default confirmation prompt (for those who feel brave).
- `<map>`: Path to the Minetest world/map to edit; this can be either a world - `<map>`: Path to the Minetest world/map to edit; this can be either a world
directory or a `map.sqlite` file within a world folder. This file will be directory or a `map.sqlite` file within a world folder. This file will be
modified, so *always* shut down the game/server before executing any command. modified, so *always* shut down the game/server before executing any command.
@ -75,8 +76,8 @@ Arguments:
- `--p1, --p2`: Area containing mapblocks to delete. By default, only mapblocks - `--p1, --p2`: Area containing mapblocks to delete. By default, only mapblocks
fully within this area will be deleted. fully within this area will be deleted.
- `--invert`: Delete all mapblocks fully *outside* the given area. Use with - `--invert`: If present, delete all mapblocks fully *outside* the given area.
caution; you could erase a large portion of your world! Use with caution; you could erase a large portion of your world!
**Note:** Deleting mapblocks is *not* the same as filling them with air! Mapgen **Note:** Deleting mapblocks is *not* the same as filling them with air! Mapgen
will be invoked where the blocks were deleted, and this sometimes causes will be invoked where the blocks were deleted, and this sometimes causes
@ -91,11 +92,11 @@ contents) are also deleted.
Arguments: Arguments:
- `--node`: Only delete metadata of nodes with the given name. If not - `--node`: (Optional) Name of node to delete metadata from. If not specified,
specified, metadata will be deleted in all matching nodes. metadata will be deleted from any node.
- `--p1, --p2`: Area in which to delete metadata. If not specified, metadata - `--p1, --p2`: (Optional) Area in which to delete metadata. If not specified,
will be deleted everywhere. metadata will be deleted everywhere.
- `--invert`: Only delete metadata *outside* the given area. - `--invert`: If present, delete metadata *outside* the given area.
### deleteobjects ### deleteobjects

View File

@ -139,6 +139,12 @@ fn parse_cmd_line_args() -> anyhow::Result<InstArgs> {
For additional information, see the manual.") For additional information, see the manual.")
.version(crate_version!()) .version(crate_version!())
.author(crate_authors!()) .author(crate_authors!())
.arg(Arg::with_name("yes")
.long("yes")
.short("y")
.global(true)
.help("Skip the default confirmation prompt.")
)
// TODO: Move map arg to subcommands? // TODO: Move map arg to subcommands?
.arg(Arg::with_name("map") .arg(Arg::with_name("map")
.required(true) .required(true)
@ -153,6 +159,7 @@ fn parse_cmd_line_args() -> anyhow::Result<InstArgs> {
let sub_matches = matches.subcommand_matches(&sub_name).unwrap(); let sub_matches = matches.subcommand_matches(&sub_name).unwrap();
Ok(InstArgs { Ok(InstArgs {
do_confirmation: !matches.is_present("yes"),
command: sub_name, command: sub_name,
map_path: matches.value_of("map").unwrap().to_string(), map_path: matches.value_of("map").unwrap().to_string(),
input_map_path: sub_matches.value_of("input_map").map(str::to_string), input_map_path: sub_matches.value_of("input_map").map(str::to_string),
@ -214,7 +221,7 @@ fn print_editing_status(done: usize, total: usize, real_start: Instant,
let num_bars = (progress * TOTAL_BARS as f32) as usize; let num_bars = (progress * TOTAL_BARS as f32) as usize;
let bars = "=".repeat(num_bars); let bars = "=".repeat(num_bars);
eprint!( print!(
"\r[{bars:<total_bars$}] {progress:.1}% | {elapsed} elapsed \ "\r[{bars:<total_bars$}] {progress:.1}% | {elapsed} elapsed \
| {remaining} remaining", | {remaining} remaining",
bars=bars, bars=bars,
@ -228,7 +235,7 @@ fn print_editing_status(done: usize, total: usize, real_start: Instant,
} }
); );
} else { } else {
eprint!("\rProcessing... {} elapsed", fmt_duration(real_elapsed)); print!("\rProcessing... {} elapsed", fmt_duration(real_elapsed));
} }
std::io::stdout().flush().unwrap(); std::io::stdout().flush().unwrap();
@ -239,13 +246,21 @@ fn print_log(log_type: LogType, msg: String) {
let prefix = format!("{}: ", log_type); let prefix = format!("{}: ", log_type);
let indented = msg.lines().collect::<Vec<_>>() let indented = msg.lines().collect::<Vec<_>>()
.join(&format!( "\n{}", " ".repeat(prefix.len()) )); .join(&format!( "\n{}", " ".repeat(prefix.len()) ));
eprintln!("{}{}", prefix, indented); println!("{}{}", prefix, indented);
}
fn get_confirmation() -> bool {
print!("Proceed? (Y/n): ");
let mut result = String::new();
std::io::stdin().read_line(&mut result).unwrap();
result.trim().to_ascii_lowercase() == "y"
} }
pub fn run_cmd_line() { pub fn run_cmd_line() {
use std::sync::mpsc; use std::sync::mpsc;
use crate::instance::{InstState, InstEvent, spawn_compute_thread}; use crate::instance::{InstState, ServerEvent, spawn_compute_thread};
let args = match parse_cmd_line_args() { let args = match parse_cmd_line_args() {
Ok(a) => a, Ok(a) => a,
@ -265,13 +280,24 @@ pub fn run_cmd_line() {
let mut cur_state = InstState::Ignore; let mut cur_state = InstState::Ignore;
let mut need_newline = false; let mut need_newline = false;
let newline_if = |condition: &mut bool| {
if *condition {
println!();
*condition = false;
}
};
loop { /* Main command-line logging loop */ loop { /* Main command-line logging loop */
let now = Instant::now(); let now = Instant::now();
let mut forced_update = InstState::Ignore; let mut forced_update = InstState::Ignore;
match status.event_rx.recv_timeout(TICK) { match status.receiver().recv_timeout(TICK) {
Ok(event) => match event { Ok(event) => match event {
InstEvent::NewState(new_state) => { ServerEvent::Log(log_type, msg) => {
newline_if(&mut need_newline);
print_log(log_type, msg);
},
ServerEvent::NewState(new_state) => {
// Force progress updates at the beginning and end of // Force progress updates at the beginning and end of
// querying/editing stages. // querying/editing stages.
if (cur_state == InstState::Ignore) != if (cur_state == InstState::Ignore) !=
@ -290,13 +316,10 @@ pub fn run_cmd_line() {
} }
cur_state = new_state; cur_state = new_state;
}, },
InstEvent::Log(log_type, msg) => { ServerEvent::ConfirmRequest => {
if need_newline { newline_if(&mut need_newline);
eprintln!(); status.confirm(get_confirmation());
need_newline = false; },
}
print_log(log_type, msg);
}
}, },
Err(err) => { Err(err) => {
// Compute thread has exited; break out of the loop. // Compute thread has exited; break out of the loop.
@ -311,8 +334,8 @@ pub fn run_cmd_line() {
if forced_update == InstState::Querying if forced_update == InstState::Querying
|| (cur_state == InstState::Querying && timed_update_ready) || (cur_state == InstState::Querying && timed_update_ready)
{ {
eprint!("\rQuerying mapblocks... {} found.", print!("\rQuerying mapblocks... {} found.",
status.get().blocks_total); status.get_status().blocks_total);
std::io::stdout().flush().unwrap(); std::io::stdout().flush().unwrap();
last_update = now; last_update = now;
need_newline = true; need_newline = true;
@ -320,8 +343,7 @@ pub fn run_cmd_line() {
else if forced_update == InstState::Editing else if forced_update == InstState::Editing
|| (cur_state == InstState::Editing && timed_update_ready) || (cur_state == InstState::Editing && timed_update_ready)
{ {
let s = status.get(); let s = status.get_status();
// TODO: Update duration format? e.g. 1m 42s remaining
print_editing_status(s.blocks_done, s.blocks_total, print_editing_status(s.blocks_done, s.blocks_total,
querying_start, editing_start, s.show_progress); querying_start, editing_start, s.show_progress);
last_update = now; last_update = now;
@ -329,9 +351,8 @@ pub fn run_cmd_line() {
} }
// Print a newline after the last querying/editing message. // Print a newline after the last querying/editing message.
if need_newline && cur_state == InstState::Ignore { if cur_state == InstState::Ignore {
eprintln!(); newline_if(&mut need_newline);
need_newline = false;
} }
} }

View File

@ -1,4 +1,4 @@
use super::{Command, BLOCK_CACHE_SIZE}; use super::{Command, ArgResult, BLOCK_CACHE_SIZE};
use crate::{unwrap_or, opt_unwrap_or}; use crate::{unwrap_or, opt_unwrap_or};
use crate::spatial::{Vec3, Area, MAP_LIMIT}; use crate::spatial::{Vec3, Area, MAP_LIMIT};
@ -10,7 +10,7 @@ use crate::instance::{ArgType, InstBundle, InstArgs};
use crate::utils::{CacheMap, query_keys}; use crate::utils::{CacheMap, query_keys};
fn verify_args(args: &InstArgs) -> anyhow::Result<()> { fn verify_args(args: &InstArgs) -> ArgResult {
let map_area = Area::new( let map_area = Area::new(
Vec3::new(-MAP_LIMIT, -MAP_LIMIT, -MAP_LIMIT), Vec3::new(-MAP_LIMIT, -MAP_LIMIT, -MAP_LIMIT),
Vec3::new(MAP_LIMIT, MAP_LIMIT, MAP_LIMIT) Vec3::new(MAP_LIMIT, MAP_LIMIT, MAP_LIMIT)
@ -19,10 +19,10 @@ fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
if map_area.intersection(args.area.unwrap() + args.offset.unwrap()) if map_area.intersection(args.area.unwrap() + args.offset.unwrap())
.is_none() .is_none()
{ {
anyhow::bail!("Destination area is outside map bounds."); return ArgResult::error("Destination area is outside map bounds.");
} }
Ok(()) ArgResult::Ok
} }

View File

@ -25,7 +25,8 @@ pub fn get_command() -> Command {
args: vec![ args: vec![
(ArgType::Area(true), "Area containing mapblocks to delete"), (ArgType::Area(true), "Area containing mapblocks to delete"),
(ArgType::Invert, (ArgType::Invert,
"Delete all mapblocks fully *outside* the given area.") "If present, delete all mapblocks fully *outside* the given \
area.")
], ],
help: "Delete all mapblocks inside or outside an area." help: "Delete all mapblocks inside or outside an area."
} }

View File

@ -1,12 +1,22 @@
use super::Command; use super::{Command, ArgResult};
use crate::unwrap_or; use crate::unwrap_or;
use crate::spatial::Vec3; use crate::spatial::Vec3;
use crate::instance::{ArgType, InstBundle}; use crate::instance::{ArgType, InstArgs, InstBundle};
use crate::map_block::{MapBlock, NodeMetadataList, NodeMetadataListExt}; use crate::map_block::{MapBlock, NodeMetadataList, NodeMetadataListExt};
use crate::utils::{query_keys, to_bytes, to_slice, fmt_big_num}; use crate::utils::{query_keys, to_bytes, to_slice, fmt_big_num};
fn verify_args(args: &InstArgs) -> ArgResult {
if !args.area.is_some() && !args.node.is_some() {
return ArgResult::warning(
"No area or node specified. ALL metadata will be deleted!");
}
ArgResult::Ok
}
fn delete_metadata(inst: &mut InstBundle) { fn delete_metadata(inst: &mut InstBundle) {
let node = inst.args.node.as_ref().map(to_bytes); let node = inst.args.node.as_ref().map(to_bytes);
@ -71,14 +81,13 @@ fn delete_metadata(inst: &mut InstBundle) {
pub fn get_command() -> Command { pub fn get_command() -> Command {
Command { Command {
func: delete_metadata, func: delete_metadata,
verify_args: None, verify_args: Some(verify_args),
args: vec![ args: vec![
(ArgType::Area(false), "Area in which to delete metadata"), (ArgType::Area(false), "Area in which to delete metadata"),
(ArgType::Invert, "Delete all metadata outside the given area."), (ArgType::Invert,
(ArgType::Node(false), "If present, delete metadata *outside* the given area."),
"Node to delete metadata from. If not specified, all metadata \ (ArgType::Node(false), "Name of node to delete metadata from")
will be deleted.")
], ],
help: "Delete node metadata." help: "Delete node metadata of certain nodes."
} }
} }

View File

@ -61,8 +61,7 @@ fn delete_objects(inst: &mut InstBundle) {
// is specified. // is specified.
let search_item = inst.args.items.as_ref().and_then(|items| items.get(0)) let search_item = inst.args.items.as_ref().and_then(|items| items.get(0))
.map(to_bytes); .map(to_bytes);
let item_searcher = search_item.as_ref() let item_searcher = search_item.as_ref().map(|s| TwoWaySearcher::new(s));
.map(|s| TwoWaySearcher::new(s));
let keys = query_keys(&mut inst.db, &mut inst.status, let keys = query_keys(&mut inst.db, &mut inst.status,
to_slice(&search_obj), inst.args.area, inst.args.invert, true); to_slice(&search_obj), inst.args.area, inst.args.invert, true);
@ -71,8 +70,9 @@ fn delete_objects(inst: &mut InstBundle) {
let mut count: u64 = 0; let mut count: u64 = 0;
for key in keys { for key in keys {
inst.status.inc_done(); inst.status.inc_done();
let data = unwrap_or!(inst.db.get_block(key), continue); let data = inst.db.get_block(key).unwrap();
let mut block = unwrap_or!(MapBlock::deserialize(&data), continue); let mut block = unwrap_or!(MapBlock::deserialize(&data),
{ inst.status.inc_failed(); continue; });
let mut modified = false; let mut modified = false;
for i in (0..block.static_objects.len()).rev() { for i in (0..block.static_objects.len()).rev() {

View File

@ -19,9 +19,30 @@ mod vacuum;
pub const BLOCK_CACHE_SIZE: usize = 1024; pub const BLOCK_CACHE_SIZE: usize = 1024;
pub enum ArgResult {
Ok,
Warning(String),
Error(String),
}
impl ArgResult {
/// Create a new ArgResult::Warning from a &str.
#[inline]
pub fn warning(msg: &str) -> Self {
Self::Warning(msg.to_string())
}
/// Create a new ArgResult::Error from a &str.
#[inline]
pub fn error(msg: &str) -> Self {
Self::Error(msg.to_string())
}
}
pub struct Command { pub struct Command {
pub func: fn(&mut InstBundle), pub func: fn(&mut InstBundle),
pub verify_args: Option<fn(&InstArgs) -> anyhow::Result<()>>, pub verify_args: Option<fn(&InstArgs) -> ArgResult>,
pub help: &'static str, pub help: &'static str,
pub args: Vec<(ArgType, &'static str)> pub args: Vec<(ArgType, &'static str)>
} }

View File

@ -1,4 +1,4 @@
use super::{Command, BLOCK_CACHE_SIZE}; use super::{Command, ArgResult, BLOCK_CACHE_SIZE};
use crate::{unwrap_or, opt_unwrap_or}; use crate::{unwrap_or, opt_unwrap_or};
use crate::spatial::{Vec3, Area, MAP_LIMIT}; use crate::spatial::{Vec3, Area, MAP_LIMIT};
@ -10,11 +10,11 @@ use crate::block_utils::{merge_blocks, merge_metadata, clean_name_id_map};
use crate::utils::{query_keys, CacheMap}; use crate::utils::{query_keys, CacheMap};
fn verify_args(args: &InstArgs) -> anyhow::Result<()> { fn verify_args(args: &InstArgs) -> ArgResult {
if args.invert if args.invert
&& args.offset.filter(|&ofs| ofs != Vec3::new(0, 0, 0)).is_some() && args.offset.filter(|&ofs| ofs != Vec3::new(0, 0, 0)).is_some()
{ {
anyhow::bail!("Inverted selections cannot be offset."); return ArgResult::error("Inverted selections cannot be offset.");
} }
let offset = args.offset.unwrap_or(Vec3::new(0, 0, 0)); let offset = args.offset.unwrap_or(Vec3::new(0, 0, 0));
@ -26,10 +26,10 @@ fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
if map_area.intersection(args.area.unwrap_or(map_area) + offset) if map_area.intersection(args.area.unwrap_or(map_area) + offset)
.is_none() .is_none()
{ {
anyhow::bail!("Destination area is outside map bounds."); return ArgResult::error("Destination area is outside map bounds.");
} }
Ok(()) ArgResult::Ok
} }

View File

@ -1,4 +1,4 @@
use super::Command; use super::{Command, ArgResult};
use crate::unwrap_or; use crate::unwrap_or;
use crate::spatial::Vec3; use crate::spatial::Vec3;
@ -129,14 +129,15 @@ fn replace_in_inv(inst: &mut InstBundle) {
} }
fn verify_args(args: &InstArgs) -> anyhow::Result<()> { fn verify_args(args: &InstArgs) -> ArgResult {
if args.new_item.is_none() && !args.delete_item && !args.delete_meta { if args.new_item.is_none() && !args.delete_item && !args.delete_meta {
anyhow::bail!( return ArgResult::error(
"new_item is required unless --delete or --deletemeta is used.") "new_item is required unless --delete or --deletemeta is used.");
} else if args.new_item.is_some() && args.delete_item { } else if args.new_item.is_some() && args.delete_item {
anyhow::bail!("Cannot delete items if new_item is specified."); return ArgResult::error(
"Cannot delete items if new_item is specified.");
} }
Ok(()) ArgResult::Ok
} }

View File

@ -1,4 +1,4 @@
use super::Command; use super::{Command, ArgResult};
use crate::unwrap_or; use crate::unwrap_or;
use crate::spatial::{Vec3, Area, InverseBlockIterator}; use crate::spatial::{Vec3, Area, InverseBlockIterator};
@ -127,10 +127,12 @@ fn replace_nodes(inst: &mut InstBundle) {
} }
fn verify_args(args: &InstArgs) -> anyhow::Result<()> { fn verify_args(args: &InstArgs) -> ArgResult {
anyhow::ensure!(args.node != args.new_node, if args.node == args.new_node {
"node and new_node must be different."); return ArgResult::error("node and new_node must be different.");
Ok(()) }
ArgResult::Ok
} }

View File

@ -1,4 +1,4 @@
use super::Command; use super::{Command, ArgResult};
use crate::unwrap_or; use crate::unwrap_or;
use crate::spatial::{Vec3, Area, InverseBlockIterator}; use crate::spatial::{Vec3, Area, InverseBlockIterator};
@ -107,10 +107,12 @@ fn set_param2(inst: &mut InstBundle) {
} }
fn verify_args(args: &InstArgs) -> anyhow::Result<()> { fn verify_args(args: &InstArgs) -> ArgResult {
anyhow::ensure!(args.area.is_some() || args.node.is_some(), if args.area.is_none() && args.node.is_none() {
"An area and/or node must be provided."); return ArgResult::error("An area and/or node must be provided.");
Ok(()) }
ArgResult::Ok
} }

View File

@ -7,6 +7,7 @@ use anyhow::Context;
use crate::spatial::{Vec3, Area, MAP_LIMIT}; use crate::spatial::{Vec3, Area, MAP_LIMIT};
use crate::map_database::MapDatabase; use crate::map_database::MapDatabase;
use crate::commands; use crate::commands;
use crate::commands::ArgResult;
#[derive(Clone)] #[derive(Clone)]
@ -32,6 +33,7 @@ pub enum ArgType {
#[derive(Debug)] #[derive(Debug)]
pub struct InstArgs { pub struct InstArgs {
pub do_confirmation: bool,
pub command: String, pub command: String,
pub map_path: String, pub map_path: String,
pub input_map_path: Option<String>, pub input_map_path: Option<String>,
@ -86,6 +88,7 @@ impl InstStatus {
pub enum LogType { pub enum LogType {
Info, Info,
Warning,
Error Error
} }
@ -93,22 +96,29 @@ impl std::fmt::Display for LogType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Info => write!(f, "info"), Self::Info => write!(f, "info"),
Self::Warning => write!(f, "warning"),
Self::Error => write!(f, "error") Self::Error => write!(f, "error")
} }
} }
} }
pub enum InstEvent { pub enum ServerEvent {
Log(LogType, String),
NewState(InstState), NewState(InstState),
Log(LogType, String) ConfirmRequest,
}
pub enum ClientEvent {
ConfirmResponse(bool),
} }
#[derive(Clone)]
pub struct StatusServer { pub struct StatusServer {
status: Arc<Mutex<InstStatus>>, status: Arc<Mutex<InstStatus>>,
event_tx: mpsc::Sender<InstEvent> event_tx: mpsc::Sender<ServerEvent>,
event_rx: mpsc::Receiver<ClientEvent>,
} }
impl StatusServer { impl StatusServer {
@ -118,7 +128,7 @@ impl StatusServer {
pub fn set_state(&self, new_state: InstState) { pub fn set_state(&self, new_state: InstState) {
self.status.lock().unwrap().state = new_state; self.status.lock().unwrap().state = new_state;
self.event_tx.send(InstEvent::NewState(new_state)).unwrap(); self.event_tx.send(ServerEvent::NewState(new_state)).unwrap();
} }
pub fn set_total(&self, total: usize) { pub fn set_total(&self, total: usize) {
@ -146,8 +156,18 @@ impl StatusServer {
self.set_state(InstState::Ignore); self.set_state(InstState::Ignore);
} }
pub fn log<S: AsRef<str>>(&self, lt: LogType, msg: S) { pub fn get_confirmation(&self) -> bool {
self.event_tx.send(InstEvent::Log(lt, msg.as_ref().to_string())) self.event_tx.send(ServerEvent::ConfirmRequest).unwrap();
while let Ok(event) = self.event_rx.recv() {
match event {
ClientEvent::ConfirmResponse(res) => return res
}
}
false
}
fn log<S: AsRef<str>>(&self, lt: LogType, msg: S) {
self.event_tx.send(ServerEvent::Log(lt, msg.as_ref().to_string()))
.unwrap(); .unwrap();
} }
@ -155,6 +175,10 @@ impl StatusServer {
self.log(LogType::Info, msg); self.log(LogType::Info, msg);
} }
pub fn log_warning<S: AsRef<str>>(&self, msg: S) {
self.log(LogType::Warning, msg);
}
pub fn log_error<S: AsRef<str>>(&self, msg: S) { pub fn log_error<S: AsRef<str>>(&self, msg: S) {
self.log(LogType::Error, msg); self.log(LogType::Error, msg);
} }
@ -162,14 +186,44 @@ impl StatusServer {
pub struct StatusClient { pub struct StatusClient {
pub event_rx: mpsc::Receiver<InstEvent>, status: Arc<Mutex<InstStatus>>,
status: Arc<Mutex<InstStatus>> event_tx: mpsc::Sender<ClientEvent>,
event_rx: mpsc::Receiver<ServerEvent>,
} }
impl StatusClient { impl StatusClient {
pub fn get(&self) -> InstStatus { pub fn get_status(&self) -> InstStatus {
self.status.lock().unwrap().clone() self.status.lock().unwrap().clone()
} }
#[inline]
pub fn receiver(&self) -> &mpsc::Receiver<ServerEvent> {
&self.event_rx
}
pub fn confirm(&self, choice: bool) {
self.event_tx.send(ClientEvent::ConfirmResponse(choice)).unwrap();
}
}
fn status_link() -> (StatusServer, StatusClient) {
let status1 = Arc::new(Mutex::new(InstStatus::new()));
let status2 = status1.clone();
let (s_event_tx, s_event_rx) = mpsc::channel();
let (c_event_tx, c_event_rx) = mpsc::channel();
(
StatusServer {
status: status1,
event_tx: s_event_tx,
event_rx: c_event_rx,
},
StatusClient {
status: status2,
event_tx: c_event_tx,
event_rx: s_event_rx,
}
)
} }
@ -181,17 +235,6 @@ pub struct InstBundle<'a> {
} }
fn status_channel() -> (StatusServer, StatusClient) {
let status1 = Arc::new(Mutex::new(InstStatus::new()));
let status2 = status1.clone();
let (event_tx, event_rx) = mpsc::channel();
(
StatusServer {status: status1, event_tx},
StatusClient {status: status2, event_rx}
)
}
fn verify_args(args: &InstArgs) -> anyhow::Result<()> { fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
// TODO: Complete verifications. // TODO: Complete verifications.
@ -280,14 +323,17 @@ fn open_map(path: PathBuf, flags: sqlite::OpenFlags)
} }
fn compute_thread(args: InstArgs, status: StatusServer) fn compute_thread(args: InstArgs, status: StatusServer) -> anyhow::Result<()> {
-> anyhow::Result<()>
{
verify_args(&args)?; verify_args(&args)?;
let commands = commands::get_commands(); let commands = commands::get_commands();
let mut cmd_warning = None;
if let Some(cmd_verify) = commands[args.command.as_str()].verify_args { if let Some(cmd_verify) = commands[args.command.as_str()].verify_args {
cmd_verify(&args)? cmd_warning = match cmd_verify(&args) {
ArgResult::Ok => None,
ArgResult::Warning(w) => Some(w),
ArgResult::Error(e) => anyhow::bail!(e)
}
} }
let db_conn = open_map(PathBuf::from(&args.map_path), let db_conn = open_map(PathBuf::from(&args.map_path),
@ -303,14 +349,27 @@ fn compute_thread(args: InstArgs, status: StatusServer)
Some(conn) => Some(MapDatabase::new(conn)?), Some(conn) => Some(MapDatabase::new(conn)?),
None => None None => None
}; };
// TODO: Standard warning?
let func = commands[args.command.as_str()].func; let func = commands[args.command.as_str()].func;
let mut inst = InstBundle {args, status, db, idb}; let mut inst = InstBundle {args, status, db, idb};
func(&mut inst);
// Issue warnings and confirmation prompt.
if inst.args.do_confirmation {
inst.status.log_warning(
"This tool can permanently damage your Minetest world.\n\
Always EXIT Minetest and BACK UP the map database before use.");
}
if let Some(w) = cmd_warning {
inst.status.log_warning(w);
}
if inst.args.do_confirmation && !inst.status.get_confirmation() {
return Ok(());
}
func(&mut inst); // The real thing!
let fails = inst.status.get_status().blocks_failed; let fails = inst.status.get_status().blocks_failed;
if fails > 0 { if fails > 0 {
// TODO: log_warning
inst.status.log_info(format!( inst.status.log_info(format!(
"Skipped {} invalid/unsupported mapblocks.", fails)); "Skipped {} invalid/unsupported mapblocks.", fails));
} }
@ -327,16 +386,18 @@ fn compute_thread(args: InstArgs, status: StatusServer)
pub fn spawn_compute_thread(args: InstArgs) pub fn spawn_compute_thread(args: InstArgs)
-> (std::thread::JoinHandle<()>, StatusClient) -> (std::thread::JoinHandle<()>, StatusClient)
{ {
let (status_tx, status_rx) = status_channel(); let (status_server, status_client) = status_link();
// Clone within this thread to avoid issue #39364 (hopefully). // Clone within this thread to avoid issue #39364 (hopefully).
let status_tx_2 = status_tx.clone(); let raw_event_tx = status_server.event_tx.clone();
let h = std::thread::Builder::new() let h = std::thread::Builder::new()
.name("compute".to_string()) .name("compute".to_string())
.spawn(move || { .spawn(move || {
compute_thread(args, status_tx_2).unwrap_or_else( compute_thread(args, status_server).unwrap_or_else(
|err| status_tx.log_error(&err.to_string()) // TODO: Find a cleaner way to do this.
|err| raw_event_tx.send(
ServerEvent::Log(LogType::Error, err.to_string())).unwrap()
); );
}) })
.unwrap(); .unwrap();
(h, status_rx) (h, status_client)
} }