initial commit
7
rust/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
6373
rust/Cargo.lock
generated
Normal file
36
rust/Cargo.toml
Normal file
@ -0,0 +1,36 @@
|
||||
[package]
|
||||
name = "startliner"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["akanyan"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "startliner_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = ["protocol-asset"] }
|
||||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_json5 = "0.1.0"
|
||||
serde_derive = "1.0.217"
|
||||
reqwest = { version = "0.12.12", features = ["stream"] }
|
||||
derive_builder = "0.20.2"
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "full"] }
|
||||
flate2 = "1.0.35"
|
||||
async-compression = { version = "0.4.18", features = ["gzip", "futures-io", "tokio"] }
|
||||
futures = "0.3.31"
|
||||
tauri-plugin-shell = "2"
|
||||
directories = "6.0.0"
|
||||
rust-ini = "0.21.1"
|
||||
simple_logger = "5.0.0"
|
||||
log = "0.4.25"
|
||||
regex = "1.11.1"
|
||||
zip = "2.2.2"
|
||||
tauri-plugin-dialog = "2"
|
||||
|
3
rust/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
19
rust/capabilities/default.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"core:window:allow-close",
|
||||
"core:app:allow-app-hide",
|
||||
"shell:default",
|
||||
"shell:default",
|
||||
"opener:default",
|
||||
"dialog:default",
|
||||
"dialog:default"
|
||||
]
|
||||
}
|
BIN
rust/icons/128x128.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
rust/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
rust/icons/32x32.png
Normal file
After Width: | Height: | Size: 974 B |
BIN
rust/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
rust/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
rust/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
rust/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
rust/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 903 B |
BIN
rust/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
rust/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
rust/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
rust/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
rust/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
rust/icons/icon.icns
Normal file
BIN
rust/icons/icon.ico
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
rust/icons/icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
182
rust/src/cmd.rs
Normal file
@ -0,0 +1,182 @@
|
||||
|
||||
|
||||
use log;
|
||||
use std::fs::{self, File};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::pkg_remote;
|
||||
use crate::pkg_local;
|
||||
use crate::util;
|
||||
use crate::{types, AppData};
|
||||
use crate::types::{local, Package};
|
||||
|
||||
use tauri::State;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn startline(
|
||||
state: State<'_, Mutex<AppData>>,
|
||||
) -> Result<Option<local::Profile>, ()> {
|
||||
log::debug!("invoke: startline");
|
||||
|
||||
let appd = state.lock().await;
|
||||
|
||||
Ok(appd.profile.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn download_package(pkg: Package) {
|
||||
log::debug!("invoke: download_package");
|
||||
|
||||
use futures::StreamExt;
|
||||
|
||||
let zip_path = util::get_dirs().cache_dir().join(format!(
|
||||
"{}-{}-{}.zip",
|
||||
pkg.namespace, pkg.name, pkg.version
|
||||
));
|
||||
|
||||
if !zip_path.exists() {
|
||||
// let zip_path_part = zip_path.add_extension("part");
|
||||
let mut zip_path_part = zip_path.clone();
|
||||
zip_path_part.set_extension("zip.part");
|
||||
let mut cache_file_w = tokio::fs::File::create(&zip_path_part).await.unwrap();
|
||||
let mut byte_stream = reqwest::get(&pkg.download_url)
|
||||
.await
|
||||
.unwrap()
|
||||
.bytes_stream();
|
||||
|
||||
log::info!("downloading: {}", pkg.download_url);
|
||||
while let Some(item) = byte_stream.next().await {
|
||||
let i = item.unwrap();
|
||||
cache_file_w.write_all(&mut i.as_ref()).await.unwrap();
|
||||
}
|
||||
cache_file_w.sync_all().await.unwrap();
|
||||
tokio::fs::rename(&zip_path_part, &zip_path).await.unwrap();
|
||||
}
|
||||
|
||||
let cache_file_r = File::open(&zip_path).unwrap();
|
||||
let mut archive = zip::ZipArchive::new(cache_file_r).unwrap();
|
||||
delete_package(pkg.namespace.clone(), pkg.name.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
let path = util::get_dirs()
|
||||
.data_dir()
|
||||
.join("pkg")
|
||||
.join(format!("{}-{}", pkg.namespace, pkg.name));
|
||||
fs::create_dir(&path).unwrap();
|
||||
archive.extract(path).unwrap();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_package(namespace: String, name: String) -> Result<(), String> {
|
||||
log::debug!("invoke: download_package");
|
||||
|
||||
let path = util::get_dirs()
|
||||
.data_dir()
|
||||
.join("pkg")
|
||||
.join(format!("{}-{}", namespace, name));
|
||||
if path.exists() && path.join("manifest.json").exists() {
|
||||
log::debug!("rm -r'ing {}", path.to_string_lossy());
|
||||
return tokio::fs::remove_dir_all(&path)
|
||||
.await
|
||||
.map_err(|e| e.to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn reload_packages(state: State<'_, tokio::sync::Mutex<AppData>>) -> Result<(), ()> {
|
||||
log::debug!("invoke: reload_packages");
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
// todo: this should only fetch new things
|
||||
appd.mods_local = pkg_local::walk_packages(false).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_packages(state: State<'_, Mutex<AppData>>) -> Result<Vec<Package>, ()> {
|
||||
log::debug!("invoke: get_packages");
|
||||
|
||||
let appd = state.lock().await;
|
||||
log::debug!("Returning {} packages", appd.mods_local.len());
|
||||
|
||||
Ok(appd.mods_local.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_listings(state: State<'_, Mutex<AppData>>) -> Result<Vec<Package>, String> {
|
||||
log::debug!("invoke: get_listings");
|
||||
|
||||
let should_fetch;
|
||||
{
|
||||
let appd = state.lock().await;
|
||||
should_fetch = appd.mods_store.len() == 0;
|
||||
}
|
||||
if should_fetch {
|
||||
let listings = pkg_remote::fetch_listings().await?;
|
||||
let mut appd = state.lock().await;
|
||||
appd.mods_store = listings;
|
||||
Ok(appd.mods_store.clone())
|
||||
} else {
|
||||
let appd = state.lock().await;
|
||||
Ok(appd.mods_store.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_current_profile(
|
||||
state: State<'_, Mutex<AppData>>,
|
||||
) -> Result<Option<local::Profile>, ()> {
|
||||
log::debug!("invoke: get_current_profile");
|
||||
|
||||
let appd = state.lock().await;
|
||||
|
||||
Ok(appd.profile.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_profile(
|
||||
state: State<'_, Mutex<AppData>>
|
||||
) -> Result<(), ()> {
|
||||
log::debug!("invoke: save_profile");
|
||||
|
||||
let appd = state.lock().await;
|
||||
|
||||
if let Some(profile) = &appd.profile {
|
||||
let path = util::get_dirs().config_dir().join("profile-ongeki-default.json");
|
||||
let s = serde_json::to_string_pretty(profile).unwrap();
|
||||
tokio::fs::write(&path, s).await.unwrap();
|
||||
log::info!("Written to {}", path.to_string_lossy());
|
||||
} else {
|
||||
log::error!("No profile to save");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn init_profile(
|
||||
state: State<'_, Mutex<AppData>>,
|
||||
path: String
|
||||
) -> Result<local::Profile, ()> {
|
||||
log::debug!("invoke: init_profile");
|
||||
|
||||
let new_profile = local::Profile {
|
||||
game: types::misc::Game::Ongeki,
|
||||
path: path,
|
||||
name: "ongeki-default".to_owned(),
|
||||
mods: [].to_vec()
|
||||
};
|
||||
|
||||
{
|
||||
let mut appd = state.lock().await;
|
||||
appd.profile = Some(new_profile.clone());
|
||||
}
|
||||
|
||||
save_profile(state).await.unwrap();
|
||||
|
||||
Ok(new_profile)
|
||||
}
|
67
rust/src/lib.rs
Normal file
@ -0,0 +1,67 @@
|
||||
mod cfg;
|
||||
mod types;
|
||||
mod cmd;
|
||||
mod pkg_remote;
|
||||
mod pkg_local;
|
||||
mod util;
|
||||
|
||||
use tokio::{fs, try_join};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use types::{local, Package};
|
||||
|
||||
use tauri::Manager;
|
||||
|
||||
struct AppData {
|
||||
profile: Option<local::Profile>,
|
||||
mods_local: Vec<Package>,
|
||||
mods_store: Vec<Package>,
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub async fn run(args: Vec<String>) {
|
||||
simple_logger::init_with_env().unwrap();
|
||||
|
||||
log::info!(
|
||||
"Running from {}",
|
||||
std::env::current_dir().unwrap_or_default().to_str().unwrap_or_default()
|
||||
);
|
||||
|
||||
try_join!(
|
||||
fs::create_dir_all(util::config_dir()),
|
||||
fs::create_dir_all(util::pkg_dir()),
|
||||
fs::create_dir_all(util::cache_dir())
|
||||
).expect("Unable to create working directories");
|
||||
|
||||
let app_data = AppData {
|
||||
profile: pkg_local::load_config(),
|
||||
mods_local: pkg_local::walk_packages(true).await,
|
||||
mods_store: [].to_vec(),
|
||||
};
|
||||
|
||||
if args.len() == 1 {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.setup(|app| {
|
||||
app.manage(Mutex::new(app_data));
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
cmd::get_packages,
|
||||
cmd::reload_packages,
|
||||
cmd::get_listings,
|
||||
cmd::download_package,
|
||||
cmd::delete_package,
|
||||
cmd::get_current_profile,
|
||||
cmd::init_profile,
|
||||
cmd::save_profile,
|
||||
cmd::startline
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
} else {
|
||||
panic!("Not implemented");
|
||||
}
|
||||
}
|
9
rust/src/main.rs
Normal file
@ -0,0 +1,9 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
startliner_lib::run(env::args().collect()).await;
|
||||
}
|
90
rust/src/pkg_local.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use std::fs::{self, File};
|
||||
|
||||
use crate::{pkg_remote, types::{self, local, Package}, util};
|
||||
|
||||
pub fn load_config() -> Option<local::Profile> {
|
||||
let path = util::get_dirs().config_dir().join("profile-ongeki-default.json");
|
||||
if let Ok(s) = fs::read_to_string(path) {
|
||||
Some(serde_json::from_str(&s).expect("Invalid profile json"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn walk_packages(fetch_remote: bool) -> Vec<Package> {
|
||||
let mut res = [].to_vec();
|
||||
|
||||
let packages = fs::read_dir(util::get_dirs().data_dir().join("pkg")).unwrap();
|
||||
|
||||
for package in packages {
|
||||
let dir = package.unwrap().path();
|
||||
let mft: local::PackageManifest =
|
||||
serde_json::from_reader(File::open(dir.join("manifest.json")).unwrap()).unwrap();
|
||||
let regex = regex::Regex::new(r"([A-Za-z0-9_]+)-([A-Za-z0-9_]+)$").unwrap();
|
||||
let dir_name = dir.file_name().to_owned().unwrap().to_str().unwrap();
|
||||
let namespace;
|
||||
|
||||
if let Some(caps) = regex.captures(dir_name) {
|
||||
if caps.len() != 3 || caps.get(2).unwrap().as_str() != mft.name {
|
||||
log::error!(
|
||||
"Error reading {}: invalid manifest or directory name",
|
||||
dir_name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
namespace = caps.get(1).unwrap().as_str();
|
||||
} else {
|
||||
log::error!("Error reading {}: invalid directory name", dir_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut builder = types::PackageBuilder::default();
|
||||
|
||||
builder
|
||||
.name(mft.name.to_owned())
|
||||
.namespace(namespace.to_owned())
|
||||
.enabled(false);
|
||||
|
||||
builder.package_url(format!(
|
||||
"https://rainy.patafour.zip/package/{}/{}",
|
||||
namespace, mft.name
|
||||
));
|
||||
|
||||
builder
|
||||
.version(mft.version_number.clone())
|
||||
.description(mft.description)
|
||||
.icon(
|
||||
dir.join("icon.png")
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_owned(),
|
||||
)
|
||||
.path(dir.as_os_str().to_str().unwrap().to_owned())
|
||||
.dependencies([].to_vec());
|
||||
|
||||
builder
|
||||
.version_available(mft.version_number)
|
||||
.download_url("".to_owned())
|
||||
.deprecated(false);
|
||||
|
||||
if fetch_remote == true {
|
||||
if let Ok(rem) = pkg_remote::get_remote(namespace, &mft.name).await {
|
||||
builder.version_available(rem.latest.version_number);
|
||||
builder.download_url(rem.latest.download_url);
|
||||
builder.deprecated(rem.is_deprecated);
|
||||
}
|
||||
}
|
||||
|
||||
match builder.build() {
|
||||
Ok(r) => {
|
||||
res.push(r);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Bad package: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
51
rust/src/pkg_remote.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use crate::types::{rainy, Package};
|
||||
|
||||
pub async fn fetch_listings() -> Result<Vec<Package>, String> {
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use futures::{
|
||||
io::{self, BufReader, ErrorKind},
|
||||
prelude::*,
|
||||
};
|
||||
let response = reqwest::get("https://rainy.patafour.zip/c/ongeki/api/v1/package/")
|
||||
.await
|
||||
.unwrap();
|
||||
let reader = response
|
||||
.bytes_stream()
|
||||
.map_err(|e| io::Error::new(ErrorKind::Other, e))
|
||||
.into_async_read();
|
||||
let mut decoder = GzipDecoder::new(BufReader::new(reader));
|
||||
let mut data = String::new();
|
||||
decoder.read_to_string(&mut data).await.unwrap();
|
||||
|
||||
let listings: Vec<rainy::V1Package> = serde_json::from_str(&data).expect("Fuck2");
|
||||
|
||||
let mut res: Vec<Package> = [].to_vec();
|
||||
for l in listings {
|
||||
let v = l.versions.last().unwrap();
|
||||
let mut p = Package::default();
|
||||
p.name = v.name.clone();
|
||||
p.namespace = l.owner.clone();
|
||||
p.description = v.description.clone();
|
||||
p.version = v.version_number.clone();
|
||||
p.icon = v.icon.clone();
|
||||
p.package_url = l.package_url.clone();
|
||||
p.download_url = v.download_url.clone();
|
||||
res.push(p);
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn get_remote(
|
||||
namespace: &str,
|
||||
name: &str,
|
||||
) -> Result<rainy::V0Package, Box<dyn std::error::Error>> {
|
||||
let url = format!(
|
||||
"https://rainy.patafour.zip/api/experimental/package/{}/{}/",
|
||||
namespace, name
|
||||
);
|
||||
let res = reqwest::get(url).await?.text().await?;
|
||||
let package: rainy::V0Package = serde_json::from_str(&res)?;
|
||||
|
||||
Ok(package)
|
||||
}
|
24
rust/src/types/local.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::misc;
|
||||
|
||||
// manifest.json
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct PackageManifest {
|
||||
pub name: String,
|
||||
pub version_number: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
// {game}-profile-{name}.json
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Profile {
|
||||
pub game: misc::Game,
|
||||
pub path: String,
|
||||
pub name: String,
|
||||
pub mods: Vec<String>,
|
||||
}
|
33
rust/src/types/misc.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Game {
|
||||
Ongeki,
|
||||
Chunithm,
|
||||
}
|
||||
|
||||
impl Serialize for Game {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Game::Ongeki => serializer.serialize_str("ongeki"),
|
||||
Game::Chunithm => serializer.serialize_str("chunithm"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Game {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Game, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
match s.as_str() {
|
||||
"chunithm" => Ok(Game::Chunithm),
|
||||
"ongeki" => Ok(Game::Ongeki),
|
||||
_ => Err(de::Error::custom("unknown game")),
|
||||
}
|
||||
}
|
||||
}
|
23
rust/src/types/mod.rs
Normal file
@ -0,0 +1,23 @@
|
||||
pub mod local;
|
||||
pub mod misc;
|
||||
pub mod rainy;
|
||||
|
||||
use derive_builder::Builder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Builder, Default, Serialize, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Package {
|
||||
pub namespace: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub package_url: String,
|
||||
pub download_url: String,
|
||||
pub path: String,
|
||||
pub enabled: bool,
|
||||
pub icon: String,
|
||||
pub version: String,
|
||||
pub version_available: String,
|
||||
pub deprecated: bool,
|
||||
pub dependencies: Vec<Package>,
|
||||
}
|
47
rust/src/types/rainy.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
// /c/{game}/api/v1/package
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct V1Package {
|
||||
pub owner: String,
|
||||
pub package_url: String,
|
||||
pub is_deprecated: bool,
|
||||
pub versions: Vec<V1Version>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct V1Version {
|
||||
// no namespace
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub version_number: String,
|
||||
pub icon: String,
|
||||
pub dependencies: Vec<String>,
|
||||
pub download_url: String,
|
||||
}
|
||||
|
||||
// /api/experimental/{namespace}/{name}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct V0Package {
|
||||
pub owner: String,
|
||||
pub package_url: String,
|
||||
pub is_deprecated: bool,
|
||||
pub latest: V0Version,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct V0Version {
|
||||
pub namespace: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub version_number: String,
|
||||
pub icon: String,
|
||||
pub dependencies: Vec<String>,
|
||||
pub download_url: String,
|
||||
}
|
19
rust/src/util.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use std::path::PathBuf;
|
||||
use directories::ProjectDirs;
|
||||
|
||||
pub fn get_dirs() -> ProjectDirs {
|
||||
ProjectDirs::from("org", "7EVENDAYSHOLIDAYS", "STARTLINER")
|
||||
.expect("Unable to set up config directories")
|
||||
}
|
||||
|
||||
pub fn config_dir() -> PathBuf {
|
||||
get_dirs().config_dir().to_owned()
|
||||
}
|
||||
|
||||
pub fn pkg_dir() -> PathBuf {
|
||||
get_dirs().data_dir().join("pkg").to_owned()
|
||||
}
|
||||
|
||||
pub fn cache_dir() -> PathBuf {
|
||||
get_dirs().cache_dir().to_owned()
|
||||
}
|
48
rust/tauri.conf.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "STARTLINER",
|
||||
"version": "0.1.0",
|
||||
"identifier": "moe.tendokyu.akanyan.startliner",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"beforeBuildCommand": "bun run build",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"plugins": {
|
||||
"fs": {
|
||||
"requireLiteralLeadingDot": false
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "STARTLINER",
|
||||
"width": 600,
|
||||
"height": 500,
|
||||
"minWidth": 600,
|
||||
"minHeight": 500
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": {
|
||||
"img-src": "'self' asset: https: blob: data:"
|
||||
},
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": ["**/*", "**/.*/**/*"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|