Done??
This commit is contained in:
228
src/app.rs
228
src/app.rs
@@ -1,6 +1,13 @@
|
|||||||
use crate::types;
|
use crate::types;
|
||||||
use crate::util::clamp;
|
use crate::util::clamp;
|
||||||
use eframe::egui;
|
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;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
pub struct Application {
|
pub struct Application {
|
||||||
@@ -12,6 +19,7 @@ pub struct Application {
|
|||||||
show_side_panel: bool,
|
show_side_panel: bool,
|
||||||
|
|
||||||
search_ctx: types::SearchContext,
|
search_ctx: types::SearchContext,
|
||||||
|
download_ctx: types::DownloadContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application {
|
impl Application {
|
||||||
@@ -38,7 +46,8 @@ impl Application {
|
|||||||
// Search button and input
|
// Search button and input
|
||||||
ui.label("Search: ");
|
ui.label("Search: ");
|
||||||
if ui.text_edit_singleline(&mut self.query).lost_focus()
|
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 query = self.query.clone();
|
||||||
let url = self.server_url.clone();
|
let url = self.server_url.clone();
|
||||||
@@ -66,9 +75,11 @@ impl Application {
|
|||||||
"Page {} of {}",
|
"Page {} of {}",
|
||||||
self.search_ctx.page, self.search_ctx.total_pages
|
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 =
|
self.search_ctx.page =
|
||||||
clamp(self.search_ctx.page - 1, 1, self.search_ctx.total_pages);
|
clamp(self.search_ctx.page - 1, 1, self.search_ctx.total_pages);
|
||||||
|
if !skip {
|
||||||
let query = self.query.clone();
|
let query = self.query.clone();
|
||||||
let url = self.server_url.clone();
|
let url = self.server_url.clone();
|
||||||
let page = self.search_ctx.page.clone();
|
let page = self.search_ctx.page.clone();
|
||||||
@@ -89,10 +100,13 @@ impl Application {
|
|||||||
});
|
});
|
||||||
ctx.forget_all_images();
|
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 =
|
self.search_ctx.page =
|
||||||
clamp(self.search_ctx.page + 1, 1, self.search_ctx.total_pages);
|
clamp(self.search_ctx.page + 1, 1, self.search_ctx.total_pages);
|
||||||
|
if !skip {
|
||||||
let query = self.query.clone();
|
let query = self.query.clone();
|
||||||
let url = self.server_url.clone();
|
let url = self.server_url.clone();
|
||||||
let page = self.search_ctx.page.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 res = search_files(url, query, page, page_size).await;
|
||||||
let _ = tx.send(res).await;
|
let _ = tx.send(res).await;
|
||||||
});
|
});
|
||||||
|
ctx.forget_all_images();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.label(&self.status);
|
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) {
|
fn process_channels(&mut self, ctx: &egui::Context) {
|
||||||
if self.search_ctx.search_rx.is_some() {
|
if self.search_ctx.search_rx.is_some() {
|
||||||
let mut clear_rx = false;
|
let mut clear_rx = false;
|
||||||
@@ -188,6 +311,36 @@ impl Application {
|
|||||||
self.search_ctx.search_rx = None;
|
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(),
|
status: "Ready!".to_string(),
|
||||||
search_ctx: types::SearchContext::default(),
|
search_ctx: types::SearchContext::default(),
|
||||||
show_side_panel: false,
|
show_side_panel: false,
|
||||||
|
download_ctx: types::DownloadContext::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,9 +365,7 @@ impl eframe::App for Application {
|
|||||||
if self.show_side_panel {
|
if self.show_side_panel {
|
||||||
self.render_side_panel(ctx);
|
self.render_side_panel(ctx);
|
||||||
}
|
}
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
self.render_center_panel(ctx);
|
||||||
ui.heading("// TODO: Put shit here");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,3 +409,67 @@ async fn search_files(
|
|||||||
|
|
||||||
Ok((results.results, results.metadata))
|
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())
|
||||||
|
}
|
||||||
|
|||||||
10
src/types.rs
10
src/types.rs
@@ -22,7 +22,13 @@ pub struct FileEntry {
|
|||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
pub page: usize,
|
pub page: usize,
|
||||||
pub total_pages: 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 {
|
pub struct SearchContext {
|
||||||
@@ -30,7 +36,7 @@ pub struct SearchContext {
|
|||||||
pub search_rx: Option<mpsc::Receiver<Result<(Vec<FileEntry>, Metadata), String>>>,
|
pub search_rx: Option<mpsc::Receiver<Result<(Vec<FileEntry>, Metadata), String>>>,
|
||||||
pub search_results: Vec<FileEntry>,
|
pub search_results: Vec<FileEntry>,
|
||||||
pub page: usize,
|
pub page: usize,
|
||||||
pub per_page : usize,
|
pub per_page: usize,
|
||||||
pub total_pages: usize,
|
pub total_pages: usize,
|
||||||
pub total_results: usize,
|
pub total_results: usize,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user