diff --git a/Cargo.lock b/Cargo.lock index db74d5e80..7f560601a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -886,6 +886,7 @@ dependencies = [ "toml", "ureq", "url", + "uuid", ] [[package]] @@ -1697,9 +1698,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.5.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", ] diff --git a/Cargo.toml b/Cargo.toml index 1afb7b281..8ae18df86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ html_parser = "0.7.0" url = "2.3" ureq = "2.9" const_format = "0.2.31" +uuid = "1.8" [dev-dependencies] tempfile = "3" diff --git a/src/resources/retrieve.rs b/src/resources/retrieve.rs index 5429707e6..83e44141b 100644 --- a/src/resources/retrieve.rs +++ b/src/resources/retrieve.rs @@ -22,7 +22,7 @@ pub(crate) trait ContentRetriever { fs::create_dir_all(cache_dir)?; } debug!("Downloading asset : {}", url); - let mut file = OpenOptions::new().create(true).write(true).open(dest)?; + let mut file = OpenOptions::new().create(true).truncate(true).write(true).open(dest)?; let mut resp = self.retrieve(url.as_str())?; io::copy(&mut resp, &mut file)?; debug!("Downloaded asset by '{}'", url); @@ -117,6 +117,27 @@ mod tests { panic!("{}", r.unwrap_err().to_string()); } + #[test] + fn download_parametrized_avatar_image() { + use std::io::Read; + + struct TestHandler; + impl ContentRetriever for TestHandler { + fn retrieve(&self, _url: &str) -> Result { + Ok(Box::new("Downloaded content".as_bytes())) + } + } + let cr = TestHandler {}; + let a = temp_remote_asset("https://avatars.githubusercontent.com/u/274803?v=4").unwrap(); + let r = cr.download(&a); + assert!(r.is_ok()); + + let mut buffer = String::new(); + let mut f = std::fs::File::open(&a.location_on_disk).unwrap(); + f.read_to_string(&mut buffer).unwrap(); + assert_eq!(buffer, "Downloaded content"); + } + fn temp_remote_asset(url: &str) -> Result { let tmp_dir = TempDir::new().unwrap(); let dest_dir = tmp_dir.path().join("mdbook-epub"); diff --git a/src/utils.rs b/src/utils.rs index 7ef2034fc..55b062866 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,6 +2,7 @@ use pulldown_cmark::{Options, Parser}; use std::ffi::OsStr; use std::path::{Component, Path, PathBuf}; use url::Url; +use uuid::Uuid; pub(crate) fn create_new_pull_down_parser(text: &str) -> Parser<'_, '_> { let mut opts = Options::empty(); @@ -40,6 +41,10 @@ pub fn normalize_path(path: &Path) -> PathBuf { ret } +/// Generate file name + extension from supplied remote URL. +/// If url does not contain file extension because of 'parametrized url' +/// then file's extension is generated as UUID4 value and file name +/// is hashed from URL itself. pub(crate) fn hash_link(url: &Url) -> String { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -47,10 +52,11 @@ pub(crate) fn hash_link(url: &Url) -> String { let mut hasher = DefaultHasher::new(); url.hash(&mut hasher); let path = PathBuf::from(url.path()); + let _generated_file_extension = Uuid::new_v4().to_string(); let ext = path .extension() .and_then(OsStr::to_str) - .unwrap_or_else(|| panic!("Unable to extract file ext from {url}")); + .unwrap_or(_generated_file_extension.as_str()); format!("{:x}.{}", hasher.finish(), ext) } @@ -58,6 +64,20 @@ pub(crate) fn hash_link(url: &Url) -> String { mod tests { use super::*; + #[test] + fn test_hash_named_url_with_extention() { + let test_url = "https://www.rust-lang.org/static/images/rust-logo-blk.svg"; + let hashed_filename = hash_link(&test_url.parse::().unwrap()); + assert_eq!("b20b2723e874918.svg", hashed_filename); + } + + #[test] + fn test_hash_parametrized_url_no_extension() { + let test_avatar_url = "https://avatars.githubusercontent.com/u/274803?v=4"; + let hashed_filename = hash_link(&test_avatar_url.parse::().unwrap()); + assert!(hashed_filename.starts_with("4dbdb25800b6fa1b.")); + } + #[cfg(not(target_os = "windows"))] #[test] fn test_normalize_path() { diff --git a/tests/dummy/src/01_getting_started/02_article.md b/tests/dummy/src/01_getting_started/02_article.md index 9a8e6d288..0a7e4ffda 100644 --- a/tests/dummy/src/01_getting_started/02_article.md +++ b/tests/dummy/src/01_getting_started/02_article.md @@ -3,4 +3,15 @@ We all love how Rust empowers us to write fast, safe software. **Fig 1: Before and after example** -![swap_problem](../assets/rust-logo.png) \ No newline at end of file +![swap_problem](../assets/rust-logo.png) + + +```powershell +if (Test-Path "myproject.zip") { + del "myproject.zip"; +} +& "c:\Program Files\7-Zip\7z.exe" a myproject.zip .\myproject\ "-xr!.git" "-xr!.gitignore" +pause +``` + +![Image](https://avatars.githubusercontent.com/u/274803?v=4) \ No newline at end of file diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index bdf8f79b3..5e96b6d76 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -134,7 +134,7 @@ fn rendered_document_contains_all_chapter_files_and_assets() { let mut doc = generate_epub().unwrap(); debug!("Number of internal epub resources = {:?}", doc.0.resources); // number of internal epub resources for dummy test book - assert_eq!(8, doc.0.resources.len()); + assert_eq!(9, doc.0.resources.len()); assert_eq!(2, doc.0.spine.len()); assert_eq!(doc.0.mdata("title").unwrap(), "DummyBook"); assert_eq!(doc.0.mdata("language").unwrap(), "en");