@@ -8,9 +8,12 @@ use manifest::Component;
88use manifest:: Manifest as ManifestV2 ;
99use manifestation:: { Manifestation , UpdateStatus , Changes } ;
1010
11- use std:: path:: Path ;
11+ use std:: path:: { Path , PathBuf } ;
12+ use std:: ops;
13+ use url:: Url ;
1214use std:: fmt;
1315use std:: env;
16+ use std:: fs;
1417
1518use regex:: Regex ;
1619use sha2:: { Sha256 , Digest } ;
@@ -482,9 +485,87 @@ pub fn download_and_check<'a>(url_str: &str,
482485pub struct DownloadCfg < ' a > {
483486 pub dist_root : & ' a str ,
484487 pub temp_cfg : & ' a temp:: Cfg ,
488+ pub download_dir : & ' a PathBuf ,
485489 pub notify_handler : & ' a Fn ( Notification ) ,
486490}
487491
492+
493+ pub struct File {
494+ path : PathBuf ,
495+ }
496+
497+ impl ops:: Deref for File {
498+ type Target = Path ;
499+
500+ fn deref ( & self ) -> & Path {
501+ ops:: Deref :: deref ( & self . path )
502+ }
503+ }
504+
505+ impl < ' a > DownloadCfg < ' a > {
506+
507+ pub fn download ( & self , url : & Url , hash : & str , notify_handler : & ' a Fn ( Notification ) ) -> Result < File > {
508+
509+ try!( utils:: ensure_dir_exists ( "Download Directory" , & self . download_dir , & |n| notify_handler ( n. into ( ) ) ) ) ;
510+ let target_file = self . download_dir . join ( Path :: new ( hash) ) ;
511+
512+ if target_file. exists ( ) {
513+ let mut hasher = Sha256 :: new ( ) ;
514+ use std:: io:: Read ;
515+ let mut downloaded = try!( fs:: File :: open ( & target_file) . chain_err ( || "opening already downloaded file" ) ) ;
516+ let mut buf = [ 0 ; 1024 ] ;
517+ loop {
518+ if let Ok ( n) = downloaded. read ( & mut buf) {
519+ if n == 0 { break ; }
520+ hasher. input ( & buf[ ..n] ) ;
521+ } else {
522+ break ;
523+ }
524+ }
525+ let cached_result = hasher. result_str ( ) ;
526+ if hash == cached_result {
527+ notify_handler ( Notification :: FileAlreadyDownloaded ) ;
528+ notify_handler ( Notification :: ChecksumValid ( & url. to_string ( ) ) ) ;
529+ return Ok ( File { path : target_file, } ) ;
530+ } else {
531+ notify_handler ( Notification :: CachedFileChecksumFailed ) ;
532+ try!( fs:: remove_file ( & target_file) . chain_err ( || "cleaning up previous download" ) ) ;
533+ }
534+ }
535+
536+ let mut hasher = Sha256 :: new ( ) ;
537+
538+ try!( utils:: download_file ( & url,
539+ & target_file,
540+ Some ( & mut hasher) ,
541+ & |n| notify_handler ( n. into ( ) ) ) ) ;
542+
543+ let actual_hash = hasher. result_str ( ) ;
544+
545+ if hash != actual_hash {
546+ // Incorrect hash
547+ return Err ( ErrorKind :: ChecksumFailed {
548+ url : url. to_string ( ) ,
549+ expected : hash. to_string ( ) ,
550+ calculated : actual_hash,
551+ } . into ( ) ) ;
552+ } else {
553+ notify_handler ( Notification :: ChecksumValid ( & url. to_string ( ) ) ) ;
554+ return Ok ( File { path : target_file, } )
555+ }
556+ }
557+
558+ pub fn clean ( & self , hashes : & Vec < String > ) -> Result < ( ) > {
559+ for hash in hashes. iter ( ) {
560+ let used_file = self . download_dir . join ( hash) ;
561+ if self . download_dir . join ( & used_file) . exists ( ) {
562+ try!( fs:: remove_file ( used_file) . chain_err ( || "cleaning up cached downloads" ) ) ;
563+ }
564+ }
565+ Ok ( ( ) )
566+ }
567+ }
568+
488569pub fn download_hash ( url : & str , cfg : DownloadCfg ) -> Result < String > {
489570 let hash_url = try!( utils:: parse_url ( & ( url. to_owned ( ) + ".sha256" ) ) ) ;
490571 let hash_file = try!( cfg. temp_cfg . new_file ( ) ) ;
@@ -551,7 +632,7 @@ pub fn update_from_dist_<'a>(download: DownloadCfg<'a>,
551632 Ok ( Some ( ( m, hash) ) ) => {
552633 return match try!( manifestation. update ( & m,
553634 changes,
554- & download. temp_cfg ,
635+ & download,
555636 download. notify_handler . clone ( ) ) ) {
556637 UpdateStatus :: Unchanged => Ok ( None ) ,
557638 UpdateStatus :: Changed => Ok ( Some ( hash) ) ,
0 commit comments