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
+283
-45
No files matched your search
@@ -32,6 +32,21 @@
|
||||
},
|
||||
{
|
||||
"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": "$TEMP"
|
||||
},
|
||||
{
|
||||
"path": "$TEMP/*"
|
||||
},
|
||||
{
|
||||
"path": "$TEMP/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -60,6 +90,21 @@
|
||||
},
|
||||
{
|
||||
"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": "$TEMP"
|
||||
},
|
||||
{
|
||||
"path": "$TEMP/*"
|
||||
},
|
||||
{
|
||||
"path": "$TEMP/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -88,6 +148,21 @@
|
||||
},
|
||||
{
|
||||
"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": "$TEMP"
|
||||
},
|
||||
{
|
||||
"path": "$TEMP/*"
|
||||
},
|
||||
{
|
||||
"path": "$TEMP/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -116,6 +206,21 @@
|
||||
},
|
||||
{
|
||||
"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": "$TEMP"
|
||||
},
|
||||
{
|
||||
"path": "$TEMP/*"
|
||||
},
|
||||
{
|
||||
"path": "$TEMP/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -144,6 +264,21 @@
|
||||
},
|
||||
{
|
||||
"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(())
|
||||
}
|
||||
|
||||
#[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]
|
||||
pub async fn extract_theme(
|
||||
app: AppHandle,
|
||||
|
||||
@@ -19,7 +19,7 @@ pub mod utils;
|
||||
use crate::{
|
||||
commands::{
|
||||
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,
|
||||
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,
|
||||
@@ -192,6 +192,7 @@ pub fn run() {
|
||||
restore_theme_backup,
|
||||
extract_theme,
|
||||
download_and_extract_theme,
|
||||
download_and_extract_community_theme,
|
||||
save_theme_project,
|
||||
export_theme,
|
||||
hash_file_sha256,
|
||||
|
||||
+32
-44
@@ -1,4 +1,5 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import * as path from '@tauri-apps/api/path';
|
||||
import * as fs from '@tauri-apps/plugin-fs';
|
||||
import { config } from './config';
|
||||
@@ -424,7 +425,7 @@ export async function publishThemeToWorkshop(options: {
|
||||
onProgress('Packaging theme archive...', 5);
|
||||
|
||||
const baseThemesDir = await themesDirectory();
|
||||
const tmpDir = await path.join(baseThemesDir, '.tmp');
|
||||
const tmpDir = await path.join(baseThemesDir, 'export_temp');
|
||||
if (!(await fs.exists(tmpDir))) {
|
||||
await fs.mkdir(tmpDir, { recursive: true });
|
||||
}
|
||||
@@ -555,53 +556,41 @@ export async function downloadAndInstallCommunityTheme(
|
||||
|
||||
reportProgress('Downloading theme package...', 10);
|
||||
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(theme.downloadUrl);
|
||||
if (!res.ok && (theme.apiVersion === '1.0' || theme.tags?.includes('legacy'))) {
|
||||
res = await fetch(
|
||||
`https://git.ez-pp.farm/EZPPFarm/EZPPLauncher-Themes/raw/branch/main/themes/${theme.id}.ezpplauncher-theme`,
|
||||
{ headers: { 'User-Agent': 'EZPPLauncher' } }
|
||||
);
|
||||
const unlistenDownload = await listen<{ received: number; total: number; progress: number }>(
|
||||
'download_progress',
|
||||
(event) => {
|
||||
reportProgress('Downloading theme package...', Math.round(10 + event.payload.progress * 50));
|
||||
}
|
||||
} catch (err) {
|
||||
if (theme.apiVersion === '1.0' || theme.tags?.includes('legacy')) {
|
||||
res = await fetch(
|
||||
`https://git.ez-pp.farm/EZPPFarm/EZPPLauncher-Themes/raw/branch/main/themes/${theme.id}.ezpplauncher-theme`,
|
||||
{ headers: { 'User-Agent': 'EZPPLauncher' } }
|
||||
);
|
||||
|
||||
const unlistenExtract = await listen<{ extracted: number; total: number; progress: number }>(
|
||||
'extract_progress',
|
||||
(event) => {
|
||||
reportProgress('Extracting theme files...', Math.round(60 + event.payload.progress * 38));
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
try {
|
||||
await invoke('download_and_extract_community_theme', {
|
||||
downloadUrl: theme.downloadUrl,
|
||||
themeFolder: targetFolder,
|
||||
themeName: theme.name,
|
||||
});
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
const arrayBuffer = await res.arrayBuffer();
|
||||
await fs.writeFile(tempFile, new Uint8Array(arrayBuffer));
|
||||
|
||||
reportProgress('Extracting theme files...', 70);
|
||||
|
||||
if (!(await fs.exists(targetFolder))) {
|
||||
await fs.mkdir(targetFolder, { recursive: true });
|
||||
}
|
||||
|
||||
await invoke('extract_theme', {
|
||||
filePath: tempFile,
|
||||
themeFolder: targetFolder,
|
||||
themeName: theme.name,
|
||||
});
|
||||
|
||||
reportProgress('Theme installed!', 100);
|
||||
|
||||
// Refresh themes in launcher state
|
||||
@@ -609,8 +598,7 @@ export async function downloadAndInstallCommunityTheme(
|
||||
|
||||
return { success: true, folderName };
|
||||
} finally {
|
||||
if (await fs.exists(tempFile)) {
|
||||
await fs.remove(tempFile).catch(() => {});
|
||||
}
|
||||
unlistenDownload();
|
||||
unlistenExtract();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user