Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for reading files to file-explorer #47

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 65 additions & 25 deletions ctru-rs/examples/file-explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use ctru::services::hid::KeyPad;
use ctru::services::{Apt, Hid};
use ctru::Gfx;
use std::fs::DirEntry;
use std::os::horizon::fs::MetadataExt;
use std::path::{Path, PathBuf};

fn main() {
Expand Down Expand Up @@ -74,8 +75,37 @@ impl<'a> FileExplorer<'a> {
}

fn print_menu(&mut self) {
println!("Viewing {}", self.path.display());
match std::fs::metadata(&self.path) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this error after reading romfs:/test-file.txt:

Failed to read romfs:: Illegal byte sequence (os error 138)

This is probably due to the weird path handling? Maybe we should handle this case in the code until the underlying path issue is fixed?

Copy link
Member Author

@ian-h-chamberlain ian-h-chamberlain Feb 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is because of Meziu/rust-horizon#13 as far as I know. I didn't want it to block this PR because I think it was an issue before but got more exposed by this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not blocking, but maybe we can do something like in run with self.path.components().count() > 1 to avoid popping the path in this case. Or maybe we just need to reset it manually. Up to you.

Ok(metadata) => {
println!(
"Viewing {} (size {} bytes, mode {:#o})",
self.path.display(),
metadata.len(),
metadata.st_mode(),
);

if metadata.is_file() {
self.print_file_contents();
// let the user continue navigating from the parent dir
// after dumping the file
self.path.pop();
self.print_menu();
return;
} else if metadata.is_dir() {
self.print_dir_entries();
} else {
println!("unsupported file type: {:?}", metadata.file_type());
}
}
Err(e) => {
println!("Failed to read {}: {}", self.path.display(), e)
}
};

println!("Start to exit, A to select an entry by number, B to go up a directory, X to set the path.");
}

fn print_dir_entries(&mut self) {
let dir_listing = std::fs::read_dir(&self.path).expect("Failed to open path");
self.entries = Vec::new();

Expand All @@ -85,34 +115,49 @@ impl<'a> FileExplorer<'a> {
println!("{:2} - {}", i, entry.file_name().to_string_lossy());
self.entries.push(entry);

// Paginate the output
if (i + 1) % 20 == 0 {
println!("Press A to go to next page, or Start to exit");

while self.apt.main_loop() {
self.hid.scan_input();
let input = self.hid.keys_down();

if input.contains(KeyPad::KEY_A) {
break;
}

if input.contains(KeyPad::KEY_START) {
self.running = false;
return;
}

self.gfx.wait_for_vblank();
}
self.wait_for_page_down();
}
}
Err(e) => {
println!("{} - Error: {}", i, e);
}
}
}
}

println!("Start to exit, A to select an entry by number, B to go up a directory, X to set the path.");
fn print_file_contents(&mut self) {
match std::fs::read_to_string(&self.path) {
Ok(contents) => {
println!("File contents:\n{0:->80}", "");
println!("{contents}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, for a really long file this takes a noticeably long amount of time (30 seconds on my /retroarch/retroarch.cfg), during which you see the lines scroll past pretty quickly. I wonder if the libctru console implementation is slow or something?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How big is that file, out of curiosity? I only tested with this small one but anything on the order of 30s seems worth investigating. In particular I would not expect romfs to be slow, but I haven't looked too close at the io implementations for it...

Copy link
Member

@AzureMarker AzureMarker Feb 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's 104 KB. I think it's just the default config, but here is the exact file (compressed so GitHub accepts it):
retroarch_config.zip

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it looks like that they really needed a scroll effect, but since the console cooperative thread doesn’t yield the scroll blocks the whole app, and takes quite a while, too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, the scroll effect is semi-intentional? I guess fixing that would require libctru changes...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should paginate the output here too? I wonder if we should move the pagination message to the lower screen to avoid interrupting the top screen's output.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, good point, I think when I had BufReader here it made more sense but using read_to_string is probably questionable when trying to paginate. Using the bottom screen to prompt for it probably takes a little work but sounds like a potentially nicer UX for this example.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the prompt move should be as making a new temporary console and selecting it.

println!("{0:->80}", "");
}
Err(err) => {
println!("Error reading file: {}", err);
}
}
}

/// Paginate output
fn wait_for_page_down(&mut self) {
println!("Press A to go to next page, or Start to exit");

while self.apt.main_loop() {
self.hid.scan_input();
let input = self.hid.keys_down();

if input.contains(KeyPad::KEY_A) {
break;
}

if input.contains(KeyPad::KEY_START) {
self.running = false;
return;
}

self.gfx.wait_for_vblank();
}
}

fn get_input_and_run(&mut self, action: impl FnOnce(&mut Self, String)) {
Expand Down Expand Up @@ -154,11 +199,6 @@ impl<'a> FileExplorer<'a> {
}
};

if !next_entry.file_type().unwrap().is_dir() {
println!("Not a directory: {}", next_path_index);
return;
}

self.console.clear();
self.path = next_entry.path();
self.print_menu();
Expand Down
1 change: 1 addition & 0 deletions ctru-rs/examples/romfs/garbage-data
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
c��:7vi���z�`�n����ʢ��fb �
4 changes: 3 additions & 1 deletion ctru-rs/examples/romfs/test-file.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
test
test file contents

another line