Modernize upload command

This commit is contained in:
Lucien Greathouse
2021-06-08 21:53:56 -04:00
parent 561a3e3256
commit d0482a004e
2 changed files with 75 additions and 106 deletions

View File

@@ -8,14 +8,7 @@ mod plugin;
mod serve; mod serve;
mod upload; mod upload;
use std::{ use std::{borrow::Cow, env, path::Path, str::FromStr};
borrow::Cow,
env,
error::Error,
fmt,
path::{Path, PathBuf},
str::FromStr,
};
use structopt::StructOpt; use structopt::StructOpt;
use thiserror::Error; use thiserror::Error;
@@ -26,7 +19,7 @@ pub use self::fmt_project::FmtProjectCommand;
pub use self::init::{InitCommand, InitKind}; pub use self::init::{InitCommand, InitKind};
pub use self::plugin::{PluginCommand, PluginSubcommand}; pub use self::plugin::{PluginCommand, PluginSubcommand};
pub use self::serve::ServeCommand; pub use self::serve::ServeCommand;
pub use self::upload::*; pub use self::upload::UploadCommand;
/// Command line options that Rojo accepts, defined using the structopt crate. /// Command line options that Rojo accepts, defined using the structopt crate.
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
@@ -46,7 +39,7 @@ impl Options {
Subcommand::Init(subcommand) => subcommand.run(), Subcommand::Init(subcommand) => subcommand.run(),
Subcommand::Serve(subcommand) => subcommand.run(self.global), Subcommand::Serve(subcommand) => subcommand.run(self.global),
Subcommand::Build(subcommand) => subcommand.run(), Subcommand::Build(subcommand) => subcommand.run(),
Subcommand::Upload(upload_options) => upload(upload_options), Subcommand::Upload(subcommand) => subcommand.run(),
Subcommand::FmtProject(subcommand) => subcommand.run(), Subcommand::FmtProject(subcommand) => subcommand.run(),
Subcommand::Doc(subcommand) => subcommand.run(), Subcommand::Doc(subcommand) => subcommand.run(),
Subcommand::Plugin(subcommand) => subcommand.run(), Subcommand::Plugin(subcommand) => subcommand.run(),
@@ -124,71 +117,6 @@ pub enum Subcommand {
Plugin(PluginCommand), Plugin(PluginCommand),
} }
/// Builds the project and uploads it to Roblox.
#[derive(Debug, StructOpt)]
pub struct UploadCommand {
/// Path to the project to upload. Defaults to the current directory.
#[structopt(default_value = "")]
pub project: PathBuf,
/// Authenication cookie to use. If not specified, Rojo will attempt to find one from the system automatically.
#[structopt(long)]
pub cookie: Option<String>,
/// Asset ID to upload to.
#[structopt(long = "asset_id")]
pub asset_id: u64,
}
impl UploadCommand {
pub fn absolute_project(&self) -> Cow<'_, Path> {
resolve_path(&self.project)
}
}
/// The kind of asset to upload to the website. Affects what endpoints Rojo uses
/// and changes how the asset is built.
#[derive(Debug, Clone, Copy)]
pub enum UploadKind {
/// Upload to a place.
Place,
/// Upload to a model-like asset, like a Model, Plugin, or Package.
Model,
}
impl FromStr for UploadKind {
type Err = UploadKindParseError;
fn from_str(source: &str) -> Result<Self, Self::Err> {
match source {
"place" => Ok(UploadKind::Place),
"model" => Ok(UploadKind::Model),
_ => Err(UploadKindParseError {
attempted: source.to_owned(),
}),
}
}
}
/// Error type for failing to parse an `UploadKind`.
#[derive(Debug)]
pub struct UploadKindParseError {
attempted: String,
}
impl Error for UploadKindParseError {}
impl fmt::Display for UploadKindParseError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(
formatter,
"Invalid upload kind '{}'. Valid kinds are: place, model",
self.attempted
)
}
}
pub(super) fn resolve_path(path: &Path) -> Cow<'_, Path> { pub(super) fn resolve_path(path: &Path) -> Cow<'_, Path> {
if path.is_absolute() { if path.is_absolute() {
Cow::Borrowed(path) Cow::Borrowed(path)

View File

@@ -1,46 +1,87 @@
use std::path::PathBuf;
use std::str::FromStr;
use anyhow::{bail, format_err, Context};
use memofs::Vfs; use memofs::Vfs;
use reqwest::{ use reqwest::{
header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT}, header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT},
StatusCode, StatusCode,
}; };
use thiserror::Error; use structopt::StructOpt;
use crate::{auth_cookie::get_auth_cookie, cli::UploadCommand, serve_session::ServeSession}; use crate::{auth_cookie::get_auth_cookie, serve_session::ServeSession};
#[derive(Debug, Error)] use super::resolve_path;
enum Error {
#[error("Rojo could not find your Roblox auth cookie. Please pass one via --cookie.")]
NeedAuthCookie,
#[error("The Roblox API returned an unexpected error: {body}")] /// Builds the project and uploads it to Roblox.
RobloxApi { body: String }, #[derive(Debug, StructOpt)]
pub struct UploadCommand {
/// Path to the project to upload. Defaults to the current directory.
#[structopt(default_value = "")]
pub project: PathBuf,
/// Authenication cookie to use. If not specified, Rojo will attempt to find one from the system automatically.
#[structopt(long)]
pub cookie: Option<String>,
/// Asset ID to upload to.
#[structopt(long = "asset_id")]
pub asset_id: u64,
} }
pub fn upload(options: UploadCommand) -> Result<(), anyhow::Error> { impl UploadCommand {
let cookie = options pub fn run(self) -> Result<(), anyhow::Error> {
.cookie let project_path = resolve_path(&self.project);
.clone()
.or_else(get_auth_cookie)
.ok_or(Error::NeedAuthCookie)?;
let vfs = Vfs::new_default(); let cookie = self.cookie.or_else(get_auth_cookie).context(
"Rojo could not find your Roblox auth cookie. Please pass one via --cookie.",
)?;
let session = ServeSession::new(vfs, &options.absolute_project())?; let vfs = Vfs::new_default();
let tree = session.tree(); let session = ServeSession::new(vfs, project_path)?;
let inner_tree = tree.inner();
let root = inner_tree.root();
let encode_ids = match root.class.as_str() { let tree = session.tree();
"DataModel" => root.children().to_vec(), let inner_tree = tree.inner();
_ => vec![root.referent()], let root = inner_tree.root();
};
let mut buffer = Vec::new(); let encode_ids = match root.class.as_str() {
"DataModel" => root.children().to_vec(),
_ => vec![root.referent()],
};
log::trace!("Encoding binary model"); let mut buffer = Vec::new();
rbx_binary::to_writer_default(&mut buffer, tree.inner(), &encode_ids)?;
do_upload(buffer, options.asset_id, &cookie) log::trace!("Encoding binary model");
rbx_binary::to_writer_default(&mut buffer, tree.inner(), &encode_ids)?;
do_upload(buffer, self.asset_id, &cookie)
}
}
/// The kind of asset to upload to the website. Affects what endpoints Rojo uses
/// and changes how the asset is built.
#[derive(Debug, Clone, Copy)]
enum UploadKind {
/// Upload to a place.
Place,
/// Upload to a model-like asset, like a Model, Plugin, or Package.
Model,
}
impl FromStr for UploadKind {
type Err = anyhow::Error;
fn from_str(source: &str) -> Result<Self, Self::Err> {
match source {
"place" => Ok(UploadKind::Place),
"model" => Ok(UploadKind::Model),
attempted => Err(format_err!(
"Invalid upload kind '{}'. Valid kinds are: place, model",
attempted
)),
}
}
} }
fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()> { fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()> {
@@ -76,10 +117,10 @@ fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()>
let status = response.status(); let status = response.status();
if !status.is_success() { if !status.is_success() {
return Err(Error::RobloxApi { bail!(
body: response.text()?, "The Roblox API returned an unexpected error: {}",
} response.text()?
.into()); );
} }
Ok(()) Ok(())