fix(themes): implement native download_and_extract_community_theme and expand fs capabilities
This commit is contained in:
1 parent
4a6e8c5cab
commit
a051c021d7
4 files changed
+284
-46
No files matched your search
@@ -32,6 +32,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "$HOME/.ezpplauncher/**/*"
|
"path": "$HOME/.ezpplauncher/**/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/**/*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -46,6 +61,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "$HOME/.ezpplauncher/**/*"
|
"path": "$HOME/.ezpplauncher/**/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/**/*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -60,6 +90,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "$HOME/.ezpplauncher/**/*"
|
"path": "$HOME/.ezpplauncher/**/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/**/*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -74,6 +119,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "$HOME/.ezpplauncher/**/*"
|
"path": "$HOME/.ezpplauncher/**/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/**/*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -88,6 +148,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "$HOME/.ezpplauncher/**/*"
|
"path": "$HOME/.ezpplauncher/**/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/**/*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -102,6 +177,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "$HOME/.ezpplauncher/**/*"
|
"path": "$HOME/.ezpplauncher/**/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/**/*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -116,6 +206,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "$HOME/.ezpplauncher/**/*"
|
"path": "$HOME/.ezpplauncher/**/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/**/*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -130,6 +235,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "$HOME/.ezpplauncher/**/*"
|
"path": "$HOME/.ezpplauncher/**/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/**/*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -144,6 +264,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "$HOME/.ezpplauncher/**/*"
|
"path": "$HOME/.ezpplauncher/**/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$HOME/.ezpplauncher/**/.*/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$TEMP/**/*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1552,6 +1552,120 @@ pub async fn download_and_extract_theme(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn download_and_extract_community_theme(
|
||||||
|
app: AppHandle,
|
||||||
|
download_url: String,
|
||||||
|
theme_folder: String,
|
||||||
|
theme_name: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let response = client
|
||||||
|
.get(&download_url)
|
||||||
|
.header(
|
||||||
|
"User-Agent",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to download theme: {}", e))?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(format!("Download failed (HTTP {})", response.status()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let total = response.content_length().unwrap_or(0);
|
||||||
|
let mut received: u64 = 0;
|
||||||
|
let mut chunks: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
|
let mut stream = response.bytes_stream();
|
||||||
|
while let Some(chunk) = futures_util::StreamExt::next(&mut stream).await {
|
||||||
|
let chunk = chunk.map_err(|e| format!("Failed to read chunk: {}", e))?;
|
||||||
|
received += chunk.len() as u64;
|
||||||
|
chunks.extend_from_slice(&chunk);
|
||||||
|
let _ = app.emit(
|
||||||
|
"download_progress",
|
||||||
|
DownloadProgress {
|
||||||
|
theme_name: theme_name.clone(),
|
||||||
|
received,
|
||||||
|
total,
|
||||||
|
progress: if total > 0 {
|
||||||
|
received as f64 / total as f64
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse zip on blocking thread
|
||||||
|
let entries: Vec<(String, Vec<u8>, bool)> = tokio::task::spawn_blocking(move || {
|
||||||
|
let cursor = std::io::Cursor::new(chunks);
|
||||||
|
let mut archive = zip::ZipArchive::new(cursor)
|
||||||
|
.map_err(|e| format!("Failed to read zip archive: {}", e))?;
|
||||||
|
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
for i in 0..archive.len() {
|
||||||
|
let mut zip_entry = archive
|
||||||
|
.by_index(i)
|
||||||
|
.map_err(|e| format!("Failed to read zip entry: {}", e))?;
|
||||||
|
|
||||||
|
let name = match zip_entry.enclosed_name() {
|
||||||
|
Some(p) => p.to_string_lossy().to_string(),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_dir = zip_entry.is_dir();
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
if !is_dir {
|
||||||
|
std::io::copy(&mut zip_entry, &mut buf)
|
||||||
|
.map_err(|e| format!("Failed to read zip entry data: {}", e))?;
|
||||||
|
}
|
||||||
|
entries.push((name, buf, is_dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<_, String>(entries)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Thread error: {}", e))??;
|
||||||
|
|
||||||
|
let total_files = entries.len();
|
||||||
|
let mut extracted = 0;
|
||||||
|
|
||||||
|
for (name, buf, is_dir) in entries {
|
||||||
|
let entry_path = std::path::Path::new(&theme_folder).join(&name);
|
||||||
|
|
||||||
|
if is_dir {
|
||||||
|
tokio::fs::create_dir_all(&entry_path)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to create directory: {}", e))?;
|
||||||
|
} else {
|
||||||
|
if let Some(parent) = entry_path.parent() {
|
||||||
|
tokio::fs::create_dir_all(parent)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to create parent directory: {}", e))?;
|
||||||
|
}
|
||||||
|
tokio::fs::write(&entry_path, &buf)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to write file: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
extracted += 1;
|
||||||
|
let _ = app.emit(
|
||||||
|
"extract_progress",
|
||||||
|
ExtractProgress {
|
||||||
|
theme_name: theme_name.clone(),
|
||||||
|
total: total_files,
|
||||||
|
extracted,
|
||||||
|
progress: extracted as f64 / total_files as f64,
|
||||||
|
current_file: name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn extract_theme(
|
pub async fn extract_theme(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub mod utils;
|
|||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{
|
||||||
backup_theme_folder, check_for_corruption, close_presence_editor, close_theme_editor, config_all, config_clear, config_delete,
|
backup_theme_folder, check_for_corruption, close_presence_editor, close_theme_editor, config_all, config_clear, config_delete,
|
||||||
config_exists, config_get, config_set, download_and_extract_theme,
|
config_exists, config_get, config_set, download_and_extract_community_theme, download_and_extract_theme,
|
||||||
download_ezpp_launcher_update, download_ezpp_launcher_update_files, encrypt_string, exit,
|
download_ezpp_launcher_update, download_ezpp_launcher_update_files, encrypt_string, exit,
|
||||||
export_theme, extract_theme, file_size, find_osu_installation, focus_presence_editor, focus_theme_editor, get_beatmapsets_count,
|
export_theme, extract_theme, file_size, find_osu_installation, focus_presence_editor, focus_theme_editor, get_beatmapsets_count,
|
||||||
get_ezpp_launcher_update_files, get_hwid, get_launcher_version, get_osu_release_stream,
|
get_ezpp_launcher_update_files, get_hwid, get_launcher_version, get_osu_release_stream,
|
||||||
@@ -192,6 +192,7 @@ pub fn run() {
|
|||||||
restore_theme_backup,
|
restore_theme_backup,
|
||||||
extract_theme,
|
extract_theme,
|
||||||
download_and_extract_theme,
|
download_and_extract_theme,
|
||||||
|
download_and_extract_community_theme,
|
||||||
save_theme_project,
|
save_theme_project,
|
||||||
export_theme,
|
export_theme,
|
||||||
hash_file_sha256,
|
hash_file_sha256,
|
||||||
|
|||||||
+33
-45
@@ -1,4 +1,5 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import * as path from '@tauri-apps/api/path';
|
import * as path from '@tauri-apps/api/path';
|
||||||
import * as fs from '@tauri-apps/plugin-fs';
|
import * as fs from '@tauri-apps/plugin-fs';
|
||||||
import { config } from './config';
|
import { config } from './config';
|
||||||
@@ -424,7 +425,7 @@ export async function publishThemeToWorkshop(options: {
|
|||||||
onProgress('Packaging theme archive...', 5);
|
onProgress('Packaging theme archive...', 5);
|
||||||
|
|
||||||
const baseThemesDir = await themesDirectory();
|
const baseThemesDir = await themesDirectory();
|
||||||
const tmpDir = await path.join(baseThemesDir, '.tmp');
|
const tmpDir = await path.join(baseThemesDir, 'export_temp');
|
||||||
if (!(await fs.exists(tmpDir))) {
|
if (!(await fs.exists(tmpDir))) {
|
||||||
await fs.mkdir(tmpDir, { recursive: true });
|
await fs.mkdir(tmpDir, { recursive: true });
|
||||||
}
|
}
|
||||||
@@ -555,53 +556,41 @@ export async function downloadAndInstallCommunityTheme(
|
|||||||
|
|
||||||
reportProgress('Downloading theme package...', 10);
|
reportProgress('Downloading theme package...', 10);
|
||||||
|
|
||||||
let res: Response;
|
const unlistenDownload = await listen<{ received: number; total: number; progress: number }>(
|
||||||
try {
|
'download_progress',
|
||||||
res = await fetch(theme.downloadUrl);
|
(event) => {
|
||||||
if (!res.ok && (theme.apiVersion === '1.0' || theme.tags?.includes('legacy'))) {
|
reportProgress('Downloading theme package...', Math.round(10 + event.payload.progress * 50));
|
||||||
res = await fetch(
|
|
||||||
`https://git.ez-pp.farm/EZPPFarm/EZPPLauncher-Themes/raw/branch/main/themes/${theme.id}.ezpplauncher-theme`,
|
|
||||||
{ headers: { 'User-Agent': 'EZPPLauncher' } }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
);
|
||||||
if (theme.apiVersion === '1.0' || theme.tags?.includes('legacy')) {
|
|
||||||
res = await fetch(
|
const unlistenExtract = await listen<{ extracted: number; total: number; progress: number }>(
|
||||||
`https://git.ez-pp.farm/EZPPFarm/EZPPLauncher-Themes/raw/branch/main/themes/${theme.id}.ezpplauncher-theme`,
|
'extract_progress',
|
||||||
{ headers: { 'User-Agent': 'EZPPLauncher' } }
|
(event) => {
|
||||||
);
|
reportProgress('Extracting theme files...', Math.round(60 + event.payload.progress * 38));
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`Download failed (HTTP ${res.status})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const tmpDir = await path.join(baseThemesDir, '.tmp');
|
|
||||||
if (!(await fs.exists(tmpDir))) {
|
|
||||||
await fs.mkdir(tmpDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const tempFile = await path.join(tmpDir, `${folderName}_download.ezpplauncher-theme`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const arrayBuffer = await res.arrayBuffer();
|
try {
|
||||||
await fs.writeFile(tempFile, new Uint8Array(arrayBuffer));
|
await invoke('download_and_extract_community_theme', {
|
||||||
|
downloadUrl: theme.downloadUrl,
|
||||||
reportProgress('Extracting theme files...', 70);
|
themeFolder: targetFolder,
|
||||||
|
themeName: theme.name,
|
||||||
if (!(await fs.exists(targetFolder))) {
|
});
|
||||||
await fs.mkdir(targetFolder, { recursive: true });
|
} catch (err) {
|
||||||
|
// Fallback for legacy themes directly to git repo
|
||||||
|
if (theme.apiVersion === '1.0' || theme.tags?.includes('legacy')) {
|
||||||
|
const fallbackUrl = `https://git.ez-pp.farm/EZPPFarm/EZPPLauncher-Themes/raw/branch/main/themes/${theme.id}.ezpplauncher-theme`;
|
||||||
|
await invoke('download_and_extract_community_theme', {
|
||||||
|
downloadUrl: fallbackUrl,
|
||||||
|
themeFolder: targetFolder,
|
||||||
|
themeName: theme.name,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await invoke('extract_theme', {
|
|
||||||
filePath: tempFile,
|
|
||||||
themeFolder: targetFolder,
|
|
||||||
themeName: theme.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
reportProgress('Theme installed!', 100);
|
reportProgress('Theme installed!', 100);
|
||||||
|
|
||||||
// Refresh themes in launcher state
|
// Refresh themes in launcher state
|
||||||
@@ -609,8 +598,7 @@ export async function downloadAndInstallCommunityTheme(
|
|||||||
|
|
||||||
return { success: true, folderName };
|
return { success: true, folderName };
|
||||||
} finally {
|
} finally {
|
||||||
if (await fs.exists(tempFile)) {
|
unlistenDownload();
|
||||||
await fs.remove(tempFile).catch(() => {});
|
unlistenExtract();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user