Setup base eframe application + serialization + arg parsing and configuration

This commit is contained in:
2026-09-16 20:13:36 -04:00
commit a33a40c27f
12 changed files with 6855 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# .cargo/config.toml
[env]
CMAKE_POLICY_VERSION_MINIMUM = "3.5"
+1
View File
@@ -0,0 +1 @@
/target
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/yt-offline.iml" filepath="$PROJECT_DIR$/.idea/yt-offline.iml" />
</modules>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
Generated
+6622
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "yt-offline"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.6.7", features = ["derive"] }
dirs = "7.0.0"
eframe = {version = "0.36.2", features = ["persistence"]}
egui-sharkplayer = "0.5.1"
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151"
tokio = {version = "1.53.1", features = ["macros", "rt-multi-thread"]}
tracing = "0.1.44"
tracing-subscriber = {version = "0.3.23", features = ["env-filter"]}
yt-dlp = "2.8.3"
[profile.release]
opt-level = 3
lto = true
panic = 'abort'
overflow-checks = false
debug-assertions = false
incremental = false
rpath = false
codegen-units = 1
strip = true
+59
View File
@@ -0,0 +1,59 @@
use std::time::Duration;
use eframe::{egui, Frame, Storage, CreationContext};
use eframe::egui::{Context, Ui};
use serde::{Deserialize, Serialize};
#[derive(Default)]
pub(crate) struct Application {
settings: AppSettings,
}
#[derive(Serialize, Deserialize, Default)]
struct AppSettings {
serialization_test_string: String,
}
impl Application {
pub fn new(cc: &CreationContext) -> Self {
let mut settings = AppSettings::default();
if let Some(storage) = cc.storage {
if let Some(stored_str) = storage.get_string("app_settings") {
if let Ok(parsed) = serde_json::from_str(&stored_str) {
settings = parsed;
}
}
}
Self { settings }
}
}
impl eframe::App for Application {
fn logic(&mut self, ctx: &Context, frame: &mut Frame) {
}
fn ui(&mut self, ui: &mut Ui, frame: &mut Frame) {
egui::CentralPanel::default().show(ui, |ui| {
ui.heading("Hello!");
ui.text_edit_singleline(&mut self.settings.serialization_test_string);
});
}
fn save(&mut self, _storage: &mut dyn Storage) {
if let Ok(serialized) = serde_json::to_string(&self.settings) {
_storage.set_string("app_settings", serialized);
}
}
fn on_exit(&mut self) {
}
fn auto_save_interval(&self) -> Duration {
Duration::from_secs(5)
}
}
+43
View File
@@ -0,0 +1,43 @@
use clap::Parser;
use crate::{args};
#[derive(Parser, Debug)]
pub(crate) struct Args {
/// Override the state file location
pub(crate) state: Option<String>,
/// Override the default data dir
#[clap(short, long)]
pub(crate) data_dir: Option<String>,
/// Enable verbose logging
#[clap(short, long)]
pub(crate) verbose: Option<bool>,
}
#[derive(Debug)]
pub(crate) struct ArgsConfig {
pub(crate) state: String,
pub(crate) data_dir: String,
pub(crate) verbose: bool,
}
pub(crate) fn parse_args() -> ArgsConfig {
let args = args::Args::parse();
let mut args_config = args::ArgsConfig {
state: "".to_string(),
data_dir: "".to_string(),
verbose: false,
};
if args.data_dir.is_none() {
let data_dir = dirs::data_dir().unwrap_or_else(|| "./".into());
args_config.data_dir = format!("{}/yt-offline", data_dir.into_string().expect("Failed to convert data directory into string"));
}
// TODO: add a check in case the passed in data dir as a / at the end
if args.state.is_some() {
args_config.state = format!("{}/{}", args_config.data_dir, args.state.expect("Failed to unwrap config arg"));
} else {
args_config.state = format!("{}/state.json", args_config.data_dir);
}
args_config
}
+31
View File
@@ -0,0 +1,31 @@
use tracing::log::LevelFilter;
use tracing_subscriber::EnvFilter;
use tracing::debug;
#[allow(unused_variables)]
pub fn init(is_verbose: bool) {
let lvl_filter;
let directive;
#[cfg(debug_assertions)]
{
lvl_filter = LevelFilter::Info;
directive = "yt_offline=DEBUG";
}
#[cfg(not(debug_assertions))]
{
if is_verbose{
lvl_filter = LevelFilter::Info;
directive = "yt_offline=INFO";
} else {
lvl_filter = LevelFilter::Warn;
directive = "yt_offline=WARN";
}
}
let filter = EnvFilter::builder()
.with_default_directive(lvl_filter.as_str().parse().expect("Failed to parse lvl_filter into directive"))
.from_env().expect("Failed to parse env filter")
.add_directive(directive.parse().expect("Failed to parse directive"));
tracing_subscriber::fmt().with_env_filter(filter).compact().init();
debug!("Successfully setup tracing subscriber.");
}
+34
View File
@@ -0,0 +1,34 @@
use std::fs;
use std::path::PathBuf;
use std::process::exit;
use tracing::{debug, error};
mod logging;
mod args;
mod app;
fn main() {
let args = args::parse_args();
logging::init(args.verbose);
debug!("Using args: \n{:#?}\n", args);
let data_dir_path = PathBuf::from(&args.data_dir);
if data_dir_path.is_file() {
error!("data_dir is a file... If you have set the data_dir manually, please pass in a directory. If you didn't pass in a path at all, please report this.");
error!("{:#?}", args);
exit(1);
}
if !data_dir_path.exists() {
if let Err(e) = fs::create_dir(data_dir_path.as_path()) {
error!("Failed to create data_dir path with fs error: {}", e);
exit(1);
}
}
let mut options = eframe::NativeOptions::default();
options.persistence_path = Some(args.state.parse().expect("Failed to parse state config file path into path"));
eframe::run_native(
"yt-offline",
options,
Box::new(|cc| {
Ok(Box::new(app::Application::new(cc)))
})).expect("TODO: panic message");
}