use crate::utils::{
get_text, last_modified, parse_filesize, process_shadow_api_response, pubkey_arg,
shadow_client_factory, shadow_file_with_basename, storage_object_url,
wait_for_user_confirmation, FileMetadata, FILE_UPLOAD_BATCH_SIZE,
};
use byte_unit::Byte;
use clap::Parser;
use futures::StreamExt;
use shadow_drive_sdk::{Pubkey, ShadowDriveClient, StorageAccountVersion};
use shadow_rpc_auth::genesysgo_auth::{authenticate, parse_account_id_from_url};
use solana_sdk::signature::Signer;
use std::path::PathBuf;
#[derive(Debug, Parser)]
pub enum DriveCommand {
ShadowRpcAuth,
CreateStorageAccount {
name: String,
#[clap(parse(try_from_str = parse_filesize))]
size: Byte,
},
DeleteStorageAccount {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
},
CancelDeleteStorageAccount {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
},
ClaimStake {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
},
AddStorage {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
#[clap(parse(try_from_str = parse_filesize))]
size: Byte,
},
AddImmutableStorage {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
#[clap(parse(try_from_str = parse_filesize))]
size: Byte,
},
ReduceStorage {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
#[clap(parse(try_from_str = parse_filesize))]
size: Byte,
},
MakeStorageImmutable {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
},
GetStorageAccount {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
},
GetStorageAccounts {
#[clap(parse(try_from_str = pubkey_arg))]
owner: Option<Pubkey>,
},
ListFiles {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
},
GetText {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
filename: String,
},
GetObjectData {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
file: String,
},
DeleteFile {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
filename: String,
},
EditFile {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
path: PathBuf,
},
StoreFiles {
#[clap(parse(try_from_str = pubkey_arg))]
storage_account: Pubkey,
#[clap(min_values = 1)]
files: Vec<PathBuf>,
},
}
impl DriveCommand {
pub async fn process<T: Signer>(
&self,
signer: &T,
client_signer: T,
rpc_url: &str,
skip_confirm: bool,
auth: Option<String>,
) -> anyhow::Result<()> {
let signer_pubkey = signer.pubkey();
println!("Signing with {:?}", signer_pubkey);
println!("Sending RPC requests to {}", rpc_url);
match self {
DriveCommand::ShadowRpcAuth => {
let account_id = parse_account_id_from_url(rpc_url.to_string())?;
let resp = authenticate(signer as &dyn Signer, &account_id).await?;
println!("{:#?}", resp);
}
DriveCommand::CreateStorageAccount { name, size } => {
let client = shadow_client_factory(client_signer, rpc_url, auth);
println!("Create Storage Account {}: {}", name, size);
wait_for_user_confirmation(skip_confirm)?;
let response = client
.create_storage_account(name, size.clone(), StorageAccountVersion::v2())
.await;
let resp = process_shadow_api_response(response)?;
println!("{:#?}", resp);
}
DriveCommand::DeleteStorageAccount { storage_account } => {
let client = shadow_client_factory(client_signer, rpc_url, auth);
println!("Delete Storage Account {}", storage_account.to_string());
wait_for_user_confirmation(skip_confirm)?;
let response = client.delete_storage_account(storage_account).await;
let resp = process_shadow_api_response(response)?;
println!("{:#?}", resp);
}
DriveCommand::CancelDeleteStorageAccount { storage_account } => {
let client = shadow_client_factory(client_signer, rpc_url, auth);
println!(
"Cancellation of Delete Storage Account {}",
storage_account.to_string()
);
wait_for_user_confirmation(skip_confirm)?;
let response = client.cancel_delete_storage_account(storage_account).await;
let resp = process_shadow_api_response(response)?;
println!("{:#?}", resp);
}
DriveCommand::ClaimStake { storage_account } => {
let client = shadow_client_factory(client_signer, rpc_url, auth);
println!(
"Claim Stake on Storage Account {}",
storage_account.to_string()
);
wait_for_user_confirmation(skip_confirm)?;
let response = client.claim_stake(storage_account).await;
let resp = process_shadow_api_response(response)?;
println!("{:#?}", resp);
}
DriveCommand::ReduceStorage {
storage_account,
size,
} => {
let client = shadow_client_factory(client_signer, rpc_url, auth);
println!(
"Reduce Storage Capacity {}: {}",
storage_account.to_string(),
size
);
wait_for_user_confirmation(skip_confirm)?;
let response = client.reduce_storage(storage_account, size.clone()).await;
let resp = process_shadow_api_response(response)?;
println!("{:#?}", resp);
}
DriveCommand::AddStorage {
storage_account,
size,
} => {
let client = shadow_client_factory(client_signer, rpc_url, auth);
println!("Increase Storage {}: {}", storage_account.to_string(), size);
wait_for_user_confirmation(skip_confirm)?;
let response = client.add_storage(storage_account, size.clone()).await;
let resp = process_shadow_api_response(response)?;
println!("{:#?}", resp);
}
DriveCommand::AddImmutableStorage {
storage_account,
size,
} => {
let client = shadow_client_factory(client_signer, rpc_url, auth);
println!(
"Increase Immutable Storage {}: {}",
storage_account.to_string(),
size
);
wait_for_user_confirmation(skip_confirm)?;
let response = client
.add_immutable_storage(storage_account, size.clone())
.await;
let resp = process_shadow_api_response(response)?;
println!("{:#?}", resp);
}
DriveCommand::MakeStorageImmutable { storage_account } => {
let client = shadow_client_factory(client_signer, rpc_url, auth);
println!("Make Storage Immutable {}", storage_account.to_string());
wait_for_user_confirmation(skip_confirm)?;
let response = client.make_storage_immutable(storage_account).await;
let resp = process_shadow_api_response(response)?;
println!("{:#?}", resp);
}
DriveCommand::GetStorageAccount { storage_account } => {
let client = ShadowDriveClient::new(client_signer, rpc_url);
println!("Get Storage Account {}", storage_account.to_string());
let response = client.get_storage_account(storage_account).await;
let act = process_shadow_api_response(response)?;
println!("{:#?}", act);
}
DriveCommand::GetStorageAccounts { owner } => {
let client = shadow_client_factory(client_signer, rpc_url, auth.clone());
let owner = owner.as_ref().unwrap_or(&signer_pubkey);
println!("Get Storage Accounts Owned By {}", owner.to_string());
let response = client.get_storage_accounts(owner).await;
let accounts = process_shadow_api_response(response)?;
println!("{:#?}", accounts);
}
DriveCommand::ListFiles { storage_account } => {
let client = ShadowDriveClient::new(client_signer, rpc_url);
println!(
"List Files for Storage Account {}",
storage_account.to_string()
);
let response = client.list_objects(storage_account).await;
let files = process_shadow_api_response(response)?;
println!("{:#?}", files);
}
DriveCommand::GetText {
storage_account,
filename,
} => {
let url = storage_object_url(storage_account, filename);
let resp = get_text(&url).await?;
let last_modified = last_modified(resp.headers())?;
println!("Get Text at {}", &url);
println!("Last Modified: {}", last_modified);
println!("");
println!("{}", resp.text().await?);
}
DriveCommand::DeleteFile {
storage_account,
filename,
} => {
let client = ShadowDriveClient::new(client_signer, rpc_url);
let url = storage_object_url(storage_account, filename);
println!("Delete file {}", &url);
wait_for_user_confirmation(skip_confirm)?;
let response = client.delete_file(storage_account, url.clone()).await;
let resp = process_shadow_api_response(response)?;
println!("{:#?}", resp);
}
DriveCommand::EditFile {
storage_account,
path,
} => {
let client = ShadowDriveClient::new(client_signer, rpc_url);
let shadow_file = shadow_file_with_basename(path);
println!(
"Edit file {} {}",
storage_account.to_string(),
path.display()
);
wait_for_user_confirmation(skip_confirm)?;
let response = client.edit_file(storage_account, shadow_file).await;
let resp = process_shadow_api_response(response)?;
println!("{:#?}", resp);
}
DriveCommand::GetObjectData {
storage_account,
file,
} => {
let url = storage_object_url(storage_account, file);
println!("Get object data {} {}", storage_account.to_string(), file);
let http_client = reqwest::Client::new();
let response = http_client.head(url).send().await?;
let data = FileMetadata::from_headers(response.headers())?;
println!("{:#?}", data);
}
DriveCommand::StoreFiles {
storage_account,
files,
} => {
let client = ShadowDriveClient::new(client_signer, rpc_url);
println!("Store Files {} {:#?}", storage_account.to_string(), files);
println!(
"WARNING: This CLI does not add any encryption on its own. \
The files in their current state become public as soon as they're uploaded."
);
wait_for_user_confirmation(skip_confirm)?;
let mut responses = Vec::new();
for chunk in files.chunks(5) {
let response = async {
let resp = client
.store_files(
&storage_account,
chunk
.into_iter()
.map(|path: &PathBuf| shadow_file_with_basename(path))
.collect(),
)
.await;
let resp = process_shadow_api_response(resp).unwrap();
println!("{:#?}", resp);
};
responses.push(response);
}
futures::stream::iter(responses)
.buffer_unordered(100)
.collect::<Vec<_>>()
.await;
}
}
Ok(())
}
}