Skip to content

Commit

Permalink
support force_install_dir
Browse files Browse the repository at this point in the history
  • Loading branch information
changhe3 committed Jul 16, 2022
1 parent 98faae1 commit 8761a8b
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 36 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ serde-aux = "3.0.1"
chrono = { version = "0.4.19", features = ["serde"] }
itertools = "0.10.3"
dialoguer = "0.10.1"
shlex = "1.1.0"
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ swd 0.1.1

(This software has not been extensively tested, use at your own risk. Require steamcmd under PATH)

A command-line utility to download workshop item and collections from steam workshop. You need
[SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD#Downloading_SteamCMD) to use this software, and remember to
include it under the PATH environment variable. The default download directory is
/path/to/steamcmd/steamapps/workshop/content/.
You would need [SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD#Downloading_SteamCMD) to use this software,
and remember to include it under the PATH environment variable. A command-line utility to download workshop item and
collections from steam workshop. This software assembles a command for SteamCMD to execute. By default, this command is
only printed to standard output, you need to use the `-e` flag to automatically execute the command. The default
download directory is /path/to/steamcmd/steamapps/workshop/content/. You can set an alternative location with `-o`.


```
USAGE:
Expand All @@ -27,14 +29,17 @@ FLAGS:
OPTIONS:
--save=<save>
--save=<format>
Save the mod orders of collections to specified format to the current working directory [possible values:
simple, csv]
-o, --output <path>
Set the path of the download location. The path will be passed to force_install_dir in SteamCMD
-u, --username <username>
Steam username for non-anonymous download [default: anonymous]
ARGS:
<files>...
File IDs of the mods and collections to download, can be found at the end of the url for each workshop item
```
```
81 changes: 53 additions & 28 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,23 @@ use net::{
};
use std::{
collections::{HashMap, HashSet},
fmt::Debug,
fmt::{format, Debug},
fs::File,
path::PathBuf,
process::Command,
str::FromStr,
};
use std::{env::current_dir, fmt::Write as FmtWrite, io::Write as IoWrite};
use std::{env::current_dir, io::Write as IoWrite};
use structopt::StructOpt;
use util::PrettyCmd;

///
/// (This software has not been extensively tested, use at your own risk. Require steamcmd under PATH)
///
/// You would need [SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD#Downloading_SteamCMD) to use this software, and remember to include it under the PATH environment variable.
/// A command-line utility to download workshop item and collections from steam workshop.
/// You need [SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD#Downloading_SteamCMD) to use this software, and remember to include it under the PATH environment variable.
/// The default download directory is /path/to/steamcmd/steamapps/workshop/content/.
/// This software assembles a command for SteamCMD to execute. By default, this command is only printed to standard output, you need to use the `-e` flag to automatically execute the command.
/// The default download directory is /path/to/steamcmd/steamapps/workshop/content/. You can set an alternative location with `-o`.
#[derive(Debug, StructOpt)]
struct Params {
/// Execute the produced command through steamcmd, otherwise the command is only printed to standard output and need to be executed manually.
Expand All @@ -40,12 +44,17 @@ struct Params {
#[structopt(short, long, default_value = "anonymous")]
username: String,

/// Set the path of the download location. The path will be passed to force_install_dir in SteamCMD.
#[structopt(short, long, name = "path")]
output: Option<PathBuf>,

/// Save the mod orders of collections to specified format to the current working directory.
#[structopt(
long,
takes_value(true),
require_equals(true),
possible_values(&["simple", "csv"]),
name = "format",
)]
save: Option<String>,

Expand Down Expand Up @@ -202,8 +211,15 @@ impl WFiles {
println!("Invalid File ID: {}", id);
}

fn build_cmd(&self) -> Result<String> {
let mut cmd = format!("steamcmd +login {} ", self.params.username);
fn build_cmd(&self) -> Result<Command> {
let mut cmd = Command::new("steamcmd");
if let Some(path) = self.params.output.as_deref() {
cmd.arg("+force_install_dir");
cmd.arg(path);
}

cmd.arg("+login").arg(&self.params.username);

let wd = current_dir().unwrap();
let mut all_mods = HashSet::new();

Expand All @@ -215,11 +231,10 @@ impl WFiles {

if !file.is_collection() {
if all_mods.insert(file_id) {
write!(
cmd,
"+workshop_download_item {} {} ",
cmd.arg(format!(
"+workshop_download_item {} {}",
file.app_id, file.file_id
)?;
));
}
} else {
let save_path = self
Expand All @@ -235,11 +250,10 @@ impl WFiles {
match inner_file.prompt(self.params.review)? {
ReviewOptions::Yes => {
if all_mods.insert(&inner_file.file_id) {
write!(
cmd,
"+workshop_download_item {} {} ",
cmd.arg(format!(
"+workshop_download_item {} {}",
inner_file.app_id, inner_file.file_id
)?;
));
}

if let Some(save_file) = save_file.as_mut() {
Expand Down Expand Up @@ -272,21 +286,18 @@ impl WFiles {
}
}

cmd.push_str("+quit ");
Ok(cmd.replace('\n', ""))
cmd.arg("+quit");
Ok(cmd)
}

fn run(&self) -> Result<()> {
let cmd = self.build_cmd()?;
let mut cmd = self.build_cmd()?;
if !self.params.exec {
println!("\n{}", cmd);
println!("\n{}", PrettyCmd::new(&cmd))
} else if let Ok(mut proc) = cmd.spawn() {
proc.wait().unwrap();
} else {
let mut cmd = cmd.split_ascii_whitespace();
if let Ok(mut proc) = Command::new(cmd.next().unwrap()).args(cmd).spawn() {
proc.wait().unwrap();
} else {
return Err(color_eyre::eyre::eyre!("steamcmd failed"));
}
return Err(color_eyre::eyre::eyre!("steamcmd failed"));
}
Ok(())
}
Expand All @@ -309,15 +320,28 @@ mod tests {
}

#[test]
fn test_collection_review() -> Result<()> {
let params =
Params::from_iter(["swd", "-r", "--save=csv", "368330611", "116676096"].into_iter());
fn test_output() -> Result<()> {
let params = Params::from_iter(
[
"swd",
"-o",
r"C:\User\admin\My Documents\My Mods",
"368330611",
]
.into_iter(),
);
__main__(params)
}

// #[test]
// fn test_collection_review() -> Result<()> {
// let params =
// Params::from_iter(["swd", "-r", "--save=csv", "368330611", "116676096"].into_iter());
// __main__(params)
// }
}

fn __main__(params: Params) -> Result<()> {
color_eyre::install()?;
if params.files.is_empty() {
Params::clap().print_long_help()?;
} else {
Expand All @@ -329,5 +353,6 @@ fn __main__(params: Params) -> Result<()> {
}

fn main() -> Result<()> {
color_eyre::install()?;
__main__(Params::from_args())
}
1 change: 0 additions & 1 deletion src/net/get_collection_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ mod tests {

#[test]
fn test() {
color_eyre::install().unwrap();
let resp = call([1626860092, 2529002857].into_iter()).unwrap();
println!("{:#?}", resp);
}
Expand Down
1 change: 0 additions & 1 deletion src/net/get_published_file_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ mod tests {

#[test]
fn test() {
color_eyre::install().unwrap();
let resp = call([2824342092, 2529002857, 1111].into_iter()).unwrap();
println!("{:#?}", resp);
}
Expand Down
62 changes: 62 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -1 +1,63 @@
use std::{ffi::OsString, fmt::Display, process::Command};

pub(crate) struct OsStrBuf<'a> {
pub(crate) inner: &'a mut OsString,
}

impl<'a> core::fmt::Write for OsStrBuf<'a> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.inner.push(s);
Ok(())
}
}

pub(crate) struct PrettyCmd<'a> {
cmd: &'a Command,
}

impl<'a> Display for PrettyCmd<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let iter = std::iter::once(self.cmd.get_program()).chain(self.cmd.get_args());
for arg in iter {
if let Some(utf8) = arg.to_str() {
f.write_str(&shlex::quote(utf8))?;
f.write_str(" ")?;
} else {
f.write_fmt(format_args!("{:?} ", arg))?;
}
}
Ok(())
}
}

impl<'a> PrettyCmd<'a> {
pub(crate) fn new(cmd: &'a Command) -> Self {
Self { cmd }
}
}

#[allow(unused_macros)]
macro_rules! os_str_format {
($($arg:tt)*) => {{
use core::fmt::Write;

let mut res = std::ffi::OsString::new();
let mut buf = $crate::util::OsStrBuf {
inner: &mut res
};
core::write!(buf, $($arg)*).unwrap();
res
}};
}

#[allow(unused_imports)]
pub(crate) use os_str_format;

#[cfg(test)]
mod tests {

#[test]
fn test() {
os_str_format!("hello {} {}", "world", 123);
}
}

0 comments on commit 8761a8b

Please sign in to comment.