Skip to content

Commit

Permalink
Merge pull request #4 from aramrw/tray-icon
Browse files Browse the repository at this point in the history
Add Tray Icon (Windows only)
  • Loading branch information
aramrw authored Aug 19, 2024
2 parents bb23fbf + d8eb7ca commit 3f35e56
Show file tree
Hide file tree
Showing 9 changed files with 440 additions and 203 deletions.
383 changes: 263 additions & 120 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ name = "yomichan_audio_server"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = { version = "4" }
actix-files = { version = "0.6.5" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-native-tls", "sqlite" ] }
sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite"] }
tokio = { version = "1", features = ["full"] }
sysinfo = { version = "0.30.10" }
rayon = "1.10.0"
bimap = { version = "0.6.3", features = ["std"] }
# tray-icon = "0.15.1"
tray-item = "0.10.0"

[build-dependencies]
embed-resource = "2.4.3"
Binary file added app-icon.ico
Binary file not shown.
5 changes: 5 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extern crate embed_resource;

fn main() {
embed_resource::compile("tray.rc", embed_resource::NONE);
}
35 changes: 18 additions & 17 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::os::windows::process::CommandExt;
use std::path::Path;

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Config {
pub exit_minutes: u64,
pub debug: bool,
Expand All @@ -11,10 +11,10 @@ pub struct Config {
pub fn create_config() -> Config {
let config_path = "config.json";
if !Path::new(config_path).exists() {
let config = Config {
let config = Config {
exit_minutes: 30,
debug: false,
};
};

let config_json = serde_json::to_string_pretty(&config).unwrap();
std::fs::write(config_path, config_json).unwrap();
Expand All @@ -32,19 +32,21 @@ fn get_config() -> Config {
}

#[cfg(target_os = "windows")]
pub fn handle_debugger(config: &Config) {
let args: Vec<String> = std::env::args().collect();
let is_secondary_instance = args.len() > 1 && args[1] == "debug";

if !is_secondary_instance && !config.debug {
// main terminal
pub fn handle_debugger(hidden: bool) {
if hidden {
// Launch hidden instance
std::process::Command::new("yomichan_audio_server.exe")
.arg("hidden")
.creation_flags(0x00000008) // CREATE_NO_WINDOW
.spawn()
.unwrap();
}
if !hidden {
// Launch visible instance
std::process::Command::new("yomichan_audio_server.exe")
.arg("debug")
.creation_flags(0x08000000) // CREATE_NO_WINDOW
.spawn()
.unwrap();

std::process::exit(0);
}
}

Expand All @@ -54,9 +56,8 @@ pub fn kill_previous_instance() {

for (pid, proc) in sys.processes() {
if proc.name().contains("yomichan_audio_server") && pid.as_u32() != std::process::id() {
println!("Killing previous instance with PID: {}", pid);
proc.kill();
}
println!("Killing previous instance with PID: {}", pid);
proc.kill();
}
}

}
72 changes: 25 additions & 47 deletions src/database.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use sqlx::{sqlite::SqlitePool, Error, Row};
//use std::time::Instant;
use sqlx::{prelude::FromRow, sqlite::SqlitePool, Error};
use tokio::join;

#[derive(Default, Deserialize, Serialize, Debug)]
#[derive(Default, Deserialize, Serialize, Debug, FromRow)]
pub struct Entry {
pub expression: String,
pub reading: Option<String>,
Expand All @@ -14,78 +14,56 @@ pub struct Entry {
}

pub async fn query_database(term: &str, reading: &str) -> Result<Vec<Entry>, Error> {
//let instant = Instant::now();
let sqlite_pool = SqlitePool::connect("./audio/entries.db").await?;

let result = sqlx::query("SELECT * FROM entries WHERE expression = ? AND reading = ?")
.bind(term)
.bind(reading)
.fetch_all(&sqlite_pool);
let fetch_result =
sqlx::query_as::<_, Entry>("SELECT * FROM entries WHERE expression = ? AND reading = ?")
.bind(term)
.bind(reading)
.fetch_all(&sqlite_pool);

let forvo_result = sqlx::query(
let fetch_forvo_result = sqlx::query_as::<_, Entry>(
"SELECT * FROM entries WHERE expression = ? AND source = 'forvo' ORDER BY speaker DESC",
)
.bind(term)
.fetch_all(&sqlite_pool);

let (result, forvo_result) = tokio::try_join!(result, forvo_result)?;
// Await them concurrently
let (result, forvo_result) = join!(fetch_result, fetch_forvo_result);
let result = result?;
let forvo_result = forvo_result?;

/* Handle Results */

let mut query_entries: Vec<Entry> = Vec::new();

let dict_entries: Vec<Entry> = result
.par_iter()
.map(|row| {
let reading: Option<String> = row.try_get("reading").unwrap_or_default();
let speaker: Option<String> = row.try_get("speaker").unwrap_or_default();

.into_iter()
.map(|mut ent| {
// file _might_ start with the folder name so cut it out
let mut file: String = row.get("file");
file = file.rsplit_once('\\').unwrap().1.to_string();

Entry {
expression: row.try_get("expression").unwrap_or_default(),
reading,
source: row.get("source"),
speaker,
display: row.get("display"),
file,
}
let file: String = ent.file;
ent.file = file.rsplit_once('\\').unwrap().1.to_string();
ent
})
.collect();

let forvo_entries: Vec<Entry> = forvo_result
.par_iter()
.map(|row| {
let reading: Option<String> = row.try_get("reading").unwrap_or_default();
let speaker: Option<String> = row.try_get("speaker").unwrap_or_default();

// file starts with the folder name so cut it out
let mut file: String = row.get("file");
file = file.rsplit_once('\\').unwrap().1.to_string();

Entry {
expression: row.try_get("expression").unwrap_or_default(),
reading,
source: row.get("source"),
speaker,
display: row.get("display"),
file,
}
.into_iter()
.map(|mut ent| {
// file _might_ start with the folder name so cut it out
let file: String = ent.file;
ent.file = file.rsplit_once('\\').unwrap().1.to_string();
ent
})
.collect();

query_entries.extend(dict_entries);
query_entries.extend(forvo_entries);

query_entries.par_sort_unstable_by(|a, b| {
let order = ["nhk16", "shinmeikai8", "daijisen", "forvo", "jpod"];
let order = ["daijisen", "nhk16", "shinmeikai8", "forvo", "jpod"];
let a_index = order.iter().position(|&x| x == a.source).unwrap();
let b_index = order.iter().position(|&x| x == b.source).unwrap();
a_index.cmp(&b_index)
});

//println!("Fetched Queries in {}ms", instant.elapsed().as_micros());
Ok(query_entries)
}
49 changes: 48 additions & 1 deletion src/helper.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::database;
//use rayon::prelude::*;
use bimap::BiHashMap;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::sync::LazyLock;

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct AudioSource {
Expand Down Expand Up @@ -121,11 +123,56 @@ fn construct_audio_source(
url: format!("http://localhost:8080/{}/{}", main_dir, file_name),
};
}

// dict files
let display = format!("{} {}", dict_name, entry_display);
AudioSource {
name: display,
url: format!("http://localhost:8080/{}/{}", main_dir, file_name),
}
}

pub fn convert_kana(term: &str) -> String {
let chars = term.chars();

chars
.map(|c| {
let mut tmp = [0u8];
let str = c.encode_utf8(&mut tmp);
match KANA_MAP.get_by_left(str) {
Some(hg) => *hg,
None => *KANA_MAP.get_by_right(str).unwrap(),
}
})
.collect::<Vec<&str>>()
.concat()
}

#[rustfmt::skip]
pub static KANA_MAP: LazyLock<BiHashMap<&'static str, &'static str>> = LazyLock::new(|| {
BiHashMap::from_iter([
("ア", "あ"), ("イ", "い"), ("ウ", "う"), ("エ", "え"), ("オ", "お"),
("カ", "か"), ("キ", "き"), ("ク", "く"), ("ケ", "け"), ("コ", "こ"),
("サ", "さ"), ("シ", "し"), ("ス", "す"), ("セ", "せ"), ("ソ", "そ"),
("タ", "た"), ("チ", "ち"), ("ツ", "つ"), ("テ", "て"), ("ト", "と"),
("ナ", "な"), ("ニ", "に"), ("ヌ", "ぬ"), ("ネ", "ね"), ("ノ", "の"),
("ハ", "は"), ("ヒ", "ひ"), ("フ", "ふ"), ("ヘ", "へ"), ("ホ", "ほ"),
("マ", "ま"), ("ミ", "み"), ("ム", "む"), ("メ", "め"), ("モ", "も"),
("ヤ", "や"), ("ユ", "ゆ"), ("ヨ", "よ"), ("ラ", "ら"), ("リ", "り"),
("ル", "る"), ("レ", "れ"), ("ロ", "ろ"), ("ワ", "わ"), ("ヲ", "を"),
("ン", "ん"), ("ガ", "が"), ("ギ", "ぎ"), ("グ", "ぐ"), ("ゲ", "げ"),
("ゴ", "ご"), ("ザ", "ざ"), ("ジ", "じ"), ("ズ", "ず"), ("ゼ", "ぜ"),
("ゾ", "ぞ"), ("ダ", "だ"), ("ヂ", "ぢ"), ("ヅ", "づ"), ("デ", "で"),
("ド", "ど"), ("バ", "ば"), ("ビ", "び"), ("ブ", "ぶ"), ("ベ", "べ"),
("ボ", "ぼ"), ("パ", "ぱ"), ("ピ", "ぴ"), ("プ", "ぷ"), ("ペ", "ぺ"),
("ポ", "ぽ"), ("キャ", "きゃ"), ("キュ", "きゅ"), ("キョ", "きょ"),
("シャ", "しゃ"), ("シュ", "しゅ"), ("ショ", "しょ"), ("チャ", "ちゃ"),
("チュ", "ちゅ"), ("チョ", "ちょ"), ("ニャ", "にゃ"), ("ニュ", "にゅ"),
("ニョ", "にょ"), ("ヒャ", "ひゃ"), ("ヒュ", "ひゅ"), ("ヒョ", "ひょ"),
("ミャ", "みゃ"), ("ミュ", "みゅ"), ("ミョ", "みょ"), ("リャ", "りゃ"),
("リュ", "りゅ"), ("リョ", "りょ"), ("ギャ", "ぎゃ"), ("ギュ", "ぎゅ"),
("ギョ", "ぎょ"), ("ジャ", "じゃ"), ("ジュ", "じゅ"), ("ジョ", "じょ"),
("ビャ", "びゃ"), ("ビュ", "びゅ"), ("ビョ", "びょ"), ("ピャ", "ぴゃ"),
("ピュ", "ぴゅ"), ("ピョ", "ぴょ"),
])
});
Loading

0 comments on commit 3f35e56

Please sign in to comment.