diff --git a/src/docbuilder/queue.rs b/src/docbuilder/queue.rs index 23fdc7757..c85c21eda 100644 --- a/src/docbuilder/queue.rs +++ b/src/docbuilder/queue.rs @@ -18,11 +18,23 @@ impl DocBuilder { // I belive this will fix ordering of queue if we get more than one crate from changes changes.reverse(); - for krate in changes.iter().filter(|k| k.kind != ChangeKind::Yanked) { - conn.execute("INSERT INTO queue (name, version) VALUES ($1, $2)", - &[&krate.name, &krate.version]) - .ok(); - debug!("{}-{} added into build queue", krate.name, krate.version); + for krate in &changes { + match krate.kind { + ChangeKind::Yanked => { + // FIXME: remove built doc files? set build as failed? + conn.execute("UPDATE releases SET yanked = TRUE FROM crates WHERE \ + crates.id = releases.crate_id AND name = $1 AND version = $2", + &[&krate.name, &krate.version]) + .ok(); + debug!("{}-{} yanked", krate.name, krate.version); + } + ChangeKind::Added => { + conn.execute("INSERT INTO queue (name, version) VALUES ($1, $2)", + &[&krate.name, &krate.version]) + .ok(); + debug!("{}-{} added into build queue", krate.name, krate.version); + } + } } let queue_count = conn.query("SELECT COUNT(*) FROM queue WHERE attempt < 5", &[]) diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index 9c7b6aeb1..36a1f1b2b 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -44,6 +44,7 @@ pub struct CrateDetails { github_issues: Option, metadata: MetaData, is_library: bool, + yanked: bool, doc_targets: Option, license: Option, documentation_url: Option, @@ -82,6 +83,7 @@ impl ToJson for CrateDetails { m.insert("github_issues".to_string(), self.github_issues.to_json()); m.insert("metadata".to_string(), self.metadata.to_json()); m.insert("is_library".to_string(), self.is_library.to_json()); + m.insert("yanked".to_string(), self.yanked.to_json()); m.insert("doc_targets".to_string(), self.doc_targets.to_json()); m.insert("license".to_string(), self.license.to_json()); m.insert("documentation_url".to_string(), self.documentation_url.to_json()); @@ -116,6 +118,7 @@ impl CrateDetails { crates.github_forks, crates.github_issues, releases.is_library, + releases.yanked, releases.doc_targets, releases.license, releases.documentation_url @@ -185,9 +188,10 @@ impl CrateDetails { github_issues: rows.get(0).get(20), metadata: metadata, is_library: rows.get(0).get(21), - doc_targets: rows.get(0).get(22), - license: rows.get(0).get(23), - documentation_url: rows.get(0).get(24), + yanked: rows.get(0).get(22), + doc_targets: rows.get(0).get(23), + license: rows.get(0).get(24), + documentation_url: rows.get(0).get(25), }; if let Some(repository_url) = crate_details.repository_url.clone() { diff --git a/src/web/mod.rs b/src/web/mod.rs index 2efba3bce..76725eb8f 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -54,18 +54,28 @@ use iron::{self, Handler, status}; use iron::headers::{CacheControl, CacheDirective, ContentType}; use router::{Router, NoRoute}; use staticfile::Static; -use handlebars_iron::{HandlebarsEngine, DirectorySource}; +use handlebars_iron::{HandlebarsEngine, DirectorySource, SourceError}; use time; use postgres::Connection; use semver::{Version, VersionReq}; use rustc_serialize::json::{Json, ToJson}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; /// Duration of static files for staticfile and DatabaseFileHandler (in seconds) const STATIC_FILE_CACHE_DURATION: u64 = 60 * 60 * 24 * 30 * 12; // 12 months const STYLE_CSS: &'static str = include_str!(concat!(env!("OUT_DIR"), "/style.css")); const OPENSEARCH_XML: &'static [u8] = include_bytes!("opensearch.xml"); +fn handlebars_engine() -> Result { + // TODO: Use DocBuilderOptions for paths + let mut hbse = HandlebarsEngine::new(); + hbse.add(Box::new(DirectorySource::new("./templates", ".hbs"))); + + // load templates + try!(hbse.reload()); + + Ok(hbse) +} struct CratesfyiHandler { shared_resource_handler: Box, @@ -77,14 +87,10 @@ struct CratesfyiHandler { impl CratesfyiHandler { fn chain(base: H) -> Chain { - // TODO: Use DocBuilderOptions for paths - let mut hbse = HandlebarsEngine::new(); - hbse.add(Box::new(DirectorySource::new("./templates", ".hbs"))); - - // load templates - if let Err(e) = hbse.reload() { - panic!("Failed to load handlebar templates: {}", e.description()); - } + let hbse = match handlebars_engine() { + Ok(hbse) => hbse, + Err(e) => panic!("Failed to load handlebar templates: {}", e.description()), + }; let mut chain = Chain::new(base); chain.link_before(pool::Pool::new()); @@ -301,6 +307,23 @@ fn match_version(conn: &Connection, name: &str, version: Option<&str>) -> Option versions }; + let yanked_versions = { + let rows = conn.query("SELECT version FROM crates JOIN releases \ + ON releases.crate_id = crates.id \ + WHERE crates.name = $1 AND releases.yanked = true", + &[&name]).unwrap(); + let mut versions = BTreeSet::::new(); + + for v in &rows { + let v: String = v.get(0); + if let Ok(semv) = Version::parse(&v) { + versions.insert(semv); + } + } + + versions + }; + // first check for exact match // we can't expect users to use semver in query for version in &versions { @@ -335,14 +358,18 @@ fn match_version(conn: &Connection, name: &str, version: Option<&str>) -> Option }; // semver is acting weird for '*' (any) range if a crate only have pre-release versions - // return first version if requested version is '*' - if req_version == "*" && !versions_sem.is_empty() { - return Some(format!("{}", versions_sem[0])); + // return first non-yanked version if requested version is '*' + if req_version == "*" { + return versions_sem.iter() + .find(|v| !yanked_versions.contains(v)) + .map(|v| v.to_string()); } for version in &versions_sem { if req_sem_ver.matches(&version) { - return Some(format!("{}", version)); + if !yanked_versions.contains(&version) { + return Some(version.to_string()); + } } } @@ -539,4 +566,11 @@ mod test { assert_eq!(latest_version(&versions, "0.9.0"), Some("1.1.0".to_owned())); assert_eq!(latest_version(&versions, "invalidversion"), None); } + + #[test] + fn test_templates_are_valid() { + if let Err(e) = handlebars_engine() { + panic!("Failed to load handlebar templates: {}", e.description()); + } + } } diff --git a/src/web/releases.rs b/src/web/releases.rs index 1605e19c5..233f08565 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -485,37 +485,14 @@ pub fn search_handler(req: &mut Request) -> IronResult { if let Some(version) = match_version(&conn, &query, None) { - // FIXME: This is a super dirty way to check if crate have rustdocs generated. - // match_version should handle this instead of this code block. - // This block is introduced to fix #163 - let rustdoc_status = { - let rows = ctry!(conn.query("SELECT rustdoc_status - FROM releases - INNER JOIN crates - ON crates.id = releases.crate_id - WHERE crates.name = $1 AND releases.version = $2", - &[query, &version])); - if rows.is_empty() { - false - } else { - rows.get(0).get(0) - } - }; - let url = if rustdoc_status { - ctry!(Url::parse(&format!("{}://{}:{}/{}/{}", - req.url.scheme(), - req.url.host(), - req.url.port(), - query, - version)[..])) - } else { - ctry!(Url::parse(&format!("{}://{}:{}/crate/{}/{}", - req.url.scheme(), - req.url.host(), - req.url.port(), - query, - version)[..])) - }; + // If the crate doesn't have docs, the `crate/version` route will redirect to the + // crate details page instead + let url = ctry!(Url::parse(&format!("{}://{}:{}/{}/{}", + req.url.scheme(), + req.url.host(), + req.url.port(), + query, + version)[..])); let mut resp = Response::with((status::Found, Redirect(url))); use iron::headers::{Expires, HttpDate}; diff --git a/templates/crate_details.hbs b/templates/crate_details.hbs index cc0063af3..a09b4d810 100644 --- a/templates/crate_details.hbs +++ b/templates/crate_details.hbs @@ -61,6 +61,9 @@ {{#unless is_library}}
{{name}}-{{version}} is not a library.
{{else}} + {{#if yanked}} +
{{name}}-{{version}} has been yanked.
+ {{else}} {{#unless build_status}}
docs.rs failed to build {{name}}-{{version}}
Please check build logs and if you believe this is docs.rs' fault, report into this issue report.
{{else}} @@ -68,6 +71,7 @@
{{name}}-{{version}} doesn't have any documentation.
{{/unless}} {{/unless}} + {{/if}} {{/unless}} {{#if readme}} {{{readme}}}