Skip to content
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
169 changes: 89 additions & 80 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use ratatui::{
widgets::Block,
Frame,
};
use std::{path::PathBuf, rc::Rc, sync::Arc};
use std::{io::BufWriter, rc::Rc, sync::Arc};
use tokio::spawn;

use crate::{
Expand All @@ -19,7 +19,7 @@ use crate::{
CompleteLoadObjectsResult, CompletePreviewObjectResult, CompleteReloadBucketsResult,
CompleteReloadObjectsResult, Sender,
},
file::{copy_to_clipboard, save_binary, save_error_log},
file::{copy_to_clipboard, create_binary_file, save_error_log},
object::{AppObjects, FileDetail, ObjectItem, RawObject},
pages::page::{Page, PageStack},
widget::{Header, LoadingDialog, Status, StatusType},
Expand Down Expand Up @@ -421,9 +421,10 @@ impl App {
self.is_loading = true;
}

pub fn preview_download_object(&self, obj: RawObject, path: String) {
let result = CompleteDownloadObjectResult::new(Ok(obj), PathBuf::from(path));
self.tx.send(AppEventType::CompleteDownloadObject(result));
pub fn preview_download_object(&mut self, file_detail: FileDetail, version_id: Option<String>) {
self.tx
.send(AppEventType::DownloadObject(file_detail, version_id));
self.is_loading = true;
}

pub fn open_preview(&mut self, file_detail: FileDetail, version_id: Option<String>) {
Expand All @@ -436,16 +437,35 @@ impl App {
let object_name = file_detail.name;
let size_byte = file_detail.size_byte;

self.download_object_and(
&object_name,
size_byte,
None,
version_id,
|tx, obj, path| {
let result = CompleteDownloadObjectResult::new(obj, path);
tx.send(AppEventType::CompleteDownloadObject(result));
},
)
let object_key = match self.page_stack.current_page() {
page @ Page::ObjectDetail(_) => page.as_object_detail().current_object_key(),
page @ Page::ObjectPreview(_) => page.as_object_preview().current_object_key(),
page => panic!("Invalid page: {:?}", page),
};

let bucket = object_key.bucket_name.clone();
let key = object_key.joined_object_path(true);

let path = self.ctx.config.download_file_path(&object_name);
let writer = create_binary_file(&path);

let (client, tx) = self.unwrap_client_tx();
let loading = self.handle_loading_size(size_byte, tx.clone());

spawn(async move {
match writer {
Ok(mut writer) => {
let result = client
.download_object(&bucket, &key, version_id, &mut writer, loading)
.await;
let result = CompleteDownloadObjectResult::new(result, path);
tx.send(AppEventType::CompleteDownloadObject_(result));
}
Err(e) => {
tx.send(AppEventType::CompleteDownloadObject_(Err(e)));
}
}
});
}

pub fn download_object_as(
Expand All @@ -454,30 +474,42 @@ impl App {
input: String,
version_id: Option<String>,
) {
let object_name = file_detail.name;
let size_byte = file_detail.size_byte;

self.download_object_and(
&object_name,
size_byte,
Some(&input),
version_id,
|tx, obj, path| {
let result = CompleteDownloadObjectResult::new(obj, path);
tx.send(AppEventType::CompleteDownloadObject(result));
},
)
let object_key = match self.page_stack.current_page() {
page @ Page::ObjectDetail(_) => page.as_object_detail().current_object_key(),
page @ Page::ObjectPreview(_) => page.as_object_preview().current_object_key(),
page => panic!("Invalid page: {:?}", page),
};

let bucket = object_key.bucket_name.clone();
let key = object_key.joined_object_path(true);

let path = self.ctx.config.download_file_path(&input);
let writer = create_binary_file(&path);

let (client, tx) = self.unwrap_client_tx();
let loading = self.handle_loading_size(size_byte, tx.clone());

spawn(async move {
match writer {
Ok(mut writer) => {
let result = client
.download_object(&bucket, &key, version_id, &mut writer, loading)
.await;
let result = CompleteDownloadObjectResult::new(result, path);
tx.send(AppEventType::CompleteDownloadObject_(result));
}
Err(e) => {
tx.send(AppEventType::CompleteDownloadObject_(Err(e)));
}
}
});
}

pub fn complete_download_object(&mut self, result: Result<CompleteDownloadObjectResult>) {
let result = match result {
Ok(CompleteDownloadObjectResult { obj, path }) => {
save_binary(&path, &obj.bytes).map(|_| path)
}
Err(e) => Err(e),
};
match result {
Ok(path) => {
Ok(CompleteDownloadObjectResult { path }) => {
let msg = format!(
"Download completed successfully: {}",
path.to_string_lossy()
Expand All @@ -498,19 +530,32 @@ impl App {
}

pub fn preview_object(&self, file_detail: FileDetail, version_id: Option<String>) {
let object_name = file_detail.name.clone();
let size_byte = file_detail.size_byte;

self.download_object_and(
&object_name,
size_byte,
None,
version_id.clone(),
|tx, obj, path| {
let result = CompletePreviewObjectResult::new(obj, file_detail, version_id, path);
tx.send(AppEventType::CompletePreviewObject(result));
},
)
let object_key = match self.page_stack.current_page() {
page @ Page::ObjectDetail(_) => page.as_object_detail().current_object_key(),
page @ Page::ObjectPreview(_) => page.as_object_preview().current_object_key(),
page => panic!("Invalid page: {:?}", page),
};

let bucket = object_key.bucket_name.clone();
let key = object_key.joined_object_path(true);

let (client, tx) = self.unwrap_client_tx();
let loading = self.handle_loading_size(size_byte, tx.clone());

spawn(async move {
let mut bytes = Vec::with_capacity(size_byte);
let result = {
let mut writer = BufWriter::new(&mut bytes);
client
.download_object(&bucket, &key, version_id.clone(), &mut writer, loading)
.await
};
let obj = result.map(|_| RawObject { bytes });
let result = CompletePreviewObjectResult::new(obj, file_detail, version_id);
tx.send(AppEventType::CompletePreviewObject(result));
});
}

pub fn complete_preview_object(&mut self, result: Result<CompletePreviewObjectResult>) {
Expand All @@ -522,13 +567,11 @@ impl App {
obj,
file_detail,
file_version_id,
path,
}) => {
let object_preview_page = Page::of_object_preview(
file_detail,
file_version_id,
obj,
path.to_string_lossy().into(),
current_object_key,
Rc::clone(&self.ctx),
self.tx.clone(),
Expand All @@ -543,40 +586,6 @@ impl App {
self.is_loading = false;
}

fn download_object_and<F>(
&self,
object_name: &str,
size_byte: usize,
save_file_name: Option<&str>,
version_id: Option<String>,
f: F,
) where
F: FnOnce(Sender, Result<RawObject>, PathBuf) + Send + 'static,
{
let object_key = match self.page_stack.current_page() {
page @ Page::ObjectDetail(_) => page.as_object_detail().current_object_key(),
page @ Page::ObjectPreview(_) => page.as_object_preview().current_object_key(),
page => panic!("Invalid page: {:?}", page),
};

let bucket = object_key.bucket_name.clone();
let key = object_key.joined_object_path(true);

let path = self
.ctx
.config
.download_file_path(save_file_name.unwrap_or(object_name));

let (client, tx) = self.unwrap_client_tx();
let loading = self.handle_loading_size(size_byte, tx.clone());
spawn(async move {
let obj = client
.download_object(&bucket, &key, version_id, size_byte, loading)
.await;
f(tx, obj, path);
});
}

fn handle_loading_size(&self, total_size: usize, tx: Sender) -> Box<dyn Fn(usize) + Send> {
if total_size < 10_000_000 {
return Box::new(|_| {});
Expand Down
25 changes: 15 additions & 10 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::fmt::Debug;
use std::{
fmt::Debug,
io::{BufWriter, Write},
};

use aws_config::{default_provider::region, meta::region::RegionProviderChain, BehaviorVersion};
use aws_sdk_s3::{
Expand All @@ -10,7 +13,7 @@ use chrono::TimeZone;

use crate::{
error::{AppError, Result},
object::{BucketItem, FileDetail, FileVersion, ObjectItem, RawObject},
object::{BucketItem, FileDetail, FileVersion, ObjectItem},
};

const DELIMITER: &str = "/";
Expand Down Expand Up @@ -244,15 +247,16 @@ impl Client {
Ok(versions)
}

pub async fn download_object<F>(
pub async fn download_object<W, F>(
&self,
bucket: &str,
key: &str,
version_id: Option<String>,
size_byte: usize,
writer: &mut BufWriter<W>,
f: F,
) -> Result<RawObject>
) -> Result<()>
where
W: std::io::Write,
F: Fn(usize),
{
let mut request = self.client.get_object().bucket(bucket).key(key);
Expand All @@ -263,25 +267,26 @@ impl Client {
let result = request.send().await;
let output = result.map_err(|e| AppError::new("Failed to download object", e))?;

let mut bytes: Vec<u8> = Vec::with_capacity(size_byte);
let mut stream = output.body;
let mut i = 0;
let mut total_bytes = 0;
while let Some(buf) = stream // buf: 32 KiB
.try_next()
.await
.map_err(|e| AppError::new("Failed to collect body", e))?
{
bytes.extend(buf.to_vec());
writer.write_all(&buf).map_err(AppError::error)?;
total_bytes += buf.len();

// suppress too many calls (32 KiB * 32 = 1 MiB)
if i >= 32 {
f(bytes.len());
f(total_bytes);
i = 0;
}
i += 1;
}

Ok(RawObject { bytes })
writer.flush().map_err(AppError::error)?;
Ok(())
}

pub fn open_management_console_buckets(&self) -> Result<()> {
Expand Down
14 changes: 5 additions & 9 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub enum AppEventType {
CompleteLoadObjectVersions(Result<CompleteLoadObjectVersionsResult>),
DownloadObject(FileDetail, Option<String>),
DownloadObjectAs(FileDetail, String, Option<String>),
CompleteDownloadObject(Result<CompleteDownloadObjectResult>),
CompleteDownloadObject_(Result<CompleteDownloadObjectResult>),
PreviewObject(FileDetail, Option<String>),
CompletePreviewObject(Result<CompletePreviewObjectResult>),
BucketListMoveDown,
Expand All @@ -44,7 +44,7 @@ pub enum AppEventType {
OpenPreview(FileDetail, Option<String>),
DetailDownloadObject(FileDetail, Option<String>),
DetailDownloadObjectAs(FileDetail, String, Option<String>),
PreviewDownloadObject(RawObject, String),
PreviewDownloadObject(FileDetail, Option<String>),
PreviewDownloadObjectAs(FileDetail, String, Option<String>),
PreviewRerenderImage,
BucketListOpenManagementConsole,
Expand Down Expand Up @@ -158,14 +158,13 @@ impl CompleteLoadObjectVersionsResult {

#[derive(Debug)]
pub struct CompleteDownloadObjectResult {
pub obj: RawObject,
pub path: PathBuf,
}

impl CompleteDownloadObjectResult {
pub fn new(obj: Result<RawObject>, path: PathBuf) -> Result<CompleteDownloadObjectResult> {
let obj = obj?;
Ok(CompleteDownloadObjectResult { obj, path })
pub fn new(result: Result<()>, path: PathBuf) -> Result<CompleteDownloadObjectResult> {
result?;
Ok(CompleteDownloadObjectResult { path })
}
}

Expand All @@ -174,22 +173,19 @@ pub struct CompletePreviewObjectResult {
pub obj: RawObject,
pub file_detail: FileDetail,
pub file_version_id: Option<String>,
pub path: PathBuf,
}

impl CompletePreviewObjectResult {
pub fn new(
obj: Result<RawObject>,
file_detail: FileDetail,
file_version_id: Option<String>,
path: PathBuf,
) -> Result<CompletePreviewObjectResult> {
let obj = obj?;
Ok(CompletePreviewObjectResult {
obj,
file_detail,
file_version_id,
path,
})
}
}
Expand Down
10 changes: 2 additions & 8 deletions src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,10 @@ use std::{

use crate::error::{AppError, Result};

pub fn save_binary<P: AsRef<Path>>(path: P, bytes: &[u8]) -> Result<()> {
pub fn create_binary_file<P: AsRef<Path>>(path: P) -> Result<BufWriter<File>> {
create_dirs(&path)?;

let f = File::create(&path).map_err(|e| AppError::new("Failed to create file", e))?;
let mut writer = BufWriter::new(f);
writer
.write_all(bytes)
.map_err(|e| AppError::new("Failed to write file", e))?;

Ok(())
Ok(BufWriter::new(f))
}

pub fn save_error_log<P: AsRef<Path>>(path: P, e: &AppError) -> Result<()> {
Expand Down
Loading