diff --git a/.env.sample b/.env.sample index e292f69fbc8..a2bfa74b844 100644 --- a/.env.sample +++ b/.env.sample @@ -3,6 +3,9 @@ # `postgres://postgres:@localhost/cargo_registry`. export DATABASE_URL="" +# If you are running a mirror of crates.io, uncomment this line. +# export MIRROR=1 + # Key to sign and encrypt cookies with. Change this to a long, random string # for production. export SESSION_KEY="badkey" diff --git a/src/bin/fill-in-user-id.rs b/src/bin/fill-in-user-id.rs index df3a17a0588..7a522755866 100644 --- a/src/bin/fill-in-user-id.rs +++ b/src/bin/fill-in-user-id.rs @@ -33,6 +33,7 @@ fn main() { db_url: env("DATABASE_URL"), env: cargo_registry::Env::Production, max_upload_size: 0, + mirror: false, }; let app = cargo_registry::App::new(&config); { diff --git a/src/bin/server.rs b/src/bin/server.rs index 3c6ebd2c134..bf0749d65e4 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -57,6 +57,7 @@ fn main() { db_url: env("DATABASE_URL"), env: cargo_env, max_upload_size: 10 * 1024 * 1024, + mirror: env::var("MIRROR").is_ok(), }; let app = cargo_registry::App::new(&config); let app = cargo_registry::middleware(Arc::new(app)); diff --git a/src/config.rs b/src/config.rs index 32833d5653f..76483c58321 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,6 +14,7 @@ pub struct Config { pub db_url: String, pub env: ::Env, pub max_upload_size: u64, + pub mirror: bool, } impl Config { diff --git a/src/krate.rs b/src/krate.rs index fac3625f01f..951ea962479 100644 --- a/src/krate.rs +++ b/src/krate.rs @@ -827,6 +827,31 @@ fn read_fill(r: &mut R, mut slice: &mut [u8]) pub fn download(req: &mut Request) -> CargoResult { let crate_name = &req.params()["crate_id"]; let version = &req.params()["version"]; + + // If we are a mirror, ignore failure to update download counts. + // API-only mirrors won't have any crates in their database, and + // incrementing the download count will look up the crate in the + // database. Mirrors just want to pass along a redirect URL. + if req.app().config.mirror { + let _ = increment_download_counts(req, crate_name, version); + } else { + try!(increment_download_counts(req, crate_name, version)); + } + + let redirect_url = format!("https://{}/crates/{}/{}-{}.crate", + req.app().bucket.host(), + crate_name, crate_name, version); + + if req.wants_json() { + #[derive(RustcEncodable)] + struct R { url: String } + Ok(req.json(&R{ url: redirect_url })) + } else { + Ok(req.redirect(redirect_url)) + } +} + +fn increment_download_counts(req: &Request, crate_name: &str, version: &str) -> CargoResult<()> { let tx = try!(req.tx()); let stmt = try!(tx.prepare("SELECT versions.id as version_id FROM crates @@ -836,7 +861,7 @@ pub fn download(req: &mut Request) -> CargoResult { canon_crate_name($1) AND versions.num = $2 LIMIT 1")); - let rows = try!(stmt.query(&[crate_name, version])); + let rows = try!(stmt.query(&[&crate_name, &version])); let row = try!(rows.iter().next().chain_error(|| { human("crate or version not found") })); @@ -862,19 +887,7 @@ pub fn download(req: &mut Request) -> CargoResult { try!(tx.execute("INSERT INTO version_downloads (version_id) VALUES ($1)", &[&version_id])); } - - // Now that we've done our business, redirect to the actual data. - let redirect_url = format!("https://{}/crates/{}/{}-{}.crate", - req.app().bucket.host(), - crate_name, crate_name, version); - - if req.wants_json() { - #[derive(RustcEncodable)] - struct R { url: String } - Ok(req.json(&R{ url: redirect_url })) - } else { - Ok(req.redirect(redirect_url)) - } + Ok(()) } /// Handles the `GET /crates/:crate_id/downloads` route. diff --git a/src/tests/all.rs b/src/tests/all.rs index e5122359fd2..10fd5609a31 100755 --- a/src/tests/all.rs +++ b/src/tests/all.rs @@ -90,6 +90,7 @@ fn app() -> (record::Bomb, Arc, conduit_middleware::MiddlewareBuilder) { db_url: env("TEST_DATABASE_URL"), env: cargo_registry::Env::Test, max_upload_size: 1000, + mirror: false, }; INIT.call_once(|| db_setup(&config.db_url)); let app = App::new(&config);