Skip to content

Commit bfd028d

Browse files
committed
Make Drive deal with bytes
A storage drive shouldn't be restricted to containing text files, and in fact, we need to support binary files if we want to store images. Switch the Drive interface to expose byte streams instead to support them.
1 parent ffe88c5 commit bfd028d

File tree

11 files changed

+101
-79
lines changed

11 files changed

+101
-79
lines changed

client/src/cmds.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,7 @@ mod tests {
901901
#[tokio::test]
902902
async fn test_share_print_no_acls() {
903903
let mut t = ClientTester::default();
904-
t.get_storage().borrow_mut().put("MEMORY:/FOO", "").await.unwrap();
904+
t.get_storage().borrow_mut().put("MEMORY:/FOO", b"").await.unwrap();
905905
t.run(r#"SHARE "MEMORY:/FOO""#)
906906
.expect_prints(["", " No ACLs on MEMORY:/FOO", ""])
907907
.expect_file("MEMORY:/FOO", "")
@@ -914,7 +914,7 @@ mod tests {
914914
{
915915
let storage = t.get_storage();
916916
let mut storage = storage.borrow_mut();
917-
storage.put("MEMORY:/FOO", "").await.unwrap();
917+
storage.put("MEMORY:/FOO", b"").await.unwrap();
918918
storage
919919
.update_acls(
920920
"MEMORY:/FOO",
@@ -933,7 +933,7 @@ mod tests {
933933
#[tokio::test]
934934
async fn test_share_make_public() {
935935
let mut t = ClientTester::default();
936-
t.get_storage().borrow_mut().put("MEMORY:/FOO.BAS", "").await.unwrap();
936+
t.get_storage().borrow_mut().put("MEMORY:/FOO.BAS", b"").await.unwrap();
937937
t.get_service().borrow_mut().do_login().await;
938938
let mut checker = t.run(r#"SHARE "MEMORY:/FOO.BAS", "Public+r""#);
939939
let output = flatten_output(checker.take_captured_out());

client/src/drive.rs

+15-18
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,12 @@ impl Drive for CloudDrive {
6161
))
6262
}
6363

64-
async fn get(&self, filename: &str) -> io::Result<String> {
64+
async fn get(&self, filename: &str) -> io::Result<Vec<u8>> {
6565
let request = GetFileRequest::default().with_get_content();
6666
let response =
6767
self.service.borrow_mut().get_file(&self.username, filename, &request).await?;
6868
match response.decoded_content()? {
69-
Some(content) => match String::from_utf8(content) {
70-
Ok(s) => Ok(s),
71-
Err(e) => Err(io::Error::new(
72-
io::ErrorKind::InvalidData,
73-
format!("Requested file is not valid UTF-8: {}", e),
74-
)),
75-
},
69+
Some(content) => Ok(content),
7670
None => Err(io::Error::new(
7771
io::ErrorKind::InvalidData,
7872
"Server response is missing the file content".to_string(),
@@ -93,8 +87,8 @@ impl Drive for CloudDrive {
9387
}
9488
}
9589

96-
async fn put(&mut self, filename: &str, content: &str) -> io::Result<()> {
97-
let request = PatchFileRequest::default().with_content(content.as_bytes());
90+
async fn put(&mut self, filename: &str, content: &[u8]) -> io::Result<()> {
91+
let request = PatchFileRequest::default().with_content(content);
9892
self.service.borrow_mut().patch_file(&self.username, filename, &request).await
9993
}
10094

@@ -214,7 +208,7 @@ mod tests {
214208
};
215209
service.borrow_mut().add_mock_get_file("the-user", "the-filename", request, Ok(response));
216210
let result = drive.get("the-filename").await.unwrap();
217-
assert_eq!("some content", result);
211+
assert_eq!("some content".as_bytes(), result);
218212

219213
service.take().verify_all_used();
220214
}
@@ -235,21 +229,24 @@ mod tests {
235229
service.take().verify_all_used();
236230
}
237231

232+
#[allow(invalid_from_utf8)]
238233
#[tokio::test]
239234
async fn test_clouddrive_get_invalid_utf8() {
235+
const BAD_UTF8: &[u8] = &[0x00, 0xc3, 0x28];
236+
assert!(str::from_utf8(BAD_UTF8).is_err());
237+
240238
let service = Rc::from(RefCell::from(MockService::default()));
241239
service.borrow_mut().do_login().await;
242240
let drive = CloudDrive::new(service.clone(), "the-user");
243241

244242
let request = GetFileRequest::default().with_get_content();
245243
let response = GetFileResponse {
246-
content: Some(BASE64_STANDARD.encode([0x00, 0xc3, 0x28])),
244+
content: Some(BASE64_STANDARD.encode(BAD_UTF8)),
247245
..Default::default()
248246
};
249247
service.borrow_mut().add_mock_get_file("the-user", "the-filename", request, Ok(response));
250-
let err = drive.get("the-filename").await.unwrap_err();
251-
assert_eq!(io::ErrorKind::InvalidData, err.kind());
252-
assert!(format!("{}", err).contains("not valid UTF-8"));
248+
let result = drive.get("the-filename").await.unwrap();
249+
assert_eq!(BAD_UTF8, result);
253250

254251
service.take().verify_all_used();
255252
}
@@ -296,7 +293,7 @@ mod tests {
296293

297294
let request = PatchFileRequest::default().with_content("some content");
298295
service.borrow_mut().add_mock_patch_file("the-user", "the-filename", request, Ok(()));
299-
drive.put("the-filename", "some content").await.unwrap();
296+
drive.put("the-filename", b"some content").await.unwrap();
300297

301298
service.take().verify_all_used();
302299
}
@@ -309,11 +306,11 @@ mod tests {
309306

310307
let request = PatchFileRequest::default().with_content("some content");
311308
service.borrow_mut().add_mock_patch_file("the-user", "the-filename", request, Ok(()));
312-
drive.put("the-filename", "some content").await.unwrap();
309+
drive.put("the-filename", b"some content").await.unwrap();
313310

314311
let request = PatchFileRequest::default().with_content("some other content");
315312
service.borrow_mut().add_mock_patch_file("the-user", "the-filename", request, Ok(()));
316-
drive.put("the-filename", "some other content").await.unwrap();
313+
drive.put("the-filename", b"some other content").await.unwrap();
317314

318315
service.take().verify_all_used();
319316
}

repl/src/demos.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,18 @@ impl Drive for DemosDrive {
125125
Ok(DriveFiles::new(entries, disk_quota, disk_free))
126126
}
127127

128-
async fn get(&self, name: &str) -> io::Result<String> {
128+
async fn get(&self, name: &str) -> io::Result<Vec<u8>> {
129129
let uc_name = name.to_ascii_uppercase();
130130
match self.demos.get(&uc_name.as_ref()) {
131131
Some(value) => {
132132
let (_metadata, content) = value;
133-
Ok(content.to_string())
133+
Ok(content.as_bytes().to_owned())
134134
}
135135
None => Err(io::Error::new(io::ErrorKind::NotFound, "Demo not found")),
136136
}
137137
}
138138

139-
async fn put(&mut self, _name: &str, _content: &str) -> io::Result<()> {
139+
async fn put(&mut self, _name: &str, _content: &[u8]) -> io::Result<()> {
140140
Err(io::Error::new(io::ErrorKind::PermissionDenied, "The demos drive is read-only"))
141141
}
142142
}
@@ -206,12 +206,12 @@ mod tests {
206206
assert_eq!(io::ErrorKind::NotFound, block_on(drive.get("unknown.bas")).unwrap_err().kind());
207207

208208
assert_eq!(
209-
process_demo(include_bytes!("../examples/hello.bas")),
210-
block_on(drive.get("hello.bas")).unwrap()
209+
process_demo(include_bytes!("../examples/hello.bas")).as_bytes(),
210+
block_on(drive.get("hello.bas")).unwrap().as_slice()
211211
);
212212
assert_eq!(
213-
process_demo(include_bytes!("../examples/hello.bas")),
214-
block_on(drive.get("Hello.Bas")).unwrap()
213+
process_demo(include_bytes!("../examples/hello.bas")).as_bytes(),
214+
block_on(drive.get("Hello.Bas")).unwrap().as_slice()
215215
);
216216
}
217217

@@ -221,16 +221,16 @@ mod tests {
221221

222222
assert_eq!(
223223
io::ErrorKind::PermissionDenied,
224-
block_on(drive.put("hello.bas", "")).unwrap_err().kind()
224+
block_on(drive.put("hello.bas", b"")).unwrap_err().kind()
225225
);
226226
assert_eq!(
227227
io::ErrorKind::PermissionDenied,
228-
block_on(drive.put("Hello.BAS", "")).unwrap_err().kind()
228+
block_on(drive.put("Hello.BAS", b"")).unwrap_err().kind()
229229
);
230230

231231
assert_eq!(
232232
io::ErrorKind::PermissionDenied,
233-
block_on(drive.put("unknown.bas", "")).unwrap_err().kind()
233+
block_on(drive.put("unknown.bas", b"")).unwrap_err().kind()
234234
);
235235
}
236236

repl/src/lib.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ pub async fn try_load_autoexec(
7070
}
7171
};
7272

73-
match machine.exec(&mut code.as_bytes()).await {
73+
match machine.exec(&mut code.as_slice()).await {
7474
Ok(_) => Ok(()),
7575
Err(e) => {
7676
console.borrow_mut().print(&format!("AUTOEXEC.BAS failed: {}", e))?;
@@ -107,6 +107,14 @@ pub async fn run_from_cloud(
107107

108108
console.borrow_mut().print(&format!("Loading {}...", path))?;
109109
let content = storage.borrow().get(&path).await?;
110+
let content = match String::from_utf8(content) {
111+
Ok(text) => text,
112+
Err(e) => {
113+
let mut console = console.borrow_mut();
114+
console.print(&format!("Invalid program to run '{}': {}", path, e))?;
115+
return Ok(1);
116+
}
117+
};
110118
program.borrow_mut().load(Some(&path), &content);
111119

112120
console.borrow_mut().print("Starting...")?;
@@ -360,7 +368,7 @@ mod tests {
360368
impl DriveFactory for MockDriveFactory {
361369
fn create(&self, target: &str) -> io::Result<Box<dyn Drive>> {
362370
let mut drive = InMemoryDrive::default();
363-
block_on(drive.put(self.exp_file, Self::SCRIPT)).unwrap();
371+
block_on(drive.put(self.exp_file, Self::SCRIPT.as_bytes())).unwrap();
364372
assert_eq!(self.exp_username, target);
365373
Ok(Box::from(drive))
366374
}

std/src/program.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,15 @@ impl Callable for LoadCommand {
394394
.make_canonical_with_extension(&pathname, DEFAULT_EXTENSION)
395395
.map_err(|e| scope.io_error(e))?;
396396
let content = storage.get(&full_name).await.map_err(|e| scope.io_error(e))?;
397+
let content = match String::from_utf8(content) {
398+
Ok(text) => text,
399+
Err(e) => {
400+
return Err(scope.io_error(io::Error::new(
401+
io::ErrorKind::InvalidData,
402+
format!("Invalid file content: {}", e),
403+
)));
404+
}
405+
};
397406
(full_name, content)
398407
};
399408
self.program.borrow_mut().load(Some(&full_name), &content);
@@ -593,7 +602,11 @@ impl Callable for SaveCommand {
593602
.make_canonical_with_extension(&name, DEFAULT_EXTENSION)
594603
.map_err(|e| scope.io_error(e))?;
595604
let content = self.program.borrow().text();
596-
self.storage.borrow_mut().put(&full_name, &content).await.map_err(|e| scope.io_error(e))?;
605+
self.storage
606+
.borrow_mut()
607+
.put(&full_name, content.as_bytes())
608+
.await
609+
.map_err(|e| scope.io_error(e))?;
597610
self.program.borrow_mut().set_name(&full_name);
598611

599612
self.console

std/src/storage/cmds.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ mod tests {
503503
#[test]
504504
fn test_dir_other_by_argument() {
505505
let mut other = InMemoryDrive::default();
506-
block_on(other.put("foo.bas", "hello")).unwrap();
506+
block_on(other.put("foo.bas", b"hello")).unwrap();
507507

508508
let mut t = Tester::default().write_file("empty.bas", "");
509509
t.get_storage().borrow_mut().attach("other", "z://", Box::from(other)).unwrap();
@@ -544,7 +544,7 @@ mod tests {
544544
#[test]
545545
fn test_dir_other_by_cwd() {
546546
let mut other = InMemoryDrive::default();
547-
block_on(other.put("foo.bas", "hello")).unwrap();
547+
block_on(other.put("foo.bas", b"hello")).unwrap();
548548

549549
let mut t = Tester::default().write_file("empty.bas", "");
550550
t.get_storage().borrow_mut().attach("other", "z://", Box::from(other)).unwrap();

std/src/storage/fs.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,18 @@ impl Drive for DirectoryDrive {
9898
Ok(DriveFiles::new(entries, None, None))
9999
}
100100

101-
async fn get(&self, name: &str) -> io::Result<String> {
101+
async fn get(&self, name: &str) -> io::Result<Vec<u8>> {
102102
let path = self.dir.join(name);
103103
let input = File::open(path)?;
104-
let mut content = String::new();
105-
io::BufReader::new(input).read_to_string(&mut content)?;
104+
let mut content = vec![];
105+
io::BufReader::new(input).read_to_end(&mut content)?;
106106
Ok(content)
107107
}
108108

109-
async fn put(&mut self, name: &str, content: &str) -> io::Result<()> {
109+
async fn put(&mut self, name: &str, content: &[u8]) -> io::Result<()> {
110110
let path = self.dir.join(name);
111111
let mut output = OpenOptions::new().create(true).write(true).truncate(true).open(path)?;
112-
output.write_all(content.as_bytes())?;
112+
output.write_all(content)?;
113113
output.sync_all()
114114
}
115115

@@ -266,15 +266,18 @@ mod tests {
266266
write_file(&dir.path().join("some file.bas"), &["one line", "two lines"]);
267267

268268
let drive = DirectoryDrive::new(dir.path()).unwrap();
269-
assert_eq!("one line\ntwo lines\n", block_on(drive.get("some file.bas")).unwrap());
269+
assert_eq!(
270+
b"one line\ntwo lines\n",
271+
block_on(drive.get("some file.bas")).unwrap().as_slice()
272+
);
270273
}
271274

272275
#[test]
273276
fn test_directorydrive_put() {
274277
let dir = tempfile::tempdir().unwrap();
275278

276279
let mut drive = DirectoryDrive::new(dir.path()).unwrap();
277-
block_on(drive.put("some file.bas", "a b c\nd e\n")).unwrap();
280+
block_on(drive.put("some file.bas", b"a b c\nd e\n")).unwrap();
278281
check_file(&dir.path().join("some file.bas"), &["a b c", "d e"]);
279282
}
280283

std/src/storage/mem.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use std::str;
2424
/// A drive that records all data in memory only.
2525
#[derive(Default)]
2626
pub struct InMemoryDrive {
27-
programs: HashMap<String, (String, HashSet<String>)>,
27+
programs: HashMap<String, (Vec<u8>, HashSet<String>)>,
2828

2929
// TODO(jmmv): These fields are currently exposed only to allow testing for the consumers of
3030
// these details and are not enforced in the drive. It might be nice to actually implement
@@ -52,7 +52,7 @@ impl Drive for InMemoryDrive {
5252
Ok(DriveFiles::new(entries, self.fake_disk_quota, self.fake_disk_free))
5353
}
5454

55-
async fn get(&self, name: &str) -> io::Result<String> {
55+
async fn get(&self, name: &str) -> io::Result<Vec<u8>> {
5656
match self.programs.get(name) {
5757
Some((content, _readers)) => Ok(content.to_owned()),
5858
None => Err(io::Error::new(io::ErrorKind::NotFound, "Entry not found")),
@@ -72,7 +72,7 @@ impl Drive for InMemoryDrive {
7272
}
7373
}
7474

75-
async fn put(&mut self, name: &str, content: &str) -> io::Result<()> {
75+
async fn put(&mut self, name: &str, content: &[u8]) -> io::Result<()> {
7676
if let Some((prev_content, _readers)) = self.programs.get_mut(name) {
7777
content.clone_into(prev_content);
7878
return Ok(());
@@ -130,30 +130,30 @@ mod tests {
130130
#[tokio::test]
131131
async fn test_inmemorydrive_put_respects_acls() {
132132
let mut drive = InMemoryDrive::default();
133-
drive.put("untouched", "some content").await.unwrap();
133+
drive.put("untouched", b"some content").await.unwrap();
134134

135-
drive.put("file", "before").await.unwrap();
135+
drive.put("file", b"before").await.unwrap();
136136
drive.update_acls("file", &readers(&["r1"]), &FileAcls::default()).await.unwrap();
137137

138-
assert_eq!("before", drive.get("file").await.unwrap());
138+
assert_eq!(b"before", drive.get("file").await.unwrap().as_slice());
139139
assert_eq!(readers(&["r1"]), drive.get_acls("file").await.unwrap());
140140

141-
drive.put("file", "after").await.unwrap();
141+
drive.put("file", b"after").await.unwrap();
142142

143-
assert_eq!("after", drive.get("file").await.unwrap());
143+
assert_eq!(b"after", drive.get("file").await.unwrap().as_slice());
144144
assert_eq!(readers(&["r1"]), drive.get_acls("file").await.unwrap());
145145
}
146146

147147
#[tokio::test]
148148
async fn test_inmemorydrive_get_update_acls() {
149149
let mut drive = InMemoryDrive::default();
150-
drive.put("untouched", "some content").await.unwrap();
150+
drive.put("untouched", b"some content").await.unwrap();
151151

152152
let err =
153153
drive.update_acls("file", &readers(&["r0"]), &FileAcls::default()).await.unwrap_err();
154154
assert_eq!(io::ErrorKind::NotFound, err.kind());
155155

156-
drive.put("file", "some content").await.unwrap();
156+
drive.put("file", b"some content").await.unwrap();
157157
assert_eq!(FileAcls::default(), drive.get_acls("file").await.unwrap());
158158

159159
// Add some new readers and try to remove a non-existing one.

0 commit comments

Comments
 (0)