diff --git a/CHANGELOG.md b/CHANGELOG.md
index 81b08973..9542554a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,9 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [1.3.0]
+
+### Added
+
+- New feature for automatically accepting invite requests while on orange/green status, optionally based on a white- or blacklist.
+- Korean language support, thanks to [@soumt-r](https://github.com/soumt-r).
+
+### Changed
+
+- Made sleeping position animation automations automatically trigger when the automation is enabled.
+- Prevent Oyasumi from being opened twice and instead focus the window for the instance already running.
+
## [1.2.2]
### Fixed
+
- Fixed issue where the main window would load before the app was ready, due to a bug in a new version of the `tao` crate.
## [1.2.1]
diff --git a/README.md b/README.md
index ec29bd10..ca597f70 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,11 @@
- This is the main repository for Oyasumi. It is an open source tool to assist with sleeping in virtual reality (VRChat).
+ Oyasumi is an open source tool to assist with sleeping in virtual reality (VRChat).
+
+ Oyasumiは、バーチャルリアリティの中で睡眠をアシストするオープンソースソフトウェアです。
+
+ booth.pmのストアページには、日本語の説明文が掲載されています。
@@ -28,9 +32,39 @@
Grab the latest installer over on the [Releases](https://github.com/Raphiiko/Oyasumi/releases) page.
-|Sleeping Animations |GPU Power Limiting |Sleep Detection |Battery Automations |Device Overview |Status Automations |
-|-------------------------------------------------------------------------------------------------------------------------------- |---------------------------------------------------------------------------------------------------------------------------- |----------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
-| | |||||
+
+
+
+
Device Overview
+
Sleeping Animations
+
GPU Power Limiting
+
Sleep Detection
+
Battery Automations
+
+
+
+
+
+
+
+
+
+
+
+
Status Automations
+
General Settings
+
Auto Accept Invite Requests
+
+
+
+
+
+
+
+
+
+
+
## Features
@@ -57,6 +91,9 @@ Grab the latest installer over on the [Releases](https://github.com/Raphiiko/Oya
- Automatically change your status based on the number of players in your world:
_Switch to blue when you are sleeping alone so your friends can join you, and switch to orange when there's enough people around!_
- :wrench: Turning off all trackers and/or controllers with a single click.
+- :email: Automatically accept invite requests
+ - Automatically let friends in while you are asleep!
+ - Configure whose invite requests are accepted using a black- or whitelist.
- :zzz: Manage automations with a sleep mode in various ways:
- Detect falling asleep:
- When a controller or tracker battery percentage falls below a threshold
@@ -70,6 +107,7 @@ Grab the latest installer over on the [Releases](https://github.com/Raphiiko/Oya
- English
- Dutch (Nederlands)
- Japanese (日本語)
+ - Korean (한국어) (Community contribution by [Soumt](https://github.com/soumt-r))
If you would like to help out with adding more languages and/or missing translations, please check out [the wiki page on adding translations](https://github.com/Raphiiko/Oyasumi/wiki/Adding-Translations) for instructions on how to get started!
diff --git a/docs/img/screenshot_auto_accept_invite_requests.png b/docs/img/screenshot_auto_accept_invite_requests.png
new file mode 100644
index 00000000..a8f0b0be
Binary files /dev/null and b/docs/img/screenshot_auto_accept_invite_requests.png differ
diff --git a/docs/img/screenshot_friend_select.png b/docs/img/screenshot_friend_select.png
new file mode 100644
index 00000000..2f100d2e
Binary files /dev/null and b/docs/img/screenshot_friend_select.png differ
diff --git a/package.json b/package.json
index a962b49a..f10b54be 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "oyasumi",
- "version": "1.2.2",
+ "version": "1.3.0",
"author": "Raphiiko",
"license": "MIT",
"type": "module",
@@ -26,6 +26,7 @@
"@angular/router": "^14.2.9",
"@fontsource/fira-code": "^4.5.12",
"@fontsource/noto-sans-jp": "^4.5.12",
+ "@fontsource/noto-sans-kr": "^4.5.12",
"@fontsource/poppins": "^4.5.10",
"@fortawesome/fontawesome-free": "^6.2.0",
"@ngx-translate/core": "^14.0.0",
@@ -33,6 +34,7 @@
"@tauri-apps/api": "^1.2.0",
"cookie": "^0.5.0",
"flag-icons": "^6.6.6",
+ "fuse.js": "^6.6.2",
"lodash": "^4.17.21",
"marked": "^4.2.2",
"material-icons": "^1.12.1",
diff --git a/src-elevated-sidecar/Cargo.toml b/src-elevated-sidecar/Cargo.toml
index a7ab2dbb..d7716b87 100644
--- a/src-elevated-sidecar/Cargo.toml
+++ b/src-elevated-sidecar/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "oyasumi-elevated-sidecar"
-version = "1.2.2"
+version = "1.3.0"
authors = ["Raphiiko"]
license = "MIT"
edition = "2021"
diff --git a/src-shared/Cargo.toml b/src-shared/Cargo.toml
index 0192564f..ecd135cf 100644
--- a/src-shared/Cargo.toml
+++ b/src-shared/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "oyasumi-shared"
-version = "1.2.2"
+version = "1.3.0"
authors = ["Raphiiko"]
edition = "2021"
license = "MIT"
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index ecfb7d39..2c17fc27 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -53,6 +53,100 @@ version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
+[[package]]
+name = "async-broadcast"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61"
+dependencies = [
+ "event-listener",
+ "futures-core",
+ "parking_lot",
+]
+
+[[package]]
+name = "async-channel"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b"
+dependencies = [
+ "async-lock",
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-io"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794"
+dependencies = [
+ "async-lock",
+ "autocfg",
+ "concurrent-queue",
+ "futures-lite",
+ "libc",
+ "log",
+ "parking",
+ "polling",
+ "slab",
+ "socket2",
+ "waker-fn",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
+dependencies = [
+ "event-listener",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-recursion"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async-task"
+version = "4.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
+
+[[package]]
+name = "async-trait"
+version = "0.1.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "atk"
version = "0.15.1"
@@ -351,6 +445,15 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "concurrent-queue"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b"
+dependencies = [
+ "crossbeam-utils",
+]
+
[[package]]
name = "convert_case"
version = "0.4.0"
@@ -622,6 +725,17 @@ dependencies = [
"byteorder",
]
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "derive_more"
version = "0.99.17"
@@ -645,6 +759,15 @@ dependencies = [
"crypto-common",
]
+[[package]]
+name = "dirs"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
+dependencies = [
+ "dirs-sys",
+]
+
[[package]]
name = "dirs-next"
version = "2.0.0"
@@ -655,6 +778,17 @@ dependencies = [
"dirs-sys-next",
]
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
@@ -714,6 +848,33 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "enumflags2"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb"
+dependencies = [
+ "enumflags2_derive",
+ "serde",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
[[package]]
name = "fastrand"
version = "1.8.0"
@@ -852,6 +1013,21 @@ version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
+[[package]]
+name = "futures-lite"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
[[package]]
name = "futures-macro"
version = "0.3.25"
@@ -1228,6 +1404,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
[[package]]
name = "html5ever"
version = "0.25.2"
@@ -1639,6 +1821,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+[[package]]
+name = "md5"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
+
[[package]]
name = "memchr"
version = "2.5.0"
@@ -1660,6 +1848,16 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+[[package]]
+name = "mime_guess"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -1754,6 +1952,20 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
+[[package]]
+name = "nix"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
+dependencies = [
+ "autocfg",
+ "bitflags",
+ "cfg-if",
+ "libc",
+ "memoffset",
+ "pin-utils",
+]
+
[[package]]
name = "nodrop"
version = "0.1.14"
@@ -1979,6 +2191,16 @@ dependencies = [
"cmake",
]
+[[package]]
+name = "ordered-stream"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01ca8c99d73c6e92ac1358f9f692c22c0bfd9c4701fa086f5d365c0d4ea818ea"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
[[package]]
name = "os_pipe"
version = "1.1.1"
@@ -1997,7 +2219,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "oyasumi"
-version = "1.2.2"
+version = "1.3.0"
dependencies = [
"chrono",
"cronjob",
@@ -2005,6 +2227,9 @@ dependencies = [
"hyper",
"lazy_static",
"log",
+ "md5",
+ "mime",
+ "mime_guess",
"openvr",
"openvr_sys",
"oyasumi-shared",
@@ -2017,16 +2242,19 @@ dependencies = [
"tauri-build",
"tauri-plugin-fs-extra",
"tauri-plugin-log",
+ "tauri-plugin-single-instance",
"tauri-plugin-store",
"time 0.3.17",
"tokio",
+ "url",
+ "urlencoding",
"winapi",
"windows-sys 0.36.1",
]
[[package]]
name = "oyasumi-shared"
-version = "1.2.2"
+version = "1.3.0"
dependencies = [
"openvr",
"serde",
@@ -2057,6 +2285,12 @@ dependencies = [
"system-deps 6.0.3",
]
+[[package]]
+name = "parking"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
+
[[package]]
name = "parking_lot"
version = "0.12.1"
@@ -2262,6 +2496,20 @@ dependencies = [
"miniz_oxide 0.6.2",
]
+[[package]]
+name = "polling"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "166ca89eb77fd403230b9c156612965a81e094ec6ec3aa13663d4c8b113fa748"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "libc",
+ "log",
+ "wepoll-ffi",
+ "windows-sys 0.42.0",
+]
+
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -2824,6 +3072,17 @@ dependencies = [
"stable_deref_trait",
]
+[[package]]
+name = "sha1"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
[[package]]
name = "sha2"
version = "0.10.6"
@@ -2937,6 +3196,12 @@ dependencies = [
"loom",
]
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
[[package]]
name = "string_cache"
version = "0.8.4"
@@ -3214,6 +3479,16 @@ dependencies = [
"time 0.3.17",
]
+[[package]]
+name = "tauri-plugin-single-instance"
+version = "0.0.0"
+source = "git+https://github.com/tauri-apps/tauri-plugin-single-instance?branch=dev#941e10ec8d00fe58d7a25d22d71a5eba80bb9bf5"
+dependencies = [
+ "tauri",
+ "windows-sys 0.42.0",
+ "zbus",
+]
+
[[package]]
name = "tauri-plugin-store"
version = "0.0.0"
@@ -3575,6 +3850,25 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
+[[package]]
+name = "uds_windows"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d"
+dependencies = [
+ "tempfile",
+ "winapi",
+]
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
[[package]]
name = "unicode-bidi"
version = "0.3.8"
@@ -3620,6 +3914,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "urlencoding"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
+
[[package]]
name = "utf-8"
version = "0.7.6"
@@ -3687,6 +3987,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+[[package]]
+name = "waker-fn"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+
[[package]]
name = "walkdir"
version = "2.3.2"
@@ -3887,6 +4193,15 @@ dependencies = [
"windows-metadata",
]
+[[package]]
+name = "wepoll-ffi"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "winapi"
version = "0.3.9"
@@ -4272,6 +4587,69 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
+[[package]]
+name = "zbus"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbaaf011914a4f69a9ec6361225d8440be69673bda9c888275da99c6e8aad03f"
+dependencies = [
+ "async-broadcast",
+ "async-channel",
+ "async-executor",
+ "async-io",
+ "async-lock",
+ "async-recursion",
+ "async-task",
+ "async-trait",
+ "byteorder",
+ "derivative",
+ "dirs",
+ "enumflags2",
+ "event-listener",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "hex",
+ "nix",
+ "once_cell",
+ "ordered-stream",
+ "rand 0.8.5",
+ "serde",
+ "serde_repr",
+ "sha1",
+ "static_assertions",
+ "tracing",
+ "uds_windows",
+ "winapi",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a64de32c103d3a845f37adf0f46397d6346c8d48028e218db44dcde981733880"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn",
+]
+
+[[package]]
+name = "zbus_names"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c737644108627748a660d038974160e0cbb62605536091bdfa28fd7f64d43c8"
+dependencies = [
+ "serde",
+ "static_assertions",
+ "zvariant",
+]
+
[[package]]
name = "zip"
version = "0.6.3"
@@ -4282,3 +4660,29 @@ dependencies = [
"crc32fast",
"crossbeam-utils",
]
+
+[[package]]
+name = "zvariant"
+version = "3.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f8c89c183461e11867ded456db252eae90874bc6769b7adbea464caa777e51"
+dependencies = [
+ "byteorder",
+ "enumflags2",
+ "libc",
+ "serde",
+ "static_assertions",
+ "zvariant_derive",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "3.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "155247a5d1ab55e335421c104ccd95d64f17cebbd02f50cdbc1c33385f9c4d81"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 387313af..6da31a9e 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "oyasumi"
-version = "1.2.2"
+version = "1.3.0"
description = ""
authors = ["Raphiiko"]
license = "MIT"
@@ -29,6 +29,11 @@ sysinfo = "0.26.2"
chrono = "0.4.22"
log = "^0.4"
time = "0.3.16"
+url = "2.3.1"
+urlencoding = "2.1.2"
+mime = "0.3.16"
+md5 = "0.7.0"
+mime_guess = "2.0.4"
[dependencies.windows-sys]
version = "0.36.1"
@@ -46,6 +51,10 @@ features = ["full"]
version = "0.14.20"
features = ["full"]
+[dependencies.tauri-plugin-single-instance]
+git = "https://github.com/tauri-apps/tauri-plugin-single-instance"
+branch = "dev"
+
[dependencies.tauri-plugin-store]
git = "https://github.com/tauri-apps/tauri-plugin-store"
branch = "dev"
diff --git a/src-tauri/src/background/http_server.rs b/src-tauri/src/background/http_server.rs
index 85660121..869cb82a 100644
--- a/src-tauri/src/background/http_server.rs
+++ b/src-tauri/src/background/http_server.rs
@@ -4,9 +4,9 @@ use hyper::{
service::{make_service_fn, service_fn},
Body, Method, Request, Response, Server,
};
-use log::{info, error};
+use log::{error, info};
-use crate::{elevated_sidecar, MAIN_HTTP_SERVER_PORT};
+use crate::{elevated_sidecar, MAIN_HTTP_SERVER_PORT, IMAGE_CACHE};
pub fn spawn_http_server_thread() {
thread::spawn(move || {
@@ -36,6 +36,14 @@ async fn start_server() {
async fn request_handler(req: Request) -> Result, Infallible> {
match (req.method(), req.uri().path()) {
+ (&Method::GET, "/image_cache/get") => {
+ let image_cache;
+ {
+ let image_cache_guard = IMAGE_CACHE.lock().unwrap();
+ image_cache = image_cache_guard.as_ref().unwrap().clone();
+ }
+ image_cache.clone().handle_request(req).await
+ }
(&Method::POST, "/elevated_sidecar/init") => {
elevated_sidecar::handle_elevated_sidecar_init(req).await
}
diff --git a/src-tauri/src/commands/http.rs b/src-tauri/src/commands/http.rs
new file mode 100644
index 00000000..053648b5
--- /dev/null
+++ b/src-tauri/src/commands/http.rs
@@ -0,0 +1,10 @@
+use crate::MAIN_HTTP_SERVER_PORT;
+
+#[tauri::command]
+pub fn get_http_server_port() -> Option {
+ let port_guard = MAIN_HTTP_SERVER_PORT.lock().unwrap();
+ match port_guard.as_ref() {
+ Some(port) => Some(*port),
+ None => None,
+ }
+}
diff --git a/src-tauri/src/image_cache.rs b/src-tauri/src/image_cache.rs
new file mode 100644
index 00000000..c46a6080
--- /dev/null
+++ b/src-tauri/src/image_cache.rs
@@ -0,0 +1,346 @@
+use hyper::{
+ header::{CONTENT_TYPE, USER_AGENT},
+ Body, Request, Response,
+};
+use log::{error, info};
+use mime::Mime;
+use mime_guess;
+use serde_json::json;
+use std::{collections::HashMap, convert::Infallible, ffi::OsString, path::Path, str::FromStr};
+use urlencoding::decode;
+
+#[derive(Debug, Clone)]
+pub struct ImageCache {
+ cache_path_str: OsString,
+}
+
+impl ImageCache {
+ pub fn new(cache_path_str: OsString) -> ImageCache {
+ ImageCache { cache_path_str }
+ }
+
+ fn get_image(&self, url: String) -> Option<(Vec, Mime)> {
+ // Determine paths
+ let url_hash = format!("{:x}", md5::compute(url));
+ let storage_path = Path::new(&self.cache_path_str).join(url_hash);
+ let manifest_path = storage_path.join("manifest.json");
+ // If storage directory or the manifest don't exist, return None
+ if !storage_path.exists() || !manifest_path.exists() {
+ return None;
+ }
+ // Read json from manifest
+ let manifest = match std::fs::read_to_string(&manifest_path) {
+ Ok(manifest) => manifest,
+ Err(_) => {
+ error!(
+ "[Core] Could not read JSON from manifest file. {}",
+ manifest_path.display()
+ );
+ return None;
+ }
+ };
+ let manifest: serde_json::Value = serde_json::from_str(&manifest).unwrap();
+ // Get filename from manifest
+ let file_name = match manifest["filename"].as_str() {
+ Some(file_name) => file_name,
+ None => {
+ error!(
+ "[Core] Could not get filename from manifest file. {}",
+ manifest_path.display()
+ );
+ return None;
+ }
+ };
+ // Get image path
+ let image_path = storage_path.join(file_name);
+ // If image doesn't exist, return None
+ if !image_path.exists() {
+ return None;
+ }
+ // Check if image is expired
+ let ttl = match manifest["ttl"].as_u64() {
+ Some(ttl) => ttl,
+ None => {
+ error!(
+ "[Core] Could not get TTL from manifest file. {}",
+ manifest_path.display()
+ );
+ return None;
+ }
+ };
+ let created = match manifest["created"].as_u64() {
+ Some(created) => created,
+ None => {
+ error!(
+ "[Core] Could not get created timestamp from manifest file. {}",
+ manifest_path.display()
+ );
+ return None;
+ }
+ };
+ let now = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap()
+ .as_secs();
+ if now - created > ttl {
+ return None;
+ }
+ // Get mime type from manifest
+ let mime = match manifest["mime"].as_str() {
+ Some(mime) => match Mime::from_str(mime) {
+ Ok(mime) => mime,
+ Err(_) => {
+ error!(
+ "[Core] Could not parse MIME type from manifest file. {}",
+ manifest_path.display()
+ );
+ return None;
+ }
+ },
+ None => {
+ error!(
+ "[Core] Could not get MIME type from manifest file. {}",
+ manifest_path.display()
+ );
+ return None;
+ }
+ };
+ // Read image data
+ let image_data = match std::fs::read(&image_path) {
+ Ok(image_data) => image_data,
+ Err(_) => {
+ error!(
+ "[Core] Could not read image data from file. {}",
+ image_path.display()
+ );
+ return None;
+ }
+ };
+ // Return image data and mime type
+ Some((image_data, mime))
+ }
+
+ fn store_image(&self, url: &str, ttl: u64, mime: Mime, image_data: Vec) {
+ // Determine paths
+ let url_hash = format!("{:x}", md5::compute(url));
+ let storage_path = Path::new(&self.cache_path_str).join(&url_hash);
+ let manifest_path = storage_path.join("manifest.json");
+ let file_ext = self.get_ext_for_mime(mime.clone());
+ let file_name = format!("image.{}", file_ext);
+ let image_path = storage_path.join(&file_name);
+ // Delete current storage directory if it exists
+ if storage_path.exists() {
+ std::fs::remove_dir_all(&storage_path).unwrap();
+ }
+ // Create storage directory
+ std::fs::create_dir_all(&storage_path).unwrap();
+ // Store image
+ std::fs::write(&image_path, image_data).unwrap();
+ // Store manifest
+ let manifest = json!({
+ "url": url,
+ "hash": url_hash.clone(),
+ "ttl": ttl,
+ "mime": mime.to_string(),
+ "created": chrono::Utc::now().timestamp(),
+ "filename": file_name.clone(),
+ });
+ std::fs::write(&manifest_path, manifest.to_string()).unwrap();
+ }
+
+ pub fn clean(&self, only_expired: bool) {
+ // Create directory at cache_path if it doesn't exist
+ let cache_path = Path::new(&self.cache_path_str);
+ if !cache_path.exists() {
+ std::fs::create_dir_all(&cache_path).unwrap();
+ return;
+ }
+ let mut deleted = 0;
+ // Iterate over all directories in cache_path
+ for entry in std::fs::read_dir(&cache_path).unwrap() {
+ let entry = entry.unwrap();
+ let path = entry.path();
+ // Skip if path is not a directory
+ if !path.is_dir() {
+ continue;
+ }
+ // Read manifest
+ let manifest_path = path.join("manifest.json");
+ let manifest = match std::fs::read_to_string(&manifest_path) {
+ Ok(manifest) => manifest,
+ Err(_) => {
+ error!(
+ "[Core] Could not read JSON from manifest file. {}",
+ manifest_path.display()
+ );
+ // Delete path if manifest could not be read
+ std::fs::remove_dir_all(&path).unwrap();
+ continue;
+ }
+ };
+ let manifest: serde_json::Value = serde_json::from_str(&manifest).unwrap();
+ // Check if image is expired
+ let ttl = match manifest["ttl"].as_u64() {
+ Some(ttl) => ttl,
+ None => {
+ error!(
+ "[Core] Could not get TTL from manifest file. {}",
+ manifest_path.display()
+ );
+ continue;
+ }
+ };
+ let created = match manifest["created"].as_u64() {
+ Some(created) => created,
+ None => {
+ error!(
+ "[Core] Could not get created timestamp from manifest file. {}",
+ manifest_path.display()
+ );
+ continue;
+ }
+ };
+ let now = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap()
+ .as_secs();
+ if only_expired && now - created < ttl {
+ continue;
+ }
+ // Delete storage directory
+ std::fs::remove_dir_all(&path).unwrap();
+ deleted += 1;
+ }
+ if deleted > 0 {
+ info!("[Core] Deleted {} image(s) from the cache.", deleted);
+ }
+ }
+
+ fn get_ext_for_mime(&self, mime: Mime) -> String {
+ match mime_guess::get_mime_extensions(&mime) {
+ Some(exts) => exts[0].to_string(),
+ None => "bin".to_string(),
+ }
+ }
+
+ pub async fn handle_request(&self, req: Request) -> Result, Infallible> {
+ // Parse query parameters
+ let params: HashMap = req
+ .uri()
+ .query()
+ .map(|v| {
+ url::form_urlencoded::parse(v.as_bytes())
+ .into_owned()
+ .collect()
+ })
+ .unwrap_or_else(HashMap::new);
+
+ // Get URL parameter
+ let url = match params.get("url") {
+ Some(url) => decode(url).expect("UTF-8"),
+ None => {
+ return Ok(Response::builder()
+ .status(400)
+ .body("Missing 'url' query parameter".into())
+ .unwrap())
+ }
+ };
+ // Get ttl parameter
+ let ttl = match params.get("ttl") {
+ Some(ttl) => match ttl.parse::() {
+ Ok(ttl) => ttl,
+ Err(_) => {
+ return Ok(Response::builder()
+ .status(400)
+ .body("Invalid 'ttl' query parameter".into())
+ .unwrap())
+ }
+ },
+ None => {
+ return Ok(Response::builder()
+ .status(400)
+ .body("Missing 'ttl' query parameter".into())
+ .unwrap())
+ }
+ };
+ // Return cached data if present
+ if let Some((image_data, image_mime)) = self.get_image(String::from(url.as_ref())) {
+ return Ok(Response::builder()
+ .status(200)
+ .header(CONTENT_TYPE, image_mime.to_string())
+ .body(image_data.into())
+ .unwrap());
+ }
+ // Get image from URL
+ let client = reqwest::Client::new();
+ let (image_data, image_mime) = match client
+ .get(url.as_ref())
+ .header(
+ USER_AGENT,
+ format!(
+ "Oyasumi/{} (https://github.com/Raphiiko/Oyasumi)",
+ env!("CARGO_PKG_VERSION"),
+ ),
+ )
+ .send()
+ .await
+ {
+ Ok(response) => {
+ let headers = response.headers().clone();
+ let bytes = response.bytes();
+ match bytes.await {
+ Ok(bytes) => {
+ let content_type = match headers.get(CONTENT_TYPE) {
+ None => {
+ return Ok(Response::builder()
+ .status(500)
+ .body("Failed to get image content type (1)".into())
+ .unwrap())
+ }
+ Some(content_type) => {
+ let content_type_str = match content_type.to_str() {
+ Ok(content_type_str) => content_type_str,
+ Err(_) => {
+ return Ok(Response::builder()
+ .status(500)
+ .body("Failed to get image content type (2)".into())
+ .unwrap())
+ }
+ };
+ match Mime::from_str(content_type_str) {
+ Ok(content_type) => content_type,
+ Err(_) => {
+ return Ok(Response::builder()
+ .status(500)
+ .body("Failed to get image content type (3)".into())
+ .unwrap())
+ }
+ }
+ }
+ };
+ (bytes.to_vec(), content_type)
+ }
+ Err(_) => {
+ return Ok(Response::builder()
+ .status(500)
+ .body("Failed to get image data".into())
+ .unwrap())
+ }
+ }
+ }
+ Err(_) => {
+ return Ok(Response::builder()
+ .status(500)
+ .body("Failed to get image".into())
+ .unwrap())
+ }
+ };
+ // Cache image
+ self.store_image(url.as_ref(), ttl, image_mime, image_data.clone());
+ // Return image
+ Ok(Response::builder()
+ .status(200)
+ .body(image_data.into())
+ .unwrap())
+ }
+}
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 15b5af98..a1bb72c3 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -6,10 +6,11 @@
#[macro_use(lazy_static)]
extern crate lazy_static;
+use crate::image_cache::ImageCache;
use cronjob::CronJob;
use log::{error, info, LevelFilter};
use std::{net::UdpSocket, sync::Mutex};
-use tauri::Manager;
+use tauri::{api::dialog::blocking::MessageDialogBuilder, Manager};
use tauri_plugin_fs_extra::FsExtra;
use tauri_plugin_log::{LogTarget, LoggerBuilder, RotationStrategy};
use tauri_plugin_store::PluginBuilder;
@@ -22,6 +23,7 @@ mod commands {
pub mod os;
pub mod osc;
pub mod splash;
+ pub mod http;
}
mod background {
pub mod http_server;
@@ -30,6 +32,7 @@ mod background {
pub mod osc;
}
mod elevated_sidecar;
+mod image_cache;
lazy_static! {
static ref OVR_CONTEXT: Mutex