|
1 | | -use crate::models; |
2 | | -use crate::tasks::spawn_blocking; |
3 | | -use crate::util::diesel::Conn; |
4 | | -use crate::worker::Environment; |
5 | | -use anyhow::Context; |
6 | | -use crates_io_index::Repository; |
7 | | -use crates_io_worker::BackgroundJob; |
8 | | -use diesel::prelude::*; |
9 | | -use diesel_async::async_connection_wrapper::AsyncConnectionWrapper; |
10 | | -use sentry::Level; |
11 | | -use std::fs::{self, File}; |
12 | | -use std::io::{ErrorKind, Write}; |
13 | | -use std::sync::Arc; |
14 | | - |
15 | 1 | mod normalize; |
16 | 2 | mod squash; |
| 3 | +mod sync; |
17 | 4 |
|
18 | 5 | pub use normalize::NormalizeIndex; |
19 | 6 | pub use squash::SquashIndex; |
20 | | - |
21 | | -#[derive(Serialize, Deserialize)] |
22 | | -pub struct SyncToGitIndex { |
23 | | - krate: String, |
24 | | -} |
25 | | - |
26 | | -impl SyncToGitIndex { |
27 | | - pub fn new(krate: impl Into<String>) -> Self { |
28 | | - let krate = krate.into(); |
29 | | - Self { krate } |
30 | | - } |
31 | | -} |
32 | | - |
33 | | -impl BackgroundJob for SyncToGitIndex { |
34 | | - const JOB_NAME: &'static str = "sync_to_git_index"; |
35 | | - const PRIORITY: i16 = 100; |
36 | | - const QUEUE: &'static str = "repository"; |
37 | | - |
38 | | - type Context = Arc<Environment>; |
39 | | - |
40 | | - /// Regenerates or removes an index file for a single crate |
41 | | - #[instrument(skip_all, fields(krate.name = ? self.krate))] |
42 | | - async fn run(&self, env: Self::Context) -> anyhow::Result<()> { |
43 | | - info!("Syncing to git index"); |
44 | | - |
45 | | - let crate_name = self.krate.clone(); |
46 | | - let conn = env.deadpool.get().await?; |
47 | | - spawn_blocking(move || { |
48 | | - let conn: &mut AsyncConnectionWrapper<_> = &mut conn.into(); |
49 | | - |
50 | | - let new = get_index_data(&crate_name, conn).context("Failed to get index data")?; |
51 | | - |
52 | | - let repo = env.lock_index()?; |
53 | | - let dst = repo.index_file(&crate_name); |
54 | | - |
55 | | - // Read the previous crate contents |
56 | | - let old = match fs::read_to_string(&dst) { |
57 | | - Ok(content) => Some(content), |
58 | | - Err(error) if error.kind() == ErrorKind::NotFound => None, |
59 | | - Err(error) => return Err(error.into()), |
60 | | - }; |
61 | | - |
62 | | - match (old, new) { |
63 | | - (None, Some(new)) => { |
64 | | - fs::create_dir_all(dst.parent().unwrap())?; |
65 | | - let mut file = File::create(&dst)?; |
66 | | - file.write_all(new.as_bytes())?; |
67 | | - repo.commit_and_push(&format!("Create crate `{}`", &crate_name), &dst)?; |
68 | | - } |
69 | | - (Some(old), Some(new)) if old != new => { |
70 | | - let mut file = File::create(&dst)?; |
71 | | - file.write_all(new.as_bytes())?; |
72 | | - repo.commit_and_push(&format!("Update crate `{}`", &crate_name), &dst)?; |
73 | | - } |
74 | | - (Some(_old), None) => { |
75 | | - fs::remove_file(&dst)?; |
76 | | - repo.commit_and_push(&format!("Delete crate `{}`", &crate_name), &dst)?; |
77 | | - } |
78 | | - _ => debug!("Skipping sync because index is up-to-date"), |
79 | | - } |
80 | | - |
81 | | - Ok(()) |
82 | | - }) |
83 | | - .await |
84 | | - } |
85 | | -} |
86 | | - |
87 | | -#[derive(Serialize, Deserialize)] |
88 | | -pub struct SyncToSparseIndex { |
89 | | - krate: String, |
90 | | -} |
91 | | - |
92 | | -impl SyncToSparseIndex { |
93 | | - pub fn new(krate: impl Into<String>) -> Self { |
94 | | - let krate = krate.into(); |
95 | | - Self { krate } |
96 | | - } |
97 | | -} |
98 | | - |
99 | | -impl BackgroundJob for SyncToSparseIndex { |
100 | | - const JOB_NAME: &'static str = "sync_to_sparse_index"; |
101 | | - const PRIORITY: i16 = 100; |
102 | | - |
103 | | - type Context = Arc<Environment>; |
104 | | - |
105 | | - /// Regenerates or removes an index file for a single crate |
106 | | - #[instrument(skip_all, fields(krate.name = ?self.krate))] |
107 | | - async fn run(&self, env: Self::Context) -> anyhow::Result<()> { |
108 | | - info!("Syncing to sparse index"); |
109 | | - |
110 | | - let crate_name = self.krate.clone(); |
111 | | - let conn = env.deadpool.get().await?; |
112 | | - let content = spawn_blocking(move || { |
113 | | - let conn: &mut AsyncConnectionWrapper<_> = &mut conn.into(); |
114 | | - get_index_data(&crate_name, conn) |
115 | | - }) |
116 | | - .await |
117 | | - .context("Failed to get index data")?; |
118 | | - |
119 | | - let future = env.storage.sync_index(&self.krate, content); |
120 | | - future.await.context("Failed to sync index data")?; |
121 | | - |
122 | | - if let Some(cloudfront) = env.cloudfront() { |
123 | | - let path = Repository::relative_index_file_for_url(&self.krate); |
124 | | - |
125 | | - info!(%path, "Invalidating index file on CloudFront"); |
126 | | - let future = cloudfront.invalidate(&path); |
127 | | - future.await.context("Failed to invalidate CloudFront")?; |
128 | | - } |
129 | | - Ok(()) |
130 | | - } |
131 | | -} |
132 | | - |
133 | | -#[instrument(skip_all, fields(krate.name = ?name))] |
134 | | -pub fn get_index_data(name: &str, conn: &mut impl Conn) -> anyhow::Result<Option<String>> { |
135 | | - debug!("Looking up crate by name"); |
136 | | - let Some(krate): Option<models::Crate> = |
137 | | - models::Crate::by_exact_name(name).first(conn).optional()? |
138 | | - else { |
139 | | - return Ok(None); |
140 | | - }; |
141 | | - |
142 | | - debug!("Gathering remaining index data"); |
143 | | - let crates = krate |
144 | | - .index_metadata(conn) |
145 | | - .context("Failed to gather index metadata")?; |
146 | | - |
147 | | - // This can sometimes happen when we delete versions upon owner request |
148 | | - // but don't realize that the crate is now left with no versions at all. |
149 | | - // |
150 | | - // In this case we will delete the crate from the index and log a warning to |
151 | | - // Sentry to clean this up in the database. |
152 | | - if crates.is_empty() { |
153 | | - let message = format!("Crate `{name}` has no versions left"); |
154 | | - sentry::capture_message(&message, Level::Warning); |
155 | | - |
156 | | - return Ok(None); |
157 | | - } |
158 | | - |
159 | | - debug!("Serializing index data"); |
160 | | - let mut bytes = Vec::new(); |
161 | | - crates_io_index::write_crates(&crates, &mut bytes) |
162 | | - .context("Failed to serialize index metadata")?; |
163 | | - |
164 | | - let str = String::from_utf8(bytes).context("Failed to decode index metadata as utf8")?; |
165 | | - |
166 | | - Ok(Some(str)) |
167 | | -} |
| 7 | +pub use sync::{SyncToGitIndex, SyncToSparseIndex}; |
0 commit comments