Done??
This commit is contained in:
228
src/app.rs
228
src/app.rs
@@ -1,6 +1,13 @@
|
||||
use crate::types;
|
||||
use crate::util::clamp;
|
||||
use eframe::egui;
|
||||
use egui::ColorImage;
|
||||
use futures_util::stream::StreamExt;
|
||||
use human_bytes::human_bytes;
|
||||
use image::{ColorType, GenericImage, Rgba};
|
||||
use std::cmp::min;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub struct Application {
|
||||
@@ -12,6 +19,7 @@ pub struct Application {
|
||||
show_side_panel: bool,
|
||||
|
||||
search_ctx: types::SearchContext,
|
||||
download_ctx: types::DownloadContext,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
@@ -38,7 +46,8 @@ impl Application {
|
||||
// Search button and input
|
||||
ui.label("Search: ");
|
||||
if ui.text_edit_singleline(&mut self.query).lost_focus()
|
||||
&& ui.input(|i| i.key_pressed(egui::Key::Enter))
|
||||
&& ui
|
||||
.input(|i| i.key_pressed(egui::Key::Enter) && !self.search_ctx.is_searching)
|
||||
{
|
||||
let query = self.query.clone();
|
||||
let url = self.server_url.clone();
|
||||
@@ -66,9 +75,11 @@ impl Application {
|
||||
"Page {} of {}",
|
||||
self.search_ctx.page, self.search_ctx.total_pages
|
||||
));
|
||||
if ui.button(" - ").clicked() {
|
||||
if ui.button(" - ").clicked() && !self.search_ctx.is_searching {
|
||||
let skip = self.search_ctx.page == 1;
|
||||
self.search_ctx.page =
|
||||
clamp(self.search_ctx.page - 1, 1, self.search_ctx.total_pages);
|
||||
if !skip {
|
||||
let query = self.query.clone();
|
||||
let url = self.server_url.clone();
|
||||
let page = self.search_ctx.page.clone();
|
||||
@@ -89,10 +100,13 @@ impl Application {
|
||||
});
|
||||
ctx.forget_all_images();
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("+").clicked() {
|
||||
if ui.button("+").clicked() && !self.search_ctx.is_searching {
|
||||
let skip = self.search_ctx.page == self.search_ctx.total_pages;
|
||||
self.search_ctx.page =
|
||||
clamp(self.search_ctx.page + 1, 1, self.search_ctx.total_pages);
|
||||
if !skip {
|
||||
let query = self.query.clone();
|
||||
let url = self.server_url.clone();
|
||||
let page = self.search_ctx.page.clone();
|
||||
@@ -111,6 +125,8 @@ impl Application {
|
||||
let res = search_files(url, query, page, page_size).await;
|
||||
let _ = tx.send(res).await;
|
||||
});
|
||||
ctx.forget_all_images();
|
||||
}
|
||||
}
|
||||
|
||||
ui.label(&self.status);
|
||||
@@ -153,6 +169,113 @@ impl Application {
|
||||
});
|
||||
}
|
||||
|
||||
fn render_center_panel(&mut self, ctx: &egui::Context) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if !self.search_ctx.search_results.is_empty() {
|
||||
let cols = 5;
|
||||
let mut rows = self.search_ctx.search_results.len() / cols;
|
||||
if self.search_ctx.search_results.len() < cols {
|
||||
rows = 1;
|
||||
}
|
||||
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
egui_extras::StripBuilder::new(ui)
|
||||
.sizes(egui_extras::Size::relative((rows as f32).recip()), rows)
|
||||
.vertical(|mut strip| {
|
||||
for r in 0..rows {
|
||||
strip.cell(|ui| {
|
||||
egui_extras::StripBuilder::new(ui)
|
||||
.sizes(
|
||||
egui_extras::Size::relative((cols as f32).recip()),
|
||||
cols,
|
||||
)
|
||||
.horizontal(|mut strip| {
|
||||
for c in 0..cols {
|
||||
strip.cell(|ui| {
|
||||
let file = &self.search_ctx.search_results
|
||||
[r * cols + c];
|
||||
if file.preview != "None" {
|
||||
ui.add(
|
||||
egui::Image::from_uri(
|
||||
file.preview.clone(),
|
||||
)
|
||||
.shrink_to_fit(),
|
||||
);
|
||||
} else {
|
||||
let mut img_data = image::DynamicImage::new(
|
||||
1,
|
||||
1,
|
||||
ColorType::Rgb8,
|
||||
);
|
||||
img_data.put_pixel(
|
||||
0,
|
||||
0,
|
||||
Rgba([0, 0, 0, 255]),
|
||||
);
|
||||
let img = ColorImage::from_rgb(
|
||||
[1, 1],
|
||||
&img_data.as_bytes(),
|
||||
);
|
||||
let handle = ctx.load_texture(
|
||||
format!("preview_{}", file.name),
|
||||
img,
|
||||
egui::TextureOptions::default(),
|
||||
);
|
||||
let texture = egui::load::SizedTexture::new(
|
||||
handle.id(),
|
||||
egui::vec2(1.0, 1.0),
|
||||
);
|
||||
ui.add(
|
||||
egui::Image::new(
|
||||
egui::ImageSource::Texture(texture),
|
||||
)
|
||||
.shrink_to_fit(),
|
||||
);
|
||||
}
|
||||
ui.label(format!("Name: {}", file.name));
|
||||
ui.label(format!("Extension: {}", file.ext));
|
||||
ui.label(format!(
|
||||
"Size: {}",
|
||||
human_bytes(file.size as f64)
|
||||
));
|
||||
ui.horizontal(|ui| {
|
||||
let download_btn = ui.add_enabled(
|
||||
!self.download_ctx.is_downloading,
|
||||
egui::Button::new("Download"),
|
||||
);
|
||||
if download_btn.clicked() {
|
||||
let path = self
|
||||
.download_path
|
||||
.clone()
|
||||
.unwrap_or("".to_string());
|
||||
let url = file.url.clone();
|
||||
let (tx, rx) = mpsc::channel::<
|
||||
Result<String, String>,
|
||||
>(
|
||||
1
|
||||
);
|
||||
self.download_ctx.download_rx =
|
||||
Some(rx);
|
||||
self.download_ctx.is_downloading = true;
|
||||
self.status =
|
||||
"Downloading...".to_string();
|
||||
tokio::spawn(async move {
|
||||
let res = download(url, path).await;
|
||||
let _ = tx.send(res).await;
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn process_channels(&mut self, ctx: &egui::Context) {
|
||||
if self.search_ctx.search_rx.is_some() {
|
||||
let mut clear_rx = false;
|
||||
@@ -188,6 +311,36 @@ impl Application {
|
||||
self.search_ctx.search_rx = None;
|
||||
}
|
||||
}
|
||||
|
||||
if self.download_ctx.download_rx.is_some() {
|
||||
let mut clear_rx = false;
|
||||
if let Some(rx) = self.download_ctx.download_rx.as_mut() {
|
||||
match rx.try_recv() {
|
||||
Ok(Ok(final_path)) => {
|
||||
self.status = format!("Downloaded {}", final_path);
|
||||
self.download_ctx.is_downloading = false;
|
||||
clear_rx = true;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
self.status = format!("Download failed: {}", e);
|
||||
self.download_ctx.is_downloading = false;
|
||||
clear_rx = true;
|
||||
}
|
||||
Err(mpsc::error::TryRecvError::Empty) => {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
Err(mpsc::error::TryRecvError::Disconnected) => {
|
||||
self.status = "Download task ended unexpectedly".to_string();
|
||||
self.download_ctx.is_downloading = false;
|
||||
clear_rx = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if clear_rx {
|
||||
self.download_ctx.download_rx = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,6 +353,7 @@ impl Default for Application {
|
||||
status: "Ready!".to_string(),
|
||||
search_ctx: types::SearchContext::default(),
|
||||
show_side_panel: false,
|
||||
download_ctx: types::DownloadContext::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,9 +365,7 @@ impl eframe::App for Application {
|
||||
if self.show_side_panel {
|
||||
self.render_side_panel(ctx);
|
||||
}
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("// TODO: Put shit here");
|
||||
});
|
||||
self.render_center_panel(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,3 +409,67 @@ async fn search_files(
|
||||
|
||||
Ok((results.results, results.metadata))
|
||||
}
|
||||
|
||||
async fn download(url: String, dir: String) -> Result<String, String> {
|
||||
let file_name = Path::new(&url)
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let file_path = Path::new(&dir).join(file_name);
|
||||
|
||||
if file_path.exists() {
|
||||
return Ok(file_path.to_str().unwrap().to_string());
|
||||
}
|
||||
|
||||
let mut file = match std::fs::File::create(file_path.clone()) {
|
||||
Ok(file) => file,
|
||||
Err(e) => return Err(format!("Failed to create the file: {}", e)),
|
||||
};
|
||||
let client = match reqwest::Client::builder()
|
||||
.user_agent(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0",
|
||||
)
|
||||
.danger_accept_invalid_certs(true)
|
||||
.build()
|
||||
{
|
||||
Ok(client) => client,
|
||||
Err(e) => return Err(format!("Failed to create the client: {}", e)),
|
||||
};
|
||||
let res = client.get(&url).send().await;
|
||||
let response = match res {
|
||||
Ok(response_ok) => response_ok,
|
||||
Err(e) => return Err(format!("Failed to download the file: {}", e)),
|
||||
};
|
||||
if response.status() != reqwest::StatusCode::OK {
|
||||
return Err(format!(
|
||||
"Failed to download the file: {}",
|
||||
response.status()
|
||||
));
|
||||
}
|
||||
|
||||
let total_size_res = response
|
||||
.content_length()
|
||||
.ok_or("Failed to get the file size");
|
||||
let total_size = match total_size_res {
|
||||
Ok(res_ok) => res_ok,
|
||||
Err(e) => return Err(format!("Failed to get the file size: {}", e)),
|
||||
};
|
||||
|
||||
let mut downloaded: u64 = 0;
|
||||
let mut stream = response.bytes_stream();
|
||||
while let Some(item) = stream.next().await {
|
||||
let chunk = item.or(Err(format!(
|
||||
"Error while downloading file: {}",
|
||||
url.clone()
|
||||
)))?;
|
||||
file.write_all(&chunk).or(Err(format!(
|
||||
"Error while writing to file: {}",
|
||||
file_path.to_str().unwrap()
|
||||
)))?;
|
||||
let new = min(downloaded + (chunk.len() as u64), total_size);
|
||||
downloaded = new;
|
||||
}
|
||||
Ok(file_path.to_str().unwrap().to_string())
|
||||
}
|
||||
|
||||
@@ -22,7 +22,13 @@ pub struct FileEntry {
|
||||
pub struct Metadata {
|
||||
pub page: usize,
|
||||
pub total_pages: usize,
|
||||
pub page_size: usize
|
||||
pub page_size: usize,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DownloadContext {
|
||||
pub is_downloading: bool,
|
||||
pub download_rx: Option<mpsc::Receiver<Result<String, String>>>,
|
||||
}
|
||||
|
||||
pub struct SearchContext {
|
||||
|
||||
Reference in New Issue
Block a user