cargo-trim 0.10.2

Binary application to cleanup $CARGO_HOME cache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::default::Default;
use std::fs;
use std::hash::Hash;
use std::path::Path;
use std::str::FromStr;

use anyhow::{Context, Result};
use semver::Version;
use serde::Deserialize;
use url::Url;

use crate::utils::{get_size, split_name_version};

#[derive(Debug, Clone)]
pub(crate) struct CrateMetaData {
    name: String,
    version: Option<Version>,
    size: u64,
    source: Option<Url>,
}

impl CrateMetaData {
    pub(crate) fn new(
        name: String,
        version: Option<Version>,
        size: u64,
        source: Option<Url>,
    ) -> Self {
        Self {
            name,
            version,
            size,
            source,
        }
    }

    pub(crate) fn name(&self) -> &String {
        &self.name
    }

    pub(crate) fn version(&self) -> &Option<Version> {
        &self.version
    }

    pub(crate) fn size(&self) -> u64 {
        self.size
    }

    pub(crate) fn source(&self) -> &Option<Url> {
        &self.source
    }
}

impl PartialOrd for CrateMetaData {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for CrateMetaData {
    fn cmp(&self, other: &Self) -> Ordering {
        match self.name.cmp(&other.name) {
            Ordering::Equal => {
                match self.source.cmp(&other.source) {
                    Ordering::Equal => {
                        match self.version.cmp(&other.version) {
                            Ordering::Equal => self.size.cmp(&other.size),
                            ord => ord,
                        }
                    }
                    ord => ord,
                }
            }
            ord => ord,
        }
    }
}

impl PartialEq for CrateMetaData {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name && self.version == other.version && self.source == other.source
    }
}

impl Eq for CrateMetaData {}

impl Hash for CrateMetaData {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.name.hash(state);
        self.version.hash(state);
        self.source.hash(state);
    }
}

#[derive(Deserialize)]
struct IndexConfig {
    dl: Url,
    api: Option<Url>,
}

/// stores different crate size and name information
#[derive(Default)]
pub(crate) struct CrateDetail {
    source_info: HashMap<String, Url>,
    bin: HashSet<CrateMetaData>,
    git_crates_source: HashSet<CrateMetaData>,
    registry_crates_source: HashSet<CrateMetaData>,
    git_crates_archive: HashSet<CrateMetaData>,
    registry_crates_archive: HashSet<CrateMetaData>,
}

impl CrateDetail {
    /// Crate new index info
    pub(crate) fn new(index_dir: &Path, db_dir: &Path) -> Result<Self> {
        let mut source_info = HashMap::new();
        if index_dir.exists() {
            for entry in fs::read_dir(index_dir)? {
                let registry_dir = entry?.path();
                let registry_file_name = registry_dir
                    .file_name()
                    .context("Failed to get file name of registry dir")?
                    .to_str()
                    .context("Failed to convert osstr to str")?;
                let mut fetch_head_file = registry_dir.clone();
                fetch_head_file.push(".git");
                fetch_head_file.push("FETCH_HEAD");
                // Check if fetch head file exists if it exists than index is registry based
                if fetch_head_file.exists() {
                    let content = fs::read_to_string(fetch_head_file)
                        .context("Failed to read FETCH_HEAD file")?;
                    let url_path = content
                        .split_whitespace()
                        .last()
                        .context("Failed to get url part from content")?;
                    source_info.insert(
                        registry_file_name.to_string(),
                        Url::from_str(url_path).context("Fail FETCH_HEAD url conversion")?,
                    );
                // Else it is based on sparse registry
                } else {
                    let domain = registry_file_name
                        .rsplitn(2, '-')
                        .last()
                        .context("Failed to get url for sparse registry")?;
                    let mut config_file = registry_dir.clone();
                    config_file.push("config.json");
                    let content = fs::read_to_string(config_file)
                        .context("Failed to read config.json file")?;
                    let json: IndexConfig = serde_json::from_str(&content)?;
                    let scheme_url = json.api.unwrap_or(json.dl);
                    let scheme = scheme_url.scheme();
                    let url = Url::from_str(&format!("{scheme}://{domain}"))
                        .context("Failed sparse registry index url")?;
                    source_info.insert(registry_file_name.to_string(), url);
                }
            }
        }
        if db_dir.exists() {
            for entry in fs::read_dir(db_dir)? {
                let git_dir = entry?.path();
                let git_file_name = git_dir
                    .file_name()
                    .context("Failed to get file name of git dir")?
                    .to_str()
                    .context("Failed to convert osstr to str")?;
                let mut fetch_head_file = git_dir.clone();
                fetch_head_file.push("FETCH_HEAD");
                let content = fs::read_to_string(fetch_head_file)
                    .context("Failed to read FETCH_HEAD file")?;
                let url_path = content
                    .split_whitespace()
                    .last()
                    .context("Failed to get url part from content")?;
                source_info.insert(
                    git_file_name.to_string(),
                    Url::from_str(url_path).context("Failed to convert db dir FETCH_HEAD")?,
                );
            }
        }
        Ok(Self {
            source_info,
            ..Default::default()
        })
    }

    /// Get source value from path
    pub(crate) fn source_url_from_path(&self, path: &Path) -> Result<Url> {
        let file_name = path
            .file_name()
            .context("Failed to get file name of path")?
            .to_str()
            .context("Failed to convert osstr to str")?;
        Ok(self
            .source_info
            .get(file_name)
            .context("Failed to get url for path")?
            .clone())
    }

    /// return bin crates metadata
    pub(crate) fn bin(&self) -> &HashSet<CrateMetaData> {
        &self.bin
    }

    /// return git crates source
    pub(crate) fn git_crates_source(&self) -> &HashSet<CrateMetaData> {
        &self.git_crates_source
    }

    /// return registry crates source metadata
    pub(crate) fn registry_crates_source(&self) -> &HashSet<CrateMetaData> {
        &self.registry_crates_source
    }

    /// return git crates archive metadata
    pub(crate) fn git_crates_archive(&self) -> &HashSet<CrateMetaData> {
        &self.git_crates_archive
    }

    /// return registry crates archive metadata
    pub(crate) fn registry_crates_archive(&self) -> &HashSet<CrateMetaData> {
        &self.registry_crates_archive
    }

    /// add bin information to crate detail
    fn add_bin(&mut self, bin_metadata: &CrateMetaData) {
        self.bin.insert(bin_metadata.clone());
    }

    /// add git crate source information to crate detail
    fn add_git_crate_source(&mut self, crate_metadata: &CrateMetaData) {
        self.git_crates_source.insert(crate_metadata.clone());
    }

    /// add registry crate source information to crate detail
    fn add_registry_crate_source(&mut self, crate_metadata: &CrateMetaData) {
        self.registry_crates_source.insert(crate_metadata.clone());
    }

    /// add git crate archive information to crate detail
    fn add_git_crate_archive(&mut self, crate_metadata: &CrateMetaData) {
        self.git_crates_archive.insert(crate_metadata.clone());
    }

    /// add registry crate archive information to crate detail
    fn add_registry_crate_archive(&mut self, crate_metadata: &CrateMetaData) {
        self.registry_crates_archive.insert(crate_metadata.clone());
    }

    /// list installed bin
    pub(crate) fn list_installed_bin(&mut self, bin_dir: &Path) -> Result<Vec<CrateMetaData>> {
        let mut installed_bin = Vec::new();
        if bin_dir.exists() {
            for entry in fs::read_dir(bin_dir).context("failed to read bin directory")? {
                let entry = entry?.path();
                let bin_size = get_size(&entry).context("failed to get size of bin directory")?;
                let file_name = entry
                    .file_name()
                    .context("failed to get file name from bin directory")?;
                let bin_name = file_name
                    .to_str()
                    .context("failed to convert file name of bin to str")?
                    .to_string();
                let bin_metadata = CrateMetaData {
                    name: bin_name,
                    version: None,
                    size: bin_size,
                    source: None,
                };
                self.add_bin(&bin_metadata);
                installed_bin.push(bin_metadata);
            }
        }
        installed_bin.sort();
        Ok(installed_bin)
    }

    /// list all installed registry crates
    pub(crate) fn list_installed_crate_registry(
        &mut self,
        src_dir: &Path,
        cache_dir: &Path,
    ) -> Result<Vec<CrateMetaData>> {
        let mut installed_crate_registry = HashSet::new();
        // read src dir to get installed crate
        if src_dir.exists() {
            for entry in fs::read_dir(src_dir).context("failed to read src directory")? {
                let registry = entry?.path();
                let source = self.source_url_from_path(&registry)?;
                for entry in fs::read_dir(registry).context("failed to read registry folder")? {
                    let entry = entry?.path();
                    let crate_size =
                        get_size(&entry).context("failed to get registry crate size")?;
                    let file_name = entry
                        .file_name()
                        .context("failed to get file name from main entry")?;
                    let crate_name = file_name
                        .to_str()
                        .context("Failed to convert crate file name to str")?;
                    let (name, version) = split_name_version(crate_name)?;
                    let crate_metadata = CrateMetaData {
                        name,
                        version: Some(version),
                        size: crate_size,
                        source: Some(source.clone()),
                    };
                    self.add_registry_crate_source(&crate_metadata);
                    update_crate_list(&mut installed_crate_registry, &crate_metadata)?;
                }
            }
        }
        // read cache dir to get installed crate
        if cache_dir.exists() {
            for entry in fs::read_dir(cache_dir).context("failed to read cache dir")? {
                let registry = entry?.path();
                let source = self.source_url_from_path(&registry)?;
                for entry in
                    fs::read_dir(registry).context("failed to read cache dir registry folder")?
                {
                    let entry = entry?.path();
                    let file_name = entry
                        .file_name()
                        .context("failed to get file name from cache dir")?;
                    let crate_size = get_size(&entry).context("failed to get size")?;
                    let crate_name = file_name
                        .to_str()
                        .context("Failed to convert crate file name to str")?;
                    let (name, version) = split_name_version(crate_name)?;
                    let crate_metadata = CrateMetaData {
                        name,
                        version: Some(version),
                        size: crate_size,
                        source: Some(source.clone()),
                    };
                    self.add_registry_crate_archive(&crate_metadata);
                    update_crate_list(&mut installed_crate_registry, &crate_metadata)?;
                }
            }
        }
        let mut installed_crates = Vec::new();
        for crate_metadata in installed_crate_registry {
            installed_crates.push(crate_metadata);
        }
        installed_crates.sort();
        Ok(installed_crates)
    }

    /// list all installed git crates
    pub(crate) fn list_installed_crate_git(
        &mut self,
        checkout_dir: &Path,
        db_dir: &Path,
    ) -> Result<Vec<CrateMetaData>> {
        let mut installed_crate_git = HashSet::new();
        if checkout_dir.exists() {
            // read checkout dir to list crate name in form of crate_name-rev_sha
            for entry in fs::read_dir(checkout_dir).context("failed to read checkout directory")? {
                let entry = entry?.path();
                let source = self.source_url_from_path(&entry)?;
                let file_path = entry
                    .file_name()
                    .context("failed to obtain checkout directory sub folder file name")?;
                for git_sha_entry in
                    fs::read_dir(&entry).context("failed to read checkout dir sub folder")?
                {
                    let git_sha_entry = git_sha_entry?.path();
                    let crate_size =
                        get_size(&git_sha_entry).context("failed to get folder size")?;
                    let git_sha_file_name = git_sha_entry
                        .file_name()
                        .context("failed to get file name")?;
                    let git_sha = git_sha_file_name
                        .to_str()
                        .context("Failed to convert git sha name to str")?;
                    let file_name = file_path
                        .to_str()
                        .context("Failed to convert file path file name to str")?;
                    let full_name = format!("{file_name}-{git_sha}");
                    let crate_metadata = CrateMetaData {
                        name: full_name,
                        version: None,
                        size: crate_size,
                        source: Some(source.clone()),
                    };
                    self.add_git_crate_archive(&crate_metadata);
                    update_crate_list(&mut installed_crate_git, &crate_metadata)?;
                }
            }
        }
        // read a database directory to list a git crate in form of crate_name-HEAD
        if db_dir.exists() {
            for entry in fs::read_dir(db_dir).context("failed to read db dir")? {
                let entry = entry?.path();
                let source = self.source_url_from_path(&entry)?;
                let crate_size =
                    get_size(&entry).context("failed to get size of db dir folders")?;
                let file_name = entry.file_name().context("failed to get file name")?;
                let file_name = file_name
                    .to_str()
                    .context("Failed to convert db dir file name to str")?;
                let full_name = format!("{file_name}-HEAD");
                let crate_metadata = CrateMetaData {
                    name: full_name,
                    version: None,
                    size: crate_size,
                    source: Some(source),
                };
                self.add_git_crate_source(&crate_metadata);
                update_crate_list(&mut installed_crate_git, &crate_metadata)?;
            }
        }
        let mut installed_crates = Vec::new();
        for crate_metadata in installed_crate_git {
            installed_crates.push(crate_metadata);
        }
        installed_crates.sort();
        Ok(installed_crates)
    }
}

fn update_crate_list(
    hash_set: &mut HashSet<CrateMetaData>,
    temp_crate_metadata: &CrateMetaData,
) -> Result<()> {
    let meta_data_exists = hash_set.get(temp_crate_metadata).is_some();
    let mut current_size = temp_crate_metadata.size;
    if meta_data_exists {
        current_size += hash_set
            .get(temp_crate_metadata)
            .context("failed to get metadata from hash set")?
            .size;
    }
    hash_set.remove(temp_crate_metadata);
    hash_set.insert(CrateMetaData {
        size: current_size,
        ..temp_crate_metadata.clone()
    });
    Ok(())
}