@@ -4,9 +4,34 @@ use anyhow::{ensure, Context, Result};
4
4
use sha2:: Digest ;
5
5
use tokio:: io:: AsyncWriteExt ;
6
6
7
+ /// Describes the naming convention that `verified_download` is permitted
8
+ /// to assume in the directory it saves downloads to.
9
+ ///
10
+ /// Consumers (direct or indirect) of `verified_download` are expected to check
11
+ /// if the file is already downloaded before calling it. This enum exists
12
+ /// to address race conditions when the same blob is downloaded several times
13
+ /// concurrently.
14
+ ///
15
+ /// The significance of this is for when the destination file turns out to already
16
+ /// exist after all (that is, has been created since the caller originally checked
17
+ /// existence). In the ContentIndexed case, the name already existing guarantees that
18
+ /// the file matches the download. If a caller uses `verified_download` for a
19
+ /// *non*-content-indexed case then they must provide and handle a new variant
20
+ /// of the enum.
21
+ pub enum DestinationConvention {
22
+ /// The download destination is content-indexed; therefore, in the event
23
+ /// of a race, the loser of the race can be safely discarded.
24
+ ContentIndexed ,
25
+ }
26
+
7
27
/// Downloads content from `url` which will be verified to match `digest` and
8
28
/// then moved to `dest`.
9
- pub async fn verified_download ( url : & str , digest : & str , dest : & Path ) -> Result < ( ) > {
29
+ pub async fn verified_download (
30
+ url : & str ,
31
+ digest : & str ,
32
+ dest : & Path ,
33
+ convention : DestinationConvention ,
34
+ ) -> Result < ( ) > {
10
35
tracing:: debug!( "Downloading content from {url:?}" ) ;
11
36
12
37
// Prepare tempfile destination
@@ -38,9 +63,16 @@ pub async fn verified_download(url: &str, digest: &str, dest: &Path) -> Result<(
38
63
) ;
39
64
40
65
// Move to final destination
41
- temp_path
42
- . persist_noclobber ( dest)
43
- . with_context ( || format ! ( "Failed to save download from {url} to {}" , dest. display( ) ) ) ?;
66
+ let persist_result = temp_path. persist_noclobber ( dest) ;
44
67
45
- Ok ( ( ) )
68
+ persist_result. or_else ( |e| {
69
+ let file_already_exists = e. error . kind ( ) == std:: io:: ErrorKind :: AlreadyExists ;
70
+ if file_already_exists && matches ! ( convention, DestinationConvention :: ContentIndexed ) {
71
+ Ok ( ( ) )
72
+ } else {
73
+ Err ( e) . with_context ( || {
74
+ format ! ( "Failed to save download from {url} to {}" , dest. display( ) )
75
+ } )
76
+ }
77
+ } )
46
78
}
0 commit comments