@@ -48,36 +48,6 @@ enum ImageOutputFormat {
48
48
}
49
49
50
50
impl < ' a > BlobObject < ' a > {
51
- /// Creates a new blob object with a unique name.
52
- ///
53
- /// Creates a new file in the blob directory. The name will be
54
- /// derived from the platform-agnostic basename of the suggested
55
- /// name, followed by a random number and followed by a possible
56
- /// extension. The `data` will be written into the file without
57
- /// race-conditions.
58
- pub async fn create (
59
- context : & ' a Context ,
60
- suggested_name : & str ,
61
- data : & [ u8 ] ,
62
- ) -> Result < BlobObject < ' a > > {
63
- let blobdir = context. get_blobdir ( ) ;
64
- let ( stem, ext) = BlobObject :: sanitise_name ( suggested_name) ;
65
- let ( name, mut file) = BlobObject :: create_new_file ( context, blobdir, & stem, & ext) . await ?;
66
- file. write_all ( data) . await . context ( "file write failure" ) ?;
67
-
68
- // workaround a bug in async-std
69
- // (the executor does not handle blocking operation in Drop correctly,
70
- // see <https://github.com/async-rs/async-std/issues/900>)
71
- let _ = file. flush ( ) . await ;
72
-
73
- let blob = BlobObject {
74
- blobdir,
75
- name : format ! ( "$BLOBDIR/{name}" ) ,
76
- } ;
77
- context. emit_event ( EventType :: NewBlobFile ( blob. as_name ( ) . to_string ( ) ) ) ;
78
- Ok ( blob)
79
- }
80
-
81
51
/// Creates a new file, returning a tuple of the name and the handle.
82
52
async fn create_new_file (
83
53
context : & Context ,
@@ -115,8 +85,8 @@ impl<'a> BlobObject<'a> {
115
85
116
86
/// Creates a new blob object with unique name by copying an existing file.
117
87
///
118
- /// This creates a new blob as described in [BlobObject::create]
119
- /// but also copies an existing file into it. This is done in a
88
+ /// This creates a new blob
89
+ /// and copies an existing file into it. This is done in a
120
90
/// in way which avoids race-conditions when multiple files are
121
91
/// concurrently created.
122
92
pub async fn create_and_copy ( context : & ' a Context , src : & Path ) -> Result < BlobObject < ' a > > {
@@ -134,8 +104,8 @@ impl<'a> BlobObject<'a> {
134
104
return Err ( err) . context ( "failed to copy file" ) ;
135
105
}
136
106
137
- // workaround, see create() for details
138
- let _ = dst_file. flush ( ) . await ;
107
+ // Ensure that all buffered bytes are written
108
+ dst_file. flush ( ) . await ? ;
139
109
140
110
let blob = BlobObject {
141
111
blobdir : context. get_blobdir ( ) ,
@@ -158,7 +128,7 @@ impl<'a> BlobObject<'a> {
158
128
pub fn create_and_deduplicate (
159
129
context : & ' a Context ,
160
130
src : & Path ,
161
- original_name : & str ,
131
+ original_name : & Path ,
162
132
) -> Result < BlobObject < ' a > > {
163
133
// `create_and_deduplicate{_from_bytes}()` do blocking I/O, but can still be called
164
134
// from an async context thanks to `block_in_place()`.
@@ -188,17 +158,15 @@ impl<'a> BlobObject<'a> {
188
158
let hash = file_hash ( src_in_blobdir) ?. to_hex ( ) ;
189
159
let hash = hash. as_str ( ) ;
190
160
let hash = hash. get ( 0 ..31 ) . unwrap_or ( hash) ;
191
- let new_file = if let Some ( extension) = Path :: new ( original_name)
192
- . extension ( )
193
- . filter ( |e| e. len ( ) <= 32 )
194
- {
195
- format ! (
196
- "$BLOBDIR/{hash}.{}" ,
197
- extension. to_string_lossy( ) . to_lowercase( )
198
- )
199
- } else {
200
- format ! ( "$BLOBDIR/{hash}" )
201
- } ;
161
+ let new_file =
162
+ if let Some ( extension) = original_name. extension ( ) . filter ( |e| e. len ( ) <= 32 ) {
163
+ format ! (
164
+ "$BLOBDIR/{hash}.{}" ,
165
+ extension. to_string_lossy( ) . to_lowercase( )
166
+ )
167
+ } else {
168
+ format ! ( "$BLOBDIR/{hash}" )
169
+ } ;
202
170
203
171
let blob = BlobObject {
204
172
blobdir,
@@ -238,7 +206,7 @@ impl<'a> BlobObject<'a> {
238
206
std:: fs:: write ( & temp_path, data) . context ( "writing new blobfile failed" ) ?;
239
207
} ;
240
208
241
- BlobObject :: create_and_deduplicate ( context, & temp_path, original_name)
209
+ BlobObject :: create_and_deduplicate ( context, & temp_path, Path :: new ( original_name) )
242
210
} )
243
211
}
244
212
@@ -902,21 +870,26 @@ mod tests {
902
870
} )
903
871
}
904
872
873
+ const FILE_BYTES : & [ u8 ] = b"hello" ;
874
+ const FILE_DEDUPLICATED : & str = "ea8f163db38682925e4491c5e58d4bb.txt" ;
875
+
905
876
#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
906
877
async fn test_create ( ) {
907
878
let t = TestContext :: new ( ) . await ;
908
- let blob = BlobObject :: create ( & t, "foo" , b"hello" ) . await . unwrap ( ) ;
909
- let fname = t. get_blobdir ( ) . join ( "foo" ) ;
879
+ let blob =
880
+ BlobObject :: create_and_deduplicate_from_bytes ( & t, FILE_BYTES , "foo.txt" ) . unwrap ( ) ;
881
+ let fname = t. get_blobdir ( ) . join ( FILE_DEDUPLICATED ) ;
910
882
let data = fs:: read ( fname) . await . unwrap ( ) ;
911
- assert_eq ! ( data, b"hello" ) ;
912
- assert_eq ! ( blob. as_name( ) , "$BLOBDIR/foo" ) ;
913
- assert_eq ! ( blob. to_abs_path( ) , t. get_blobdir( ) . join( "foo" ) ) ;
883
+ assert_eq ! ( data, FILE_BYTES ) ;
884
+ assert_eq ! ( blob. as_name( ) , format! ( "$BLOBDIR/{FILE_DEDUPLICATED}" ) ) ;
885
+ assert_eq ! ( blob. to_abs_path( ) , t. get_blobdir( ) . join( FILE_DEDUPLICATED ) ) ;
914
886
}
915
887
916
888
#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
917
889
async fn test_lowercase_ext ( ) {
918
890
let t = TestContext :: new ( ) . await ;
919
- let blob = BlobObject :: create_and_deduplicate_from_bytes ( & t, b"hello" , "foo.TXT" ) . unwrap ( ) ;
891
+ let blob =
892
+ BlobObject :: create_and_deduplicate_from_bytes ( & t, FILE_BYTES , "foo.TXT" ) . unwrap ( ) ;
920
893
assert ! (
921
894
blob. as_name( ) . ends_with( ".txt" ) ,
922
895
"Blob {blob:?} should end with .txt"
@@ -926,70 +899,66 @@ mod tests {
926
899
#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
927
900
async fn test_as_file_name ( ) {
928
901
let t = TestContext :: new ( ) . await ;
929
- let blob = BlobObject :: create_and_deduplicate_from_bytes ( & t, b"hello" , "foo.txt" ) . unwrap ( ) ;
930
- assert_eq ! ( blob. as_file_name( ) , "ea8f163db38682925e4491c5e58d4bb.txt" ) ;
902
+ let blob =
903
+ BlobObject :: create_and_deduplicate_from_bytes ( & t, FILE_BYTES , "foo.txt" ) . unwrap ( ) ;
904
+ assert_eq ! ( blob. as_file_name( ) , FILE_DEDUPLICATED ) ;
931
905
}
932
906
933
907
#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
934
908
async fn test_as_rel_path ( ) {
935
909
let t = TestContext :: new ( ) . await ;
936
- let blob = BlobObject :: create_and_deduplicate_from_bytes ( & t, b"hello" , "foo.txt" ) . unwrap ( ) ;
937
- assert_eq ! (
938
- blob. as_rel_path( ) ,
939
- Path :: new( "ea8f163db38682925e4491c5e58d4bb.txt" )
940
- ) ;
910
+ let blob =
911
+ BlobObject :: create_and_deduplicate_from_bytes ( & t, FILE_BYTES , "foo.txt" ) . unwrap ( ) ;
912
+ assert_eq ! ( blob. as_rel_path( ) , Path :: new( FILE_DEDUPLICATED ) ) ;
941
913
}
942
914
943
915
#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
944
916
async fn test_suffix ( ) {
945
917
let t = TestContext :: new ( ) . await ;
946
- let blob = BlobObject :: create ( & t, "foo.txt" , b"hello" ) . await . unwrap ( ) ;
918
+ let blob =
919
+ BlobObject :: create_and_deduplicate_from_bytes ( & t, FILE_BYTES , "foo.txt" ) . unwrap ( ) ;
947
920
assert_eq ! ( blob. suffix( ) , Some ( "txt" ) ) ;
948
- let blob = BlobObject :: create ( & t, "bar" , b"world" ) . await . unwrap ( ) ;
921
+ let blob = BlobObject :: create_and_deduplicate_from_bytes ( & t, FILE_BYTES , "bar" ) . unwrap ( ) ;
949
922
assert_eq ! ( blob. suffix( ) , None ) ;
950
923
}
951
924
952
925
#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
953
926
async fn test_create_dup ( ) {
954
927
let t = TestContext :: new ( ) . await ;
955
- BlobObject :: create ( & t, "foo.txt" , b"hello" ) . await . unwrap ( ) ;
956
- let foo_path = t. get_blobdir ( ) . join ( "foo.txt" ) ;
928
+ BlobObject :: create_and_deduplicate_from_bytes ( & t, FILE_BYTES , "foo.txt" ) . unwrap ( ) ;
929
+ let foo_path = t. get_blobdir ( ) . join ( FILE_DEDUPLICATED ) ;
957
930
assert ! ( foo_path. exists( ) ) ;
958
- BlobObject :: create ( & t, "foo.txt ", b"world" ) . await . unwrap ( ) ;
931
+ BlobObject :: create_and_deduplicate_from_bytes ( & t, b"world ", "foo.txt" ) . unwrap ( ) ;
959
932
let mut dir = fs:: read_dir ( t. get_blobdir ( ) ) . await . unwrap ( ) ;
960
933
while let Ok ( Some ( dirent) ) = dir. next_entry ( ) . await {
961
934
let fname = dirent. file_name ( ) ;
962
935
if fname == foo_path. file_name ( ) . unwrap ( ) {
963
- assert_eq ! ( fs:: read( & foo_path) . await . unwrap( ) , b"hello" ) ;
936
+ assert_eq ! ( fs:: read( & foo_path) . await . unwrap( ) , FILE_BYTES ) ;
964
937
} else {
965
938
let name = fname. to_str ( ) . unwrap ( ) ;
966
- assert ! ( name. starts_with( "foo" ) ) ;
967
939
assert ! ( name. ends_with( ".txt" ) ) ;
968
940
}
969
941
}
970
942
}
971
943
972
944
#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
973
- async fn test_double_ext_preserved ( ) {
945
+ async fn test_double_ext ( ) {
974
946
let t = TestContext :: new ( ) . await ;
975
- BlobObject :: create ( & t, "foo.tar.gz" , b"hello" )
976
- . await
977
- . unwrap ( ) ;
978
- let foo_path = t. get_blobdir ( ) . join ( "foo.tar.gz" ) ;
947
+ BlobObject :: create_and_deduplicate_from_bytes ( & t, FILE_BYTES , "foo.tar.gz" ) . unwrap ( ) ;
948
+ let foo_path = t. get_blobdir ( ) . join ( FILE_DEDUPLICATED ) . with_extension ( "gz" ) ;
979
949
assert ! ( foo_path. exists( ) ) ;
980
- BlobObject :: create ( & t, "foo.tar.gz" , b"world" )
981
- . await
982
- . unwrap ( ) ;
950
+ BlobObject :: create_and_deduplicate_from_bytes ( & t, b"world" , "foo.tar.gz" ) . unwrap ( ) ;
983
951
let mut dir = fs:: read_dir ( t. get_blobdir ( ) ) . await . unwrap ( ) ;
984
952
while let Ok ( Some ( dirent) ) = dir. next_entry ( ) . await {
985
953
let fname = dirent. file_name ( ) ;
986
954
if fname == foo_path. file_name ( ) . unwrap ( ) {
987
- assert_eq ! ( fs:: read( & foo_path) . await . unwrap( ) , b"hello" ) ;
955
+ assert_eq ! ( fs:: read( & foo_path) . await . unwrap( ) , FILE_BYTES ) ;
988
956
} else {
989
957
let name = fname. to_str ( ) . unwrap ( ) ;
990
958
println ! ( "{name}" ) ;
991
- assert ! ( name. starts_with( "foo" ) ) ;
992
- assert ! ( name. ends_with( ".tar.gz" ) ) ;
959
+ assert_eq ! ( name. starts_with( "foo" ) , false ) ;
960
+ assert_eq ! ( name. ends_with( ".tar.gz" ) , false ) ;
961
+ assert ! ( name. ends_with( ".gz" ) ) ;
993
962
}
994
963
}
995
964
}
@@ -1635,28 +1604,30 @@ mod tests {
1635
1604
1636
1605
let path = t. get_blobdir ( ) . join ( "anyfile.dat" ) ;
1637
1606
fs:: write ( & path, b"bla" ) . await ?;
1638
- let blob = BlobObject :: create_and_deduplicate ( & t, & path, "anyfile.dat" ) ?;
1607
+ let blob = BlobObject :: create_and_deduplicate ( & t, & path, & path ) ?;
1639
1608
assert_eq ! ( blob. name, "$BLOBDIR/ce940175885d7b78f7b7e9f1396611f.dat" ) ;
1640
1609
assert_eq ! ( path. exists( ) , false ) ;
1641
1610
1642
1611
assert_eq ! ( fs:: read( & blob. to_abs_path( ) ) . await ?, b"bla" ) ;
1643
1612
1644
1613
fs:: write ( & path, b"bla" ) . await ?;
1645
- let blob2 = BlobObject :: create_and_deduplicate ( & t, & path, "anyfile.dat" ) ?;
1614
+ let blob2 = BlobObject :: create_and_deduplicate ( & t, & path, & path ) ?;
1646
1615
assert_eq ! ( blob2. name, blob. name) ;
1647
1616
1648
1617
let path_outside_blobdir = t. dir . path ( ) . join ( "anyfile.dat" ) ;
1649
1618
fs:: write ( & path_outside_blobdir, b"bla" ) . await ?;
1650
- let blob3 = BlobObject :: create_and_deduplicate ( & t, & path_outside_blobdir, "anyfile.dat" ) ?;
1619
+ let blob3 =
1620
+ BlobObject :: create_and_deduplicate ( & t, & path_outside_blobdir, & path_outside_blobdir) ?;
1651
1621
assert ! ( path_outside_blobdir. exists( ) ) ;
1652
1622
assert_eq ! ( blob3. name, blob. name) ;
1653
1623
1654
1624
fs:: write ( & path, b"blabla" ) . await ?;
1655
- let blob4 = BlobObject :: create_and_deduplicate ( & t, & path, "anyfile.dat" ) ?;
1625
+ let blob4 = BlobObject :: create_and_deduplicate ( & t, & path, & path ) ?;
1656
1626
assert_ne ! ( blob4. name, blob. name) ;
1657
1627
1658
1628
fs:: remove_dir_all ( t. get_blobdir ( ) ) . await ?;
1659
- let blob5 = BlobObject :: create_and_deduplicate ( & t, & path_outside_blobdir, "anyfile.dat" ) ?;
1629
+ let blob5 =
1630
+ BlobObject :: create_and_deduplicate ( & t, & path_outside_blobdir, & path_outside_blobdir) ?;
1660
1631
assert_eq ! ( blob5. name, blob. name) ;
1661
1632
1662
1633
Ok ( ( ) )
0 commit comments