Skip to content

Commit

Permalink
[examples/demo] greatly simplify the demo example
Browse files Browse the repository at this point in the history
The example should be introductory material to the crate; doing it as
simply as possible should be a goal. So I'm removing the custom iterator
and the download-with-progress loop, even though they're really cool,
because they're just not intro-level material.
  • Loading branch information
wfraser committed Nov 12, 2023
1 parent 67b455f commit f250ff1
Showing 1 changed file with 118 additions and 156 deletions.
274 changes: 118 additions & 156 deletions examples/demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,195 +3,157 @@
//! This example illustrates a few basic Dropbox API operations: getting an OAuth2 token, listing
//! the contents of a folder recursively, and fetching a file given its path.
use dropbox_sdk::{files, UserAuthClient};
use dropbox_sdk::default_client::UserAuthDefaultClient;
use dropbox_sdk::files;

use std::collections::VecDeque;
use std::io::{self, Read};
use std::io;

enum Operation {
Usage,
List,
Download { path: String },
List(String),
Download(String),
}

fn parse_args() -> Operation {
match std::env::args().nth(1).as_deref() {
None | Some("--help") | Some("-h") => Operation::Usage,
Some("--list") => Operation::List,
Some(path) if path.starts_with('/') => Operation::Download { path: path.to_owned() },
Some(bogus) => {
eprintln!("Unrecognized option {:?}", bogus);
eprintln!();
Operation::Usage
let mut ctor: Option<fn(String) -> Operation> = None;
for arg in std::env::args().skip(1) {
match arg.as_str() {
"--help" | "-h" => return Operation::Usage,
"--list" => {
ctor = Some(Operation::List);
}
"--download" => {
ctor = Some(Operation::Download);
}
path if path.starts_with('/') => {
return if let Some(ctor) = ctor {
ctor(arg)
} else {
eprintln!("Either --download or --list must be specified");
Operation::Usage
};
}
_ => {
eprintln!("Unrecognized option {arg:?}");
eprintln!();
return Operation::Usage;
}
}
}
Operation::Usage
}

fn main() {
env_logger::init();

let download_path = match parse_args() {
Operation::Usage => {
eprintln!("usage: {} [option]", std::env::args().next().unwrap());
eprintln!(" options:");
eprintln!(" --help | -h view this text");
eprintln!(" --list list all files in your Dropbox");
eprintln!(" <path> print the file at the given path to stdout");
eprintln!();
eprintln!(" If a Dropbox OAuth token is given in the environment variable");
eprintln!(" DBX_OAUTH_TOKEN, it will be used, otherwise you will be prompted for");
eprintln!(" authentication interactively.");
std::process::exit(1);
},
Operation::List => None,
Operation::Download { path } => Some(path),
};
let op = parse_args();

if let Operation::Usage = op {
eprintln!("usage: {} [option]", std::env::args().next().unwrap());
eprintln!(" options:");
eprintln!(" --help | -h view this text");
eprintln!(" --download <path> copy the contents of <path> to stdout");
eprintln!(" --list <path> recursively list all files under <path>");
eprintln!();
eprintln!(" If a Dropbox OAuth token is given in the environment variable");
eprintln!(" DBX_OAUTH_TOKEN, it will be used, otherwise you will be prompted for");
eprintln!(" authentication interactively.");
std::process::exit(1);
}

let auth = dropbox_sdk::oauth2::get_auth_from_env_or_prompt();
let client = UserAuthDefaultClient::new(auth);

if let Some(path) = download_path {
eprintln!("downloading file {}", path);
if let Operation::Download(path) = op {
eprintln!("Copying file to stdout: {}", path);
eprintln!();
let mut bytes_out = 0u64;
let download_arg = files::DownloadArg::new(path);
let stdout = io::stdout();
let mut stdout_lock = stdout.lock();
'download: loop {
let result = files::download(&client, &download_arg, Some(bytes_out), None);
match result {
Ok(Ok(download_result)) => {
let mut body = download_result.body.expect("no body received!");
loop {
// limit read to 1 MiB per loop iteration so we can output progress
#[allow(clippy::needless_borrow)] // 2022-02-09: this lint is wrong
let mut input_chunk = (&mut body).take(1024 * 1024);
match io::copy(&mut input_chunk, &mut stdout_lock) {
Ok(0) => {
eprint!("\r");
break 'download;
}
Ok(len) => {
bytes_out += len;
if let Some(total) = download_result.content_length {
eprint!("\r{:.01}%",
bytes_out as f64 / total as f64 * 100.);
} else {
eprint!("\r{} bytes", bytes_out);
}
}
Err(e) => {
eprintln!("Read error: {}", e);
continue 'download; // do another request and resume
}
}

match files::download(&client, &files::DownloadArg::new(path), None, None) {
Ok(Ok(result)) => {
match io::copy(
&mut result.body.expect("there must be a response body"),
&mut io::stdout(),
) {
Ok(n) => {
eprintln!("Downloaded {n} bytes");
}
},
Ok(Err(download_error)) => {
eprintln!("Download error: {}", download_error);
},
Err(request_error) => {
eprintln!("Error: {}", request_error);
}
}
break 'download;
}
} else {
eprintln!("listing all files");
match list_directory(&client, "/", true) {
Ok(Ok(iterator)) => {
for entry_result in iterator {
match entry_result {
Ok(Ok(files::Metadata::Folder(entry))) => {
println!("Folder: {}", entry.path_display.unwrap_or(entry.name));
},
Ok(Ok(files::Metadata::File(entry))) => {
println!("File: {}", entry.path_display.unwrap_or(entry.name));
},
Ok(Ok(files::Metadata::Deleted(entry))) => {
panic!("unexpected deleted entry: {:?}", entry);
},
Ok(Err(e)) => {
eprintln!("Error from files/list_folder_continue: {}", e);
break;
},
Err(e) => {
eprintln!("API request error: {}", e);
break;
},
Err(e) => {
eprintln!("I/O error: {e}");
}
}
},
}
Ok(Err(e)) => {
eprintln!("Error from files/list_folder: {}", e);
},
eprintln!("Error from files/download: {e}");
}
Err(e) => {
eprintln!("API request error: {}", e);
eprintln!("API request error: {e}");
}
}
}
}
} else if let Operation::List(mut path) = op {
eprintln!("Listing recursively: {path}");

fn list_directory<'a, T: UserAuthClient>(client: &'a T, path: &str, recursive: bool)
-> dropbox_sdk::Result<Result<DirectoryIterator<'a, T>, files::ListFolderError>>
{
assert!(path.starts_with('/'), "path needs to be absolute (start with a '/')");
let requested_path = if path == "/" {
// Root folder should be requested as empty string
String::new()
} else {
path.to_owned()
};
match files::list_folder(
client,
&files::ListFolderArg::new(requested_path)
.with_recursive(recursive))
{
Ok(Ok(result)) => {
let cursor = if result.has_more {
Some(result.cursor)
} else {
None
};

Ok(Ok(DirectoryIterator {
client,
cursor,
buffer: result.entries.into(),
}))
},
Ok(Err(e)) => Ok(Err(e)),
Err(e) => Err(e),
}
}
// Special case: the root folder is empty string. All other paths need to start with '/'.
if path == "/" {
path.clear();
}

struct DirectoryIterator<'a, T: UserAuthClient> {
client: &'a T,
buffer: VecDeque<files::Metadata>,
cursor: Option<String>,
}
let mut result = match files::list_folder(
&client,
&files::ListFolderArg::new(path).with_recursive(true),
) {
Ok(Ok(result)) => result,
Ok(Err(e)) => {
eprintln!("Error from files/list_folder: {e}");
return;
}
Err(e) => {
eprintln!("API request error: {e}");
return;
}
};

impl<'a, T: UserAuthClient> Iterator for DirectoryIterator<'a, T> {
type Item = dropbox_sdk::Result<Result<files::Metadata, files::ListFolderContinueError>>;
let mut num_entries = result.entries.len();
let mut num_pages = 1;

fn next(&mut self) -> Option<Self::Item> {
if let Some(entry) = self.buffer.pop_front() {
Some(Ok(Ok(entry)))
} else if let Some(cursor) = self.cursor.take() {
match files::list_folder_continue(self.client, &files::ListFolderContinueArg::new(cursor)) {
Ok(Ok(result)) => {
self.buffer.extend(result.entries);
if result.has_more {
self.cursor = Some(result.cursor);
loop {
for entry in result.entries {
match entry {
files::Metadata::Folder(entry) => {
println!("Folder: {}", entry.path_display.unwrap_or(entry.name));
}
files::Metadata::File(entry) => {
println!("File: {}", entry.path_display.unwrap_or(entry.name));
}
self.buffer.pop_front().map(|entry| Ok(Ok(entry)))
},
Ok(Err(e)) => Some(Ok(Err(e))),
Err(e) => Some(Err(e)),
files::Metadata::Deleted(entry) => {
panic!("unexpected deleted entry: {:?}", entry);
}
}
}

if !result.has_more {
break;
}

result = match files::list_folder_continue(
&client,
&files::ListFolderContinueArg::new(result.cursor),
) {
Ok(Ok(result)) => {
num_pages += 1;
num_entries += result.entries.len();
result
}
Ok(Err(e)) => {
eprintln!("Error from files/list_folder_continue: {e}");
break;
}
Err(e) => {
eprintln!("API request error: {e}");
break;
}
}
} else {
None
}

eprintln!("{num_entries} entries from {num_pages} result pages");
}
}

0 comments on commit f250ff1

Please sign in to comment.