fix: offload process listing from main thread
Move native process enumeration into a worker so process watching does not block the main process, and add LF text-integrity checks for normalized files.
@@ -1,57 +1,57 @@
|
||||
# Hydra Project Rules
|
||||
|
||||
## Logging
|
||||
|
||||
- **Always use `logger` instead of `console` for logging** in both main and renderer processes
|
||||
- In main process: `import { logger } from "@main/services";`
|
||||
- In renderer process: `import { logger } from "@renderer/logger";`
|
||||
- Replace all instances of:
|
||||
- `console.log()` → `logger.log()`
|
||||
- `console.error()` → `logger.error()`
|
||||
- `console.warn()` → `logger.warn()`
|
||||
- `console.info()` → `logger.info()`
|
||||
- `console.debug()` → `logger.debug()`
|
||||
- Do not use `console` for any logging purposes
|
||||
|
||||
## Internationalization (i18n)
|
||||
|
||||
- All user-facing strings must be translated using i18next
|
||||
- Use the `useTranslation` hook in React components: `const { t } = useTranslation("namespace");`
|
||||
- Add new translation keys to `src/locales/en/translation.json`
|
||||
- Never hardcode English strings in the UI code
|
||||
- Placeholder text in form fields must also be translated
|
||||
|
||||
## Code Style
|
||||
|
||||
- Use ESLint and Prettier for code formatting
|
||||
- Follow TypeScript strict mode conventions
|
||||
- Use async/await instead of promises when possible
|
||||
- Prefer named exports over default exports for utilities and services
|
||||
|
||||
## ESLint Issues
|
||||
|
||||
- **Always try to fix ESLint errors properly before disabling rules**
|
||||
- When encountering ESLint errors, explore these solutions in order:
|
||||
1. **Fix the code to comply with the rule** (e.g., add missing required elements, fix accessibility issues)
|
||||
2. **Use minimal markup to satisfy the rule** (e.g., add empty `<track>` elements for videos without captions, add `role` attributes)
|
||||
3. **Only disable the rule as a last resort** when no reasonable solution exists
|
||||
- When disabling a rule, always include a comment explaining why it's necessary
|
||||
- Examples of proper fixes:
|
||||
- For `jsx-a11y/media-has-caption`: Add `<track kind="captions" />` even if no captions are available
|
||||
- For `jsx-a11y/alt-text`: Add meaningful alt text or `alt=""` for decorative images
|
||||
- For accessibility rules: Add appropriate ARIA attributes rather than disabling
|
||||
|
||||
## TypeScript Array Syntax
|
||||
|
||||
- **Always use `T[]` syntax instead of `Array<T>`** for array types
|
||||
- Prefer: `string[]`, `number[]`, `MyType[]`
|
||||
- Avoid: `Array<string>`, `Array<number>`, `Array<MyType>`
|
||||
- This applies to all type annotations, type assertions, and generic type parameters
|
||||
|
||||
## Comments
|
||||
|
||||
- Keep comments concise and purposeful; avoid verbose explanations.
|
||||
- Focus on the "why" or non-obvious context, not restating the code.
|
||||
- Prefer self-explanatory naming and structure over excessive comments.
|
||||
- Do not comment every line or obvious behavior; remove stale comments.
|
||||
- Use docblocks only where they add value (public APIs, complex logic).
|
||||
# Hydra Project Rules
|
||||
|
||||
## Logging
|
||||
|
||||
- **Always use `logger` instead of `console` for logging** in both main and renderer processes
|
||||
- In main process: `import { logger } from "@main/services";`
|
||||
- In renderer process: `import { logger } from "@renderer/logger";`
|
||||
- Replace all instances of:
|
||||
- `console.log()` → `logger.log()`
|
||||
- `console.error()` → `logger.error()`
|
||||
- `console.warn()` → `logger.warn()`
|
||||
- `console.info()` → `logger.info()`
|
||||
- `console.debug()` → `logger.debug()`
|
||||
- Do not use `console` for any logging purposes
|
||||
|
||||
## Internationalization (i18n)
|
||||
|
||||
- All user-facing strings must be translated using i18next
|
||||
- Use the `useTranslation` hook in React components: `const { t } = useTranslation("namespace");`
|
||||
- Add new translation keys to `src/locales/en/translation.json`
|
||||
- Never hardcode English strings in the UI code
|
||||
- Placeholder text in form fields must also be translated
|
||||
|
||||
## Code Style
|
||||
|
||||
- Use ESLint and Prettier for code formatting
|
||||
- Follow TypeScript strict mode conventions
|
||||
- Use async/await instead of promises when possible
|
||||
- Prefer named exports over default exports for utilities and services
|
||||
|
||||
## ESLint Issues
|
||||
|
||||
- **Always try to fix ESLint errors properly before disabling rules**
|
||||
- When encountering ESLint errors, explore these solutions in order:
|
||||
1. **Fix the code to comply with the rule** (e.g., add missing required elements, fix accessibility issues)
|
||||
2. **Use minimal markup to satisfy the rule** (e.g., add empty `<track>` elements for videos without captions, add `role` attributes)
|
||||
3. **Only disable the rule as a last resort** when no reasonable solution exists
|
||||
- When disabling a rule, always include a comment explaining why it's necessary
|
||||
- Examples of proper fixes:
|
||||
- For `jsx-a11y/media-has-caption`: Add `<track kind="captions" />` even if no captions are available
|
||||
- For `jsx-a11y/alt-text`: Add meaningful alt text or `alt=""` for decorative images
|
||||
- For accessibility rules: Add appropriate ARIA attributes rather than disabling
|
||||
|
||||
## TypeScript Array Syntax
|
||||
|
||||
- **Always use `T[]` syntax instead of `Array<T>`** for array types
|
||||
- Prefer: `string[]`, `number[]`, `MyType[]`
|
||||
- Avoid: `Array<string>`, `Array<number>`, `Array<MyType>`
|
||||
- This applies to all type annotations, type assertions, and generic type parameters
|
||||
|
||||
## Comments
|
||||
|
||||
- Keep comments concise and purposeful; avoid verbose explanations.
|
||||
- Focus on the "why" or non-obvious context, not restating the code.
|
||||
- Prefer self-explanatory naming and structure over excessive comments.
|
||||
- Do not comment every line or obvious behavior; remove stale comments.
|
||||
- Use docblocks only where they add value (public APIs, complex logic).
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
@@ -1,7 +1,7 @@
|
||||
MAIN_VITE_API_URL=
|
||||
MAIN_VITE_AUTH_URL=
|
||||
MAIN_VITE_WS_URL=
|
||||
MAIN_VITE_NIMBUS_API_URL=
|
||||
RENDERER_VITE_REAL_DEBRID_REFERRAL_ID=
|
||||
RENDERER_VITE_TORBOX_REFERRAL_CODE=
|
||||
MAIN_VITE_LAUNCHER_SUBDOMAIN=
|
||||
MAIN_VITE_API_URL=
|
||||
MAIN_VITE_AUTH_URL=
|
||||
MAIN_VITE_WS_URL=
|
||||
MAIN_VITE_NIMBUS_API_URL=
|
||||
RENDERER_VITE_REAL_DEBRID_REFERRAL_ID=
|
||||
RENDERER_VITE_TORBOX_REFERRAL_CODE=
|
||||
MAIN_VITE_LAUNCHER_SUBDOMAIN=
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
node_modules
|
||||
dist
|
||||
out
|
||||
.gitignore
|
||||
migration.stub
|
||||
hydra-python-rpc/
|
||||
hydra-native/
|
||||
src/main/generated/
|
||||
node_modules
|
||||
dist
|
||||
out
|
||||
.gitignore
|
||||
migration.stub
|
||||
hydra-python-rpc/
|
||||
hydra-native/
|
||||
src/main/generated/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Text Integrity
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'release/**'
|
||||
|
||||
jobs:
|
||||
text-integrity:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Verify LF line endings
|
||||
run: |
|
||||
files_with_cr="$(git grep -Il $'\r' -- . || true)"
|
||||
|
||||
if [ -n "$files_with_cr" ]; then
|
||||
echo "Tracked text files must use LF line endings. Files containing CR characters:"
|
||||
echo "$files_with_cr"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify LICENSE hash
|
||||
run: |
|
||||
expected="32619612c2e0223e86c4908747ec14bef64c3c423fee80910c1aa944769b66f9"
|
||||
actual="$(sha256sum LICENSE | cut -d ' ' -f1)"
|
||||
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "LICENSE hash changed."
|
||||
echo "Expected: $expected"
|
||||
echo "Actual: $actual"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,3 +1,3 @@
|
||||
[submodule "proto"]
|
||||
path = proto
|
||||
url = https://github.com/hydralauncher/hydra-protos.git
|
||||
[submodule "proto"]
|
||||
path = proto
|
||||
url = https://github.com/hydralauncher/hydra-protos.git
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
out
|
||||
dist
|
||||
seeds
|
||||
pnpm-lock.yaml
|
||||
LICENSE.md
|
||||
tsconfig.json
|
||||
tsconfig.*.json
|
||||
out
|
||||
dist
|
||||
seeds
|
||||
pnpm-lock.yaml
|
||||
LICENSE.md
|
||||
tsconfig.json
|
||||
tsconfig.*.json
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Los Broxas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Los Broxas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
!macro customUnInstall
|
||||
${ifNot} ${isUpdated}
|
||||
RMDir /r "$LOCALAPPDATA\hydralauncher-updater"
|
||||
${endIf}
|
||||
!macroend
|
||||
!macro customUnInstall
|
||||
${ifNot} ${isUpdated}
|
||||
RMDir /r "$LOCALAPPDATA\hydralauncher-updater"
|
||||
${endIf}
|
||||
!macroend
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
[package]
|
||||
name = "hydra-native"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
image = { version = "0.25.8", default-features = false, features = ["gif", "jpeg", "png", "webp"] }
|
||||
mime_guess = "2.0.5"
|
||||
napi = { version = "3.5.2", default-features = false, features = ["napi8", "tokio_rt"] }
|
||||
napi-derive = "3.3.2"
|
||||
sysinfo = "0.37.2"
|
||||
uuid = { version = "1.11.0", features = ["v4"] }
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.3.1"
|
||||
[package]
|
||||
name = "hydra-native"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
image = { version = "0.25.8", default-features = false, features = ["gif", "jpeg", "png", "webp"] }
|
||||
mime_guess = "2.0.5"
|
||||
napi = { version = "3.5.2", default-features = false, features = ["napi8", "tokio_rt"] }
|
||||
napi-derive = "3.3.2"
|
||||
sysinfo = "0.37.2"
|
||||
uuid = { version = "1.11.0", features = ["v4"] }
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.3.1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=src/lib.rs");
|
||||
}
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=src/lib.rs");
|
||||
}
|
||||
|
||||
@@ -1,224 +1,224 @@
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{cmp::Ordering, collections::HashMap};
|
||||
|
||||
use image::codecs::gif::GifDecoder;
|
||||
use image::codecs::png::PngDecoder;
|
||||
use image::codecs::webp::WebPDecoder;
|
||||
use image::{AnimationDecoder, ImageFormat, ImageReader};
|
||||
use napi::bindgen_prelude::Error;
|
||||
use napi_derive::napi;
|
||||
use sysinfo::{ProcessesToUpdate, System};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[napi(object)]
|
||||
pub struct ProcessedImageData {
|
||||
pub image_path: String,
|
||||
pub mime_type: String,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct NativeProcessPayload {
|
||||
pub exe: Option<String>,
|
||||
pub pid: u32,
|
||||
pub name: String,
|
||||
pub environ: Option<HashMap<String, String>>,
|
||||
pub cwd: Option<String>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn process_profile_image(
|
||||
image_path: String,
|
||||
target_extension: Option<String>,
|
||||
) -> napi::Result<ProcessedImageData> {
|
||||
let input_path = PathBuf::from(image_path);
|
||||
|
||||
if !input_path.exists() {
|
||||
return Err(Error::from_reason("Image file not found"));
|
||||
}
|
||||
|
||||
let format = detect_image_format(&input_path)?;
|
||||
let animated = is_animated_image(&input_path, format)?;
|
||||
|
||||
if !animated {
|
||||
return Ok(ProcessedImageData {
|
||||
image_path: input_path.to_string_lossy().to_string(),
|
||||
mime_type: mime_type_from_format_or_path(format, &input_path),
|
||||
});
|
||||
}
|
||||
|
||||
let extension = target_extension
|
||||
.map(|value| value.to_ascii_lowercase())
|
||||
.unwrap_or_else(|| "webp".to_string());
|
||||
|
||||
let output_format = output_format_from_extension(&extension)?;
|
||||
let output_path = build_temp_output_path(&extension);
|
||||
|
||||
let image = ImageReader::open(&input_path)
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?
|
||||
.with_guessed_format()
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?
|
||||
.decode()
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
image
|
||||
.save_with_format(&output_path, output_format)
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
Ok(ProcessedImageData {
|
||||
image_path: output_path.to_string_lossy().to_string(),
|
||||
mime_type: mime_type_from_format_or_path(Some(output_format), &output_path),
|
||||
})
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn list_processes() -> Vec<NativeProcessPayload> {
|
||||
let mut system = System::new_all();
|
||||
system.refresh_processes(ProcessesToUpdate::All, true);
|
||||
|
||||
let mut processes: Vec<NativeProcessPayload> = system
|
||||
.processes()
|
||||
.values()
|
||||
.map(|process| {
|
||||
let include_linux_extras = !cfg!(target_os = "windows");
|
||||
|
||||
NativeProcessPayload {
|
||||
exe: process
|
||||
.exe()
|
||||
.map(|value| value.to_string_lossy().to_string()),
|
||||
pid: process.pid().as_u32(),
|
||||
name: process.name().to_string_lossy().to_string(),
|
||||
cwd: if include_linux_extras {
|
||||
process
|
||||
.cwd()
|
||||
.map(|value| value.to_string_lossy().to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
environ: if include_linux_extras {
|
||||
let env_map: HashMap<String, String> = process
|
||||
.environ()
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
let entry_value = entry.to_string_lossy();
|
||||
entry_value.split_once('=').and_then(|(key, value)| {
|
||||
if key.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((key.to_string(), value.to_string()))
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
if env_map.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(env_map)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
processes.sort_by(|left, right| {
|
||||
let by_pid = left.pid.cmp(&right.pid);
|
||||
if by_pid == Ordering::Equal {
|
||||
left.name.cmp(&right.name)
|
||||
} else {
|
||||
by_pid
|
||||
}
|
||||
});
|
||||
|
||||
processes
|
||||
}
|
||||
|
||||
fn detect_image_format(path: &Path) -> napi::Result<Option<ImageFormat>> {
|
||||
let reader = ImageReader::open(path).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
let guessed = reader
|
||||
.with_guessed_format()
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
Ok(guessed.format())
|
||||
}
|
||||
|
||||
fn is_animated_image(path: &Path, format: Option<ImageFormat>) -> napi::Result<bool> {
|
||||
match format {
|
||||
Some(ImageFormat::Gif) => is_gif_animated(path),
|
||||
Some(ImageFormat::WebP) => is_webp_animated(path),
|
||||
Some(ImageFormat::Png) => is_apng(path),
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_gif_animated(path: &Path) -> napi::Result<bool> {
|
||||
let file = File::open(path).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
let decoder =
|
||||
GifDecoder::new(BufReader::new(file)).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
let mut frames = decoder.into_frames();
|
||||
let _ = frames.next().transpose();
|
||||
Ok(matches!(frames.next().transpose(), Ok(Some(_))))
|
||||
}
|
||||
|
||||
fn is_webp_animated(path: &Path) -> napi::Result<bool> {
|
||||
let file = File::open(path).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
let decoder = WebPDecoder::new(BufReader::new(file))
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
Ok(decoder.has_animation())
|
||||
}
|
||||
|
||||
fn is_apng(path: &Path) -> napi::Result<bool> {
|
||||
let file = File::open(path).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
let decoder =
|
||||
PngDecoder::new(BufReader::new(file)).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
decoder
|
||||
.is_apng()
|
||||
.map_err(|err| Error::from_reason(err.to_string()))
|
||||
}
|
||||
|
||||
fn output_format_from_extension(extension: &str) -> napi::Result<ImageFormat> {
|
||||
match extension {
|
||||
"png" => Ok(ImageFormat::Png),
|
||||
"jpg" | "jpeg" => Ok(ImageFormat::Jpeg),
|
||||
"webp" => Ok(ImageFormat::WebP),
|
||||
_ => Err(Error::from_reason("Unsupported target extension")),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_temp_output_path(extension: &str) -> PathBuf {
|
||||
let mut output_path = std::env::temp_dir();
|
||||
output_path.push(format!("{}.{}", Uuid::new_v4(), extension));
|
||||
output_path
|
||||
}
|
||||
|
||||
fn mime_type_from_format_or_path(format: Option<ImageFormat>, path: &Path) -> String {
|
||||
if let Some(value) = mime_type_from_image_format(format) {
|
||||
return value.to_string();
|
||||
}
|
||||
|
||||
mime_guess::from_path(path)
|
||||
.first_or_octet_stream()
|
||||
.essence_str()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn mime_type_from_image_format(format: Option<ImageFormat>) -> Option<&'static str> {
|
||||
match format {
|
||||
Some(ImageFormat::Png) => Some("image/png"),
|
||||
Some(ImageFormat::Jpeg) => Some("image/jpeg"),
|
||||
Some(ImageFormat::Gif) => Some("image/gif"),
|
||||
Some(ImageFormat::WebP) => Some("image/webp"),
|
||||
Some(ImageFormat::Bmp) => Some("image/bmp"),
|
||||
Some(ImageFormat::Ico) => Some("image/x-icon"),
|
||||
Some(ImageFormat::Tiff) => Some("image/tiff"),
|
||||
Some(ImageFormat::Avif) => Some("image/avif"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{cmp::Ordering, collections::HashMap};
|
||||
|
||||
use image::codecs::gif::GifDecoder;
|
||||
use image::codecs::png::PngDecoder;
|
||||
use image::codecs::webp::WebPDecoder;
|
||||
use image::{AnimationDecoder, ImageFormat, ImageReader};
|
||||
use napi::bindgen_prelude::Error;
|
||||
use napi_derive::napi;
|
||||
use sysinfo::{ProcessesToUpdate, System};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[napi(object)]
|
||||
pub struct ProcessedImageData {
|
||||
pub image_path: String,
|
||||
pub mime_type: String,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct NativeProcessPayload {
|
||||
pub exe: Option<String>,
|
||||
pub pid: u32,
|
||||
pub name: String,
|
||||
pub environ: Option<HashMap<String, String>>,
|
||||
pub cwd: Option<String>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn process_profile_image(
|
||||
image_path: String,
|
||||
target_extension: Option<String>,
|
||||
) -> napi::Result<ProcessedImageData> {
|
||||
let input_path = PathBuf::from(image_path);
|
||||
|
||||
if !input_path.exists() {
|
||||
return Err(Error::from_reason("Image file not found"));
|
||||
}
|
||||
|
||||
let format = detect_image_format(&input_path)?;
|
||||
let animated = is_animated_image(&input_path, format)?;
|
||||
|
||||
if !animated {
|
||||
return Ok(ProcessedImageData {
|
||||
image_path: input_path.to_string_lossy().to_string(),
|
||||
mime_type: mime_type_from_format_or_path(format, &input_path),
|
||||
});
|
||||
}
|
||||
|
||||
let extension = target_extension
|
||||
.map(|value| value.to_ascii_lowercase())
|
||||
.unwrap_or_else(|| "webp".to_string());
|
||||
|
||||
let output_format = output_format_from_extension(&extension)?;
|
||||
let output_path = build_temp_output_path(&extension);
|
||||
|
||||
let image = ImageReader::open(&input_path)
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?
|
||||
.with_guessed_format()
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?
|
||||
.decode()
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
image
|
||||
.save_with_format(&output_path, output_format)
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
Ok(ProcessedImageData {
|
||||
image_path: output_path.to_string_lossy().to_string(),
|
||||
mime_type: mime_type_from_format_or_path(Some(output_format), &output_path),
|
||||
})
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn list_processes() -> Vec<NativeProcessPayload> {
|
||||
let mut system = System::new_all();
|
||||
system.refresh_processes(ProcessesToUpdate::All, true);
|
||||
|
||||
let mut processes: Vec<NativeProcessPayload> = system
|
||||
.processes()
|
||||
.values()
|
||||
.map(|process| {
|
||||
let include_linux_extras = !cfg!(target_os = "windows");
|
||||
|
||||
NativeProcessPayload {
|
||||
exe: process
|
||||
.exe()
|
||||
.map(|value| value.to_string_lossy().to_string()),
|
||||
pid: process.pid().as_u32(),
|
||||
name: process.name().to_string_lossy().to_string(),
|
||||
cwd: if include_linux_extras {
|
||||
process
|
||||
.cwd()
|
||||
.map(|value| value.to_string_lossy().to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
environ: if include_linux_extras {
|
||||
let env_map: HashMap<String, String> = process
|
||||
.environ()
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
let entry_value = entry.to_string_lossy();
|
||||
entry_value.split_once('=').and_then(|(key, value)| {
|
||||
if key.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((key.to_string(), value.to_string()))
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
if env_map.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(env_map)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
processes.sort_by(|left, right| {
|
||||
let by_pid = left.pid.cmp(&right.pid);
|
||||
if by_pid == Ordering::Equal {
|
||||
left.name.cmp(&right.name)
|
||||
} else {
|
||||
by_pid
|
||||
}
|
||||
});
|
||||
|
||||
processes
|
||||
}
|
||||
|
||||
fn detect_image_format(path: &Path) -> napi::Result<Option<ImageFormat>> {
|
||||
let reader = ImageReader::open(path).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
let guessed = reader
|
||||
.with_guessed_format()
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
Ok(guessed.format())
|
||||
}
|
||||
|
||||
fn is_animated_image(path: &Path, format: Option<ImageFormat>) -> napi::Result<bool> {
|
||||
match format {
|
||||
Some(ImageFormat::Gif) => is_gif_animated(path),
|
||||
Some(ImageFormat::WebP) => is_webp_animated(path),
|
||||
Some(ImageFormat::Png) => is_apng(path),
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_gif_animated(path: &Path) -> napi::Result<bool> {
|
||||
let file = File::open(path).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
let decoder =
|
||||
GifDecoder::new(BufReader::new(file)).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
let mut frames = decoder.into_frames();
|
||||
let _ = frames.next().transpose();
|
||||
Ok(matches!(frames.next().transpose(), Ok(Some(_))))
|
||||
}
|
||||
|
||||
fn is_webp_animated(path: &Path) -> napi::Result<bool> {
|
||||
let file = File::open(path).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
let decoder = WebPDecoder::new(BufReader::new(file))
|
||||
.map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
Ok(decoder.has_animation())
|
||||
}
|
||||
|
||||
fn is_apng(path: &Path) -> napi::Result<bool> {
|
||||
let file = File::open(path).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
let decoder =
|
||||
PngDecoder::new(BufReader::new(file)).map_err(|err| Error::from_reason(err.to_string()))?;
|
||||
|
||||
decoder
|
||||
.is_apng()
|
||||
.map_err(|err| Error::from_reason(err.to_string()))
|
||||
}
|
||||
|
||||
fn output_format_from_extension(extension: &str) -> napi::Result<ImageFormat> {
|
||||
match extension {
|
||||
"png" => Ok(ImageFormat::Png),
|
||||
"jpg" | "jpeg" => Ok(ImageFormat::Jpeg),
|
||||
"webp" => Ok(ImageFormat::WebP),
|
||||
_ => Err(Error::from_reason("Unsupported target extension")),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_temp_output_path(extension: &str) -> PathBuf {
|
||||
let mut output_path = std::env::temp_dir();
|
||||
output_path.push(format!("{}.{}", Uuid::new_v4(), extension));
|
||||
output_path
|
||||
}
|
||||
|
||||
fn mime_type_from_format_or_path(format: Option<ImageFormat>, path: &Path) -> String {
|
||||
if let Some(value) = mime_type_from_image_format(format) {
|
||||
return value.to_string();
|
||||
}
|
||||
|
||||
mime_guess::from_path(path)
|
||||
.first_or_octet_stream()
|
||||
.essence_str()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn mime_type_from_image_format(format: Option<ImageFormat>) -> Option<&'static str> {
|
||||
match format {
|
||||
Some(ImageFormat::Png) => Some("image/png"),
|
||||
Some(ImageFormat::Jpeg) => Some("image/jpeg"),
|
||||
Some(ImageFormat::Gif) => Some("image/gif"),
|
||||
Some(ImageFormat::WebP) => Some("image/webp"),
|
||||
Some(ImageFormat::Bmp) => Some("image/bmp"),
|
||||
Some(ImageFormat::Ico) => Some("image/x-icon"),
|
||||
Some(ImageFormat::Tiff) => Some("image/tiff"),
|
||||
Some(ImageFormat::Avif) => Some("image/avif"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydralauncher",
|
||||
"version": "3.9.6",
|
||||
"version": "3.9.7",
|
||||
"description": "Hydra",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "Los Broxas",
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from cx_Freeze import Executable, setup
|
||||
|
||||
|
||||
def get_windows_openssl_includes():
|
||||
if sys.platform != "win32":
|
||||
return []
|
||||
|
||||
dll_dir = os.path.join(sys.base_prefix, "DLLs")
|
||||
source_by_target = {
|
||||
"libcrypto-1_1.dll": "libcrypto-1_1.dll",
|
||||
"libcrypto-1_1-x64.dll": "libcrypto-1_1.dll",
|
||||
"libssl-1_1.dll": "libssl-1_1.dll",
|
||||
"libssl-1_1-x64.dll": "libssl-1_1.dll",
|
||||
}
|
||||
|
||||
include_files = []
|
||||
for target_name, source_name in source_by_target.items():
|
||||
source_path = os.path.join(dll_dir, source_name)
|
||||
if os.path.exists(source_path):
|
||||
include_files.append((source_path, os.path.join("lib", target_name)))
|
||||
|
||||
return include_files
|
||||
|
||||
|
||||
build_exe_options = {
|
||||
"packages": ["libtorrent"],
|
||||
"build_exe": "hydra-python-rpc",
|
||||
"include_msvcr": True,
|
||||
"include_files": get_windows_openssl_includes(),
|
||||
}
|
||||
|
||||
setup(
|
||||
name="hydra-python-rpc",
|
||||
version="0.1",
|
||||
description="Hydra",
|
||||
options={"build_exe": build_exe_options},
|
||||
executables=[
|
||||
Executable(
|
||||
"python_rpc/main.py",
|
||||
target_name="hydra-python-rpc",
|
||||
icon="build/icon.ico",
|
||||
)
|
||||
],
|
||||
)
|
||||
import os
|
||||
import sys
|
||||
|
||||
from cx_Freeze import Executable, setup
|
||||
|
||||
|
||||
def get_windows_openssl_includes():
|
||||
if sys.platform != "win32":
|
||||
return []
|
||||
|
||||
dll_dir = os.path.join(sys.base_prefix, "DLLs")
|
||||
source_by_target = {
|
||||
"libcrypto-1_1.dll": "libcrypto-1_1.dll",
|
||||
"libcrypto-1_1-x64.dll": "libcrypto-1_1.dll",
|
||||
"libssl-1_1.dll": "libssl-1_1.dll",
|
||||
"libssl-1_1-x64.dll": "libssl-1_1.dll",
|
||||
}
|
||||
|
||||
include_files = []
|
||||
for target_name, source_name in source_by_target.items():
|
||||
source_path = os.path.join(dll_dir, source_name)
|
||||
if os.path.exists(source_path):
|
||||
include_files.append((source_path, os.path.join("lib", target_name)))
|
||||
|
||||
return include_files
|
||||
|
||||
|
||||
build_exe_options = {
|
||||
"packages": ["libtorrent"],
|
||||
"build_exe": "hydra-python-rpc",
|
||||
"include_msvcr": True,
|
||||
"include_files": get_windows_openssl_includes(),
|
||||
}
|
||||
|
||||
setup(
|
||||
name="hydra-python-rpc",
|
||||
version="0.1",
|
||||
description="Hydra",
|
||||
options={"build_exe": build_exe_options},
|
||||
executables=[
|
||||
Executable(
|
||||
"python_rpc/main.py",
|
||||
target_name="hydra-python-rpc",
|
||||
icon="build/icon.ico",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,391 +1,391 @@
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from typing import List, Optional, Set
|
||||
|
||||
import libtorrent as lt
|
||||
|
||||
|
||||
class TorrentDownloader:
|
||||
def __init__(
|
||||
self,
|
||||
torrent_session,
|
||||
flags=lt.torrent_flags.auto_managed,
|
||||
session_lock: Optional[threading.RLock] = None,
|
||||
):
|
||||
self.torrent_handle = None
|
||||
self.session = torrent_session
|
||||
self.flags = flags
|
||||
self.session_lock = session_lock or threading.RLock()
|
||||
self.selected_file_indices = None
|
||||
self.selected_size_bytes = None
|
||||
self.logger = logging.getLogger("hydra.torrent")
|
||||
self.trackers = [
|
||||
"udp://tracker.opentrackr.org:1337/announce",
|
||||
"http://tracker.opentrackr.org:1337/announce",
|
||||
"udp://open.tracker.cl:1337/announce",
|
||||
"udp://open.demonii.com:1337/announce",
|
||||
"udp://open.stealth.si:80/announce",
|
||||
"udp://tracker.torrent.eu.org:451/announce",
|
||||
"udp://exodus.desync.com:6969/announce",
|
||||
"udp://tracker.theoks.net:6969/announce",
|
||||
"udp://tracker-udp.gbitt.info:80/announce",
|
||||
"udp://explodie.org:6969/announce",
|
||||
"https://tracker.tamersunion.org:443/announce",
|
||||
"udp://tracker2.dler.org:80/announce",
|
||||
"udp://tracker1.myporn.club:9337/announce",
|
||||
"udp://tracker.tiny-vps.com:6969/announce",
|
||||
"udp://tracker.dler.org:6969/announce",
|
||||
"udp://tracker.bittor.pw:1337/announce",
|
||||
"udp://tracker.0x7c0.com:6969/announce",
|
||||
"udp://retracker01-msk-virt.corbina.net:80/announce",
|
||||
"udp://opentracker.io:6969/announce",
|
||||
"udp://open.free-tracker.ga:6969/announce",
|
||||
"udp://new-line.net:6969/announce",
|
||||
"udp://moonburrow.club:6969/announce",
|
||||
"udp://leet-tracker.moe:1337/announce",
|
||||
"udp://bt2.archive.org:6969/announce",
|
||||
"udp://bt1.archive.org:6969/announce",
|
||||
"http://tracker2.dler.org:80/announce",
|
||||
"http://tracker1.bt.moack.co.kr:80/announce",
|
||||
"http://tracker.dler.org:6969/announce",
|
||||
"http://tr.kxmp.cf:80/announce",
|
||||
"udp://u.peer-exchange.download:6969/announce",
|
||||
"udp://ttk2.nbaonlineservice.com:6969/announce",
|
||||
"udp://tracker.tryhackx.org:6969/announce",
|
||||
"udp://tracker.srv00.com:6969/announce",
|
||||
"udp://tracker.skynetcloud.site:6969/announce",
|
||||
"udp://tracker.jamesthebard.net:6969/announce",
|
||||
"udp://tracker.fnix.net:6969/announce",
|
||||
"udp://tracker.filemail.com:6969/announce",
|
||||
"udp://tracker.farted.net:6969/announce",
|
||||
"udp://tracker.edkj.club:6969/announce",
|
||||
"udp://tracker.dump.cl:6969/announce",
|
||||
"udp://tracker.deadorbit.nl:6969/announce",
|
||||
"udp://tracker.darkness.services:6969/announce",
|
||||
"udp://tracker.ccp.ovh:6969/announce",
|
||||
"udp://tamas3.ynh.fr:6969/announce",
|
||||
"udp://ryjer.com:6969/announce",
|
||||
"udp://run.publictracker.xyz:6969/announce",
|
||||
"udp://public.tracker.vraphim.com:6969/announce",
|
||||
"udp://p4p.arenabg.com:1337/announce",
|
||||
"udp://p2p.publictracker.xyz:6969/announce",
|
||||
"udp://open.u-p.pw:6969/announce",
|
||||
"udp://open.publictracker.xyz:6969/announce",
|
||||
"udp://open.dstud.io:6969/announce",
|
||||
"udp://open.demonoid.ch:6969/announce",
|
||||
"udp://odd-hd.fr:6969/announce",
|
||||
"udp://martin-gebhardt.eu:25/announce",
|
||||
"udp://jutone.com:6969/announce",
|
||||
"udp://isk.richardsw.club:6969/announce",
|
||||
"udp://evan.im:6969/announce",
|
||||
"udp://epider.me:6969/announce",
|
||||
"udp://d40969.acod.regrucolo.ru:6969/announce",
|
||||
"udp://bt.rer.lol:6969/announce",
|
||||
"udp://amigacity.xyz:6969/announce",
|
||||
"udp://1c.premierzal.ru:6969/announce",
|
||||
"https://trackers.run:443/announce",
|
||||
"https://tracker.yemekyedim.com:443/announce",
|
||||
"https://tracker.renfei.net:443/announce",
|
||||
"https://tracker.pmman.tech:443/announce",
|
||||
"https://tracker.lilithraws.org:443/announce",
|
||||
"https://tracker.imgoingto.icu:443/announce",
|
||||
"https://tracker.cloudit.top:443/announce",
|
||||
"https://tracker-zhuqiy.dgj055.icu:443/announce",
|
||||
"http://tracker.renfei.net:8080/announce",
|
||||
"http://tracker.mywaifu.best:6969/announce",
|
||||
"http://tracker.ipv6tracker.org:80/announce",
|
||||
"http://tracker.files.fm:6969/announce",
|
||||
"http://tracker.edkj.club:6969/announce",
|
||||
"http://tracker.bt4g.com:2095/announce",
|
||||
"http://tracker-zhuqiy.dgj055.icu:80/announce",
|
||||
"http://t1.aag.moe:17715/announce",
|
||||
"http://t.overflow.biz:6969/announce",
|
||||
"http://bittorrent-tracker.e-n-c-r-y-p-t.net:1337/announce",
|
||||
"udp://torrents.artixlinux.org:6969/announce",
|
||||
"udp://mail.artixlinux.org:6969/announce",
|
||||
"udp://ipv4.rer.lol:2710/announce",
|
||||
"udp://concen.org:6969/announce",
|
||||
"udp://bt.rer.lol:2710/announce",
|
||||
"udp://aegir.sexy:6969/announce",
|
||||
"https://www.peckservers.com:9443/announce",
|
||||
"https://tracker.ipfsscan.io:443/announce",
|
||||
"https://tracker.gcrenwp.top:443/announce",
|
||||
"http://www.peckservers.com:9000/announce",
|
||||
"http://tracker1.itzmx.com:8080/announce",
|
||||
"http://ch3oh.ru:6969/announce",
|
||||
"http://bvarf.tracker.sh:2086/announce",
|
||||
]
|
||||
|
||||
def set_download_limit(self, max_download_speed: int = None):
|
||||
download_limit = (
|
||||
max_download_speed if max_download_speed and max_download_speed > 0 else 0
|
||||
)
|
||||
try:
|
||||
self.session.set_download_rate_limit(download_limit)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _wait_for_metadata(self, timeout_seconds: float = 30.0, poll_interval: float = 0.25):
|
||||
if not self.torrent_handle or not self.torrent_handle.is_valid():
|
||||
return False
|
||||
|
||||
deadline = time.monotonic() + max(timeout_seconds, 1.0)
|
||||
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
status = self.torrent_handle.status()
|
||||
except RuntimeError:
|
||||
return False
|
||||
|
||||
if status.has_metadata:
|
||||
return True
|
||||
|
||||
time.sleep(max(poll_interval, 0.05))
|
||||
|
||||
return False
|
||||
|
||||
def wait_for_metadata(self, timeout_seconds: float = 30.0):
|
||||
return self._wait_for_metadata(timeout_seconds=timeout_seconds)
|
||||
|
||||
def _sanitize_file_indices(self, file_indices: List[int], files_storage):
|
||||
if file_indices is None:
|
||||
return None
|
||||
|
||||
if not isinstance(file_indices, list):
|
||||
raise ValueError("invalid_file_indices")
|
||||
|
||||
max_index = files_storage.num_files() - 1
|
||||
sanitized: Set[int] = set()
|
||||
|
||||
for index in file_indices:
|
||||
if isinstance(index, bool) or not isinstance(index, int):
|
||||
raise ValueError("invalid_file_indices")
|
||||
|
||||
if index < 0 or index > max_index:
|
||||
raise ValueError("invalid_file_indices")
|
||||
|
||||
sanitized.add(index)
|
||||
|
||||
if not sanitized:
|
||||
raise ValueError("empty_selection")
|
||||
|
||||
return sorted(sanitized)
|
||||
|
||||
def _set_selected_file_priorities(self, selected_indices: List[int], files_storage):
|
||||
priorities = [0] * files_storage.num_files()
|
||||
for index in selected_indices:
|
||||
priorities[index] = 1
|
||||
|
||||
self.torrent_handle.prioritize_files(priorities)
|
||||
|
||||
deadline = time.monotonic() + 3.0
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
current_priorities = [int(priority) for priority in self.torrent_handle.get_file_priorities()]
|
||||
except RuntimeError:
|
||||
break
|
||||
|
||||
if current_priorities == priorities:
|
||||
return
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
self.logger.warning("File priority synchronization timeout")
|
||||
|
||||
def start_download(
|
||||
self,
|
||||
magnet: str,
|
||||
save_path: str,
|
||||
file_indices: Optional[List[int]] = None,
|
||||
wait_timeout_seconds: float = 30.0,
|
||||
):
|
||||
selective_download = file_indices is not None
|
||||
|
||||
with self.session_lock:
|
||||
if self.torrent_handle and self.torrent_handle.is_valid():
|
||||
if not selective_download:
|
||||
self.torrent_handle.set_flags(lt.torrent_flags.auto_managed)
|
||||
self.torrent_handle.resume()
|
||||
return
|
||||
|
||||
self.torrent_handle.pause()
|
||||
self.session.remove_torrent(self.torrent_handle)
|
||||
self.torrent_handle = None
|
||||
|
||||
initial_flags = self.flags | lt.torrent_flags.paused
|
||||
|
||||
if selective_download:
|
||||
initial_flags |= lt.torrent_flags.default_dont_download
|
||||
initial_flags |= lt.torrent_flags.auto_managed
|
||||
else:
|
||||
initial_flags |= lt.torrent_flags.auto_managed
|
||||
|
||||
params = {
|
||||
"url": magnet,
|
||||
"save_path": save_path,
|
||||
"trackers": self.trackers,
|
||||
"flags": initial_flags,
|
||||
}
|
||||
|
||||
if self.torrent_handle is None or not self.torrent_handle.is_valid():
|
||||
self.torrent_handle = self.session.add_torrent(params)
|
||||
|
||||
self.selected_file_indices = None
|
||||
self.selected_size_bytes = None
|
||||
|
||||
if selective_download:
|
||||
try:
|
||||
self.torrent_handle.set_flags(lt.torrent_flags.auto_managed)
|
||||
self.torrent_handle.resume()
|
||||
|
||||
if not self._wait_for_metadata(timeout_seconds=wait_timeout_seconds):
|
||||
raise TimeoutError("metadata_timeout")
|
||||
|
||||
try:
|
||||
info = self.torrent_handle.get_torrent_info()
|
||||
files_storage = info.files()
|
||||
except RuntimeError as error:
|
||||
raise RuntimeError("metadata_incomplete") from error
|
||||
|
||||
self.torrent_handle.pause()
|
||||
self.torrent_handle.unset_flags(lt.torrent_flags.auto_managed)
|
||||
|
||||
sanitized_indices = self._sanitize_file_indices(file_indices, files_storage)
|
||||
self._set_selected_file_priorities(sanitized_indices, files_storage)
|
||||
|
||||
self.selected_file_indices = sanitized_indices
|
||||
self.selected_size_bytes = sum(files_storage.file_size(index) for index in sanitized_indices)
|
||||
except Exception:
|
||||
self.cancel_download()
|
||||
raise
|
||||
|
||||
self.torrent_handle.set_flags(lt.torrent_flags.auto_managed)
|
||||
self.torrent_handle.resume()
|
||||
|
||||
def get_torrent_files(self, timeout_seconds: float = 30.0, max_files: int = 100000):
|
||||
if not self._wait_for_metadata(timeout_seconds=timeout_seconds):
|
||||
raise TimeoutError("metadata_timeout")
|
||||
|
||||
try:
|
||||
info = self.torrent_handle.get_torrent_info()
|
||||
except RuntimeError as error:
|
||||
raise RuntimeError("metadata_incomplete") from error
|
||||
|
||||
files_storage = info.files()
|
||||
file_count = files_storage.num_files()
|
||||
|
||||
if file_count > max_files:
|
||||
raise OverflowError("too_many_files")
|
||||
|
||||
files = []
|
||||
for index in range(file_count):
|
||||
files.append(
|
||||
{
|
||||
"index": index,
|
||||
"path": files_storage.file_path(index),
|
||||
"length": files_storage.file_size(index),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"name": info.name(),
|
||||
"totalSize": info.total_size(),
|
||||
"files": files,
|
||||
}
|
||||
|
||||
def pause_download(self):
|
||||
if self.torrent_handle:
|
||||
self.torrent_handle.pause()
|
||||
self.torrent_handle.unset_flags(lt.torrent_flags.auto_managed)
|
||||
|
||||
def cancel_download(self):
|
||||
with self.session_lock:
|
||||
if self.torrent_handle:
|
||||
if self.torrent_handle.is_valid():
|
||||
self.torrent_handle.pause()
|
||||
self.session.remove_torrent(self.torrent_handle, lt.session.delete_partfile)
|
||||
self.torrent_handle = None
|
||||
self.selected_file_indices = None
|
||||
self.selected_size_bytes = None
|
||||
|
||||
def abort_session(self):
|
||||
self.cancel_download()
|
||||
self.session.abort()
|
||||
self.torrent_handle = None
|
||||
self.selected_file_indices = None
|
||||
self.selected_size_bytes = None
|
||||
|
||||
def _get_handle_status(self):
|
||||
if self.torrent_handle is None:
|
||||
return None
|
||||
|
||||
if not self.torrent_handle.is_valid():
|
||||
return None
|
||||
|
||||
try:
|
||||
return self.torrent_handle.status()
|
||||
except RuntimeError:
|
||||
return None
|
||||
|
||||
def _get_torrent_info_if_available(self, status):
|
||||
if not status.has_metadata:
|
||||
return None
|
||||
|
||||
try:
|
||||
return self.torrent_handle.get_torrent_info()
|
||||
except RuntimeError:
|
||||
return None
|
||||
|
||||
def _get_file_size(self, status, info):
|
||||
total_wanted = getattr(status, "total_wanted", 0)
|
||||
if total_wanted > 0:
|
||||
return total_wanted
|
||||
|
||||
if self.selected_size_bytes is not None:
|
||||
return self.selected_size_bytes
|
||||
|
||||
if info:
|
||||
return info.total_size()
|
||||
|
||||
return 0
|
||||
|
||||
def _get_bytes_downloaded(self, status, file_size):
|
||||
total_wanted_done = getattr(status, "total_wanted_done", -1)
|
||||
if total_wanted_done >= 0:
|
||||
return total_wanted_done
|
||||
|
||||
if file_size > 0:
|
||||
return int(status.progress * file_size)
|
||||
|
||||
return status.all_time_download
|
||||
|
||||
def _get_progress(self, status, file_size, bytes_downloaded):
|
||||
if file_size <= 0:
|
||||
return status.progress
|
||||
|
||||
return min(max(bytes_downloaded / file_size, 0), 1)
|
||||
|
||||
def get_download_status(self):
|
||||
status = self._get_handle_status()
|
||||
if status is None:
|
||||
return None
|
||||
|
||||
info = self._get_torrent_info_if_available(status)
|
||||
file_size = self._get_file_size(status, info)
|
||||
bytes_downloaded = self._get_bytes_downloaded(status, file_size)
|
||||
progress = self._get_progress(status, file_size, bytes_downloaded)
|
||||
|
||||
response = {
|
||||
'folderName': info.name() if info else "",
|
||||
'fileSize': file_size,
|
||||
'progress': progress,
|
||||
'downloadSpeed': status.download_rate,
|
||||
'uploadSpeed': status.upload_rate,
|
||||
'numPeers': status.num_peers,
|
||||
'numSeeds': status.num_seeds,
|
||||
'status': status.state,
|
||||
'bytesDownloaded': bytes_downloaded,
|
||||
}
|
||||
|
||||
return response
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from typing import List, Optional, Set
|
||||
|
||||
import libtorrent as lt
|
||||
|
||||
|
||||
class TorrentDownloader:
|
||||
def __init__(
|
||||
self,
|
||||
torrent_session,
|
||||
flags=lt.torrent_flags.auto_managed,
|
||||
session_lock: Optional[threading.RLock] = None,
|
||||
):
|
||||
self.torrent_handle = None
|
||||
self.session = torrent_session
|
||||
self.flags = flags
|
||||
self.session_lock = session_lock or threading.RLock()
|
||||
self.selected_file_indices = None
|
||||
self.selected_size_bytes = None
|
||||
self.logger = logging.getLogger("hydra.torrent")
|
||||
self.trackers = [
|
||||
"udp://tracker.opentrackr.org:1337/announce",
|
||||
"http://tracker.opentrackr.org:1337/announce",
|
||||
"udp://open.tracker.cl:1337/announce",
|
||||
"udp://open.demonii.com:1337/announce",
|
||||
"udp://open.stealth.si:80/announce",
|
||||
"udp://tracker.torrent.eu.org:451/announce",
|
||||
"udp://exodus.desync.com:6969/announce",
|
||||
"udp://tracker.theoks.net:6969/announce",
|
||||
"udp://tracker-udp.gbitt.info:80/announce",
|
||||
"udp://explodie.org:6969/announce",
|
||||
"https://tracker.tamersunion.org:443/announce",
|
||||
"udp://tracker2.dler.org:80/announce",
|
||||
"udp://tracker1.myporn.club:9337/announce",
|
||||
"udp://tracker.tiny-vps.com:6969/announce",
|
||||
"udp://tracker.dler.org:6969/announce",
|
||||
"udp://tracker.bittor.pw:1337/announce",
|
||||
"udp://tracker.0x7c0.com:6969/announce",
|
||||
"udp://retracker01-msk-virt.corbina.net:80/announce",
|
||||
"udp://opentracker.io:6969/announce",
|
||||
"udp://open.free-tracker.ga:6969/announce",
|
||||
"udp://new-line.net:6969/announce",
|
||||
"udp://moonburrow.club:6969/announce",
|
||||
"udp://leet-tracker.moe:1337/announce",
|
||||
"udp://bt2.archive.org:6969/announce",
|
||||
"udp://bt1.archive.org:6969/announce",
|
||||
"http://tracker2.dler.org:80/announce",
|
||||
"http://tracker1.bt.moack.co.kr:80/announce",
|
||||
"http://tracker.dler.org:6969/announce",
|
||||
"http://tr.kxmp.cf:80/announce",
|
||||
"udp://u.peer-exchange.download:6969/announce",
|
||||
"udp://ttk2.nbaonlineservice.com:6969/announce",
|
||||
"udp://tracker.tryhackx.org:6969/announce",
|
||||
"udp://tracker.srv00.com:6969/announce",
|
||||
"udp://tracker.skynetcloud.site:6969/announce",
|
||||
"udp://tracker.jamesthebard.net:6969/announce",
|
||||
"udp://tracker.fnix.net:6969/announce",
|
||||
"udp://tracker.filemail.com:6969/announce",
|
||||
"udp://tracker.farted.net:6969/announce",
|
||||
"udp://tracker.edkj.club:6969/announce",
|
||||
"udp://tracker.dump.cl:6969/announce",
|
||||
"udp://tracker.deadorbit.nl:6969/announce",
|
||||
"udp://tracker.darkness.services:6969/announce",
|
||||
"udp://tracker.ccp.ovh:6969/announce",
|
||||
"udp://tamas3.ynh.fr:6969/announce",
|
||||
"udp://ryjer.com:6969/announce",
|
||||
"udp://run.publictracker.xyz:6969/announce",
|
||||
"udp://public.tracker.vraphim.com:6969/announce",
|
||||
"udp://p4p.arenabg.com:1337/announce",
|
||||
"udp://p2p.publictracker.xyz:6969/announce",
|
||||
"udp://open.u-p.pw:6969/announce",
|
||||
"udp://open.publictracker.xyz:6969/announce",
|
||||
"udp://open.dstud.io:6969/announce",
|
||||
"udp://open.demonoid.ch:6969/announce",
|
||||
"udp://odd-hd.fr:6969/announce",
|
||||
"udp://martin-gebhardt.eu:25/announce",
|
||||
"udp://jutone.com:6969/announce",
|
||||
"udp://isk.richardsw.club:6969/announce",
|
||||
"udp://evan.im:6969/announce",
|
||||
"udp://epider.me:6969/announce",
|
||||
"udp://d40969.acod.regrucolo.ru:6969/announce",
|
||||
"udp://bt.rer.lol:6969/announce",
|
||||
"udp://amigacity.xyz:6969/announce",
|
||||
"udp://1c.premierzal.ru:6969/announce",
|
||||
"https://trackers.run:443/announce",
|
||||
"https://tracker.yemekyedim.com:443/announce",
|
||||
"https://tracker.renfei.net:443/announce",
|
||||
"https://tracker.pmman.tech:443/announce",
|
||||
"https://tracker.lilithraws.org:443/announce",
|
||||
"https://tracker.imgoingto.icu:443/announce",
|
||||
"https://tracker.cloudit.top:443/announce",
|
||||
"https://tracker-zhuqiy.dgj055.icu:443/announce",
|
||||
"http://tracker.renfei.net:8080/announce",
|
||||
"http://tracker.mywaifu.best:6969/announce",
|
||||
"http://tracker.ipv6tracker.org:80/announce",
|
||||
"http://tracker.files.fm:6969/announce",
|
||||
"http://tracker.edkj.club:6969/announce",
|
||||
"http://tracker.bt4g.com:2095/announce",
|
||||
"http://tracker-zhuqiy.dgj055.icu:80/announce",
|
||||
"http://t1.aag.moe:17715/announce",
|
||||
"http://t.overflow.biz:6969/announce",
|
||||
"http://bittorrent-tracker.e-n-c-r-y-p-t.net:1337/announce",
|
||||
"udp://torrents.artixlinux.org:6969/announce",
|
||||
"udp://mail.artixlinux.org:6969/announce",
|
||||
"udp://ipv4.rer.lol:2710/announce",
|
||||
"udp://concen.org:6969/announce",
|
||||
"udp://bt.rer.lol:2710/announce",
|
||||
"udp://aegir.sexy:6969/announce",
|
||||
"https://www.peckservers.com:9443/announce",
|
||||
"https://tracker.ipfsscan.io:443/announce",
|
||||
"https://tracker.gcrenwp.top:443/announce",
|
||||
"http://www.peckservers.com:9000/announce",
|
||||
"http://tracker1.itzmx.com:8080/announce",
|
||||
"http://ch3oh.ru:6969/announce",
|
||||
"http://bvarf.tracker.sh:2086/announce",
|
||||
]
|
||||
|
||||
def set_download_limit(self, max_download_speed: int = None):
|
||||
download_limit = (
|
||||
max_download_speed if max_download_speed and max_download_speed > 0 else 0
|
||||
)
|
||||
try:
|
||||
self.session.set_download_rate_limit(download_limit)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _wait_for_metadata(self, timeout_seconds: float = 30.0, poll_interval: float = 0.25):
|
||||
if not self.torrent_handle or not self.torrent_handle.is_valid():
|
||||
return False
|
||||
|
||||
deadline = time.monotonic() + max(timeout_seconds, 1.0)
|
||||
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
status = self.torrent_handle.status()
|
||||
except RuntimeError:
|
||||
return False
|
||||
|
||||
if status.has_metadata:
|
||||
return True
|
||||
|
||||
time.sleep(max(poll_interval, 0.05))
|
||||
|
||||
return False
|
||||
|
||||
def wait_for_metadata(self, timeout_seconds: float = 30.0):
|
||||
return self._wait_for_metadata(timeout_seconds=timeout_seconds)
|
||||
|
||||
def _sanitize_file_indices(self, file_indices: List[int], files_storage):
|
||||
if file_indices is None:
|
||||
return None
|
||||
|
||||
if not isinstance(file_indices, list):
|
||||
raise ValueError("invalid_file_indices")
|
||||
|
||||
max_index = files_storage.num_files() - 1
|
||||
sanitized: Set[int] = set()
|
||||
|
||||
for index in file_indices:
|
||||
if isinstance(index, bool) or not isinstance(index, int):
|
||||
raise ValueError("invalid_file_indices")
|
||||
|
||||
if index < 0 or index > max_index:
|
||||
raise ValueError("invalid_file_indices")
|
||||
|
||||
sanitized.add(index)
|
||||
|
||||
if not sanitized:
|
||||
raise ValueError("empty_selection")
|
||||
|
||||
return sorted(sanitized)
|
||||
|
||||
def _set_selected_file_priorities(self, selected_indices: List[int], files_storage):
|
||||
priorities = [0] * files_storage.num_files()
|
||||
for index in selected_indices:
|
||||
priorities[index] = 1
|
||||
|
||||
self.torrent_handle.prioritize_files(priorities)
|
||||
|
||||
deadline = time.monotonic() + 3.0
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
current_priorities = [int(priority) for priority in self.torrent_handle.get_file_priorities()]
|
||||
except RuntimeError:
|
||||
break
|
||||
|
||||
if current_priorities == priorities:
|
||||
return
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
self.logger.warning("File priority synchronization timeout")
|
||||
|
||||
def start_download(
|
||||
self,
|
||||
magnet: str,
|
||||
save_path: str,
|
||||
file_indices: Optional[List[int]] = None,
|
||||
wait_timeout_seconds: float = 30.0,
|
||||
):
|
||||
selective_download = file_indices is not None
|
||||
|
||||
with self.session_lock:
|
||||
if self.torrent_handle and self.torrent_handle.is_valid():
|
||||
if not selective_download:
|
||||
self.torrent_handle.set_flags(lt.torrent_flags.auto_managed)
|
||||
self.torrent_handle.resume()
|
||||
return
|
||||
|
||||
self.torrent_handle.pause()
|
||||
self.session.remove_torrent(self.torrent_handle)
|
||||
self.torrent_handle = None
|
||||
|
||||
initial_flags = self.flags | lt.torrent_flags.paused
|
||||
|
||||
if selective_download:
|
||||
initial_flags |= lt.torrent_flags.default_dont_download
|
||||
initial_flags |= lt.torrent_flags.auto_managed
|
||||
else:
|
||||
initial_flags |= lt.torrent_flags.auto_managed
|
||||
|
||||
params = {
|
||||
"url": magnet,
|
||||
"save_path": save_path,
|
||||
"trackers": self.trackers,
|
||||
"flags": initial_flags,
|
||||
}
|
||||
|
||||
if self.torrent_handle is None or not self.torrent_handle.is_valid():
|
||||
self.torrent_handle = self.session.add_torrent(params)
|
||||
|
||||
self.selected_file_indices = None
|
||||
self.selected_size_bytes = None
|
||||
|
||||
if selective_download:
|
||||
try:
|
||||
self.torrent_handle.set_flags(lt.torrent_flags.auto_managed)
|
||||
self.torrent_handle.resume()
|
||||
|
||||
if not self._wait_for_metadata(timeout_seconds=wait_timeout_seconds):
|
||||
raise TimeoutError("metadata_timeout")
|
||||
|
||||
try:
|
||||
info = self.torrent_handle.get_torrent_info()
|
||||
files_storage = info.files()
|
||||
except RuntimeError as error:
|
||||
raise RuntimeError("metadata_incomplete") from error
|
||||
|
||||
self.torrent_handle.pause()
|
||||
self.torrent_handle.unset_flags(lt.torrent_flags.auto_managed)
|
||||
|
||||
sanitized_indices = self._sanitize_file_indices(file_indices, files_storage)
|
||||
self._set_selected_file_priorities(sanitized_indices, files_storage)
|
||||
|
||||
self.selected_file_indices = sanitized_indices
|
||||
self.selected_size_bytes = sum(files_storage.file_size(index) for index in sanitized_indices)
|
||||
except Exception:
|
||||
self.cancel_download()
|
||||
raise
|
||||
|
||||
self.torrent_handle.set_flags(lt.torrent_flags.auto_managed)
|
||||
self.torrent_handle.resume()
|
||||
|
||||
def get_torrent_files(self, timeout_seconds: float = 30.0, max_files: int = 100000):
|
||||
if not self._wait_for_metadata(timeout_seconds=timeout_seconds):
|
||||
raise TimeoutError("metadata_timeout")
|
||||
|
||||
try:
|
||||
info = self.torrent_handle.get_torrent_info()
|
||||
except RuntimeError as error:
|
||||
raise RuntimeError("metadata_incomplete") from error
|
||||
|
||||
files_storage = info.files()
|
||||
file_count = files_storage.num_files()
|
||||
|
||||
if file_count > max_files:
|
||||
raise OverflowError("too_many_files")
|
||||
|
||||
files = []
|
||||
for index in range(file_count):
|
||||
files.append(
|
||||
{
|
||||
"index": index,
|
||||
"path": files_storage.file_path(index),
|
||||
"length": files_storage.file_size(index),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"name": info.name(),
|
||||
"totalSize": info.total_size(),
|
||||
"files": files,
|
||||
}
|
||||
|
||||
def pause_download(self):
|
||||
if self.torrent_handle:
|
||||
self.torrent_handle.pause()
|
||||
self.torrent_handle.unset_flags(lt.torrent_flags.auto_managed)
|
||||
|
||||
def cancel_download(self):
|
||||
with self.session_lock:
|
||||
if self.torrent_handle:
|
||||
if self.torrent_handle.is_valid():
|
||||
self.torrent_handle.pause()
|
||||
self.session.remove_torrent(self.torrent_handle, lt.session.delete_partfile)
|
||||
self.torrent_handle = None
|
||||
self.selected_file_indices = None
|
||||
self.selected_size_bytes = None
|
||||
|
||||
def abort_session(self):
|
||||
self.cancel_download()
|
||||
self.session.abort()
|
||||
self.torrent_handle = None
|
||||
self.selected_file_indices = None
|
||||
self.selected_size_bytes = None
|
||||
|
||||
def _get_handle_status(self):
|
||||
if self.torrent_handle is None:
|
||||
return None
|
||||
|
||||
if not self.torrent_handle.is_valid():
|
||||
return None
|
||||
|
||||
try:
|
||||
return self.torrent_handle.status()
|
||||
except RuntimeError:
|
||||
return None
|
||||
|
||||
def _get_torrent_info_if_available(self, status):
|
||||
if not status.has_metadata:
|
||||
return None
|
||||
|
||||
try:
|
||||
return self.torrent_handle.get_torrent_info()
|
||||
except RuntimeError:
|
||||
return None
|
||||
|
||||
def _get_file_size(self, status, info):
|
||||
total_wanted = getattr(status, "total_wanted", 0)
|
||||
if total_wanted > 0:
|
||||
return total_wanted
|
||||
|
||||
if self.selected_size_bytes is not None:
|
||||
return self.selected_size_bytes
|
||||
|
||||
if info:
|
||||
return info.total_size()
|
||||
|
||||
return 0
|
||||
|
||||
def _get_bytes_downloaded(self, status, file_size):
|
||||
total_wanted_done = getattr(status, "total_wanted_done", -1)
|
||||
if total_wanted_done >= 0:
|
||||
return total_wanted_done
|
||||
|
||||
if file_size > 0:
|
||||
return int(status.progress * file_size)
|
||||
|
||||
return status.all_time_download
|
||||
|
||||
def _get_progress(self, status, file_size, bytes_downloaded):
|
||||
if file_size <= 0:
|
||||
return status.progress
|
||||
|
||||
return min(max(bytes_downloaded / file_size, 0), 1)
|
||||
|
||||
def get_download_status(self):
|
||||
status = self._get_handle_status()
|
||||
if status is None:
|
||||
return None
|
||||
|
||||
info = self._get_torrent_info_if_available(status)
|
||||
file_size = self._get_file_size(status, info)
|
||||
bytes_downloaded = self._get_bytes_downloaded(status, file_size)
|
||||
progress = self._get_progress(status, file_size, bytes_downloaded)
|
||||
|
||||
response = {
|
||||
'folderName': info.name() if info else "",
|
||||
'fileSize': file_size,
|
||||
'progress': progress,
|
||||
'downloadSpeed': status.download_rate,
|
||||
'uploadSpeed': status.upload_rate,
|
||||
'numPeers': status.num_peers,
|
||||
'numSeeds': status.num_seeds,
|
||||
'status': status.state,
|
||||
'bytesDownloaded': bytes_downloaded,
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
libtorrent
|
||||
cx_Freeze == 7.2.3
|
||||
libtorrent
|
||||
cx_Freeze == 7.2.3
|
||||
|
||||
@@ -1 +1 @@
|
||||
sonar.exclusions=src/main/generated/**
|
||||
sonar.exclusions=src/main/generated/**
|
||||
|
||||
@@ -20,7 +20,7 @@ const closeGame = async (
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
const processes = NativeAddon.listProcesses();
|
||||
const processes = await NativeAddon.listProcesses();
|
||||
|
||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ const cleanupStaleCompatibilityProcesses = async (
|
||||
const defaultPrefixPath = Wine.getDefaultPrefixPathForGame(objectId);
|
||||
if (defaultPrefixPath !== winePrefixPath) return;
|
||||
|
||||
const processes = NativeAddon.listProcesses();
|
||||
const processes = await NativeAddon.listProcesses();
|
||||
|
||||
const stalePids = processes
|
||||
.filter((runningProcess) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { createRequire } from "node:module";
|
||||
import { Worker } from "node:worker_threads";
|
||||
|
||||
import { app } from "electron";
|
||||
import type { ProcessPayload } from "./download/types";
|
||||
@@ -22,8 +23,89 @@ type HydraNativeModule = {
|
||||
listProcesses: () => ProcessPayload[];
|
||||
};
|
||||
|
||||
export type SystemProcessMap = {
|
||||
processMap: Record<string, string[]>;
|
||||
winePrefixMap: Record<string, string>;
|
||||
linuxProcesses: Array<{
|
||||
name: string;
|
||||
cwd: string;
|
||||
exe: string;
|
||||
steamCompatDataPath: string | null;
|
||||
}>;
|
||||
};
|
||||
|
||||
// Runs in the worker thread (CJS context).
|
||||
// "list" → posts back the raw ProcessPayload array (used by close-game, launch-game)
|
||||
// "map" → posts back compact pre-built maps (used by the main loop's watchProcesses)
|
||||
const WORKER_CODE = `
|
||||
const { workerData, parentPort } = require('worker_threads');
|
||||
const path = require('path');
|
||||
if (process.platform === 'linux' && workerData.addonDir) {
|
||||
process.env.LD_LIBRARY_PATH = process.env.LD_LIBRARY_PATH
|
||||
? workerData.addonDir + ':' + process.env.LD_LIBRARY_PATH
|
||||
: workerData.addonDir;
|
||||
}
|
||||
const addon = require(workerData.addonPath);
|
||||
const platform = process.platform;
|
||||
|
||||
function buildMaps(processes) {
|
||||
const processMap = Object.create(null);
|
||||
const winePrefixMap = Object.create(null);
|
||||
const linuxProcesses = [];
|
||||
|
||||
for (const proc of processes) {
|
||||
const key = proc.name && proc.name.toLowerCase();
|
||||
const value = platform === 'win32'
|
||||
? proc.exe
|
||||
: path.join(proc.cwd || '', proc.name || '');
|
||||
|
||||
if (!key || !value) continue;
|
||||
|
||||
const steamCompatDataPath = proc.environ && proc.environ.STEAM_COMPAT_DATA_PATH;
|
||||
if (steamCompatDataPath) winePrefixMap[value] = steamCompatDataPath;
|
||||
|
||||
if (platform === 'linux') {
|
||||
linuxProcesses.push({
|
||||
name: key,
|
||||
cwd: (proc.cwd || '').toLowerCase(),
|
||||
exe: (proc.exe || '').toLowerCase(),
|
||||
steamCompatDataPath: steamCompatDataPath ? steamCompatDataPath.toLowerCase() : null,
|
||||
});
|
||||
}
|
||||
|
||||
if (!processMap[key]) processMap[key] = [];
|
||||
processMap[key].push(value);
|
||||
}
|
||||
|
||||
return { processMap, winePrefixMap, linuxProcesses };
|
||||
}
|
||||
|
||||
parentPort.on('message', (type) => {
|
||||
try {
|
||||
const processes = addon.listProcesses();
|
||||
if (type === 'map') {
|
||||
parentPort.postMessage({ type: 'map', result: buildMaps(processes) });
|
||||
} else {
|
||||
parentPort.postMessage({ type: 'list', result: processes });
|
||||
}
|
||||
} catch (_) {
|
||||
if (type === 'map') {
|
||||
parentPort.postMessage({ type: 'map', result: { processMap: {}, winePrefixMap: {}, linuxProcesses: [] } });
|
||||
} else {
|
||||
parentPort.postMessage({ type: 'list', result: [] });
|
||||
}
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
type PendingResolver =
|
||||
| { type: "list"; resolve: (p: ProcessPayload[]) => void }
|
||||
| { type: "map"; resolve: (m: SystemProcessMap) => void };
|
||||
|
||||
export class NativeAddon {
|
||||
private static nativeModule: HydraNativeModule | null = null;
|
||||
private static worker: Worker | null = null;
|
||||
private static pendingResolvers: PendingResolver[] = [];
|
||||
|
||||
private static resolveAddonPath() {
|
||||
if (app.isPackaged) {
|
||||
@@ -61,6 +143,55 @@ export class NativeAddon {
|
||||
return nativeModule;
|
||||
}
|
||||
|
||||
private static getWorker(): Worker {
|
||||
if (this.worker) return this.worker;
|
||||
|
||||
const addonPath = this.resolveAddonPath();
|
||||
const addonDir = path.dirname(addonPath);
|
||||
|
||||
if (!fs.existsSync(addonPath)) {
|
||||
throw new Error(`Hydra native addon not found at ${addonPath}`);
|
||||
}
|
||||
|
||||
this.worker = new Worker(WORKER_CODE, {
|
||||
eval: true,
|
||||
workerData: { addonPath, addonDir },
|
||||
});
|
||||
|
||||
this.worker.on("message", ({ result }) => {
|
||||
const pending = this.pendingResolvers.shift();
|
||||
if (!pending) return;
|
||||
if (pending.type === "list") {
|
||||
(pending.resolve as (p: ProcessPayload[]) => void)(
|
||||
(result as ProcessPayload[]).filter(
|
||||
(p): p is ProcessPayload =>
|
||||
typeof p?.pid === "number" &&
|
||||
typeof p?.name === "string" &&
|
||||
p.name.length > 0
|
||||
)
|
||||
);
|
||||
} else {
|
||||
(pending.resolve as (m: SystemProcessMap) => void)(
|
||||
result as SystemProcessMap
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.worker.on("error", (error) => {
|
||||
logger.error("Process list worker error", error);
|
||||
this.drainResolvers();
|
||||
});
|
||||
|
||||
this.worker.on("exit", (code) => {
|
||||
if (code !== 0)
|
||||
logger.error(`Process list worker exited with code ${code}`);
|
||||
this.worker = null;
|
||||
this.drainResolvers();
|
||||
});
|
||||
|
||||
return this.worker;
|
||||
}
|
||||
|
||||
public static processProfileImage(
|
||||
imagePath: string,
|
||||
targetExtension = "webp"
|
||||
@@ -88,24 +219,36 @@ export class NativeAddon {
|
||||
}
|
||||
}
|
||||
|
||||
public static listProcesses(): ProcessPayload[] {
|
||||
try {
|
||||
const response = this.load().listProcesses();
|
||||
|
||||
if (!Array.isArray(response)) {
|
||||
throw new Error("Hydra native addon returned an invalid process list");
|
||||
}
|
||||
|
||||
return response.filter((process): process is ProcessPayload => {
|
||||
return (
|
||||
typeof process?.pid === "number" &&
|
||||
typeof process?.name === "string" &&
|
||||
process.name.length > 0
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Failed to list processes via native addon", error);
|
||||
return [];
|
||||
private static drainResolvers() {
|
||||
const drained = this.pendingResolvers.splice(0);
|
||||
for (const pending of drained) {
|
||||
if (pending.type === "list") pending.resolve([]);
|
||||
else
|
||||
pending.resolve({ processMap: {}, winePrefixMap: {}, linuxProcesses: [] });
|
||||
}
|
||||
}
|
||||
|
||||
public static listProcesses(): Promise<ProcessPayload[]> {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
const worker = this.getWorker();
|
||||
this.pendingResolvers.push({ type: "list", resolve });
|
||||
worker.postMessage("list");
|
||||
} catch {
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getSystemProcessMap(): Promise<SystemProcessMap> {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
const worker = this.getWorker();
|
||||
this.pendingResolvers.push({ type: "map", resolve });
|
||||
worker.postMessage("map");
|
||||
} catch {
|
||||
resolve({ processMap: {}, winePrefixMap: {}, linuxProcesses: [] });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,38 +143,14 @@ const findGamePathByProcess = async (
|
||||
};
|
||||
|
||||
const getSystemProcessMap = async () => {
|
||||
const processes = NativeAddon.listProcesses();
|
||||
const { processMap: rawMap, winePrefixMap: rawWineMap, linuxProcesses } =
|
||||
await NativeAddon.getSystemProcessMap();
|
||||
|
||||
const processMap = new Map<string, Set<string>>();
|
||||
const winePrefixMap = new Map<string, string>();
|
||||
const linuxProcesses: LinuxProcessInfo[] = [];
|
||||
const processMap = new Map<string, Set<string>>(
|
||||
Object.entries(rawMap).map(([k, v]) => [k, new Set(v)])
|
||||
);
|
||||
|
||||
processes.forEach((process) => {
|
||||
const key = process.name?.toLowerCase();
|
||||
const value =
|
||||
platform === "win32"
|
||||
? process.exe
|
||||
: path.join(process.cwd ?? "", process.name ?? "");
|
||||
|
||||
if (!key || !value) return;
|
||||
|
||||
const STEAM_COMPAT_DATA_PATH = process.environ?.STEAM_COMPAT_DATA_PATH;
|
||||
if (STEAM_COMPAT_DATA_PATH) {
|
||||
winePrefixMap.set(value, STEAM_COMPAT_DATA_PATH);
|
||||
}
|
||||
|
||||
if (platform === "linux") {
|
||||
linuxProcesses.push({
|
||||
name: key,
|
||||
cwd: (process.cwd ?? "").toLowerCase(),
|
||||
exe: (process.exe ?? "").toLowerCase(),
|
||||
steamCompatDataPath: STEAM_COMPAT_DATA_PATH?.toLowerCase() ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
const currentSet = processMap.get(key) ?? new Set();
|
||||
processMap.set(key, currentSet.add(value));
|
||||
});
|
||||
const winePrefixMap = new Map<string, string>(Object.entries(rawWineMap));
|
||||
|
||||
return { processMap, winePrefixMap, linuxProcesses };
|
||||
};
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<svg width="55" height="49" viewBox="0 0 55 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.8501 29.1176L19.9196 28.3235L20.6957 25.6764L20.437 24.8823L18.1088 25.9411L14.487 24.3528L10.0891 23.0293L5.69128 23.5587L3.10431 25.6764L2.58691 29.1176L4.1391 33.6177L5.69128 36.7942L8.53695 38.9118L10.8652 38.3824L13.9696 34.9412V31.5L12.9348 29.1176V32.2941L10.8652 34.9412H7.50216L4.91519 32.2941L5.69128 28.3235L9.57174 26.4705L13.9696 27.5294L17.8501 29.1176Z" fill="white"/>
|
||||
<path d="M36.9585 29.1176L34.889 28.3235L34.1129 25.6764L34.3716 24.8823L36.6998 25.9411L40.3216 24.3528L44.7195 23.0293L49.1173 23.5587L51.7043 25.6764L52.2217 29.1176L50.6695 33.6177L49.1173 36.7942L46.2716 38.9118L43.9434 38.3824L40.839 34.9412V31.5L41.8738 29.1176V32.2941L43.9434 34.9412H47.3064L49.8934 32.2941L49.1173 28.3235L45.2369 26.4705L40.839 27.5294L36.9585 29.1176Z" fill="white"/>
|
||||
<path d="M40.3873 19.4005L38.8784 19.9071L38.7685 19.0593L38.5553 17.7049L39.811 14.777L41.6564 11.5721L44.483 9.44001L47.1023 9.23162L49.2244 10.9371L50.7089 14.4032L51.4925 17.1031L50.9665 19.9071L49.3381 20.8916L45.7182 20.6206L43.8957 18.6282L43.2332 16.675L44.9154 18.5142L47.5156 18.8992L49.4627 17.0344L49.5586 14.0673L47.0064 12.1987L43.7784 13.2776L41.7929 16.3293L40.3873 19.4005Z" fill="white"/>
|
||||
<path d="M14.0238 19.4005L15.5327 19.9071L15.6426 19.0593L15.8559 17.7049L14.6001 14.777L12.7548 11.5721L9.92812 9.44001L7.30879 9.23162L5.18676 10.9371L3.70221 14.4032L2.91861 17.1031L3.44468 19.9071L5.07308 20.8916L8.69292 20.6206L10.5154 18.6282L11.178 16.675L9.4957 18.5142L6.89555 18.8992L4.94841 17.0344L4.8525 14.0673L7.4047 12.1987L10.6327 13.2776L12.6182 16.3293L14.0238 19.4005Z" fill="white"/>
|
||||
<path d="M19.9494 36.4343L22.554 34.3372L21.9876 34.0884L20.9528 31.9707L19.9494 32.5001L17.5898 34.0884L15.3904 37.377L14.744 40.4414L15.2615 43.0885L17.0724 45.4709L20.9528 47.3238L23.6932 46.5297L25.435 44.7653L26.0028 42.9192L25.6093 39.0508L25.0919 37.7913L23.6932 37.0002L24.3158 39.105L24.3158 42.0296L22.3597 43.9181L19.9494 43.9181L18.0225 42.0296L17.4949 39.105L19.9494 36.4343Z" fill="white"/>
|
||||
<path d="M35.0955 36.4343L32.4909 34.3372L33.0573 34.0884L34.0921 31.9707L35.0955 32.5001L37.4552 34.0884L39.6545 37.377L40.3009 40.4414L39.7834 43.0885L37.9725 45.4709L34.0921 47.3238L31.3518 46.5297L29.6099 44.7653L29.0421 42.9192L29.4356 39.0508L29.953 37.7913L31.3518 37.0002L30.7291 39.105L30.7291 42.0296L32.6852 43.9181L35.0955 43.9181L37.0224 42.0296L37.55 39.105L35.0955 36.4343Z" fill="white"/>
|
||||
<path d="M18.8447 8.70593V5.79413L20.1382 5H22.9839L27.3817 5.79413L31.7796 5H34.6252L35.9187 5.79413V8.70593L38.7644 11.353L37.2122 15.0589L37.9883 20.8825L35.9187 23.0002L33.3317 23.7943L32.8144 26.1767L33.3317 28.8238L32.8144 30.9415L31.7796 33.0591L30.2274 33.8533H27.3817H24.536L22.9839 33.0591L21.9491 30.9415L21.4317 28.8238L21.9491 26.1767L21.4317 23.7943L18.8447 23.0002L16.7751 20.8825L17.5512 15.0589L15.999 11.353L18.8447 8.70593Z" fill="white"/>
|
||||
<path d="M15.5205 6.88232L16.2966 8.73528L17.5901 7.67645V4.76465L15.5205 6.88232Z" fill="white"/>
|
||||
<path d="M39.2861 6.88232L38.51 8.73528L37.2166 7.67645V4.76465L39.2861 6.88232Z" fill="white"/>
|
||||
<path d="M18.3667 2.11767L18.6254 4.23534L19.4015 3.70593H20.9537L20.4363 2.11767L17.0732 0L18.3667 2.11767Z" fill="white"/>
|
||||
<path d="M35.6997 2.11767L35.441 4.23534L34.6649 3.70593H33.1127L33.6301 2.11767L36.9932 0L35.6997 2.11767Z" fill="white"/>
|
||||
</svg>
|
||||
<svg width="55" height="49" viewBox="0 0 55 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.8501 29.1176L19.9196 28.3235L20.6957 25.6764L20.437 24.8823L18.1088 25.9411L14.487 24.3528L10.0891 23.0293L5.69128 23.5587L3.10431 25.6764L2.58691 29.1176L4.1391 33.6177L5.69128 36.7942L8.53695 38.9118L10.8652 38.3824L13.9696 34.9412V31.5L12.9348 29.1176V32.2941L10.8652 34.9412H7.50216L4.91519 32.2941L5.69128 28.3235L9.57174 26.4705L13.9696 27.5294L17.8501 29.1176Z" fill="white"/>
|
||||
<path d="M36.9585 29.1176L34.889 28.3235L34.1129 25.6764L34.3716 24.8823L36.6998 25.9411L40.3216 24.3528L44.7195 23.0293L49.1173 23.5587L51.7043 25.6764L52.2217 29.1176L50.6695 33.6177L49.1173 36.7942L46.2716 38.9118L43.9434 38.3824L40.839 34.9412V31.5L41.8738 29.1176V32.2941L43.9434 34.9412H47.3064L49.8934 32.2941L49.1173 28.3235L45.2369 26.4705L40.839 27.5294L36.9585 29.1176Z" fill="white"/>
|
||||
<path d="M40.3873 19.4005L38.8784 19.9071L38.7685 19.0593L38.5553 17.7049L39.811 14.777L41.6564 11.5721L44.483 9.44001L47.1023 9.23162L49.2244 10.9371L50.7089 14.4032L51.4925 17.1031L50.9665 19.9071L49.3381 20.8916L45.7182 20.6206L43.8957 18.6282L43.2332 16.675L44.9154 18.5142L47.5156 18.8992L49.4627 17.0344L49.5586 14.0673L47.0064 12.1987L43.7784 13.2776L41.7929 16.3293L40.3873 19.4005Z" fill="white"/>
|
||||
<path d="M14.0238 19.4005L15.5327 19.9071L15.6426 19.0593L15.8559 17.7049L14.6001 14.777L12.7548 11.5721L9.92812 9.44001L7.30879 9.23162L5.18676 10.9371L3.70221 14.4032L2.91861 17.1031L3.44468 19.9071L5.07308 20.8916L8.69292 20.6206L10.5154 18.6282L11.178 16.675L9.4957 18.5142L6.89555 18.8992L4.94841 17.0344L4.8525 14.0673L7.4047 12.1987L10.6327 13.2776L12.6182 16.3293L14.0238 19.4005Z" fill="white"/>
|
||||
<path d="M19.9494 36.4343L22.554 34.3372L21.9876 34.0884L20.9528 31.9707L19.9494 32.5001L17.5898 34.0884L15.3904 37.377L14.744 40.4414L15.2615 43.0885L17.0724 45.4709L20.9528 47.3238L23.6932 46.5297L25.435 44.7653L26.0028 42.9192L25.6093 39.0508L25.0919 37.7913L23.6932 37.0002L24.3158 39.105L24.3158 42.0296L22.3597 43.9181L19.9494 43.9181L18.0225 42.0296L17.4949 39.105L19.9494 36.4343Z" fill="white"/>
|
||||
<path d="M35.0955 36.4343L32.4909 34.3372L33.0573 34.0884L34.0921 31.9707L35.0955 32.5001L37.4552 34.0884L39.6545 37.377L40.3009 40.4414L39.7834 43.0885L37.9725 45.4709L34.0921 47.3238L31.3518 46.5297L29.6099 44.7653L29.0421 42.9192L29.4356 39.0508L29.953 37.7913L31.3518 37.0002L30.7291 39.105L30.7291 42.0296L32.6852 43.9181L35.0955 43.9181L37.0224 42.0296L37.55 39.105L35.0955 36.4343Z" fill="white"/>
|
||||
<path d="M18.8447 8.70593V5.79413L20.1382 5H22.9839L27.3817 5.79413L31.7796 5H34.6252L35.9187 5.79413V8.70593L38.7644 11.353L37.2122 15.0589L37.9883 20.8825L35.9187 23.0002L33.3317 23.7943L32.8144 26.1767L33.3317 28.8238L32.8144 30.9415L31.7796 33.0591L30.2274 33.8533H27.3817H24.536L22.9839 33.0591L21.9491 30.9415L21.4317 28.8238L21.9491 26.1767L21.4317 23.7943L18.8447 23.0002L16.7751 20.8825L17.5512 15.0589L15.999 11.353L18.8447 8.70593Z" fill="white"/>
|
||||
<path d="M15.5205 6.88232L16.2966 8.73528L17.5901 7.67645V4.76465L15.5205 6.88232Z" fill="white"/>
|
||||
<path d="M39.2861 6.88232L38.51 8.73528L37.2166 7.67645V4.76465L39.2861 6.88232Z" fill="white"/>
|
||||
<path d="M18.3667 2.11767L18.6254 4.23534L19.4015 3.70593H20.9537L20.4363 2.11767L17.0732 0L18.3667 2.11767Z" fill="white"/>
|
||||
<path d="M35.6997 2.11767L35.441 4.23534L34.6649 3.70593H33.1127L33.6301 2.11767L36.9932 0L35.6997 2.11767Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.3 KiB |
@@ -1,5 +1,5 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Frame 933">
|
||||
<path id="Vector" d="M29.3333 10.5H26.8333V8.83333C26.8333 8.61232 26.7455 8.40036 26.5893 8.24408C26.433 8.0878 26.221 8 26 8H11C10.779 8 10.567 8.0878 10.4107 8.24408C10.2545 8.40036 10.1667 8.61232 10.1667 8.83333V10.5H7.66667C7.22464 10.5 6.80072 10.6756 6.48816 10.9882C6.17559 11.3007 6 11.7246 6 12.1667V13.8333C6 14.9384 6.43899 15.9982 7.22039 16.7796C7.6073 17.1665 8.06663 17.4734 8.57215 17.6828C9.07768 17.8922 9.61949 18 10.1667 18H10.5469C11.0378 19.5556 11.9737 20.9333 13.2391 21.9628C14.5044 22.9923 16.0437 23.6285 17.6667 23.7927V26.3333H15.1667C14.9457 26.3333 14.7337 26.4211 14.5774 26.5774C14.4211 26.7337 14.3333 26.9457 14.3333 27.1667C14.3333 27.3877 14.4211 27.5996 14.5774 27.7559C14.7337 27.9122 14.9457 28 15.1667 28H21.8333C22.0543 28 22.2663 27.9122 22.4226 27.7559C22.5789 27.5996 22.6667 27.3877 22.6667 27.1667C22.6667 26.9457 22.5789 26.7337 22.4226 26.5774C22.2663 26.4211 22.0543 26.3333 21.8333 26.3333H19.3333V23.7896C22.6604 23.4531 25.4208 21.1187 26.425 18H26.8333C27.9384 18 28.9982 17.561 29.7796 16.7796C30.561 15.9982 31 14.9384 31 13.8333V12.1667C31 11.7246 30.8244 11.3007 30.5118 10.9882C30.1993 10.6756 29.7754 10.5 29.3333 10.5ZM10.1667 16.3333C9.50363 16.3333 8.86774 16.0699 8.3989 15.6011C7.93006 15.1323 7.66667 14.4964 7.66667 13.8333V12.1667H10.1667V15.5C10.1667 15.7778 10.1802 16.0556 10.2073 16.3333H10.1667ZM29.3333 13.8333C29.3333 14.4964 29.0699 15.1323 28.6011 15.6011C28.1323 16.0699 27.4964 16.3333 26.8333 16.3333H26.7812C26.8154 16.0255 26.8328 15.716 26.8333 15.4062V12.1667H29.3333V13.8333Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Frame 933">
|
||||
<path id="Vector" d="M29.3333 10.5H26.8333V8.83333C26.8333 8.61232 26.7455 8.40036 26.5893 8.24408C26.433 8.0878 26.221 8 26 8H11C10.779 8 10.567 8.0878 10.4107 8.24408C10.2545 8.40036 10.1667 8.61232 10.1667 8.83333V10.5H7.66667C7.22464 10.5 6.80072 10.6756 6.48816 10.9882C6.17559 11.3007 6 11.7246 6 12.1667V13.8333C6 14.9384 6.43899 15.9982 7.22039 16.7796C7.6073 17.1665 8.06663 17.4734 8.57215 17.6828C9.07768 17.8922 9.61949 18 10.1667 18H10.5469C11.0378 19.5556 11.9737 20.9333 13.2391 21.9628C14.5044 22.9923 16.0437 23.6285 17.6667 23.7927V26.3333H15.1667C14.9457 26.3333 14.7337 26.4211 14.5774 26.5774C14.4211 26.7337 14.3333 26.9457 14.3333 27.1667C14.3333 27.3877 14.4211 27.5996 14.5774 27.7559C14.7337 27.9122 14.9457 28 15.1667 28H21.8333C22.0543 28 22.2663 27.9122 22.4226 27.7559C22.5789 27.5996 22.6667 27.3877 22.6667 27.1667C22.6667 26.9457 22.5789 26.7337 22.4226 26.5774C22.2663 26.4211 22.0543 26.3333 21.8333 26.3333H19.3333V23.7896C22.6604 23.4531 25.4208 21.1187 26.425 18H26.8333C27.9384 18 28.9982 17.561 29.7796 16.7796C30.561 15.9982 31 14.9384 31 13.8333V12.1667C31 11.7246 30.8244 11.3007 30.5118 10.9882C30.1993 10.6756 29.7754 10.5 29.3333 10.5ZM10.1667 16.3333C9.50363 16.3333 8.86774 16.0699 8.3989 15.6011C7.93006 15.1323 7.66667 14.4964 7.66667 13.8333V12.1667H10.1667V15.5C10.1667 15.7778 10.1802 16.0556 10.2073 16.3333H10.1667ZM29.3333 13.8333C29.3333 14.4964 29.0699 15.1323 28.6011 15.6011C28.1323 16.0699 27.4964 16.3333 26.8333 16.3333H26.7812C26.8154 16.0255 26.8328 15.716 26.8333 15.4062V12.1667H29.3333V13.8333Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1,24 +1,24 @@
|
||||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Meteor">
|
||||
<g id="Vector">
|
||||
<path d="M10.6242 13.0003C10.6242 13.6184 10.4409 14.2226 10.0975 14.7365C9.75415 15.2504 9.26609 15.6509 8.69507 15.8875C8.12405 16.124 7.49572 16.1859 6.88953 16.0653C6.28334 15.9447 5.72652 15.6471 5.28948 15.2101C4.85244 14.773 4.55481 14.2162 4.43423 13.61C4.31366 13.0038 4.37554 12.3755 4.61206 11.8045C4.84859 11.2334 5.24913 10.7454 5.76303 10.402C6.27693 10.0586 6.88112 9.87535 7.49919 9.87535C8.32799 9.87535 9.12285 10.2046 9.7089 10.7906C10.2949 11.3767 10.6242 12.1715 10.6242 13.0003ZM16.432 10.0582L12.682 13.8082C12.5647 13.9254 12.4988 14.0845 12.4988 14.2503C12.4988 14.4162 12.5647 14.5753 12.682 14.6925C12.7993 14.8098 12.9583 14.8757 13.1242 14.8757C13.29 14.8757 13.4491 14.8098 13.5664 14.6925L17.3164 10.9425C17.3744 10.8845 17.4205 10.8155 17.4519 10.7397C17.4834 10.6638 17.4995 10.5825 17.4995 10.5003C17.4995 10.4182 17.4834 10.3369 17.4519 10.261C17.4205 10.1852 17.3744 10.1162 17.3164 10.0582C17.2583 10.0001 17.1894 9.95403 17.1135 9.9226C17.0376 9.89118 16.9563 9.875 16.8742 9.875C16.7921 9.875 16.7107 9.89118 16.6349 9.9226C16.559 9.95403 16.4901 10.0001 16.432 10.0582ZM14.8164 9.06754C14.8744 9.00947 14.9205 8.94053 14.9519 8.86466C14.9834 8.78879 14.9995 8.70747 14.9995 8.62535C14.9995 8.54323 14.9834 8.46191 14.9519 8.38604C14.9205 8.31017 14.8744 8.24123 14.8164 8.18316C14.7583 8.12509 14.6894 8.07903 14.6135 8.0476C14.5376 8.01617 14.4563 8 14.3742 8C14.2921 8 14.2107 8.01617 14.1349 8.0476C14.059 8.07903 13.9901 8.12509 13.932 8.18316L12.057 10.0582C11.9397 10.1754 11.8738 10.3345 11.8738 10.5003C11.8738 10.6662 11.9397 10.8253 12.057 10.9425C12.1743 11.0598 12.3333 11.1257 12.4992 11.1257C12.665 11.1257 12.8241 11.0598 12.9414 10.9425L14.8164 9.06754ZM17.9414 5.05816C17.8833 5.00005 17.8144 4.95395 17.7385 4.9225C17.6627 4.89105 17.5813 4.87486 17.4992 4.87486C17.4171 4.87486 17.3357 4.89105 17.2599 4.9225C17.184 4.95395 17.115 5.00005 17.057 5.05816L15.807 6.30816C15.6897 6.42544 15.6238 6.5845 15.6238 6.75035C15.6238 6.9162 15.6897 7.07526 15.807 7.19254C15.9243 7.30981 16.0833 7.37569 16.2492 7.3757C16.415 7.3757 16.5741 7.30981 16.6914 7.19254L17.9414 5.94254C17.9995 5.88449 18.0456 5.81556 18.077 5.73969C18.1085 5.66381 18.1247 5.58248 18.1247 5.50035C18.1247 5.41821 18.1085 5.33688 18.077 5.26101C18.0456 5.18514 17.9995 5.11621 17.9414 5.05816ZM9.557 8.44254C9.61505 8.50065 9.68398 8.54674 9.75985 8.5782C9.83572 8.60965 9.91705 8.62584 9.99919 8.62584C10.0813 8.62584 10.1627 8.60965 10.2385 8.5782C10.3144 8.54674 10.3833 8.50065 10.4414 8.44254L16.0664 2.81754C16.1244 2.75947 16.1705 2.69053 16.2019 2.61466C16.2334 2.53879 16.2495 2.45747 16.2495 2.37535C16.2495 2.29323 16.2334 2.21191 16.2019 2.13604C16.1705 2.06017 16.1244 1.99123 16.0664 1.93316C16.0083 1.87509 15.9394 1.82903 15.8635 1.7976C15.7876 1.76618 15.7063 1.75 15.6242 1.75C15.5421 1.75 15.4607 1.76618 15.3849 1.7976C15.309 1.82903 15.2401 1.87509 15.182 1.93316L9.557 7.55816C9.49889 7.61621 9.45279 7.68514 9.42134 7.76101C9.38989 7.83688 9.3737 7.91821 9.3737 8.00035C9.3737 8.08248 9.38989 8.16381 9.42134 8.23969C9.45279 8.31556 9.49889 8.38449 9.557 8.44254ZM10.5929 16.0941C9.77242 16.9146 8.65957 17.3756 7.49919 17.3756C6.33881 17.3756 5.22595 16.9146 4.40544 16.0941C3.58492 15.2736 3.12396 14.1607 3.12396 13.0003C3.12396 11.84 3.58492 10.7271 4.40544 9.9066L10.8703 3.44253C10.9284 3.38447 10.9744 3.31553 11.0058 3.23966C11.0373 3.16379 11.0534 3.08247 11.0534 3.00035C11.0534 2.91823 11.0373 2.83691 11.0058 2.76104C10.9744 2.68517 10.9284 2.61623 10.8703 2.55816C10.8122 2.50009 10.7433 2.45403 10.6674 2.4226C10.5915 2.39118 10.5102 2.375 10.4281 2.375C10.346 2.375 10.2647 2.39118 10.1888 2.4226C10.1129 2.45403 10.044 2.50009 9.98591 2.55816L3.52184 9.023C2.99253 9.54377 2.57156 10.1642 2.28322 10.8484C1.99488 11.5327 1.84487 12.2673 1.84184 13.0098C1.83882 13.7524 1.98284 14.4882 2.2656 15.1747C2.54836 15.8613 2.96427 16.4852 3.48932 17.0102C4.01438 17.5353 4.63819 17.9512 5.32479 18.2339C6.01138 18.5167 6.74717 18.6607 7.4897 18.6577C8.23223 18.6547 8.96682 18.5047 9.65109 18.2163C10.3354 17.928 10.9558 17.507 11.4765 16.9777C11.5888 16.8595 11.6505 16.7022 11.6484 16.5392C11.6463 16.3762 11.5806 16.2205 11.4654 16.1053C11.3501 15.99 11.1944 15.9243 11.0314 15.9223C10.8684 15.9202 10.7111 15.9818 10.5929 16.0941Z" fill="black"/>
|
||||
<path d="M10.6242 13.0003C10.6242 13.6184 10.4409 14.2226 10.0975 14.7365C9.75415 15.2504 9.26609 15.6509 8.69507 15.8875C8.12405 16.124 7.49572 16.1859 6.88953 16.0653C6.28334 15.9447 5.72652 15.6471 5.28948 15.2101C4.85244 14.773 4.55481 14.2162 4.43423 13.61C4.31366 13.0038 4.37554 12.3755 4.61206 11.8045C4.84859 11.2334 5.24913 10.7454 5.76303 10.402C6.27693 10.0586 6.88112 9.87535 7.49919 9.87535C8.32799 9.87535 9.12285 10.2046 9.7089 10.7906C10.2949 11.3767 10.6242 12.1715 10.6242 13.0003ZM16.432 10.0582L12.682 13.8082C12.5647 13.9254 12.4988 14.0845 12.4988 14.2503C12.4988 14.4162 12.5647 14.5753 12.682 14.6925C12.7993 14.8098 12.9583 14.8757 13.1242 14.8757C13.29 14.8757 13.4491 14.8098 13.5664 14.6925L17.3164 10.9425C17.3744 10.8845 17.4205 10.8155 17.4519 10.7397C17.4834 10.6638 17.4995 10.5825 17.4995 10.5003C17.4995 10.4182 17.4834 10.3369 17.4519 10.261C17.4205 10.1852 17.3744 10.1162 17.3164 10.0582C17.2583 10.0001 17.1894 9.95403 17.1135 9.9226C17.0376 9.89118 16.9563 9.875 16.8742 9.875C16.7921 9.875 16.7107 9.89118 16.6349 9.9226C16.559 9.95403 16.4901 10.0001 16.432 10.0582ZM14.8164 9.06754C14.8744 9.00947 14.9205 8.94053 14.9519 8.86466C14.9834 8.78879 14.9995 8.70747 14.9995 8.62535C14.9995 8.54323 14.9834 8.46191 14.9519 8.38604C14.9205 8.31017 14.8744 8.24123 14.8164 8.18316C14.7583 8.12509 14.6894 8.07903 14.6135 8.0476C14.5376 8.01617 14.4563 8 14.3742 8C14.2921 8 14.2107 8.01617 14.1349 8.0476C14.059 8.07903 13.9901 8.12509 13.932 8.18316L12.057 10.0582C11.9397 10.1754 11.8738 10.3345 11.8738 10.5003C11.8738 10.6662 11.9397 10.8253 12.057 10.9425C12.1743 11.0598 12.3333 11.1257 12.4992 11.1257C12.665 11.1257 12.8241 11.0598 12.9414 10.9425L14.8164 9.06754ZM17.9414 5.05816C17.8833 5.00005 17.8144 4.95395 17.7385 4.9225C17.6627 4.89105 17.5813 4.87486 17.4992 4.87486C17.4171 4.87486 17.3357 4.89105 17.2599 4.9225C17.184 4.95395 17.115 5.00005 17.057 5.05816L15.807 6.30816C15.6897 6.42544 15.6238 6.5845 15.6238 6.75035C15.6238 6.9162 15.6897 7.07526 15.807 7.19254C15.9243 7.30981 16.0833 7.37569 16.2492 7.3757C16.415 7.3757 16.5741 7.30981 16.6914 7.19254L17.9414 5.94254C17.9995 5.88449 18.0456 5.81556 18.077 5.73969C18.1085 5.66381 18.1247 5.58248 18.1247 5.50035C18.1247 5.41821 18.1085 5.33688 18.077 5.26101C18.0456 5.18514 17.9995 5.11621 17.9414 5.05816ZM9.557 8.44254C9.61505 8.50065 9.68398 8.54674 9.75985 8.5782C9.83572 8.60965 9.91705 8.62584 9.99919 8.62584C10.0813 8.62584 10.1627 8.60965 10.2385 8.5782C10.3144 8.54674 10.3833 8.50065 10.4414 8.44254L16.0664 2.81754C16.1244 2.75947 16.1705 2.69053 16.2019 2.61466C16.2334 2.53879 16.2495 2.45747 16.2495 2.37535C16.2495 2.29323 16.2334 2.21191 16.2019 2.13604C16.1705 2.06017 16.1244 1.99123 16.0664 1.93316C16.0083 1.87509 15.9394 1.82903 15.8635 1.7976C15.7876 1.76618 15.7063 1.75 15.6242 1.75C15.5421 1.75 15.4607 1.76618 15.3849 1.7976C15.309 1.82903 15.2401 1.87509 15.182 1.93316L9.557 7.55816C9.49889 7.61621 9.45279 7.68514 9.42134 7.76101C9.38989 7.83688 9.3737 7.91821 9.3737 8.00035C9.3737 8.08248 9.38989 8.16381 9.42134 8.23969C9.45279 8.31556 9.49889 8.38449 9.557 8.44254ZM10.5929 16.0941C9.77242 16.9146 8.65957 17.3756 7.49919 17.3756C6.33881 17.3756 5.22595 16.9146 4.40544 16.0941C3.58492 15.2736 3.12396 14.1607 3.12396 13.0003C3.12396 11.84 3.58492 10.7271 4.40544 9.9066L10.8703 3.44253C10.9284 3.38447 10.9744 3.31553 11.0058 3.23966C11.0373 3.16379 11.0534 3.08247 11.0534 3.00035C11.0534 2.91823 11.0373 2.83691 11.0058 2.76104C10.9744 2.68517 10.9284 2.61623 10.8703 2.55816C10.8122 2.50009 10.7433 2.45403 10.6674 2.4226C10.5915 2.39118 10.5102 2.375 10.4281 2.375C10.346 2.375 10.2647 2.39118 10.1888 2.4226C10.1129 2.45403 10.044 2.50009 9.98591 2.55816L3.52184 9.023C2.99253 9.54377 2.57156 10.1642 2.28322 10.8484C1.99488 11.5327 1.84487 12.2673 1.84184 13.0098C1.83882 13.7524 1.98284 14.4882 2.2656 15.1747C2.54836 15.8613 2.96427 16.4852 3.48932 17.0102C4.01438 17.5353 4.63819 17.9512 5.32479 18.2339C6.01138 18.5167 6.74717 18.6607 7.4897 18.6577C8.23223 18.6547 8.96682 18.5047 9.65109 18.2163C10.3354 17.928 10.9558 17.507 11.4765 16.9777C11.5888 16.8595 11.6505 16.7022 11.6484 16.5392C11.6463 16.3762 11.5806 16.2205 11.4654 16.1053C11.3501 15.99 11.1944 15.9243 11.0314 15.9223C10.8684 15.9202 10.7111 15.9818 10.5929 16.0941Z" fill="url(#paint0_linear_2850_16638)"/>
|
||||
<path d="M10.6242 13.0003C10.6242 13.6184 10.4409 14.2226 10.0975 14.7365C9.75415 15.2504 9.26609 15.6509 8.69507 15.8875C8.12405 16.124 7.49572 16.1859 6.88953 16.0653C6.28334 15.9447 5.72652 15.6471 5.28948 15.2101C4.85244 14.773 4.55481 14.2162 4.43423 13.61C4.31366 13.0038 4.37554 12.3755 4.61206 11.8045C4.84859 11.2334 5.24913 10.7454 5.76303 10.402C6.27693 10.0586 6.88112 9.87535 7.49919 9.87535C8.32799 9.87535 9.12285 10.2046 9.7089 10.7906C10.2949 11.3767 10.6242 12.1715 10.6242 13.0003ZM16.432 10.0582L12.682 13.8082C12.5647 13.9254 12.4988 14.0845 12.4988 14.2503C12.4988 14.4162 12.5647 14.5753 12.682 14.6925C12.7993 14.8098 12.9583 14.8757 13.1242 14.8757C13.29 14.8757 13.4491 14.8098 13.5664 14.6925L17.3164 10.9425C17.3744 10.8845 17.4205 10.8155 17.4519 10.7397C17.4834 10.6638 17.4995 10.5825 17.4995 10.5003C17.4995 10.4182 17.4834 10.3369 17.4519 10.261C17.4205 10.1852 17.3744 10.1162 17.3164 10.0582C17.2583 10.0001 17.1894 9.95403 17.1135 9.9226C17.0376 9.89118 16.9563 9.875 16.8742 9.875C16.7921 9.875 16.7107 9.89118 16.6349 9.9226C16.559 9.95403 16.4901 10.0001 16.432 10.0582ZM14.8164 9.06754C14.8744 9.00947 14.9205 8.94053 14.9519 8.86466C14.9834 8.78879 14.9995 8.70747 14.9995 8.62535C14.9995 8.54323 14.9834 8.46191 14.9519 8.38604C14.9205 8.31017 14.8744 8.24123 14.8164 8.18316C14.7583 8.12509 14.6894 8.07903 14.6135 8.0476C14.5376 8.01617 14.4563 8 14.3742 8C14.2921 8 14.2107 8.01617 14.1349 8.0476C14.059 8.07903 13.9901 8.12509 13.932 8.18316L12.057 10.0582C11.9397 10.1754 11.8738 10.3345 11.8738 10.5003C11.8738 10.6662 11.9397 10.8253 12.057 10.9425C12.1743 11.0598 12.3333 11.1257 12.4992 11.1257C12.665 11.1257 12.8241 11.0598 12.9414 10.9425L14.8164 9.06754ZM17.9414 5.05816C17.8833 5.00005 17.8144 4.95395 17.7385 4.9225C17.6627 4.89105 17.5813 4.87486 17.4992 4.87486C17.4171 4.87486 17.3357 4.89105 17.2599 4.9225C17.184 4.95395 17.115 5.00005 17.057 5.05816L15.807 6.30816C15.6897 6.42544 15.6238 6.5845 15.6238 6.75035C15.6238 6.9162 15.6897 7.07526 15.807 7.19254C15.9243 7.30981 16.0833 7.37569 16.2492 7.3757C16.415 7.3757 16.5741 7.30981 16.6914 7.19254L17.9414 5.94254C17.9995 5.88449 18.0456 5.81556 18.077 5.73969C18.1085 5.66381 18.1247 5.58248 18.1247 5.50035C18.1247 5.41821 18.1085 5.33688 18.077 5.26101C18.0456 5.18514 17.9995 5.11621 17.9414 5.05816ZM9.557 8.44254C9.61505 8.50065 9.68398 8.54674 9.75985 8.5782C9.83572 8.60965 9.91705 8.62584 9.99919 8.62584C10.0813 8.62584 10.1627 8.60965 10.2385 8.5782C10.3144 8.54674 10.3833 8.50065 10.4414 8.44254L16.0664 2.81754C16.1244 2.75947 16.1705 2.69053 16.2019 2.61466C16.2334 2.53879 16.2495 2.45747 16.2495 2.37535C16.2495 2.29323 16.2334 2.21191 16.2019 2.13604C16.1705 2.06017 16.1244 1.99123 16.0664 1.93316C16.0083 1.87509 15.9394 1.82903 15.8635 1.7976C15.7876 1.76618 15.7063 1.75 15.6242 1.75C15.5421 1.75 15.4607 1.76618 15.3849 1.7976C15.309 1.82903 15.2401 1.87509 15.182 1.93316L9.557 7.55816C9.49889 7.61621 9.45279 7.68514 9.42134 7.76101C9.38989 7.83688 9.3737 7.91821 9.3737 8.00035C9.3737 8.08248 9.38989 8.16381 9.42134 8.23969C9.45279 8.31556 9.49889 8.38449 9.557 8.44254ZM10.5929 16.0941C9.77242 16.9146 8.65957 17.3756 7.49919 17.3756C6.33881 17.3756 5.22595 16.9146 4.40544 16.0941C3.58492 15.2736 3.12396 14.1607 3.12396 13.0003C3.12396 11.84 3.58492 10.7271 4.40544 9.9066L10.8703 3.44253C10.9284 3.38447 10.9744 3.31553 11.0058 3.23966C11.0373 3.16379 11.0534 3.08247 11.0534 3.00035C11.0534 2.91823 11.0373 2.83691 11.0058 2.76104C10.9744 2.68517 10.9284 2.61623 10.8703 2.55816C10.8122 2.50009 10.7433 2.45403 10.6674 2.4226C10.5915 2.39118 10.5102 2.375 10.4281 2.375C10.346 2.375 10.2647 2.39118 10.1888 2.4226C10.1129 2.45403 10.044 2.50009 9.98591 2.55816L3.52184 9.023C2.99253 9.54377 2.57156 10.1642 2.28322 10.8484C1.99488 11.5327 1.84487 12.2673 1.84184 13.0098C1.83882 13.7524 1.98284 14.4882 2.2656 15.1747C2.54836 15.8613 2.96427 16.4852 3.48932 17.0102C4.01438 17.5353 4.63819 17.9512 5.32479 18.2339C6.01138 18.5167 6.74717 18.6607 7.4897 18.6577C8.23223 18.6547 8.96682 18.5047 9.65109 18.2163C10.3354 17.928 10.9558 17.507 11.4765 16.9777C11.5888 16.8595 11.6505 16.7022 11.6484 16.5392C11.6463 16.3762 11.5806 16.2205 11.4654 16.1053C11.3501 15.99 11.1944 15.9243 11.0314 15.9223C10.8684 15.9202 10.7111 15.9818 10.5929 16.0941Z" stroke="url(#paint1_linear_2850_16638)" stroke-width="0.3"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2850_16638" x1="1.95109" y1="1.75" x2="21.5698" y2="11.5208" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0CF1CA"/>
|
||||
<stop offset="0.264423" stop-color="#0BD2B0"/>
|
||||
<stop offset="0.307692" stop-color="#0CF1CA"/>
|
||||
<stop offset="0.427885" stop-color="#0CF1CA"/>
|
||||
<stop offset="0.466346" stop-color="#0FAF94"/>
|
||||
<stop offset="0.591346" stop-color="#0CA288"/>
|
||||
<stop offset="1" stop-color="#086253"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_2850_16638" x1="1.8418" y1="2.25694" x2="21.3121" y2="11.25" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Meteor">
|
||||
<g id="Vector">
|
||||
<path d="M10.6242 13.0003C10.6242 13.6184 10.4409 14.2226 10.0975 14.7365C9.75415 15.2504 9.26609 15.6509 8.69507 15.8875C8.12405 16.124 7.49572 16.1859 6.88953 16.0653C6.28334 15.9447 5.72652 15.6471 5.28948 15.2101C4.85244 14.773 4.55481 14.2162 4.43423 13.61C4.31366 13.0038 4.37554 12.3755 4.61206 11.8045C4.84859 11.2334 5.24913 10.7454 5.76303 10.402C6.27693 10.0586 6.88112 9.87535 7.49919 9.87535C8.32799 9.87535 9.12285 10.2046 9.7089 10.7906C10.2949 11.3767 10.6242 12.1715 10.6242 13.0003ZM16.432 10.0582L12.682 13.8082C12.5647 13.9254 12.4988 14.0845 12.4988 14.2503C12.4988 14.4162 12.5647 14.5753 12.682 14.6925C12.7993 14.8098 12.9583 14.8757 13.1242 14.8757C13.29 14.8757 13.4491 14.8098 13.5664 14.6925L17.3164 10.9425C17.3744 10.8845 17.4205 10.8155 17.4519 10.7397C17.4834 10.6638 17.4995 10.5825 17.4995 10.5003C17.4995 10.4182 17.4834 10.3369 17.4519 10.261C17.4205 10.1852 17.3744 10.1162 17.3164 10.0582C17.2583 10.0001 17.1894 9.95403 17.1135 9.9226C17.0376 9.89118 16.9563 9.875 16.8742 9.875C16.7921 9.875 16.7107 9.89118 16.6349 9.9226C16.559 9.95403 16.4901 10.0001 16.432 10.0582ZM14.8164 9.06754C14.8744 9.00947 14.9205 8.94053 14.9519 8.86466C14.9834 8.78879 14.9995 8.70747 14.9995 8.62535C14.9995 8.54323 14.9834 8.46191 14.9519 8.38604C14.9205 8.31017 14.8744 8.24123 14.8164 8.18316C14.7583 8.12509 14.6894 8.07903 14.6135 8.0476C14.5376 8.01617 14.4563 8 14.3742 8C14.2921 8 14.2107 8.01617 14.1349 8.0476C14.059 8.07903 13.9901 8.12509 13.932 8.18316L12.057 10.0582C11.9397 10.1754 11.8738 10.3345 11.8738 10.5003C11.8738 10.6662 11.9397 10.8253 12.057 10.9425C12.1743 11.0598 12.3333 11.1257 12.4992 11.1257C12.665 11.1257 12.8241 11.0598 12.9414 10.9425L14.8164 9.06754ZM17.9414 5.05816C17.8833 5.00005 17.8144 4.95395 17.7385 4.9225C17.6627 4.89105 17.5813 4.87486 17.4992 4.87486C17.4171 4.87486 17.3357 4.89105 17.2599 4.9225C17.184 4.95395 17.115 5.00005 17.057 5.05816L15.807 6.30816C15.6897 6.42544 15.6238 6.5845 15.6238 6.75035C15.6238 6.9162 15.6897 7.07526 15.807 7.19254C15.9243 7.30981 16.0833 7.37569 16.2492 7.3757C16.415 7.3757 16.5741 7.30981 16.6914 7.19254L17.9414 5.94254C17.9995 5.88449 18.0456 5.81556 18.077 5.73969C18.1085 5.66381 18.1247 5.58248 18.1247 5.50035C18.1247 5.41821 18.1085 5.33688 18.077 5.26101C18.0456 5.18514 17.9995 5.11621 17.9414 5.05816ZM9.557 8.44254C9.61505 8.50065 9.68398 8.54674 9.75985 8.5782C9.83572 8.60965 9.91705 8.62584 9.99919 8.62584C10.0813 8.62584 10.1627 8.60965 10.2385 8.5782C10.3144 8.54674 10.3833 8.50065 10.4414 8.44254L16.0664 2.81754C16.1244 2.75947 16.1705 2.69053 16.2019 2.61466C16.2334 2.53879 16.2495 2.45747 16.2495 2.37535C16.2495 2.29323 16.2334 2.21191 16.2019 2.13604C16.1705 2.06017 16.1244 1.99123 16.0664 1.93316C16.0083 1.87509 15.9394 1.82903 15.8635 1.7976C15.7876 1.76618 15.7063 1.75 15.6242 1.75C15.5421 1.75 15.4607 1.76618 15.3849 1.7976C15.309 1.82903 15.2401 1.87509 15.182 1.93316L9.557 7.55816C9.49889 7.61621 9.45279 7.68514 9.42134 7.76101C9.38989 7.83688 9.3737 7.91821 9.3737 8.00035C9.3737 8.08248 9.38989 8.16381 9.42134 8.23969C9.45279 8.31556 9.49889 8.38449 9.557 8.44254ZM10.5929 16.0941C9.77242 16.9146 8.65957 17.3756 7.49919 17.3756C6.33881 17.3756 5.22595 16.9146 4.40544 16.0941C3.58492 15.2736 3.12396 14.1607 3.12396 13.0003C3.12396 11.84 3.58492 10.7271 4.40544 9.9066L10.8703 3.44253C10.9284 3.38447 10.9744 3.31553 11.0058 3.23966C11.0373 3.16379 11.0534 3.08247 11.0534 3.00035C11.0534 2.91823 11.0373 2.83691 11.0058 2.76104C10.9744 2.68517 10.9284 2.61623 10.8703 2.55816C10.8122 2.50009 10.7433 2.45403 10.6674 2.4226C10.5915 2.39118 10.5102 2.375 10.4281 2.375C10.346 2.375 10.2647 2.39118 10.1888 2.4226C10.1129 2.45403 10.044 2.50009 9.98591 2.55816L3.52184 9.023C2.99253 9.54377 2.57156 10.1642 2.28322 10.8484C1.99488 11.5327 1.84487 12.2673 1.84184 13.0098C1.83882 13.7524 1.98284 14.4882 2.2656 15.1747C2.54836 15.8613 2.96427 16.4852 3.48932 17.0102C4.01438 17.5353 4.63819 17.9512 5.32479 18.2339C6.01138 18.5167 6.74717 18.6607 7.4897 18.6577C8.23223 18.6547 8.96682 18.5047 9.65109 18.2163C10.3354 17.928 10.9558 17.507 11.4765 16.9777C11.5888 16.8595 11.6505 16.7022 11.6484 16.5392C11.6463 16.3762 11.5806 16.2205 11.4654 16.1053C11.3501 15.99 11.1944 15.9243 11.0314 15.9223C10.8684 15.9202 10.7111 15.9818 10.5929 16.0941Z" fill="black"/>
|
||||
<path d="M10.6242 13.0003C10.6242 13.6184 10.4409 14.2226 10.0975 14.7365C9.75415 15.2504 9.26609 15.6509 8.69507 15.8875C8.12405 16.124 7.49572 16.1859 6.88953 16.0653C6.28334 15.9447 5.72652 15.6471 5.28948 15.2101C4.85244 14.773 4.55481 14.2162 4.43423 13.61C4.31366 13.0038 4.37554 12.3755 4.61206 11.8045C4.84859 11.2334 5.24913 10.7454 5.76303 10.402C6.27693 10.0586 6.88112 9.87535 7.49919 9.87535C8.32799 9.87535 9.12285 10.2046 9.7089 10.7906C10.2949 11.3767 10.6242 12.1715 10.6242 13.0003ZM16.432 10.0582L12.682 13.8082C12.5647 13.9254 12.4988 14.0845 12.4988 14.2503C12.4988 14.4162 12.5647 14.5753 12.682 14.6925C12.7993 14.8098 12.9583 14.8757 13.1242 14.8757C13.29 14.8757 13.4491 14.8098 13.5664 14.6925L17.3164 10.9425C17.3744 10.8845 17.4205 10.8155 17.4519 10.7397C17.4834 10.6638 17.4995 10.5825 17.4995 10.5003C17.4995 10.4182 17.4834 10.3369 17.4519 10.261C17.4205 10.1852 17.3744 10.1162 17.3164 10.0582C17.2583 10.0001 17.1894 9.95403 17.1135 9.9226C17.0376 9.89118 16.9563 9.875 16.8742 9.875C16.7921 9.875 16.7107 9.89118 16.6349 9.9226C16.559 9.95403 16.4901 10.0001 16.432 10.0582ZM14.8164 9.06754C14.8744 9.00947 14.9205 8.94053 14.9519 8.86466C14.9834 8.78879 14.9995 8.70747 14.9995 8.62535C14.9995 8.54323 14.9834 8.46191 14.9519 8.38604C14.9205 8.31017 14.8744 8.24123 14.8164 8.18316C14.7583 8.12509 14.6894 8.07903 14.6135 8.0476C14.5376 8.01617 14.4563 8 14.3742 8C14.2921 8 14.2107 8.01617 14.1349 8.0476C14.059 8.07903 13.9901 8.12509 13.932 8.18316L12.057 10.0582C11.9397 10.1754 11.8738 10.3345 11.8738 10.5003C11.8738 10.6662 11.9397 10.8253 12.057 10.9425C12.1743 11.0598 12.3333 11.1257 12.4992 11.1257C12.665 11.1257 12.8241 11.0598 12.9414 10.9425L14.8164 9.06754ZM17.9414 5.05816C17.8833 5.00005 17.8144 4.95395 17.7385 4.9225C17.6627 4.89105 17.5813 4.87486 17.4992 4.87486C17.4171 4.87486 17.3357 4.89105 17.2599 4.9225C17.184 4.95395 17.115 5.00005 17.057 5.05816L15.807 6.30816C15.6897 6.42544 15.6238 6.5845 15.6238 6.75035C15.6238 6.9162 15.6897 7.07526 15.807 7.19254C15.9243 7.30981 16.0833 7.37569 16.2492 7.3757C16.415 7.3757 16.5741 7.30981 16.6914 7.19254L17.9414 5.94254C17.9995 5.88449 18.0456 5.81556 18.077 5.73969C18.1085 5.66381 18.1247 5.58248 18.1247 5.50035C18.1247 5.41821 18.1085 5.33688 18.077 5.26101C18.0456 5.18514 17.9995 5.11621 17.9414 5.05816ZM9.557 8.44254C9.61505 8.50065 9.68398 8.54674 9.75985 8.5782C9.83572 8.60965 9.91705 8.62584 9.99919 8.62584C10.0813 8.62584 10.1627 8.60965 10.2385 8.5782C10.3144 8.54674 10.3833 8.50065 10.4414 8.44254L16.0664 2.81754C16.1244 2.75947 16.1705 2.69053 16.2019 2.61466C16.2334 2.53879 16.2495 2.45747 16.2495 2.37535C16.2495 2.29323 16.2334 2.21191 16.2019 2.13604C16.1705 2.06017 16.1244 1.99123 16.0664 1.93316C16.0083 1.87509 15.9394 1.82903 15.8635 1.7976C15.7876 1.76618 15.7063 1.75 15.6242 1.75C15.5421 1.75 15.4607 1.76618 15.3849 1.7976C15.309 1.82903 15.2401 1.87509 15.182 1.93316L9.557 7.55816C9.49889 7.61621 9.45279 7.68514 9.42134 7.76101C9.38989 7.83688 9.3737 7.91821 9.3737 8.00035C9.3737 8.08248 9.38989 8.16381 9.42134 8.23969C9.45279 8.31556 9.49889 8.38449 9.557 8.44254ZM10.5929 16.0941C9.77242 16.9146 8.65957 17.3756 7.49919 17.3756C6.33881 17.3756 5.22595 16.9146 4.40544 16.0941C3.58492 15.2736 3.12396 14.1607 3.12396 13.0003C3.12396 11.84 3.58492 10.7271 4.40544 9.9066L10.8703 3.44253C10.9284 3.38447 10.9744 3.31553 11.0058 3.23966C11.0373 3.16379 11.0534 3.08247 11.0534 3.00035C11.0534 2.91823 11.0373 2.83691 11.0058 2.76104C10.9744 2.68517 10.9284 2.61623 10.8703 2.55816C10.8122 2.50009 10.7433 2.45403 10.6674 2.4226C10.5915 2.39118 10.5102 2.375 10.4281 2.375C10.346 2.375 10.2647 2.39118 10.1888 2.4226C10.1129 2.45403 10.044 2.50009 9.98591 2.55816L3.52184 9.023C2.99253 9.54377 2.57156 10.1642 2.28322 10.8484C1.99488 11.5327 1.84487 12.2673 1.84184 13.0098C1.83882 13.7524 1.98284 14.4882 2.2656 15.1747C2.54836 15.8613 2.96427 16.4852 3.48932 17.0102C4.01438 17.5353 4.63819 17.9512 5.32479 18.2339C6.01138 18.5167 6.74717 18.6607 7.4897 18.6577C8.23223 18.6547 8.96682 18.5047 9.65109 18.2163C10.3354 17.928 10.9558 17.507 11.4765 16.9777C11.5888 16.8595 11.6505 16.7022 11.6484 16.5392C11.6463 16.3762 11.5806 16.2205 11.4654 16.1053C11.3501 15.99 11.1944 15.9243 11.0314 15.9223C10.8684 15.9202 10.7111 15.9818 10.5929 16.0941Z" fill="url(#paint0_linear_2850_16638)"/>
|
||||
<path d="M10.6242 13.0003C10.6242 13.6184 10.4409 14.2226 10.0975 14.7365C9.75415 15.2504 9.26609 15.6509 8.69507 15.8875C8.12405 16.124 7.49572 16.1859 6.88953 16.0653C6.28334 15.9447 5.72652 15.6471 5.28948 15.2101C4.85244 14.773 4.55481 14.2162 4.43423 13.61C4.31366 13.0038 4.37554 12.3755 4.61206 11.8045C4.84859 11.2334 5.24913 10.7454 5.76303 10.402C6.27693 10.0586 6.88112 9.87535 7.49919 9.87535C8.32799 9.87535 9.12285 10.2046 9.7089 10.7906C10.2949 11.3767 10.6242 12.1715 10.6242 13.0003ZM16.432 10.0582L12.682 13.8082C12.5647 13.9254 12.4988 14.0845 12.4988 14.2503C12.4988 14.4162 12.5647 14.5753 12.682 14.6925C12.7993 14.8098 12.9583 14.8757 13.1242 14.8757C13.29 14.8757 13.4491 14.8098 13.5664 14.6925L17.3164 10.9425C17.3744 10.8845 17.4205 10.8155 17.4519 10.7397C17.4834 10.6638 17.4995 10.5825 17.4995 10.5003C17.4995 10.4182 17.4834 10.3369 17.4519 10.261C17.4205 10.1852 17.3744 10.1162 17.3164 10.0582C17.2583 10.0001 17.1894 9.95403 17.1135 9.9226C17.0376 9.89118 16.9563 9.875 16.8742 9.875C16.7921 9.875 16.7107 9.89118 16.6349 9.9226C16.559 9.95403 16.4901 10.0001 16.432 10.0582ZM14.8164 9.06754C14.8744 9.00947 14.9205 8.94053 14.9519 8.86466C14.9834 8.78879 14.9995 8.70747 14.9995 8.62535C14.9995 8.54323 14.9834 8.46191 14.9519 8.38604C14.9205 8.31017 14.8744 8.24123 14.8164 8.18316C14.7583 8.12509 14.6894 8.07903 14.6135 8.0476C14.5376 8.01617 14.4563 8 14.3742 8C14.2921 8 14.2107 8.01617 14.1349 8.0476C14.059 8.07903 13.9901 8.12509 13.932 8.18316L12.057 10.0582C11.9397 10.1754 11.8738 10.3345 11.8738 10.5003C11.8738 10.6662 11.9397 10.8253 12.057 10.9425C12.1743 11.0598 12.3333 11.1257 12.4992 11.1257C12.665 11.1257 12.8241 11.0598 12.9414 10.9425L14.8164 9.06754ZM17.9414 5.05816C17.8833 5.00005 17.8144 4.95395 17.7385 4.9225C17.6627 4.89105 17.5813 4.87486 17.4992 4.87486C17.4171 4.87486 17.3357 4.89105 17.2599 4.9225C17.184 4.95395 17.115 5.00005 17.057 5.05816L15.807 6.30816C15.6897 6.42544 15.6238 6.5845 15.6238 6.75035C15.6238 6.9162 15.6897 7.07526 15.807 7.19254C15.9243 7.30981 16.0833 7.37569 16.2492 7.3757C16.415 7.3757 16.5741 7.30981 16.6914 7.19254L17.9414 5.94254C17.9995 5.88449 18.0456 5.81556 18.077 5.73969C18.1085 5.66381 18.1247 5.58248 18.1247 5.50035C18.1247 5.41821 18.1085 5.33688 18.077 5.26101C18.0456 5.18514 17.9995 5.11621 17.9414 5.05816ZM9.557 8.44254C9.61505 8.50065 9.68398 8.54674 9.75985 8.5782C9.83572 8.60965 9.91705 8.62584 9.99919 8.62584C10.0813 8.62584 10.1627 8.60965 10.2385 8.5782C10.3144 8.54674 10.3833 8.50065 10.4414 8.44254L16.0664 2.81754C16.1244 2.75947 16.1705 2.69053 16.2019 2.61466C16.2334 2.53879 16.2495 2.45747 16.2495 2.37535C16.2495 2.29323 16.2334 2.21191 16.2019 2.13604C16.1705 2.06017 16.1244 1.99123 16.0664 1.93316C16.0083 1.87509 15.9394 1.82903 15.8635 1.7976C15.7876 1.76618 15.7063 1.75 15.6242 1.75C15.5421 1.75 15.4607 1.76618 15.3849 1.7976C15.309 1.82903 15.2401 1.87509 15.182 1.93316L9.557 7.55816C9.49889 7.61621 9.45279 7.68514 9.42134 7.76101C9.38989 7.83688 9.3737 7.91821 9.3737 8.00035C9.3737 8.08248 9.38989 8.16381 9.42134 8.23969C9.45279 8.31556 9.49889 8.38449 9.557 8.44254ZM10.5929 16.0941C9.77242 16.9146 8.65957 17.3756 7.49919 17.3756C6.33881 17.3756 5.22595 16.9146 4.40544 16.0941C3.58492 15.2736 3.12396 14.1607 3.12396 13.0003C3.12396 11.84 3.58492 10.7271 4.40544 9.9066L10.8703 3.44253C10.9284 3.38447 10.9744 3.31553 11.0058 3.23966C11.0373 3.16379 11.0534 3.08247 11.0534 3.00035C11.0534 2.91823 11.0373 2.83691 11.0058 2.76104C10.9744 2.68517 10.9284 2.61623 10.8703 2.55816C10.8122 2.50009 10.7433 2.45403 10.6674 2.4226C10.5915 2.39118 10.5102 2.375 10.4281 2.375C10.346 2.375 10.2647 2.39118 10.1888 2.4226C10.1129 2.45403 10.044 2.50009 9.98591 2.55816L3.52184 9.023C2.99253 9.54377 2.57156 10.1642 2.28322 10.8484C1.99488 11.5327 1.84487 12.2673 1.84184 13.0098C1.83882 13.7524 1.98284 14.4882 2.2656 15.1747C2.54836 15.8613 2.96427 16.4852 3.48932 17.0102C4.01438 17.5353 4.63819 17.9512 5.32479 18.2339C6.01138 18.5167 6.74717 18.6607 7.4897 18.6577C8.23223 18.6547 8.96682 18.5047 9.65109 18.2163C10.3354 17.928 10.9558 17.507 11.4765 16.9777C11.5888 16.8595 11.6505 16.7022 11.6484 16.5392C11.6463 16.3762 11.5806 16.2205 11.4654 16.1053C11.3501 15.99 11.1944 15.9243 11.0314 15.9223C10.8684 15.9202 10.7111 15.9818 10.5929 16.0941Z" stroke="url(#paint1_linear_2850_16638)" stroke-width="0.3"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2850_16638" x1="1.95109" y1="1.75" x2="21.5698" y2="11.5208" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0CF1CA"/>
|
||||
<stop offset="0.264423" stop-color="#0BD2B0"/>
|
||||
<stop offset="0.307692" stop-color="#0CF1CA"/>
|
||||
<stop offset="0.427885" stop-color="#0CF1CA"/>
|
||||
<stop offset="0.466346" stop-color="#0FAF94"/>
|
||||
<stop offset="0.591346" stop-color="#0CA288"/>
|
||||
<stop offset="1" stop-color="#086253"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_2850_16638" x1="1.8418" y1="2.25694" x2="21.3121" y2="11.25" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="white" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="white" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 409 B |
@@ -1,42 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_2_1_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 491 491" style="enable-background:new 0 0 491 491;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#1A2233;}
|
||||
.st1{fill:#F50057;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
</style>
|
||||
<!-- <path class="st0" d="M455.3,491H35.7C16,491,0,475,0,455.3V35.7C0,16,16,0,35.7,0h419.7C475,0,491,16,491,35.7v419.7
|
||||
C491,475,475,491,455.3,491z"/> -->
|
||||
<g>
|
||||
<path class="st1" d="M470.5,245.5c0-29.8-37.3-58.1-94.6-75.6c13.2-58.3,7.3-104.7-18.5-119.6c-6-3.5-12.9-5.1-20.5-5.1v20.5
|
||||
c4.2,0,7.6,0.8,10.5,2.4c12.5,7.2,17.9,34.4,13.7,69.4c-1,8.6-2.7,17.7-4.7,27c-18-4.4-37.6-7.8-58.2-10
|
||||
c-12.4-17-25.2-32.4-38.2-45.9c29.9-27.8,58-43,77-43V45.1l0,0c-25.2,0-58.2,18-91.6,49.2c-33.4-31-66.4-48.8-91.6-48.8v20.5
|
||||
c19,0,47.1,15.1,77,42.7c-12.8,13.5-25.7,28.8-37.9,45.8c-20.7,2.2-40.4,5.6-58.3,10.1c-2.1-9.2-3.7-18.1-4.8-26.6
|
||||
c-4.3-35,1-62.3,13.4-69.5c2.8-1.7,6.3-2.4,10.5-2.4V45.6l0,0c-7.7,0-14.7,1.7-20.7,5.1c-25.8,14.9-31.6,61.2-18.3,119.3
|
||||
c-57.1,17.6-94.2,45.8-94.2,75.5c0,29.8,37.3,58.1,94.6,75.6c-13.2,58.3-7.3,104.7,18.5,119.6c6,3.5,12.9,5.1,20.6,5.1
|
||||
c25.2,0,58.2-18,91.6-49.2c33.4,31,66.4,48.8,91.6,48.8c7.7,0,14.7-1.7,20.7-5.1c25.8-14.9,31.6-61.2,18.3-119.3
|
||||
C433.4,303.5,470.5,275.3,470.5,245.5z M351.1,184.4c-3.4,11.8-7.6,24-12.4,36.2c-3.8-7.3-7.7-14.7-12-22
|
||||
c-4.2-7.3-8.7-14.5-13.2-21.5C326.5,179,339.1,181.4,351.1,184.4z M309.1,282.1c-7.2,12.4-14.5,24.1-22.1,35
|
||||
c-13.7,1.2-27.5,1.8-41.5,1.8c-13.9,0-27.7-0.6-41.3-1.7c-7.6-10.9-15-22.6-22.2-34.9c-7-12-13.3-24.2-19.1-36.5
|
||||
c5.7-12.3,12.1-24.6,19-36.6c7.2-12.4,14.5-24.1,22.1-35c13.7-1.2,27.5-1.8,41.5-1.8c13.9,0,27.7,0.6,41.3,1.7
|
||||
c7.6,10.9,15,22.6,22.2,34.9c7,12,13.3,24.2,19.1,36.5C322.3,257.7,315.9,270,309.1,282.1z M338.7,270.1c5,12.3,9.2,24.6,12.7,36.5
|
||||
c-12,2.9-24.7,5.4-37.8,7.3c4.5-7.1,9-14.3,13.2-21.7C331,284.9,334.9,277.5,338.7,270.1z M245.7,368c-8.5-8.8-17.1-18.6-25.5-29.4
|
||||
c8.3,0.4,16.7,0.6,25.2,0.6c8.6,0,17.2-0.2,25.5-0.6C262.7,349.4,254.1,359.2,245.7,368z M177.4,314c-13-1.9-25.6-4.3-37.6-7.2
|
||||
c3.4-11.8,7.6-24,12.4-36.2c3.8,7.3,7.7,14.7,12,22C168.5,299.8,172.9,307,177.4,314z M245.2,123.1c8.5,8.8,17.1,18.6,25.5,29.4
|
||||
c-8.3-0.4-16.7-0.6-25.2-0.6c-8.6,0-17.2,0.2-25.5,0.6C228.3,141.7,236.8,131.9,245.2,123.1z M177.3,177.1
|
||||
c-4.5,7.1-9,14.3-13.2,21.7c-4.2,7.3-8.2,14.7-11.9,22c-5-12.3-9.2-24.6-12.7-36.5C151.6,181.5,164.2,179,177.3,177.1z M94.3,292
|
||||
c-32.5-13.9-53.5-32-53.5-46.4c0-14.4,21-32.7,53.5-46.4c7.9-3.4,16.5-6.4,25.4-9.3c5.2,18,12.1,36.7,20.6,55.9
|
||||
c-8.4,19.1-15.2,37.7-20.4,55.6C110.9,298.5,102.3,295.4,94.3,292z M143.7,423c-12.5-7.2-17.9-34.4-13.7-69.4
|
||||
c1-8.6,2.7-17.7,4.7-27c18,4.4,37.6,7.8,58.2,10c12.4,17,25.2,32.4,38.2,45.9c-29.9,27.8-58,43-77,43
|
||||
C149.9,425.4,146.4,424.6,143.7,423z M361.3,353.1c4.3,35-1,62.3-13.4,69.5c-2.8,1.7-6.3,2.4-10.5,2.4c-19,0-47.1-15.1-77-42.7
|
||||
c12.8-13.5,25.7-28.8,37.9-45.8c20.7-2.2,40.4-5.6,58.3-10.1C358.6,335.7,360.2,344.6,361.3,353.1z M396.6,292
|
||||
c-7.9,3.4-16.5,6.4-25.4,9.3c-5.2-18-12.1-36.7-20.6-55.9c8.4-19.1,15.2-37.7,20.4-55.6c9.1,2.8,17.7,6,25.8,9.4
|
||||
c32.5,13.9,53.5,32,53.5,46.4C450,259.9,429,278.2,396.6,292z"/>
|
||||
<path class="st1" d="M153.6,45.5L153.6,45.5L153.6,45.5z"/>
|
||||
<circle class="st2" cx="245.4" cy="245.5" r="41.9"/>
|
||||
<path class="st1" d="M336.8,45.2L336.8,45.2L336.8,45.2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_2_1_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 491 491" style="enable-background:new 0 0 491 491;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#1A2233;}
|
||||
.st1{fill:#F50057;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
</style>
|
||||
<!-- <path class="st0" d="M455.3,491H35.7C16,491,0,475,0,455.3V35.7C0,16,16,0,35.7,0h419.7C475,0,491,16,491,35.7v419.7
|
||||
C491,475,475,491,455.3,491z"/> -->
|
||||
<g>
|
||||
<path class="st1" d="M470.5,245.5c0-29.8-37.3-58.1-94.6-75.6c13.2-58.3,7.3-104.7-18.5-119.6c-6-3.5-12.9-5.1-20.5-5.1v20.5
|
||||
c4.2,0,7.6,0.8,10.5,2.4c12.5,7.2,17.9,34.4,13.7,69.4c-1,8.6-2.7,17.7-4.7,27c-18-4.4-37.6-7.8-58.2-10
|
||||
c-12.4-17-25.2-32.4-38.2-45.9c29.9-27.8,58-43,77-43V45.1l0,0c-25.2,0-58.2,18-91.6,49.2c-33.4-31-66.4-48.8-91.6-48.8v20.5
|
||||
c19,0,47.1,15.1,77,42.7c-12.8,13.5-25.7,28.8-37.9,45.8c-20.7,2.2-40.4,5.6-58.3,10.1c-2.1-9.2-3.7-18.1-4.8-26.6
|
||||
c-4.3-35,1-62.3,13.4-69.5c2.8-1.7,6.3-2.4,10.5-2.4V45.6l0,0c-7.7,0-14.7,1.7-20.7,5.1c-25.8,14.9-31.6,61.2-18.3,119.3
|
||||
c-57.1,17.6-94.2,45.8-94.2,75.5c0,29.8,37.3,58.1,94.6,75.6c-13.2,58.3-7.3,104.7,18.5,119.6c6,3.5,12.9,5.1,20.6,5.1
|
||||
c25.2,0,58.2-18,91.6-49.2c33.4,31,66.4,48.8,91.6,48.8c7.7,0,14.7-1.7,20.7-5.1c25.8-14.9,31.6-61.2,18.3-119.3
|
||||
C433.4,303.5,470.5,275.3,470.5,245.5z M351.1,184.4c-3.4,11.8-7.6,24-12.4,36.2c-3.8-7.3-7.7-14.7-12-22
|
||||
c-4.2-7.3-8.7-14.5-13.2-21.5C326.5,179,339.1,181.4,351.1,184.4z M309.1,282.1c-7.2,12.4-14.5,24.1-22.1,35
|
||||
c-13.7,1.2-27.5,1.8-41.5,1.8c-13.9,0-27.7-0.6-41.3-1.7c-7.6-10.9-15-22.6-22.2-34.9c-7-12-13.3-24.2-19.1-36.5
|
||||
c5.7-12.3,12.1-24.6,19-36.6c7.2-12.4,14.5-24.1,22.1-35c13.7-1.2,27.5-1.8,41.5-1.8c13.9,0,27.7,0.6,41.3,1.7
|
||||
c7.6,10.9,15,22.6,22.2,34.9c7,12,13.3,24.2,19.1,36.5C322.3,257.7,315.9,270,309.1,282.1z M338.7,270.1c5,12.3,9.2,24.6,12.7,36.5
|
||||
c-12,2.9-24.7,5.4-37.8,7.3c4.5-7.1,9-14.3,13.2-21.7C331,284.9,334.9,277.5,338.7,270.1z M245.7,368c-8.5-8.8-17.1-18.6-25.5-29.4
|
||||
c8.3,0.4,16.7,0.6,25.2,0.6c8.6,0,17.2-0.2,25.5-0.6C262.7,349.4,254.1,359.2,245.7,368z M177.4,314c-13-1.9-25.6-4.3-37.6-7.2
|
||||
c3.4-11.8,7.6-24,12.4-36.2c3.8,7.3,7.7,14.7,12,22C168.5,299.8,172.9,307,177.4,314z M245.2,123.1c8.5,8.8,17.1,18.6,25.5,29.4
|
||||
c-8.3-0.4-16.7-0.6-25.2-0.6c-8.6,0-17.2,0.2-25.5,0.6C228.3,141.7,236.8,131.9,245.2,123.1z M177.3,177.1
|
||||
c-4.5,7.1-9,14.3-13.2,21.7c-4.2,7.3-8.2,14.7-11.9,22c-5-12.3-9.2-24.6-12.7-36.5C151.6,181.5,164.2,179,177.3,177.1z M94.3,292
|
||||
c-32.5-13.9-53.5-32-53.5-46.4c0-14.4,21-32.7,53.5-46.4c7.9-3.4,16.5-6.4,25.4-9.3c5.2,18,12.1,36.7,20.6,55.9
|
||||
c-8.4,19.1-15.2,37.7-20.4,55.6C110.9,298.5,102.3,295.4,94.3,292z M143.7,423c-12.5-7.2-17.9-34.4-13.7-69.4
|
||||
c1-8.6,2.7-17.7,4.7-27c18,4.4,37.6,7.8,58.2,10c12.4,17,25.2,32.4,38.2,45.9c-29.9,27.8-58,43-77,43
|
||||
C149.9,425.4,146.4,424.6,143.7,423z M361.3,353.1c4.3,35-1,62.3-13.4,69.5c-2.8,1.7-6.3,2.4-10.5,2.4c-19,0-47.1-15.1-77-42.7
|
||||
c12.8-13.5,25.7-28.8,37.9-45.8c20.7-2.2,40.4-5.6,58.3-10.1C358.6,335.7,360.2,344.6,361.3,353.1z M396.6,292
|
||||
c-7.9,3.4-16.5,6.4-25.4,9.3c-5.2-18-12.1-36.7-20.6-55.9c8.4-19.1,15.2-37.7,20.4-55.6c9.1,2.8,17.7,6,25.8,9.4
|
||||
c32.5,13.9,53.5,32,53.5,46.4C450,259.9,429,278.2,396.6,292z"/>
|
||||
<path class="st1" d="M153.6,45.5L153.6,45.5L153.6,45.5z"/>
|
||||
<circle class="st2" cx="245.4" cy="245.5" r="41.9"/>
|
||||
<path class="st1" d="M336.8,45.2L336.8,45.2L336.8,45.2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.5 KiB |
@@ -1 +1 @@
|
||||
<svg viewBox="0 0 600.75 799.79" xmlns="http://www.w3.org/2000/svg" width="1878" height="2500"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="-154.087" x2="316.283" y1="274.041" y2="501.116"><stop offset=".107" stop-color="#c957e6"/><stop offset="1" stop-color="#1a9fff"/></linearGradient><path d="M200.25 600.27C89.51 600.27 0 510.89 0 400.32s89.51-199.95 200.25-199.95 200.26 89.38 200.26 199.95-89.52 199.95-200.26 199.95z" fill="url(#a)"/><path d="M456.98 399.89c0-141.57-114.95-256.34-256.74-256.34V0c221.2 0 400.51 179.04 400.51 399.89 0 220.86-179.31 399.9-400.51 399.9V656.24c141.79 0 256.74-114.77 256.74-256.35z" fill="#1b1b1b"/></svg>
|
||||
<svg viewBox="0 0 600.75 799.79" xmlns="http://www.w3.org/2000/svg" width="1878" height="2500"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="-154.087" x2="316.283" y1="274.041" y2="501.116"><stop offset=".107" stop-color="#c957e6"/><stop offset="1" stop-color="#1a9fff"/></linearGradient><path d="M200.25 600.27C89.51 600.27 0 510.89 0 400.32s89.51-199.95 200.25-199.95 200.26 89.38 200.26 199.95-89.52 199.95-200.26 199.95z" fill="url(#a)"/><path d="M456.98 399.89c0-141.57-114.95-256.34-256.74-256.34V0c221.2 0 400.51 179.04 400.51 399.89 0 220.86-179.31 399.9-400.51 399.9V656.24c141.79 0 256.74-114.77 256.74-256.35z" fill="#1b1b1b"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 658 B After Width: | Height: | Size: 657 B |
@@ -93,6 +93,7 @@ export function SettingsDownloadSources() {
|
||||
return () => clearInterval(intervalId);
|
||||
}, [downloadSources]);
|
||||
|
||||
|
||||
const handleRemoveSource = async (downloadSource: DownloadSource) => {
|
||||
setIsRemovingDownloadSource(true);
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ export function SettingsGeneral() {
|
||||
setCanInstallCommonRedist(canInstall);
|
||||
});
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const redistInterval = setInterval(() => {
|
||||
window.electron.canInstallCommonRedist().then((canInstall) => {
|
||||
setCanInstallCommonRedist(canInstall);
|
||||
});
|
||||
@@ -90,7 +90,7 @@ export function SettingsGeneral() {
|
||||
);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
clearInterval(redistInterval);
|
||||
if (volumeUpdateTimeoutRef.current) {
|
||||
clearTimeout(volumeUpdateTimeoutRef.current);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }]
|
||||
}
|
||||
{
|
||||
"files": [],
|
||||
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }]
|
||||
}
|
||||
|
||||