use crate::commands::{ResultExt, absolute_path};
use clap::{Parser, Subcommand, ValueEnum};
use log::warn;
use std::cmp::Reverse;
use std::fmt::{Display, Formatter};
use std::path::Path;
use vrc_get_vpm::environment::{Settings, VccDatabaseConnection, find_unity_hub};
use vrc_get_vpm::io::{DefaultEnvironmentIo, DefaultProjectIo};
use vrc_get_vpm::{UnityProject, unity_hub};
#[derive(Subcommand)]
#[command(author, version)]
pub enum Vcc {
#[command(subcommand)]
Project(Project),
#[command(subcommand)]
Unity(Unity),
}
impl Vcc {
pub async fn run(self) {
warn!("vrc-get vcc is experimental and may change in the future!");
self.run_inner().await;
}
}
multi_command!(fn run_inner Vcc is Project, Unity);
#[derive(Subcommand)]
#[command(author, version)]
pub enum Project {
List(ProjectList),
Add(ProjectAdd),
Remove(ProjectRemove),
}
multi_command!(Project is List, Add, Remove);
async fn migrate_sanitize_projects(
connection: &mut VccDatabaseConnection,
io: &DefaultEnvironmentIo,
settings: &Settings,
) {
connection
.migrate(settings, io)
.await
.exit_context("migrating from settings.json");
connection.dedup_projects();
}
#[derive(Parser)]
#[command(author, version)]
pub struct ProjectList {
#[command(flatten)]
env_args: super::EnvArgs,
}
impl ProjectList {
pub async fn run(self) {
let io = DefaultEnvironmentIo::new_default();
let settings = Settings::load(&io).await.exit_context("loading settings");
let mut connection = VccDatabaseConnection::connect(&io)
.await
.exit_context("connecting to database");
migrate_sanitize_projects(&mut connection, &io, &settings).await;
connection
.sync_with_real_projects(false, &io)
.await
.exit_context("syncing with real projects");
let mut projects = connection.get_projects();
connection
.save(&io)
.await
.exit_context("saving updated database");
projects.sort_by_key(|x| Reverse(x.last_modified()));
for project in projects.iter() {
let Some(path) = project.path() else { continue };
let Some(name) = project.name() else { continue };
let unity_version = project
.unity_version()
.map(|x| x.to_string())
.unwrap_or("unknown".into());
println!("{name}:");
println!(" Path: {path}");
println!(" Unity: {unity_version}");
println!(" Target: {}", project.project_type());
println!(" Is Favorite: {}", project.favorite());
}
}
}
#[derive(Parser)]
#[command(author, version)]
pub struct ProjectAdd {
#[command(flatten)]
env_args: super::EnvArgs,
path: Box<str>,
}
impl ProjectAdd {
pub async fn run(self) {
let io = DefaultEnvironmentIo::new_default();
let mut settings = Settings::load(&io).await.exit_context("loading settings");
let mut connection = VccDatabaseConnection::connect(&io)
.await
.exit_context("connecting to database");
let project_path = absolute_path(Path::new(self.path.as_ref()));
let project_io = DefaultProjectIo::new(project_path.into());
let project = UnityProject::load(project_io)
.await
.exit_context("loading specified project");
migrate_sanitize_projects(&mut connection, &io, &settings).await;
connection
.add_project(&project)
.await
.exit_context("adding project");
connection.save(&io).await.exit_context("saving database");
settings
.load_from_db(&connection)
.exit_context("saving database");
settings.save(&io).await.exit_context("saving settings");
}
}
#[derive(Parser)]
#[command(author, version)]
pub struct ProjectRemove {
#[command(flatten)]
env_args: super::EnvArgs,
path: Box<str>,
}
impl ProjectRemove {
pub async fn run(self) {
let io = DefaultEnvironmentIo::new_default();
let mut settings = Settings::load(&io).await.exit_context("loading settings");
let mut connection = VccDatabaseConnection::connect(&io)
.await
.exit_context("connecting to database");
let Some(project) = connection
.find_project(self.path.as_ref())
.exit_context("getting projects")
else {
return println!("No project found at {}", self.path);
};
migrate_sanitize_projects(&mut connection, &io, &settings).await;
connection.remove_project(&project);
connection.save(&io).await.exit_context("saving database");
settings
.load_from_db(&connection)
.exit_context("saving database");
settings.save(&io).await.exit_context("saving environment");
}
}
#[derive(Subcommand)]
#[command(author, version)]
pub enum Unity {
List(UnityList),
Add(UnityAdd),
Remove(UnityRemove),
Update(UnityUpdate),
}
multi_command!(Unity is List, Add, Remove, Update);
#[derive(Parser)]
#[command(author, version)]
pub struct UnityList {
#[command(flatten)]
env_args: super::EnvArgs,
}
impl UnityList {
pub async fn run(self) {
let io = DefaultEnvironmentIo::new_default();
let connection = VccDatabaseConnection::connect(&io)
.await
.exit_context("connecting to database");
let mut unity_installations = connection.get_unity_installations();
unity_installations.sort_by_key(|x| Reverse(x.version()));
for unity in unity_installations.iter() {
if let Some(path) = unity.path() {
if let Some(unity_version) = unity.version() {
println!("version {unity_version} at {path}");
} else {
println!("unknown version at {path}");
}
}
}
}
}
#[derive(Parser)]
#[command(author, version)]
pub struct UnityAdd {
#[command(flatten)]
env_args: super::EnvArgs,
path: Box<str>,
}
impl UnityAdd {
pub async fn run(self) {
let io = DefaultEnvironmentIo::new_default();
let mut connection = VccDatabaseConnection::connect(&io)
.await
.exit_context("connecting to database");
let unity_version = vrc_get_vpm::unity::call_unity_for_version(self.path.as_ref().as_ref())
.await
.exit_context("calling unity for version");
connection
.add_unity_installation(self.path.as_ref(), unity_version)
.exit_context("adding unity installation");
connection.save(&io).await.exit_context("saving database");
println!("Added version {} at {}", unity_version, self.path);
}
}
#[derive(Parser)]
#[command(author, version)]
pub struct UnityRemove {
#[command(flatten)]
env_args: super::EnvArgs,
path: Box<str>,
}
impl UnityRemove {
pub async fn run(self) {
let io = DefaultEnvironmentIo::new_default();
let mut connection = VccDatabaseConnection::connect(&io)
.await
.exit_context("connecting to database");
let Some(unity) = connection
.get_unity_installations()
.into_iter()
.find(|x| x.path() == Some(self.path.as_ref()))
else {
return eprintln!("No unity installation found at {}", self.path);
};
connection.remove_unity_installation(&unity);
connection.save(&io).await.exit_context("saving database");
}
}
#[derive(Parser)]
#[command(author, version)]
pub struct UnityUpdate {
#[command(flatten)]
env_args: super::EnvArgs,
#[arg(long, default_value_t)]
method: UnityHubAccessMethod,
}
#[derive(Default, Copy, Clone, Eq, Ord, PartialOrd, PartialEq, ValueEnum)]
enum UnityHubAccessMethod {
#[default]
ReadConfig,
CallHub,
}
impl Display for UnityHubAccessMethod {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
UnityHubAccessMethod::ReadConfig => f.write_str("read-config"),
UnityHubAccessMethod::CallHub => f.write_str("call-hub"),
}
}
}
impl UnityUpdate {
pub async fn run(self) {
let io = DefaultEnvironmentIo::new_default();
let mut settings = Settings::load(&io).await.exit_context("loading settings");
let unity_hub_path = find_unity_hub(&mut settings, &io)
.await
.exit_context("loading unity hub path")
.unwrap_or_else(|| exit_with!("Unity Hub not found"));
let unity_list = match self.method {
UnityHubAccessMethod::ReadConfig => unity_hub::load_unity_by_loading_unity_hub_files()
.await
.exit_context("loading list of unity from config file")
.into_iter()
.map(|x| (x.version, x.path))
.collect::<Vec<_>>(),
UnityHubAccessMethod::CallHub => {
unity_hub::load_unity_by_calling_unity_hub(unity_hub_path.as_ref())
.await
.exit_context("loading unity list from unity hub")
}
};
let mut connection = VccDatabaseConnection::connect(&io)
.await
.exit_context("connecting to database");
connection
.update_unity_from_unity_hub_and_fs(&unity_list, &io)
.await
.exit_context("updating unity from unity hub");
connection.save(&io).await.exit_context("saving database");
settings.save(&io).await.exit_context("saving settings");
}
}