1use crate::ops::CleanContext;
118use crate::util::cache_lock::CacheLockMode;
119use crate::util::data_structures::HashMap;
120use crate::util::interning::InternedString;
121use crate::util::sqlite::{self, Migration, basic_migration};
122use crate::util::{Filesystem, Progress, ProgressStyle};
123use crate::workspace::gc::GcOpts;
124use crate::{CargoResult, GlobalContext};
125use anyhow::{Context as _, bail};
126use cargo_util::paths;
127use cargo_util_terminal::Verbosity;
128use rusqlite::{Connection, ErrorCode, params};
129use std::collections::hash_map;
130use std::path::{Path, PathBuf};
131use std::time::{Duration, SystemTime};
132use tracing::{debug, trace};
133
134const GLOBAL_CACHE_FILENAME: &str = ".global-cache";
136
137const REGISTRY_INDEX_TABLE: &str = "registry_index";
138const REGISTRY_CRATE_TABLE: &str = "registry_crate";
139const REGISTRY_SRC_TABLE: &str = "registry_src";
140const GIT_DB_TABLE: &str = "git_db";
141const GIT_CO_TABLE: &str = "git_checkout";
142
143const UPDATE_RESOLUTION: u64 = 60 * 5;
149
150type Timestamp = u64;
154
155#[derive(Clone, Debug, Hash, Eq, PartialEq)]
157pub struct RegistryIndex {
158 pub encoded_registry_name: InternedString,
160}
161
162#[derive(Clone, Debug, Hash, Eq, PartialEq)]
164pub struct RegistryCrate {
165 pub encoded_registry_name: InternedString,
167 pub crate_filename: InternedString,
169 pub size: u64,
171}
172
173#[derive(Clone, Debug, Hash, Eq, PartialEq)]
175pub struct RegistrySrc {
176 pub encoded_registry_name: InternedString,
178 pub package_dir: InternedString,
180 pub size: Option<u64>,
190}
191
192#[derive(Clone, Debug, Hash, Eq, PartialEq)]
194pub struct GitDb {
195 pub encoded_git_name: InternedString,
197}
198
199#[derive(Clone, Debug, Hash, Eq, PartialEq)]
201pub struct GitCheckout {
202 pub encoded_git_name: InternedString,
204 pub short_name: InternedString,
206 pub size: Option<u64>,
211}
212
213struct BasePaths {
217 index: PathBuf,
219 git_db: PathBuf,
221 git_co: PathBuf,
223 crate_dir: PathBuf,
225 src: PathBuf,
227}
228
229fn migrations() -> Vec<Migration> {
235 vec![
236 basic_migration(
239 "CREATE TABLE registry_index (
240 id INTEGER PRIMARY KEY AUTOINCREMENT,
241 name TEXT UNIQUE NOT NULL,
242 timestamp INTEGER NOT NULL
243 )",
244 ),
245 basic_migration(
247 "CREATE TABLE registry_crate (
248 registry_id INTEGER NOT NULL,
249 name TEXT NOT NULL,
250 size INTEGER NOT NULL,
251 timestamp INTEGER NOT NULL,
252 PRIMARY KEY (registry_id, name),
253 FOREIGN KEY (registry_id) REFERENCES registry_index (id) ON DELETE CASCADE
254 )",
255 ),
256 basic_migration(
262 "CREATE TABLE registry_src (
263 registry_id INTEGER NOT NULL,
264 name TEXT NOT NULL,
265 size INTEGER,
266 timestamp INTEGER NOT NULL,
267 PRIMARY KEY (registry_id, name),
268 FOREIGN KEY (registry_id) REFERENCES registry_index (id) ON DELETE CASCADE
269 )",
270 ),
271 basic_migration(
273 "CREATE TABLE git_db (
274 id INTEGER PRIMARY KEY AUTOINCREMENT,
275 name TEXT UNIQUE NOT NULL,
276 timestamp INTEGER NOT NULL
277 )",
278 ),
279 basic_migration(
281 "CREATE TABLE git_checkout (
282 git_id INTEGER NOT NULL,
283 name TEXT UNIQUE NOT NULL,
284 size INTEGER,
285 timestamp INTEGER NOT NULL,
286 PRIMARY KEY (git_id, name),
287 FOREIGN KEY (git_id) REFERENCES git_db (id) ON DELETE CASCADE
288 )",
289 ),
290 basic_migration(
293 "CREATE TABLE global_data (
294 last_auto_gc INTEGER NOT NULL
295 )",
296 ),
297 Box::new(|conn| {
301 conn.execute(
302 "INSERT INTO global_data (last_auto_gc) VALUES (?1)",
303 [now()],
304 )?;
305 Ok(())
306 }),
307 ]
308}
309
310#[derive(Copy, Clone, Debug, PartialEq)]
314struct ParentId(i64);
315
316impl rusqlite::types::FromSql for ParentId {
317 fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
318 let i = i64::column_result(value)?;
319 Ok(ParentId(i))
320 }
321}
322
323impl rusqlite::types::ToSql for ParentId {
324 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
325 Ok(rusqlite::types::ToSqlOutput::from(self.0))
326 }
327}
328
329#[derive(Debug)]
335pub struct GlobalCacheTracker {
336 conn: Connection,
338 auto_gc_checked_this_session: bool,
343}
344
345impl GlobalCacheTracker {
346 pub fn new(gctx: &GlobalContext) -> CargoResult<GlobalCacheTracker> {
351 let db_path = Self::db_path(gctx);
352 let db_path = gctx.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &db_path);
358 let mut conn = Connection::open(db_path)?;
359 conn.pragma_update(None, "foreign_keys", true)?;
360 sqlite::migrate(&mut conn, &migrations())?;
361 Ok(GlobalCacheTracker {
362 conn,
363 auto_gc_checked_this_session: false,
364 })
365 }
366
367 pub fn db_path(gctx: &GlobalContext) -> Filesystem {
369 gctx.home().join(GLOBAL_CACHE_FILENAME)
370 }
371
372 fn id_from_name(
376 conn: &Connection,
377 table_name: &str,
378 encoded_name: &str,
379 ) -> CargoResult<Option<ParentId>> {
380 let mut stmt =
381 conn.prepare_cached(&format!("SELECT id FROM {table_name} WHERE name = ?"))?;
382 match stmt.query_row([encoded_name], |row| row.get(0)) {
383 Ok(id) => Ok(Some(id)),
384 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
385 Err(e) => Err(e.into()),
386 }
387 }
388
389 fn get_id_map(
394 conn: &Connection,
395 table_name: &str,
396 ids: &[i64],
397 ) -> CargoResult<HashMap<i64, PathBuf>> {
398 let mut stmt =
399 conn.prepare_cached(&format!("SELECT name FROM {table_name} WHERE id = ?1"))?;
400 ids.iter()
401 .map(|id| {
402 let name = stmt.query_row(params![id], |row| {
403 Ok(PathBuf::from(row.get::<_, String>(0)?))
404 })?;
405 Ok((*id, name))
406 })
407 .collect()
408 }
409
410 pub fn registry_index_all(&self) -> CargoResult<Vec<(RegistryIndex, Timestamp)>> {
412 let mut stmt = self
413 .conn
414 .prepare_cached("SELECT name, timestamp FROM registry_index")?;
415 let rows = stmt
416 .query_map([], |row| {
417 let encoded_registry_name = row.get_unwrap(0);
418 let timestamp = row.get_unwrap(1);
419 let kind = RegistryIndex {
420 encoded_registry_name,
421 };
422 Ok((kind, timestamp))
423 })?
424 .collect::<Result<Vec<_>, _>>()?;
425 Ok(rows)
426 }
427
428 pub fn registry_crate_all(&self) -> CargoResult<Vec<(RegistryCrate, Timestamp)>> {
430 let mut stmt = self.conn.prepare_cached(
431 "SELECT registry_index.name, registry_crate.name, registry_crate.size, registry_crate.timestamp
432 FROM registry_index, registry_crate
433 WHERE registry_crate.registry_id = registry_index.id",
434 )?;
435 let rows = stmt
436 .query_map([], |row| {
437 let encoded_registry_name = row.get_unwrap(0);
438 let crate_filename = row.get_unwrap(1);
439 let size = row.get_unwrap(2);
440 let timestamp = row.get_unwrap(3);
441 let kind = RegistryCrate {
442 encoded_registry_name,
443 crate_filename,
444 size,
445 };
446 Ok((kind, timestamp))
447 })?
448 .collect::<Result<Vec<_>, _>>()?;
449 Ok(rows)
450 }
451
452 pub fn registry_src_all(&self) -> CargoResult<Vec<(RegistrySrc, Timestamp)>> {
454 let mut stmt = self.conn.prepare_cached(
455 "SELECT registry_index.name, registry_src.name, registry_src.size, registry_src.timestamp
456 FROM registry_index, registry_src
457 WHERE registry_src.registry_id = registry_index.id",
458 )?;
459 let rows = stmt
460 .query_map([], |row| {
461 let encoded_registry_name = row.get_unwrap(0);
462 let package_dir = row.get_unwrap(1);
463 let size = row.get_unwrap(2);
464 let timestamp = row.get_unwrap(3);
465 let kind = RegistrySrc {
466 encoded_registry_name,
467 package_dir,
468 size,
469 };
470 Ok((kind, timestamp))
471 })?
472 .collect::<Result<Vec<_>, _>>()?;
473 Ok(rows)
474 }
475
476 pub fn git_db_all(&self) -> CargoResult<Vec<(GitDb, Timestamp)>> {
478 let mut stmt = self
479 .conn
480 .prepare_cached("SELECT name, timestamp FROM git_db")?;
481 let rows = stmt
482 .query_map([], |row| {
483 let encoded_git_name = row.get_unwrap(0);
484 let timestamp = row.get_unwrap(1);
485 let kind = GitDb { encoded_git_name };
486 Ok((kind, timestamp))
487 })?
488 .collect::<Result<Vec<_>, _>>()?;
489 Ok(rows)
490 }
491
492 pub fn git_checkout_all(&self) -> CargoResult<Vec<(GitCheckout, Timestamp)>> {
494 let mut stmt = self.conn.prepare_cached(
495 "SELECT git_db.name, git_checkout.name, git_checkout.size, git_checkout.timestamp
496 FROM git_db, git_checkout
497 WHERE git_checkout.git_id = git_db.id",
498 )?;
499 let rows = stmt
500 .query_map([], |row| {
501 let encoded_git_name = row.get_unwrap(0);
502 let short_name = row.get_unwrap(1);
503 let size = row.get_unwrap(2);
504 let timestamp = row.get_unwrap(3);
505 let kind = GitCheckout {
506 encoded_git_name,
507 short_name,
508 size,
509 };
510 Ok((kind, timestamp))
511 })?
512 .collect::<Result<Vec<_>, _>>()?;
513 Ok(rows)
514 }
515
516 pub fn should_run_auto_gc(&mut self, frequency: Duration) -> CargoResult<bool> {
519 trace!(target: "gc", "should_run_auto_gc");
520 if self.auto_gc_checked_this_session {
521 return Ok(false);
522 }
523 let last_auto_gc: Timestamp =
524 self.conn
525 .query_row("SELECT last_auto_gc FROM global_data", [], |row| row.get(0))?;
526 let should_run = last_auto_gc + frequency.as_secs() < now();
527 trace!(target: "gc",
528 "last auto gc was {}, {}",
529 last_auto_gc,
530 if should_run { "running" } else { "skipping" }
531 );
532 self.auto_gc_checked_this_session = true;
533 Ok(should_run)
534 }
535
536 pub fn set_last_auto_gc(&self) -> CargoResult<()> {
539 self.conn
540 .execute("UPDATE global_data SET last_auto_gc = ?1", [now()])?;
541 Ok(())
542 }
543
544 pub fn clean(&mut self, clean_ctx: &mut CleanContext<'_>, gc_opts: &GcOpts) -> CargoResult<()> {
546 self.clean_inner(clean_ctx, gc_opts)
547 .context("failed to clean entries from the global cache")
548 }
549
550 #[tracing::instrument(skip_all)]
551 fn clean_inner(
552 &mut self,
553 clean_ctx: &mut CleanContext<'_>,
554 gc_opts: &GcOpts,
555 ) -> CargoResult<()> {
556 let gctx = clean_ctx.gctx;
557 let base = BasePaths {
558 index: gctx.registry_index_path().into_path_unlocked(),
559 git_db: gctx.git_db_path().into_path_unlocked(),
560 git_co: gctx.git_checkouts_path().into_path_unlocked(),
561 crate_dir: gctx.registry_cache_path().into_path_unlocked(),
562 src: gctx.registry_source_path().into_path_unlocked(),
563 };
564 let now = now();
565 trace!(target: "gc", "cleaning {gc_opts:?}");
566 let tx = self.conn.transaction()?;
567 let mut delete_paths = Vec::new();
568 if gc_opts.is_download_cache_opt_set() {
570 Self::sync_db_with_files(
572 &tx,
573 now,
574 gctx,
575 &base,
576 gc_opts.is_download_cache_size_set(),
577 &mut delete_paths,
578 )
579 .context("failed to sync tracking database")?
580 }
581 if let Some(max_age) = gc_opts.max_index_age {
582 let max_age = now - max_age.as_secs();
583 Self::get_registry_index_to_clean(&tx, max_age, &base, &mut delete_paths)?;
584 }
585 if let Some(max_age) = gc_opts.max_src_age {
586 let max_age = now - max_age.as_secs();
587 Self::get_registry_items_to_clean_age(
588 &tx,
589 max_age,
590 REGISTRY_SRC_TABLE,
591 &base.src,
592 &mut delete_paths,
593 )?;
594 }
595 if let Some(max_age) = gc_opts.max_crate_age {
596 let max_age = now - max_age.as_secs();
597 Self::get_registry_items_to_clean_age(
598 &tx,
599 max_age,
600 REGISTRY_CRATE_TABLE,
601 &base.crate_dir,
602 &mut delete_paths,
603 )?;
604 }
605 if let Some(max_age) = gc_opts.max_git_db_age {
606 let max_age = now - max_age.as_secs();
607 Self::get_git_db_items_to_clean(&tx, max_age, &base, &mut delete_paths)?;
608 }
609 if let Some(max_age) = gc_opts.max_git_co_age {
610 let max_age = now - max_age.as_secs();
611 Self::get_git_co_items_to_clean(&tx, max_age, &base.git_co, &mut delete_paths)?;
612 }
613 if let Some(max_size) = gc_opts.max_crate_size {
627 Self::get_registry_items_to_clean_size(
628 &tx,
629 max_size,
630 REGISTRY_CRATE_TABLE,
631 &base.crate_dir,
632 &mut delete_paths,
633 )?;
634 }
635 if let Some(max_size) = gc_opts.max_src_size {
636 Self::get_registry_items_to_clean_size(
637 &tx,
638 max_size,
639 REGISTRY_SRC_TABLE,
640 &base.src,
641 &mut delete_paths,
642 )?;
643 }
644 if let Some(max_size) = gc_opts.max_git_size {
645 Self::get_git_items_to_clean_size(&tx, max_size, &base, &mut delete_paths)?;
646 }
647 if let Some(max_size) = gc_opts.max_download_size {
648 Self::get_registry_items_to_clean_size_both(&tx, max_size, &base, &mut delete_paths)?;
649 }
650
651 clean_ctx.remove_paths(&delete_paths)?;
652
653 if clean_ctx.dry_run {
654 tx.rollback()?;
655 } else {
656 tx.commit()?;
657 }
658 Ok(())
659 }
660
661 fn list_dir_names(path: &Path) -> CargoResult<Vec<String>> {
664 Self::read_dir_with_filter(path, &|entry| {
665 entry.file_type().map_or(false, |ty| ty.is_dir())
666 })
667 }
668
669 fn read_dir_with_filter(
671 path: &Path,
672 filter: &dyn Fn(&std::fs::DirEntry) -> bool,
673 ) -> CargoResult<Vec<String>> {
674 let entries = match path.read_dir() {
675 Ok(e) => e,
676 Err(e) => {
677 if e.kind() == std::io::ErrorKind::NotFound {
678 return Ok(Vec::new());
679 } else {
680 return Err(
681 anyhow::Error::new(e).context(format!("failed to read path `{path:?}`"))
682 );
683 }
684 }
685 };
686 let names = entries
687 .filter_map(|entry| entry.ok())
688 .filter(|entry| filter(entry))
689 .filter_map(|entry| entry.file_name().into_string().ok())
690 .collect();
691 Ok(names)
692 }
693
694 #[tracing::instrument(skip(conn, gctx, base, delete_paths))]
714 fn sync_db_with_files(
715 conn: &Connection,
716 now: Timestamp,
717 gctx: &GlobalContext,
718 base: &BasePaths,
719 sync_size: bool,
720 delete_paths: &mut Vec<PathBuf>,
721 ) -> CargoResult<()> {
722 debug!(target: "gc", "starting db sync");
723 Self::update_parent_for_missing_from_db(conn, now, REGISTRY_INDEX_TABLE, &base.index)?;
725 Self::update_parent_for_missing_from_db(conn, now, GIT_DB_TABLE, &base.git_db)?;
726
727 Self::update_db_for_removed(
730 conn,
731 REGISTRY_INDEX_TABLE,
732 "registry_id",
733 REGISTRY_CRATE_TABLE,
734 &base.crate_dir,
735 )?;
736 Self::update_db_for_removed(
737 conn,
738 REGISTRY_INDEX_TABLE,
739 "registry_id",
740 REGISTRY_SRC_TABLE,
741 &base.src,
742 )?;
743 Self::update_db_for_removed(conn, GIT_DB_TABLE, "git_id", GIT_CO_TABLE, &base.git_co)?;
744
745 Self::update_db_parent_for_removed_from_disk(
751 conn,
752 REGISTRY_INDEX_TABLE,
753 &base.index,
754 &[&base.crate_dir, &base.src],
755 delete_paths,
756 )?;
757 Self::update_db_parent_for_removed_from_disk(
758 conn,
759 GIT_DB_TABLE,
760 &base.git_db,
761 &[&base.git_co],
762 delete_paths,
763 )?;
764
765 Self::populate_untracked_crate(conn, now, &base.crate_dir)?;
768 Self::populate_untracked(
769 conn,
770 now,
771 gctx,
772 REGISTRY_INDEX_TABLE,
773 "registry_id",
774 REGISTRY_SRC_TABLE,
775 &base.src,
776 sync_size,
777 )?;
778 Self::populate_untracked(
779 conn,
780 now,
781 gctx,
782 GIT_DB_TABLE,
783 "git_id",
784 GIT_CO_TABLE,
785 &base.git_co,
786 sync_size,
787 )?;
788
789 if sync_size {
791 Self::update_null_sizes(
792 conn,
793 gctx,
794 REGISTRY_INDEX_TABLE,
795 "registry_id",
796 REGISTRY_SRC_TABLE,
797 &base.src,
798 )?;
799 Self::update_null_sizes(
800 conn,
801 gctx,
802 GIT_DB_TABLE,
803 "git_id",
804 GIT_CO_TABLE,
805 &base.git_co,
806 )?;
807 }
808 Ok(())
809 }
810
811 #[tracing::instrument(skip(conn, now, base_path))]
813 fn update_parent_for_missing_from_db(
814 conn: &Connection,
815 now: Timestamp,
816 parent_table_name: &str,
817 base_path: &Path,
818 ) -> CargoResult<()> {
819 trace!(target: "gc", "checking for untracked parent to add to {parent_table_name}");
820 let names = Self::list_dir_names(base_path)?;
821
822 let mut stmt = conn.prepare_cached(&format!(
823 "INSERT INTO {parent_table_name} (name, timestamp)
824 VALUES (?1, ?2)
825 ON CONFLICT DO NOTHING",
826 ))?;
827 for name in names {
828 stmt.execute(params![name, now])?;
829 }
830 Ok(())
831 }
832
833 #[tracing::instrument(skip(conn, base_path))]
838 fn update_db_for_removed(
839 conn: &Connection,
840 parent_table_name: &str,
841 id_column_name: &str,
842 table_name: &str,
843 base_path: &Path,
844 ) -> CargoResult<()> {
845 trace!(target: "gc", "checking for db entries to remove from {table_name}");
846 let mut select_stmt = conn.prepare_cached(&format!(
847 "SELECT {table_name}.rowid, {parent_table_name}.name, {table_name}.name
848 FROM {parent_table_name}, {table_name}
849 WHERE {table_name}.{id_column_name} = {parent_table_name}.id",
850 ))?;
851 let mut delete_stmt =
852 conn.prepare_cached(&format!("DELETE FROM {table_name} WHERE rowid = ?1"))?;
853 let mut rows = select_stmt.query([])?;
854 while let Some(row) = rows.next()? {
855 let rowid: i64 = row.get_unwrap(0);
856 let id_name: String = row.get_unwrap(1);
857 let name: String = row.get_unwrap(2);
858 if !base_path.join(id_name).join(name).exists() {
859 delete_stmt.execute([rowid])?;
860 }
861 }
862 Ok(())
863 }
864
865 #[tracing::instrument(skip(conn, base_path, child_base_paths, delete_paths))]
867 fn update_db_parent_for_removed_from_disk(
868 conn: &Connection,
869 parent_table_name: &str,
870 base_path: &Path,
871 child_base_paths: &[&Path],
872 delete_paths: &mut Vec<PathBuf>,
873 ) -> CargoResult<()> {
874 trace!(target: "gc", "checking for db entries to remove from {parent_table_name}");
875 let mut select_stmt =
876 conn.prepare_cached(&format!("SELECT rowid, name FROM {parent_table_name}"))?;
877 let mut delete_stmt =
878 conn.prepare_cached(&format!("DELETE FROM {parent_table_name} WHERE rowid = ?1"))?;
879 let mut rows = select_stmt.query([])?;
880 while let Some(row) = rows.next()? {
881 let rowid: i64 = row.get_unwrap(0);
882 let id_name: String = row.get_unwrap(1);
883 if !base_path.join(&id_name).exists() {
884 delete_stmt.execute([rowid])?;
885 for child_base in child_base_paths {
887 let child_path = child_base.join(&id_name);
888 if child_path.exists() {
889 debug!(target: "gc", "removing orphaned path {child_path:?}");
890 delete_paths.push(child_path);
891 }
892 }
893 }
894 }
895 Ok(())
896 }
897
898 #[tracing::instrument(skip(conn, now, base_path))]
902 fn populate_untracked_crate(
903 conn: &Connection,
904 now: Timestamp,
905 base_path: &Path,
906 ) -> CargoResult<()> {
907 trace!(target: "gc", "populating untracked crate files");
908 let mut insert_stmt = conn.prepare_cached(
909 "INSERT INTO registry_crate (registry_id, name, size, timestamp)
910 VALUES (?1, ?2, ?3, ?4)
911 ON CONFLICT DO NOTHING",
912 )?;
913 let index_names = Self::list_dir_names(&base_path)?;
914 for index_name in index_names {
915 let Some(id) = Self::id_from_name(conn, REGISTRY_INDEX_TABLE, &index_name)? else {
916 continue;
919 };
920 let index_path = base_path.join(index_name);
921 let crates = Self::read_dir_with_filter(&index_path, &|entry| {
922 entry.file_type().map_or(false, |ty| ty.is_file())
923 && entry
924 .file_name()
925 .to_str()
926 .map_or(false, |name| name.ends_with(".crate"))
927 })?;
928 for crate_name in crates {
929 let size = paths::metadata(index_path.join(&crate_name))?.len();
932 insert_stmt.execute(params![id, crate_name, size, now])?;
933 }
934 }
935 Ok(())
936 }
937
938 #[tracing::instrument(skip(conn, now, gctx, base_path, populate_size))]
941 fn populate_untracked(
942 conn: &Connection,
943 now: Timestamp,
944 gctx: &GlobalContext,
945 id_table_name: &str,
946 id_column_name: &str,
947 table_name: &str,
948 base_path: &Path,
949 populate_size: bool,
950 ) -> CargoResult<()> {
951 trace!(target: "gc", "populating untracked files for {table_name}");
952 let id_names = Self::list_dir_names(&base_path)?;
954
955 let mut select_stmt = conn.prepare_cached(&format!(
959 "SELECT 1 FROM {table_name}
960 WHERE {id_column_name} = ?1 AND name = ?2",
961 ))?;
962 let mut insert_stmt = conn.prepare_cached(&format!(
963 "INSERT INTO {table_name} ({id_column_name}, name, size, timestamp)
964 VALUES (?1, ?2, ?3, ?4)
965 ON CONFLICT DO NOTHING",
966 ))?;
967 let mut progress = Progress::with_style("Scanning", ProgressStyle::Ratio, gctx);
968 for id_name in id_names {
970 let Some(id) = Self::id_from_name(conn, id_table_name, &id_name)? else {
971 continue;
974 };
975 let index_path = base_path.join(id_name);
976 let names = Self::list_dir_names(&index_path)?;
977 let max = names.len();
978 for (i, name) in names.iter().enumerate() {
979 if select_stmt.exists(params![id, name])? {
980 continue;
981 }
982 let dir_path = index_path.join(name);
983 if !dir_path.is_dir() {
984 continue;
985 }
986 progress.tick(i, max, "")?;
987 let size = if populate_size {
988 Some(du(&dir_path, table_name)?)
989 } else {
990 None
991 };
992 insert_stmt.execute(params![id, name, size, now])?;
993 }
994 }
995 Ok(())
996 }
997
998 #[tracing::instrument(skip(conn, gctx, base_path))]
1006 fn update_null_sizes(
1007 conn: &Connection,
1008 gctx: &GlobalContext,
1009 parent_table_name: &str,
1010 id_column_name: &str,
1011 table_name: &str,
1012 base_path: &Path,
1013 ) -> CargoResult<()> {
1014 trace!(target: "gc", "updating NULL size information in {table_name}");
1015 let mut null_stmt = conn.prepare_cached(&format!(
1016 "SELECT {table_name}.rowid, {table_name}.name, {parent_table_name}.name
1017 FROM {table_name}, {parent_table_name}
1018 WHERE {table_name}.size IS NULL AND {table_name}.{id_column_name} = {parent_table_name}.id",
1019 ))?;
1020 let mut update_stmt = conn.prepare_cached(&format!(
1021 "UPDATE {table_name} SET size = ?1 WHERE rowid = ?2"
1022 ))?;
1023 let mut progress = Progress::with_style("Scanning", ProgressStyle::Ratio, gctx);
1024 let rows: Vec<_> = null_stmt
1025 .query_map([], |row| {
1026 Ok((row.get_unwrap(0), row.get_unwrap(1), row.get_unwrap(2)))
1027 })?
1028 .collect();
1029 let max = rows.len();
1030 for (i, row) in rows.into_iter().enumerate() {
1031 let (rowid, name, id_name): (i64, String, String) = row?;
1032 let path = base_path.join(id_name).join(name);
1033 progress.tick(i, max, "")?;
1034 let size = du(&path, table_name)?;
1037 update_stmt.execute(params![size, rowid])?;
1038 }
1039 Ok(())
1040 }
1041
1042 fn get_registry_items_to_clean_age(
1045 conn: &Connection,
1046 max_age: Timestamp,
1047 table_name: &str,
1048 base_path: &Path,
1049 delete_paths: &mut Vec<PathBuf>,
1050 ) -> CargoResult<()> {
1051 debug!(target: "gc", "cleaning {table_name} since {max_age:?}");
1052 let mut stmt = conn.prepare_cached(&format!(
1053 "DELETE FROM {table_name} WHERE timestamp < ?1
1054 RETURNING registry_id, name"
1055 ))?;
1056 let rows = stmt
1057 .query_map(params![max_age], |row| {
1058 let registry_id = row.get_unwrap(0);
1059 let name: String = row.get_unwrap(1);
1060 Ok((registry_id, name))
1061 })?
1062 .collect::<Result<Vec<_>, _>>()?;
1063 let ids: Vec<_> = rows.iter().map(|r| r.0).collect();
1064 let id_map = Self::get_id_map(conn, REGISTRY_INDEX_TABLE, &ids)?;
1065 for (id, name) in rows {
1066 let encoded_registry_name = &id_map[&id];
1067 delete_paths.push(base_path.join(encoded_registry_name).join(name));
1068 }
1069 Ok(())
1070 }
1071
1072 fn get_registry_items_to_clean_size(
1075 conn: &Connection,
1076 max_size: u64,
1077 table_name: &str,
1078 base_path: &Path,
1079 delete_paths: &mut Vec<PathBuf>,
1080 ) -> CargoResult<()> {
1081 debug!(target: "gc", "cleaning {table_name} till under {max_size:?}");
1082 let total_size: u64 = conn.query_row(
1083 &format!("SELECT coalesce(SUM(size), 0) FROM {table_name}"),
1084 [],
1085 |row| row.get(0),
1086 )?;
1087 if total_size <= max_size {
1088 return Ok(());
1089 }
1090 let mut stmt = conn.prepare(&format!(
1105 "DELETE FROM {table_name} WHERE rowid IN \
1106 (SELECT x.rowid FROM \
1107 (SELECT rowid, size, SUM(size) OVER \
1108 (ORDER BY timestamp, name ROWS UNBOUNDED PRECEDING) AS running_amount \
1109 FROM {table_name}) x \
1110 WHERE coalesce(x.running_amount, 0) - x.size < ?1) \
1111 RETURNING registry_id, name;"
1112 ))?;
1113 let rows = stmt
1114 .query_map(params![total_size - max_size], |row| {
1115 let id = row.get_unwrap(0);
1116 let name: String = row.get_unwrap(1);
1117 Ok((id, name))
1118 })?
1119 .collect::<Result<Vec<_>, _>>()?;
1120 let ids: Vec<_> = rows.iter().map(|r| r.0).collect();
1122 let id_map = Self::get_id_map(conn, REGISTRY_INDEX_TABLE, &ids)?;
1123 for (id, name) in rows {
1124 let encoded_name = &id_map[&id];
1125 delete_paths.push(base_path.join(encoded_name).join(name));
1126 }
1127 Ok(())
1128 }
1129
1130 fn get_registry_items_to_clean_size_both(
1133 conn: &Connection,
1134 max_size: u64,
1135 base: &BasePaths,
1136 delete_paths: &mut Vec<PathBuf>,
1137 ) -> CargoResult<()> {
1138 debug!(target: "gc", "cleaning download till under {max_size:?}");
1139
1140 let mut stmt = conn.prepare_cached(
1146 "SELECT 1, registry_src.rowid, registry_src.name AS name, registry_index.name,
1147 registry_src.size, registry_src.timestamp AS timestamp
1148 FROM registry_src, registry_index
1149 WHERE registry_src.registry_id = registry_index.id AND registry_src.size NOT NULL
1150
1151 UNION
1152
1153 SELECT 2, registry_crate.rowid, registry_crate.name AS name, registry_index.name,
1154 registry_crate.size, registry_crate.timestamp AS timestamp
1155 FROM registry_crate, registry_index
1156 WHERE registry_crate.registry_id = registry_index.id
1157
1158 ORDER BY timestamp, name",
1159 )?;
1160 let mut delete_src_stmt =
1161 conn.prepare_cached("DELETE FROM registry_src WHERE rowid = ?1")?;
1162 let mut delete_crate_stmt =
1163 conn.prepare_cached("DELETE FROM registry_crate WHERE rowid = ?1")?;
1164 let rows = stmt
1165 .query_map([], |row| {
1166 Ok((
1167 row.get_unwrap(0),
1168 row.get_unwrap(1),
1169 row.get_unwrap(2),
1170 row.get_unwrap(3),
1171 row.get_unwrap(4),
1172 ))
1173 })?
1174 .collect::<Result<Vec<(i64, i64, String, String, u64)>, _>>()?;
1175 let mut total_size: u64 = rows.iter().map(|r| r.4).sum();
1176 debug!(target: "gc", "total download cache size appears to be {total_size}");
1177 for (table, rowid, name, index_name, size) in rows {
1178 if total_size <= max_size {
1179 break;
1180 }
1181 if table == 1 {
1182 delete_paths.push(base.src.join(index_name).join(name));
1183 delete_src_stmt.execute([rowid])?;
1184 } else {
1185 delete_paths.push(base.crate_dir.join(index_name).join(name));
1186 delete_crate_stmt.execute([rowid])?;
1187 }
1188 total_size -= size;
1190 }
1191 Ok(())
1192 }
1193
1194 fn get_git_items_to_clean_size(
1199 conn: &Connection,
1200 max_size: u64,
1201 base: &BasePaths,
1202 delete_paths: &mut Vec<PathBuf>,
1203 ) -> CargoResult<()> {
1204 debug!(target: "gc", "cleaning git till under {max_size:?}");
1205
1206 let mut stmt = conn.prepare_cached("SELECT rowid, name, timestamp FROM git_db")?;
1208 let mut git_info = stmt
1209 .query_map([], |row| {
1210 let rowid: i64 = row.get_unwrap(0);
1211 let name: String = row.get_unwrap(1);
1212 let timestamp: Timestamp = row.get_unwrap(2);
1213 Ok((timestamp, rowid, None, name, 0))
1216 })?
1217 .collect::<Result<Vec<_>, _>>()?;
1218 for info in &mut git_info {
1219 let size = cargo_util::du(&base.git_db.join(&info.3), &[])?;
1220 info.4 = size;
1221 }
1222
1223 let mut stmt = conn.prepare_cached(
1224 "SELECT git_checkout.rowid, git_db.name, git_checkout.name,
1225 git_checkout.size, git_checkout.timestamp
1226 FROM git_checkout, git_db
1227 WHERE git_checkout.git_id = git_db.id AND git_checkout.size NOT NULL",
1228 )?;
1229 let git_co_rows = stmt
1230 .query_map([], |row| {
1231 let rowid = row.get_unwrap(0);
1232 let db_name: String = row.get_unwrap(1);
1233 let name = row.get_unwrap(2);
1234 let size = row.get_unwrap(3);
1235 let timestamp = row.get_unwrap(4);
1236 Ok((timestamp, rowid, Some(db_name), name, size))
1237 })?
1238 .collect::<Result<Vec<_>, _>>()?;
1239 git_info.extend(git_co_rows);
1240
1241 git_info.sort_by(|a, b| (b.0, &b.3).cmp(&(a.0, &a.3)));
1245
1246 let mut delete_db_stmt = conn.prepare_cached("DELETE FROM git_db WHERE rowid = ?1")?;
1248 let mut delete_co_stmt =
1249 conn.prepare_cached("DELETE FROM git_checkout WHERE rowid = ?1")?;
1250 let mut total_size: u64 = git_info.iter().map(|r| r.4).sum();
1251 debug!(target: "gc", "total git cache size appears to be {total_size}");
1252 while let Some((_timestamp, rowid, db_name, name, size)) = git_info.pop() {
1253 if total_size <= max_size {
1254 break;
1255 }
1256 if let Some(db_name) = db_name {
1257 delete_paths.push(base.git_co.join(db_name).join(name));
1258 delete_co_stmt.execute([rowid])?;
1259 total_size -= size;
1260 } else {
1261 total_size -= size;
1262 delete_paths.push(base.git_db.join(&name));
1263 delete_db_stmt.execute([rowid])?;
1264 let mut i = 0;
1266 while i < git_info.len() {
1267 if git_info[i].2.as_deref() == Some(name.as_ref()) {
1268 let (_, rowid, db_name, name, size) = git_info.remove(i);
1269 delete_paths.push(base.git_co.join(db_name.unwrap()).join(name));
1270 delete_co_stmt.execute([rowid])?;
1271 total_size -= size;
1272 } else {
1273 i += 1;
1274 }
1275 }
1276 }
1277 }
1278 Ok(())
1279 }
1280
1281 fn get_registry_index_to_clean(
1284 conn: &Connection,
1285 max_age: Timestamp,
1286 base: &BasePaths,
1287 delete_paths: &mut Vec<PathBuf>,
1288 ) -> CargoResult<()> {
1289 debug!(target: "gc", "cleaning index since {max_age:?}");
1290 let mut stmt = conn.prepare_cached(
1291 "DELETE FROM registry_index WHERE timestamp < ?1
1292 RETURNING name",
1293 )?;
1294 let mut rows = stmt.query([max_age])?;
1295 while let Some(row) = rows.next()? {
1296 let name: String = row.get_unwrap(0);
1297 delete_paths.push(base.index.join(&name));
1298 delete_paths.push(base.src.join(&name));
1301 delete_paths.push(base.crate_dir.join(&name));
1302 }
1303 Ok(())
1304 }
1305
1306 fn get_git_co_items_to_clean(
1309 conn: &Connection,
1310 max_age: Timestamp,
1311 base_path: &Path,
1312 delete_paths: &mut Vec<PathBuf>,
1313 ) -> CargoResult<()> {
1314 debug!(target: "gc", "cleaning git co since {max_age:?}");
1315 let mut stmt = conn.prepare_cached(
1316 "DELETE FROM git_checkout WHERE timestamp < ?1
1317 RETURNING git_id, name",
1318 )?;
1319 let rows = stmt
1320 .query_map(params![max_age], |row| {
1321 let git_id = row.get_unwrap(0);
1322 let name: String = row.get_unwrap(1);
1323 Ok((git_id, name))
1324 })?
1325 .collect::<Result<Vec<_>, _>>()?;
1326 let ids: Vec<_> = rows.iter().map(|r| r.0).collect();
1327 let id_map = Self::get_id_map(conn, GIT_DB_TABLE, &ids)?;
1328 for (id, name) in rows {
1329 let encoded_git_name = &id_map[&id];
1330 delete_paths.push(base_path.join(encoded_git_name).join(name));
1331 }
1332 Ok(())
1333 }
1334
1335 fn get_git_db_items_to_clean(
1338 conn: &Connection,
1339 max_age: Timestamp,
1340 base: &BasePaths,
1341 delete_paths: &mut Vec<PathBuf>,
1342 ) -> CargoResult<()> {
1343 debug!(target: "gc", "cleaning git db since {max_age:?}");
1344 let mut stmt = conn.prepare_cached(
1345 "DELETE FROM git_db WHERE timestamp < ?1
1346 RETURNING name",
1347 )?;
1348 let mut rows = stmt.query([max_age])?;
1349 while let Some(row) = rows.next()? {
1350 let name: String = row.get_unwrap(0);
1351 delete_paths.push(base.git_db.join(&name));
1352 delete_paths.push(base.git_co.join(&name));
1355 }
1356 Ok(())
1357 }
1358}
1359
1360macro_rules! insert_or_update_parent {
1368 ($self:expr, $conn:expr, $table_name:expr, $timestamps_field:ident, $keys_field:ident, $encoded_name:ident) => {
1369 let mut select_stmt = $conn.prepare_cached(concat!(
1370 "SELECT id, timestamp FROM ",
1371 $table_name,
1372 " WHERE name = ?1"
1373 ))?;
1374 let mut insert_stmt = $conn.prepare_cached(concat!(
1375 "INSERT INTO ",
1376 $table_name,
1377 " (name, timestamp)
1378 VALUES (?1, ?2)
1379 ON CONFLICT DO UPDATE SET timestamp=excluded.timestamp
1380 RETURNING id",
1381 ))?;
1382 let mut update_stmt = $conn.prepare_cached(concat!(
1383 "UPDATE ",
1384 $table_name,
1385 " SET timestamp = ?1 WHERE id = ?2"
1386 ))?;
1387 for (parent, new_timestamp) in std::mem::take(&mut $self.$timestamps_field) {
1388 trace!(target: "gc",
1389 concat!("insert ", $table_name, " {:?} {}"),
1390 parent,
1391 new_timestamp
1392 );
1393 let mut rows = select_stmt.query([parent.$encoded_name])?;
1394 let id = if let Some(row) = rows.next()? {
1395 let id: ParentId = row.get_unwrap(0);
1396 let timestamp: Timestamp = row.get_unwrap(1);
1397 if timestamp < new_timestamp - UPDATE_RESOLUTION {
1398 update_stmt.execute(params![new_timestamp, id])?;
1399 }
1400 id
1401 } else {
1402 insert_stmt.query_row(params![parent.$encoded_name, new_timestamp], |row| {
1403 row.get(0)
1404 })?
1405 };
1406 match $self.$keys_field.entry(parent.$encoded_name) {
1407 hash_map::Entry::Occupied(o) => {
1408 assert_eq!(*o.get(), id);
1409 }
1410 hash_map::Entry::Vacant(v) => {
1411 v.insert(id);
1412 }
1413 }
1414 }
1415 return Ok(());
1416 };
1417}
1418
1419#[derive(Debug)]
1424pub struct DeferredGlobalLastUse {
1425 registry_keys: HashMap<InternedString, ParentId>,
1430 git_keys: HashMap<InternedString, ParentId>,
1435
1436 registry_index_timestamps: HashMap<RegistryIndex, Timestamp>,
1438 registry_crate_timestamps: HashMap<RegistryCrate, Timestamp>,
1440 registry_src_timestamps: HashMap<RegistrySrc, Timestamp>,
1442 git_db_timestamps: HashMap<GitDb, Timestamp>,
1444 git_checkout_timestamps: HashMap<GitCheckout, Timestamp>,
1446 save_err_has_warned: bool,
1449 now: Timestamp,
1452}
1453
1454impl DeferredGlobalLastUse {
1455 pub fn new() -> DeferredGlobalLastUse {
1456 DeferredGlobalLastUse {
1457 registry_keys: HashMap::default(),
1458 git_keys: HashMap::default(),
1459 registry_index_timestamps: HashMap::default(),
1460 registry_crate_timestamps: HashMap::default(),
1461 registry_src_timestamps: HashMap::default(),
1462 git_db_timestamps: HashMap::default(),
1463 git_checkout_timestamps: HashMap::default(),
1464 save_err_has_warned: false,
1465 now: now(),
1466 }
1467 }
1468
1469 pub fn is_empty(&self) -> bool {
1470 self.registry_index_timestamps.is_empty()
1471 && self.registry_crate_timestamps.is_empty()
1472 && self.registry_src_timestamps.is_empty()
1473 && self.git_db_timestamps.is_empty()
1474 && self.git_checkout_timestamps.is_empty()
1475 }
1476
1477 fn clear(&mut self) {
1478 self.registry_index_timestamps.clear();
1479 self.registry_crate_timestamps.clear();
1480 self.registry_src_timestamps.clear();
1481 self.git_db_timestamps.clear();
1482 self.git_checkout_timestamps.clear();
1483 }
1484
1485 pub fn mark_registry_index_used(&mut self, registry_index: RegistryIndex) {
1487 self.mark_registry_index_used_stamp(registry_index, None);
1488 }
1489
1490 pub fn mark_registry_crate_used(&mut self, registry_crate: RegistryCrate) {
1494 self.mark_registry_crate_used_stamp(registry_crate, None);
1495 }
1496
1497 pub fn mark_registry_src_used(&mut self, registry_src: RegistrySrc) {
1501 self.mark_registry_src_used_stamp(registry_src, None);
1502 }
1503
1504 pub fn mark_git_checkout_used(&mut self, git_checkout: GitCheckout) {
1508 self.mark_git_checkout_used_stamp(git_checkout, None);
1509 }
1510
1511 pub fn mark_registry_index_used_stamp(
1514 &mut self,
1515 registry_index: RegistryIndex,
1516 timestamp: Option<&SystemTime>,
1517 ) {
1518 let timestamp = timestamp.map_or(self.now, to_timestamp);
1519 self.registry_index_timestamps
1520 .insert(registry_index, timestamp);
1521 }
1522
1523 pub fn mark_registry_crate_used_stamp(
1528 &mut self,
1529 registry_crate: RegistryCrate,
1530 timestamp: Option<&SystemTime>,
1531 ) {
1532 let timestamp = timestamp.map_or(self.now, to_timestamp);
1533 let index = RegistryIndex {
1534 encoded_registry_name: registry_crate.encoded_registry_name,
1535 };
1536 self.registry_index_timestamps.insert(index, timestamp);
1537 self.registry_crate_timestamps
1538 .insert(registry_crate, timestamp);
1539 }
1540
1541 pub fn mark_registry_src_used_stamp(
1546 &mut self,
1547 registry_src: RegistrySrc,
1548 timestamp: Option<&SystemTime>,
1549 ) {
1550 let timestamp = timestamp.map_or(self.now, to_timestamp);
1551 let index = RegistryIndex {
1552 encoded_registry_name: registry_src.encoded_registry_name,
1553 };
1554 self.registry_index_timestamps.insert(index, timestamp);
1555 self.registry_src_timestamps.insert(registry_src, timestamp);
1556 }
1557
1558 pub fn mark_git_checkout_used_stamp(
1563 &mut self,
1564 git_checkout: GitCheckout,
1565 timestamp: Option<&SystemTime>,
1566 ) {
1567 let timestamp = timestamp.map_or(self.now, to_timestamp);
1568 let db = GitDb {
1569 encoded_git_name: git_checkout.encoded_git_name,
1570 };
1571 self.git_db_timestamps.insert(db, timestamp);
1572 self.git_checkout_timestamps.insert(git_checkout, timestamp);
1573 }
1574
1575 #[tracing::instrument(skip_all)]
1579 pub fn save(&mut self, tracker: &mut GlobalCacheTracker) -> CargoResult<()> {
1580 trace!(target: "gc", "saving last-use data");
1581 if self.is_empty() {
1582 return Ok(());
1583 }
1584 let tx = tracker.conn.transaction()?;
1585 self.insert_registry_index_from_cache(&tx)?;
1587 self.insert_git_db_from_cache(&tx)?;
1588 self.insert_registry_crate_from_cache(&tx)?;
1589 self.insert_registry_src_from_cache(&tx)?;
1590 self.insert_git_checkout_from_cache(&tx)?;
1591 tx.commit()?;
1592 trace!(target: "gc", "last-use save complete");
1593 Ok(())
1594 }
1595
1596 pub fn save_no_error(&mut self, gctx: &GlobalContext) {
1601 if let Err(e) = self.save_with_gctx(gctx) {
1602 self.clear();
1605 if !self.save_err_has_warned {
1606 if is_silent_error(&e) && gctx.shell().verbosity() != Verbosity::Verbose {
1607 tracing::warn!("failed to save last-use data: {e:?}");
1608 } else {
1609 crate::display_warning_with_error(
1610 "failed to save last-use data\n\
1611 This may prevent cargo from accurately tracking what is being \
1612 used in its global cache. This information is used for \
1613 automatically removing unused data in the cache.",
1614 &e,
1615 &mut gctx.shell(),
1616 );
1617 self.save_err_has_warned = true;
1618 }
1619 }
1620 }
1621 }
1622
1623 fn save_with_gctx(&mut self, gctx: &GlobalContext) -> CargoResult<()> {
1624 let mut tracker = gctx.global_cache_tracker()?;
1625 self.save(&mut tracker)
1626 }
1627
1628 fn insert_registry_index_from_cache(&mut self, conn: &Connection) -> CargoResult<()> {
1631 insert_or_update_parent!(
1632 self,
1633 conn,
1634 "registry_index",
1635 registry_index_timestamps,
1636 registry_keys,
1637 encoded_registry_name
1638 );
1639 }
1640
1641 fn insert_git_db_from_cache(&mut self, conn: &Connection) -> CargoResult<()> {
1644 insert_or_update_parent!(
1645 self,
1646 conn,
1647 "git_db",
1648 git_db_timestamps,
1649 git_keys,
1650 encoded_git_name
1651 );
1652 }
1653
1654 fn insert_registry_crate_from_cache(&mut self, conn: &Connection) -> CargoResult<()> {
1657 let registry_crate_timestamps = std::mem::take(&mut self.registry_crate_timestamps);
1658 for (registry_crate, timestamp) in registry_crate_timestamps {
1659 trace!(target: "gc", "insert registry crate {registry_crate:?} {timestamp}");
1660 let registry_id = self.registry_id(conn, registry_crate.encoded_registry_name)?;
1661 let mut stmt = conn.prepare_cached(
1662 "INSERT INTO registry_crate (registry_id, name, size, timestamp)
1663 VALUES (?1, ?2, ?3, ?4)
1664 ON CONFLICT DO UPDATE SET timestamp=excluded.timestamp
1665 WHERE timestamp < ?5
1666 ",
1667 )?;
1668 stmt.execute(params![
1669 registry_id,
1670 registry_crate.crate_filename,
1671 registry_crate.size,
1672 timestamp,
1673 timestamp - UPDATE_RESOLUTION
1674 ])?;
1675 }
1676 Ok(())
1677 }
1678
1679 fn insert_registry_src_from_cache(&mut self, conn: &Connection) -> CargoResult<()> {
1682 let registry_src_timestamps = std::mem::take(&mut self.registry_src_timestamps);
1683 for (registry_src, timestamp) in registry_src_timestamps {
1684 trace!(target: "gc", "insert registry src {registry_src:?} {timestamp}");
1685 let registry_id = self.registry_id(conn, registry_src.encoded_registry_name)?;
1686 let mut stmt = conn.prepare_cached(
1687 "INSERT INTO registry_src (registry_id, name, size, timestamp)
1688 VALUES (?1, ?2, ?3, ?4)
1689 ON CONFLICT DO UPDATE SET timestamp=excluded.timestamp
1690 WHERE timestamp < ?5
1691 ",
1692 )?;
1693 stmt.execute(params![
1694 registry_id,
1695 registry_src.package_dir,
1696 registry_src.size,
1697 timestamp,
1698 timestamp - UPDATE_RESOLUTION
1699 ])?;
1700 }
1701
1702 Ok(())
1703 }
1704
1705 fn insert_git_checkout_from_cache(&mut self, conn: &Connection) -> CargoResult<()> {
1708 let git_checkout_timestamps = std::mem::take(&mut self.git_checkout_timestamps);
1709 for (git_checkout, timestamp) in git_checkout_timestamps {
1710 let git_id = self.git_id(conn, git_checkout.encoded_git_name)?;
1711 let mut stmt = conn.prepare_cached(
1712 "INSERT INTO git_checkout (git_id, name, size, timestamp)
1713 VALUES (?1, ?2, ?3, ?4)
1714 ON CONFLICT DO UPDATE SET timestamp=excluded.timestamp
1715 WHERE timestamp < ?5",
1716 )?;
1717 stmt.execute(params![
1718 git_id,
1719 git_checkout.short_name,
1720 git_checkout.size,
1721 timestamp,
1722 timestamp - UPDATE_RESOLUTION
1723 ])?;
1724 }
1725
1726 Ok(())
1727 }
1728
1729 fn registry_id(
1734 &mut self,
1735 conn: &Connection,
1736 encoded_registry_name: InternedString,
1737 ) -> CargoResult<ParentId> {
1738 match self.registry_keys.get(&encoded_registry_name) {
1739 Some(i) => Ok(*i),
1740 None => {
1741 let Some(id) = GlobalCacheTracker::id_from_name(
1742 conn,
1743 REGISTRY_INDEX_TABLE,
1744 &encoded_registry_name,
1745 )?
1746 else {
1747 bail!(
1748 "expected registry_index {encoded_registry_name} to exist, but wasn't found"
1749 );
1750 };
1751 self.registry_keys.insert(encoded_registry_name, id);
1752 Ok(id)
1753 }
1754 }
1755 }
1756
1757 fn git_id(
1762 &mut self,
1763 conn: &Connection,
1764 encoded_git_name: InternedString,
1765 ) -> CargoResult<ParentId> {
1766 match self.git_keys.get(&encoded_git_name) {
1767 Some(i) => Ok(*i),
1768 None => {
1769 let Some(id) =
1770 GlobalCacheTracker::id_from_name(conn, GIT_DB_TABLE, &encoded_git_name)?
1771 else {
1772 bail!("expected git_db {encoded_git_name} to exist, but wasn't found")
1773 };
1774 self.git_keys.insert(encoded_git_name, id);
1775 Ok(id)
1776 }
1777 }
1778 }
1779}
1780
1781fn to_timestamp(t: &SystemTime) -> Timestamp {
1783 t.duration_since(SystemTime::UNIX_EPOCH)
1784 .expect("invalid clock")
1785 .as_secs()
1786}
1787
1788#[expect(
1796 clippy::disallowed_methods,
1797 reason = "testing only, no reason for config support"
1798)]
1799fn now() -> Timestamp {
1800 match std::env::var("__CARGO_TEST_LAST_USE_NOW") {
1801 Ok(now) => now.parse().unwrap(),
1802 Err(_) => to_timestamp(&SystemTime::now()),
1803 }
1804}
1805
1806pub fn is_silent_error(e: &anyhow::Error) -> bool {
1814 if let Some(e) = e.downcast_ref::<rusqlite::Error>() {
1815 if matches!(
1816 e.sqlite_error_code(),
1817 Some(ErrorCode::CannotOpen | ErrorCode::ReadOnly)
1818 ) {
1819 return true;
1820 }
1821 }
1822 false
1823}
1824
1825#[tracing::instrument]
1827fn du_git_checkout(path: &Path) -> CargoResult<u64> {
1828 cargo_util::du(&path, &["!.git"])
1832}
1833
1834fn du(path: &Path, table_name: &str) -> CargoResult<u64> {
1835 if table_name == GIT_CO_TABLE {
1836 du_git_checkout(path)
1837 } else {
1838 cargo_util::du(&path, &[])
1839 }
1840}