use crate::{
config_file::ConfigFile,
crate_detail::CrateDetail,
dir_path::DirPath,
utils::{clear_version_value, get_size},
};
use serde::Deserialize;
use std::{
env,
ffi::OsStr,
fs,
path::{Path, PathBuf},
};
pub(crate) struct CargoTomlLocation {
path: Vec<PathBuf>,
}
impl CargoTomlLocation {
pub(crate) fn new() -> Self {
Self { path: Vec::new() }
}
pub(crate) fn add_path(&mut self, path: PathBuf) {
self.path.push(path);
}
pub(crate) fn append(&mut self, mut lock_location: Self) {
self.path.append(&mut lock_location.path);
}
pub(crate) fn location_path(&self) -> &Vec<PathBuf> {
&self.path
}
}
#[derive(Clone, Deserialize)]
struct LockData {
package: Option<Vec<Package>>,
}
impl LockData {
fn package(&self) -> &Option<Vec<Package>> {
&self.package
}
}
#[derive(Clone, Deserialize)]
struct Package {
name: String,
version: String,
source: Option<String>,
}
impl Package {
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &str {
&self.version
}
fn source(&self) -> &Option<String> {
&self.source
}
}
pub(crate) struct CrateList {
installed_bin: Vec<String>,
installed_crate_registry: Vec<String>,
installed_crate_git: Vec<String>,
old_crate_registry: Vec<String>,
old_crate_git: Vec<String>,
used_crate_registry: Vec<String>,
used_crate_git: Vec<String>,
orphan_crate_registry: Vec<String>,
orphan_crate_git: Vec<String>,
cargo_toml_location: CargoTomlLocation,
}
impl CrateList {
#[allow(clippy::too_many_lines)]
pub(crate) fn create_list(
dir_path: &DirPath,
config_file: &ConfigFile,
crate_detail: &mut CrateDetail,
) -> Self {
let bin_dir = dir_path.bin_dir().as_path();
let cache_dir = dir_path.cache_dir();
let src_dir = dir_path.src_dir();
let checkout_dir = dir_path.checkout_dir().as_path();
let db_dir = dir_path.db_dir().as_path();
let installed_bin = get_installed_bin(bin_dir, crate_detail);
let installed_crate_registry =
get_installed_crate_registry(src_dir, cache_dir, crate_detail);
let installed_crate_git = get_installed_crate_git(checkout_dir, db_dir, crate_detail);
let mut old_crate_registry = Vec::new();
let mut version_removed_crate = remove_version(&installed_crate_registry);
version_removed_crate.sort();
if !version_removed_crate.is_empty() {
let mut common_crate_version = Vec::new();
for i in 0..(version_removed_crate.len() - 1) {
let (name, version) = &version_removed_crate[i];
let (next_name, _) = &version_removed_crate[i + 1];
if name == next_name {
common_crate_version.push((version, i));
} else {
common_crate_version.push((version, i));
let mut latest_version = version;
for (common_version, _) in &common_crate_version {
if semver::Version::parse(latest_version)
< semver::Version::parse(common_version)
{
latest_version = common_version;
}
}
for (crate_version, position) in &common_crate_version {
if crate_version.as_str() != latest_version {
old_crate_registry
.push(installed_crate_registry.get(*position).unwrap().to_string());
}
}
common_crate_version = vec![]
}
}
}
old_crate_registry.sort();
old_crate_registry.dedup();
let mut old_crate_git = Vec::new();
let mut full_name_list = Vec::new();
for crates in fs::read_dir(db_dir).expect("failed to read db dir") {
let entry = crates.unwrap().path();
let path = entry.as_path();
let file_name = path.file_name().unwrap().to_str().unwrap();
let name = file_name.rsplitn(2, '-').collect::<Vec<&str>>();
let mut rev_value = latest_rev_value(path);
rev_value.retain(|c| c != '\'');
let full_name = format!("{}-{}", name[1], rev_value);
full_name_list.push(full_name)
}
for crate_name in &installed_crate_git {
if !crate_name.contains("-HEAD") {
if !full_name_list.contains(crate_name) {
old_crate_git.push(crate_name.to_string());
}
}
}
old_crate_git.sort();
old_crate_git.dedup();
let mut used_crate_registry = Vec::new();
let mut used_crate_git = Vec::new();
let mut cargo_toml_location = CargoTomlLocation::new();
let config_directory = config_file.directory().to_owned();
for path in &config_directory {
let list_cargo_toml = list_cargo_toml(Path::new(path), &config_file);
let (mut registry_crate, mut git_crate) = read_content(list_cargo_toml.location_path());
cargo_toml_location.append(list_cargo_toml);
used_crate_registry.append(&mut registry_crate);
used_crate_git.append(&mut git_crate);
}
used_crate_registry.sort();
used_crate_registry.dedup();
used_crate_git.sort();
used_crate_registry.dedup();
let mut orphan_crate_registry = Vec::new();
let mut orphan_crate_git = Vec::new();
for crates in &installed_crate_registry {
if !used_crate_registry.contains(crates) {
orphan_crate_registry.push(crates.to_string());
}
}
for crates in &installed_crate_git {
if crates.contains("-HEAD") {
let split_installed = crates.rsplitn(2, '-').collect::<Vec<&str>>();
if used_crate_git.is_empty() {
orphan_crate_git.push(crates.to_string());
}
let mut used_in_project = false;
for used in &used_crate_git {
if used.contains(split_installed[1]) {
used_in_project = true;
break;
}
}
if !used_in_project {
orphan_crate_git.push(crates.to_string());
}
} else if !used_crate_git.contains(crates) {
orphan_crate_git.push(crates.to_string());
}
}
orphan_crate_registry.sort();
orphan_crate_registry.dedup();
orphan_crate_git.sort();
orphan_crate_git.dedup();
Self {
installed_bin,
installed_crate_registry,
installed_crate_git,
old_crate_registry,
old_crate_git,
used_crate_registry,
used_crate_git,
orphan_crate_registry,
orphan_crate_git,
cargo_toml_location,
}
}
pub(crate) fn installed_bin(&self) -> &Vec<String> {
&self.installed_bin
}
pub(crate) fn installed_registry(&self) -> &Vec<String> {
&self.installed_crate_registry
}
pub(crate) fn old_registry(&self) -> &Vec<String> {
&self.old_crate_registry
}
pub(crate) fn used_registry(&self) -> &Vec<String> {
&self.used_crate_registry
}
pub(crate) fn orphan_registry(&self) -> &Vec<String> {
&self.orphan_crate_registry
}
pub(crate) fn installed_git(&self) -> &Vec<String> {
&self.installed_crate_git
}
pub(crate) fn old_git(&self) -> &Vec<String> {
&self.old_crate_git
}
pub(crate) fn used_git(&self) -> &Vec<String> {
&self.used_crate_git
}
pub(crate) fn orphan_git(&self) -> &Vec<String> {
&self.orphan_crate_git
}
pub(crate) fn cargo_toml_location(&self) -> &CargoTomlLocation {
&self.cargo_toml_location
}
}
fn list_cargo_toml(path: &Path, config_file: &ConfigFile) -> CargoTomlLocation {
let mut cargo_trim_list = CargoTomlLocation::new();
if path.exists() {
if path.is_dir() {
for entry in std::fs::read_dir(path)
.expect("failed to read directory while trying to find cargo.toml")
{
let sub_path_buf = entry.unwrap().path();
let sub = sub_path_buf.as_path();
if sub.is_dir() {
if need_to_be_ignored(path, config_file) {
continue;
}
let kids_list = list_cargo_toml(sub, config_file);
cargo_trim_list.append(kids_list);
}
if sub.is_file() && sub.file_name() == Some(OsStr::new("Cargo.toml")) {
cargo_trim_list.add_path(path.to_path_buf());
}
}
} else if path.is_file() && path.file_name() == Some(OsStr::new("Cargo.toml")) {
cargo_trim_list.add_path(path.to_path_buf());
}
}
cargo_trim_list
}
fn need_to_be_ignored(path: &Path, config_file: &ConfigFile) -> bool {
let file_name = path.file_name().unwrap().to_str().unwrap();
let is_file_name_ignored = config_file
.ignore_file_name()
.contains(&file_name.to_owned());
let file_is_hidden = file_name.starts_with('.') && !config_file.scan_hidden_folder();
let target_dir_name = env::var("CARGO_BUILD_TARGET_DIR").unwrap_or_else(|_| {
env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| String::from("target"))
});
let file_is_target = file_name == target_dir_name && !config_file.scan_target_folder();
is_file_name_ignored || file_is_hidden || file_is_target
}
fn read_content(list: &[PathBuf]) -> (Vec<String>, Vec<String>) {
let mut present_crate_registry = Vec::new();
let mut present_crate_git = Vec::new();
for lock in list.iter() {
let mut lock_folder = lock.clone();
lock_folder.push("Cargo.lock");
if lock_folder.exists() {
let lock_file = lock_folder
.to_str()
.expect("Failed to convert lock_folder to str");
let file_content = std::fs::read_to_string(lock_file)
.expect("failed to read cargo lock content to string");
let cargo_lock_data: LockData =
toml::from_str(&file_content).expect("Failed to convert to Toml format");
if let Some(packages) = cargo_lock_data.package() {
for package in packages {
if let Some(source) = package.source() {
let name = package.name();
let version = package.version();
if source.contains("registry+") {
let full_name = format!("{}-{}", name, version);
present_crate_registry.push(full_name);
}
if source.contains("git+") {
if source.contains("?rev=")
|| source.contains("?branch=")
|| source.contains("?tag=")
{
let split_url: Vec<&str> = if source.contains("?rev=") {
source.split("?rev=").collect()
} else if source.contains("?branch=") {
source.split("?branch=").collect()
} else {
source.split("?tag=").collect()
};
let rev_sha: Vec<&str> = split_url[1].split('#').collect();
let rev_value = rev_sha[1];
let rev_short_form = &rev_value[..=6];
let full_name = format!("{}-{}", name, rev_short_form);
present_crate_git.push(full_name);
} else {
let rev_sha: Vec<&str> = source.split('#').collect();
let rev_value = rev_sha[1];
let rev_short_form = &rev_value[..=6];
let full_name = format!("{}-{}", name, rev_short_form);
present_crate_git.push(full_name);
}
}
}
}
}
}
}
(present_crate_registry, present_crate_git)
}
fn remove_version(installed_crate_registry: &[String]) -> Vec<(String, String)> {
let mut removed_version = Vec::new();
installed_crate_registry.iter().for_each(|crate_full_name| {
let data = clear_version_value(crate_full_name);
removed_version.push(data);
});
removed_version
}
fn get_installed_bin(bin_dir: &Path, crate_detail: &mut CrateDetail) -> Vec<String> {
let mut installed_bin = Vec::new();
if bin_dir.exists() {
for entry in fs::read_dir(bin_dir).expect("failed to read bin directory") {
let entry = entry.unwrap().path();
let bin_size = get_size(&entry).expect("failed to get size of bin directory");
let file_name = entry
.file_name()
.expect("failed to get file name from bin directory");
let bin_name = file_name.to_str().unwrap().to_string();
crate_detail.add_bin(bin_name.to_owned(), bin_size);
installed_bin.push(bin_name)
}
}
installed_bin.sort();
installed_bin
}
fn get_installed_crate_registry(
src_dir: &Path,
cache_dir: &Path,
crate_detail: &mut CrateDetail,
) -> Vec<String> {
let mut installed_crate_registry = Vec::new();
if src_dir.exists() {
for entry in fs::read_dir(src_dir).expect("failed to read src directory") {
let registry = entry.unwrap().path();
for entry in fs::read_dir(registry).expect("failed to read registry folder") {
let entry = entry.unwrap().path();
let crate_size = get_size(&entry).expect("failed to get registry crate size");
let file_name = entry
.file_name()
.expect("failed to get file name form main entry");
let crate_name = file_name.to_str().unwrap();
crate_detail.add_registry_crate_source(crate_name.to_owned(), crate_size);
installed_crate_registry.push(crate_name.to_owned())
}
}
}
if cache_dir.exists() {
for entry in fs::read_dir(cache_dir).expect("failed to read cache dir") {
let registry = entry.unwrap().path();
for entry in fs::read_dir(registry).expect("failed to read cache dir registry folder") {
let entry = entry.unwrap().path();
let file_name = entry
.file_name()
.expect("failed to get file name from cache dir");
let crate_size = get_size(&entry).expect("failed to get size");
let crate_name = file_name.to_str().unwrap();
let split_name = crate_name.rsplitn(2, '.').collect::<Vec<&str>>();
crate_detail.add_registry_crate_archive(split_name[1].to_owned(), crate_size);
installed_crate_registry.push(split_name[1].to_owned());
}
}
}
installed_crate_registry.sort();
installed_crate_registry.dedup();
installed_crate_registry
}
fn get_installed_crate_git(
checkout_dir: &Path,
db_dir: &Path,
crate_detail: &mut CrateDetail,
) -> Vec<String> {
let mut installed_crate_git = Vec::new();
if checkout_dir.exists() {
for entry in fs::read_dir(checkout_dir).expect("failed to read checkout directory") {
let entry = entry.unwrap().path();
let path = entry.as_path();
let file_path = path
.file_name()
.expect("failed to obtain checkout directory sub folder file name");
for git_sha_entry in fs::read_dir(path).expect("failed to read checkout dir sub folder")
{
let git_sha_entry = git_sha_entry.unwrap().path();
let crate_size = get_size(&git_sha_entry).expect("failed to get folder size");
let git_sha_file_name = git_sha_entry.file_name().expect("failed to get file name");
let git_sha = git_sha_file_name.to_str().unwrap();
let file_name = file_path.to_str().unwrap();
let split_name = file_name.rsplitn(2, '-').collect::<Vec<&str>>();
let full_name = format!("{}-{}", split_name[1], git_sha);
crate_detail.add_git_crate_archive(full_name.to_owned(), crate_size);
installed_crate_git.push(full_name)
}
}
}
if db_dir.exists() {
for entry in fs::read_dir(db_dir).expect("failed to read db dir") {
let entry = entry.unwrap().path();
let crate_size = get_size(&entry).expect("failed to get size of db dir folders");
let file_name = entry.file_name().expect("failed to get file name");
let file_name = file_name.to_str().unwrap();
let split_name = file_name.rsplitn(2, '-').collect::<Vec<&str>>();
let full_name = format!("{}-HEAD", split_name[1]);
crate_detail.add_git_crate_source(full_name.to_owned(), crate_size);
installed_crate_git.push(full_name);
}
}
installed_crate_git.sort();
installed_crate_git.dedup();
installed_crate_git
}
fn latest_rev_value(path: &Path) -> String {
let output = std::process::Command::new("git")
.arg("log")
.arg("--pretty=format:'%h'")
.arg("--max-count=1")
.current_dir(path)
.output()
.expect("failed to execute process");
std::str::from_utf8(&output.stdout)
.expect("stdout is not utf8")
.to_string()
}