Skip to content

Commit b0c8d46

Browse files
authored
refactor: Remove BlobObject::create(), use create_and_deduplicate_from_bytes() instead (#6467)
Part of #6332
1 parent 6430977 commit b0c8d46

File tree

2 files changed

+55
-84
lines changed

2 files changed

+55
-84
lines changed

src/blob.rs

Lines changed: 54 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -48,36 +48,6 @@ enum ImageOutputFormat {
4848
}
4949

5050
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-
8151
/// Creates a new file, returning a tuple of the name and the handle.
8252
async fn create_new_file(
8353
context: &Context,
@@ -115,8 +85,8 @@ impl<'a> BlobObject<'a> {
11585

11686
/// Creates a new blob object with unique name by copying an existing file.
11787
///
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
12090
/// in way which avoids race-conditions when multiple files are
12191
/// concurrently created.
12292
pub async fn create_and_copy(context: &'a Context, src: &Path) -> Result<BlobObject<'a>> {
@@ -134,8 +104,8 @@ impl<'a> BlobObject<'a> {
134104
return Err(err).context("failed to copy file");
135105
}
136106

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?;
139109

140110
let blob = BlobObject {
141111
blobdir: context.get_blobdir(),
@@ -158,7 +128,7 @@ impl<'a> BlobObject<'a> {
158128
pub fn create_and_deduplicate(
159129
context: &'a Context,
160130
src: &Path,
161-
original_name: &str,
131+
original_name: &Path,
162132
) -> Result<BlobObject<'a>> {
163133
// `create_and_deduplicate{_from_bytes}()` do blocking I/O, but can still be called
164134
// from an async context thanks to `block_in_place()`.
@@ -188,17 +158,15 @@ impl<'a> BlobObject<'a> {
188158
let hash = file_hash(src_in_blobdir)?.to_hex();
189159
let hash = hash.as_str();
190160
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+
};
202170

203171
let blob = BlobObject {
204172
blobdir,
@@ -238,7 +206,7 @@ impl<'a> BlobObject<'a> {
238206
std::fs::write(&temp_path, data).context("writing new blobfile failed")?;
239207
};
240208

241-
BlobObject::create_and_deduplicate(context, &temp_path, original_name)
209+
BlobObject::create_and_deduplicate(context, &temp_path, Path::new(original_name))
242210
})
243211
}
244212

@@ -902,21 +870,26 @@ mod tests {
902870
})
903871
}
904872

873+
const FILE_BYTES: &[u8] = b"hello";
874+
const FILE_DEDUPLICATED: &str = "ea8f163db38682925e4491c5e58d4bb.txt";
875+
905876
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
906877
async fn test_create() {
907878
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);
910882
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));
914886
}
915887

916888
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
917889
async fn test_lowercase_ext() {
918890
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();
920893
assert!(
921894
blob.as_name().ends_with(".txt"),
922895
"Blob {blob:?} should end with .txt"
@@ -926,70 +899,66 @@ mod tests {
926899
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
927900
async fn test_as_file_name() {
928901
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);
931905
}
932906

933907
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
934908
async fn test_as_rel_path() {
935909
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));
941913
}
942914

943915
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
944916
async fn test_suffix() {
945917
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();
947920
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();
949922
assert_eq!(blob.suffix(), None);
950923
}
951924

952925
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
953926
async fn test_create_dup() {
954927
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);
957930
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();
959932
let mut dir = fs::read_dir(t.get_blobdir()).await.unwrap();
960933
while let Ok(Some(dirent)) = dir.next_entry().await {
961934
let fname = dirent.file_name();
962935
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);
964937
} else {
965938
let name = fname.to_str().unwrap();
966-
assert!(name.starts_with("foo"));
967939
assert!(name.ends_with(".txt"));
968940
}
969941
}
970942
}
971943

972944
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
973-
async fn test_double_ext_preserved() {
945+
async fn test_double_ext() {
974946
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");
979949
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();
983951
let mut dir = fs::read_dir(t.get_blobdir()).await.unwrap();
984952
while let Ok(Some(dirent)) = dir.next_entry().await {
985953
let fname = dirent.file_name();
986954
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);
988956
} else {
989957
let name = fname.to_str().unwrap();
990958
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"));
993962
}
994963
}
995964
}
@@ -1635,28 +1604,30 @@ mod tests {
16351604

16361605
let path = t.get_blobdir().join("anyfile.dat");
16371606
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)?;
16391608
assert_eq!(blob.name, "$BLOBDIR/ce940175885d7b78f7b7e9f1396611f.dat");
16401609
assert_eq!(path.exists(), false);
16411610

16421611
assert_eq!(fs::read(&blob.to_abs_path()).await?, b"bla");
16431612

16441613
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)?;
16461615
assert_eq!(blob2.name, blob.name);
16471616

16481617
let path_outside_blobdir = t.dir.path().join("anyfile.dat");
16491618
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)?;
16511621
assert!(path_outside_blobdir.exists());
16521622
assert_eq!(blob3.name, blob.name);
16531623

16541624
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)?;
16561626
assert_ne!(blob4.name, blob.name);
16571627

16581628
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)?;
16601631
assert_eq!(blob5.name, blob.name);
16611632

16621633
Ok(())

src/message.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1114,7 +1114,7 @@ impl Message {
11141114
.unwrap_or_else(|| "unknown_file".to_string())
11151115
};
11161116

1117-
let blob = BlobObject::create_and_deduplicate(context, file, &name)?;
1117+
let blob = BlobObject::create_and_deduplicate(context, file, Path::new(&name))?;
11181118
self.param.set(Param::File, blob.as_name());
11191119

11201120
self.param.set(Param::Filename, name);

0 commit comments

Comments
 (0)