mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-06-22 06:03:49 +08:00
@@ -15,7 +15,7 @@ install:
|
|||||||
mkdir -p $(DESTDIR)/usr/bin/
|
mkdir -p $(DESTDIR)/usr/bin/
|
||||||
cp -rv release/*/linux-unpacked/* $(DESTDIR)/opt/spark-store/bin/
|
cp -rv release/*/linux-unpacked/* $(DESTDIR)/opt/spark-store/bin/
|
||||||
cp -rv release/*/linux-unpacked/extras/* $(DESTDIR)/opt/spark-store/extras/
|
cp -rv release/*/linux-unpacked/extras/* $(DESTDIR)/opt/spark-store/extras/
|
||||||
cp -rv tool/* $(DESTDIR)/opt/spark-store/bin/
|
cp -rv tool/* $(DESTDIR)/opt/durapps/spark-store/bin/
|
||||||
cp -rv pkg/usr/share/fish/ $(DESTDIR)/usr/share/
|
cp -rv pkg/usr/share/fish/ $(DESTDIR)/usr/share/
|
||||||
cp -rv icons/hicolor/ $(DESTDIR)/usr/share/icons/
|
cp -rv icons/hicolor/ $(DESTDIR)/usr/share/icons/
|
||||||
cp -rv pkg/usr/share/icons/hicolor/ $(DESTDIR)/usr/share/icons/
|
cp -rv pkg/usr/share/icons/hicolor/ $(DESTDIR)/usr/share/icons/
|
||||||
|
|||||||
+141
-37
@@ -15,6 +15,7 @@ import path from "node:path";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import pino from "pino";
|
import pino from "pino";
|
||||||
|
import https from "node:https";
|
||||||
import { handleCommandLine } from "./deeplink.js";
|
import { handleCommandLine } from "./deeplink.js";
|
||||||
import { isLoaded } from "../global.js";
|
import { isLoaded } from "../global.js";
|
||||||
import { tasks } from "./backend/install-manager.js";
|
import { tasks } from "./backend/install-manager.js";
|
||||||
@@ -660,7 +661,17 @@ ipcMain.handle("search-history-app", async (_event, pkgname: string, useMirror =
|
|||||||
logger.info({ arch, category, item: json }, "[Submitter] Found matching item");
|
logger.info({ arch, category, item: json }, "[Submitter] Found matching item");
|
||||||
|
|
||||||
let iconUrl = json.icons || json.icon || "";
|
let iconUrl = json.icons || json.icon || "";
|
||||||
let imgs = json.imgUrls || json.imgs || [];
|
let imgs = json.imgUrls || json.imgs || json.img_urls || [];
|
||||||
|
|
||||||
|
if (typeof imgs === "string") {
|
||||||
|
try {
|
||||||
|
imgs = JSON.parse(imgs);
|
||||||
|
logger.info({ arch, category, parsedImgsCount: Array.isArray(imgs) ? imgs.length : 0 }, "[Submitter] Parsed img_urls from string");
|
||||||
|
} catch {
|
||||||
|
imgs = [];
|
||||||
|
logger.warn({ arch, category, imgsString: imgs }, "[Submitter] Failed to parse img_urls string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (iconUrl && typeof iconUrl === "string") {
|
if (iconUrl && typeof iconUrl === "string") {
|
||||||
if (useMirror) {
|
if (useMirror) {
|
||||||
@@ -919,12 +930,15 @@ async function getOssUploadMetadata(type: "icons" | "pic" | "deb"): Promise<OssU
|
|||||||
return ossMetadata;
|
return ossMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UploadProgressCallback = (progress: number, fileType: string) => void;
|
||||||
|
|
||||||
async function uploadFileToOss(
|
async function uploadFileToOss(
|
||||||
metadata: OssUploadMetadata,
|
metadata: OssUploadMetadata,
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
mimeType: string,
|
mimeType: string,
|
||||||
fileType: string
|
fileType: string,
|
||||||
|
progressCallback?: UploadProgressCallback
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const { host, dir, ossAccessKeyId, policy, signature } = metadata.data;
|
const { host, dir, ossAccessKeyId, policy, signature } = metadata.data;
|
||||||
@@ -935,46 +949,120 @@ async function uploadFileToOss(
|
|||||||
logger.info({ uploadUrl, objectKey, filePath, fileName, mimeType, timestamp: new Date().toISOString() }, `[Submitter] Starting upload for ${fileType}`);
|
logger.info({ uploadUrl, objectKey, filePath, fileName, mimeType, timestamp: new Date().toISOString() }, `[Submitter] Starting upload for ${fileType}`);
|
||||||
|
|
||||||
const fileStat = fs.statSync(filePath);
|
const fileStat = fs.statSync(filePath);
|
||||||
logger.info({ fileSize: fileStat.size, fileSizeHuman: `${(fileStat.size / 1024 / 1024).toFixed(2)} MB` }, `[Submitter] File size for ${fileType}`);
|
const fileSize = fileStat.size;
|
||||||
|
logger.info({ fileSize, fileSizeHuman: `${(fileSize / 1024 / 1024).toFixed(2)} MB` }, `[Submitter] File size for ${fileType}`);
|
||||||
|
|
||||||
const fileBuffer = fs.readFileSync(filePath);
|
const fileBuffer = fs.readFileSync(filePath);
|
||||||
logger.info({ bufferSize: fileBuffer.length }, `[Submitter] File buffer ready for ${fileType}`);
|
logger.info({ bufferSize: fileBuffer.length }, `[Submitter] File buffer ready for ${fileType}`);
|
||||||
|
|
||||||
const form = new (globalThis as unknown as { FormData: typeof FormData }).FormData();
|
const boundary = `----SparkStoreUploadBoundary${Date.now().toString(36)}`;
|
||||||
form.append("key", objectKey);
|
const CRLF = Buffer.from("\r\n", "ascii");
|
||||||
form.append("ossAccessKeyId", ossAccessKeyId);
|
|
||||||
form.append("policy", policy);
|
|
||||||
form.append("signature", signature);
|
|
||||||
form.append("success_action_status", "200");
|
|
||||||
form.append("file", new Blob([fileBuffer]), fileName);
|
|
||||||
|
|
||||||
logger.info(`[Submitter] ============== SENDING UPLOAD REQUEST (${fileType}) ==============`);
|
|
||||||
|
|
||||||
const response = await fetch(uploadUrl, {
|
const encodeField = (str: string) => Buffer.from(str, "utf8");
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const headerBuffers: Buffer[] = [];
|
||||||
"User-Agent": getUserAgent(),
|
headerBuffers.push(encodeField(`--${boundary}\r\n`));
|
||||||
},
|
headerBuffers.push(encodeField(`Content-Disposition: form-data; name="key"\r\n\r\n`));
|
||||||
body: form as unknown as BodyInit,
|
headerBuffers.push(encodeField(objectKey));
|
||||||
|
headerBuffers.push(CRLF);
|
||||||
|
|
||||||
|
headerBuffers.push(encodeField(`--${boundary}\r\n`));
|
||||||
|
headerBuffers.push(encodeField(`Content-Disposition: form-data; name="ossAccessKeyId"\r\n\r\n`));
|
||||||
|
headerBuffers.push(encodeField(ossAccessKeyId));
|
||||||
|
headerBuffers.push(CRLF);
|
||||||
|
|
||||||
|
headerBuffers.push(encodeField(`--${boundary}\r\n`));
|
||||||
|
headerBuffers.push(encodeField(`Content-Disposition: form-data; name="policy"\r\n\r\n`));
|
||||||
|
headerBuffers.push(encodeField(policy));
|
||||||
|
headerBuffers.push(CRLF);
|
||||||
|
|
||||||
|
headerBuffers.push(encodeField(`--${boundary}\r\n`));
|
||||||
|
headerBuffers.push(encodeField(`Content-Disposition: form-data; name="signature"\r\n\r\n`));
|
||||||
|
headerBuffers.push(encodeField(signature));
|
||||||
|
headerBuffers.push(CRLF);
|
||||||
|
|
||||||
|
headerBuffers.push(encodeField(`--${boundary}\r\n`));
|
||||||
|
headerBuffers.push(encodeField(`Content-Disposition: form-data; name="success_action_status"\r\n\r\n`));
|
||||||
|
headerBuffers.push(encodeField("200"));
|
||||||
|
headerBuffers.push(CRLF);
|
||||||
|
|
||||||
|
headerBuffers.push(encodeField(`--${boundary}\r\n`));
|
||||||
|
headerBuffers.push(encodeField(`Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n`));
|
||||||
|
headerBuffers.push(encodeField(`Content-Type: ${mimeType}\r\n\r\n`));
|
||||||
|
|
||||||
|
const formHeaderBuffer = Buffer.concat(headerBuffers);
|
||||||
|
const formFooterBuffer = Buffer.concat([CRLF, encodeField(`--${boundary}--\r\n`)]);
|
||||||
|
const totalLength = formHeaderBuffer.length + fileBuffer.length + formFooterBuffer.length;
|
||||||
|
|
||||||
|
logger.info({ formHeaderLength: formHeaderBuffer.length, formFooterLength: formFooterBuffer.length, totalLength, fileSize }, `[Submitter] Form lengths for ${fileType}`);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = new URL(uploadUrl);
|
||||||
|
const options: https.RequestOptions = {
|
||||||
|
hostname: url.hostname,
|
||||||
|
port: url.port ? parseInt(url.port) : 443,
|
||||||
|
path: url.pathname + url.search,
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"User-Agent": getUserAgent(),
|
||||||
|
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
||||||
|
"Content-Length": totalLength,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info(`[Submitter] ============== SENDING UPLOAD REQUEST (${fileType}) ==============`);
|
||||||
|
|
||||||
|
const req = https.request(options, (res) => {
|
||||||
|
let responseData = "";
|
||||||
|
res.on("data", (chunk) => {
|
||||||
|
responseData += chunk;
|
||||||
|
});
|
||||||
|
res.on("end", () => {
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
logger.info(`[Submitter] ============== UPLOAD RESPONSE RECEIVED (${fileType}) ==============`);
|
||||||
|
logger.info({ status: res.statusCode, duration }, `[Submitter] Upload response for ${fileType}`);
|
||||||
|
logger.info({ responseHeaders: res.headers }, `[Submitter] Response headers for ${fileType}`);
|
||||||
|
logger.info({ responseBody: responseData.substring(0, 1000) }, `[Submitter] Response body for ${fileType}`);
|
||||||
|
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
logger.error({ errorText: responseData.substring(0, 500) }, `[Submitter] Upload failed for ${fileType}`);
|
||||||
|
reject(new Error(`${fileType} 上传失败: ${res.statusCode} - ${responseData.substring(0, 500)}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalUrl = `${host}${objectKey}`;
|
||||||
|
logger.info({ finalUrl }, `[Submitter] ${fileType} upload successful, URL: ${finalUrl}`);
|
||||||
|
resolve(finalUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on("error", (err) => {
|
||||||
|
logger.error({ err }, `[Submitter] Upload request error for ${fileType}`);
|
||||||
|
reject(new Error(`${fileType} 上传失败: ${err.message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
req.write(formHeaderBuffer);
|
||||||
|
|
||||||
|
let uploadedBytes = formHeaderBuffer.length;
|
||||||
|
|
||||||
|
const chunkSize = 1024 * 1024;
|
||||||
|
for (let offset = 0; offset < fileBuffer.length; offset += chunkSize) {
|
||||||
|
const chunk = fileBuffer.slice(offset, Math.min(offset + chunkSize, fileBuffer.length));
|
||||||
|
req.write(chunk);
|
||||||
|
uploadedBytes += chunk.length;
|
||||||
|
|
||||||
|
if (progressCallback) {
|
||||||
|
const progress = Math.min((uploadedBytes / totalLength) * 100, 100);
|
||||||
|
progressCallback(progress, fileType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.write(formFooterBuffer);
|
||||||
|
req.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
const duration = Date.now() - startTime;
|
|
||||||
logger.info(`[Submitter] ============== UPLOAD RESPONSE RECEIVED (${fileType}) ==============`);
|
|
||||||
logger.info({ status: response.status, statusText: response.statusText, duration }, `[Submitter] Upload response for ${fileType}`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
logger.error({ errorText: errorText.substring(0, 500) }, `[Submitter] Upload failed for ${fileType}`);
|
|
||||||
throw new Error(`${fileType} 上传失败: ${response.status} - ${errorText.substring(0, 200)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalUrl = `${host}${objectKey}`;
|
|
||||||
logger.info({ finalUrl }, `[Submitter] ${fileType} upload successful, URL: ${finalUrl}`);
|
|
||||||
|
|
||||||
return finalUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle("submit-app", async (_event, formData: unknown) => {
|
ipcMain.handle("submit-app", async (event, formData: unknown) => {
|
||||||
try {
|
try {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
logger.info("[Submitter] ============== SUBMIT APP START ==============");
|
logger.info("[Submitter] ============== SUBMIT APP START ==============");
|
||||||
@@ -1014,6 +1102,10 @@ ipcMain.handle("submit-app", async (_event, formData: unknown) => {
|
|||||||
return { success: false, message: `deb 文件不存在: ${debFilePath}` };
|
return { success: false, message: `deb 文件不存在: ${debFilePath}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sendUploadProgress = (step: string, progress: number, message: string) => {
|
||||||
|
event.sender.send("submit-upload-progress", { step, progress, message });
|
||||||
|
};
|
||||||
|
|
||||||
let iconUrl = "";
|
let iconUrl = "";
|
||||||
if (iconPath) {
|
if (iconPath) {
|
||||||
if (iconPath.startsWith("http://") || iconPath.startsWith("https://")) {
|
if (iconPath.startsWith("http://") || iconPath.startsWith("https://")) {
|
||||||
@@ -1023,7 +1115,11 @@ ipcMain.handle("submit-app", async (_event, formData: unknown) => {
|
|||||||
logger.info("[Submitter] ============== STEP 1: UPLOAD ICON ==============");
|
logger.info("[Submitter] ============== STEP 1: UPLOAD ICON ==============");
|
||||||
const iconMetadata = await getOssUploadMetadata("icons");
|
const iconMetadata = await getOssUploadMetadata("icons");
|
||||||
const iconFileName = getUUIDFileNameSuggestIcoPic(iconPath);
|
const iconFileName = getUUIDFileNameSuggestIcoPic(iconPath);
|
||||||
iconUrl = await uploadFileToOss(iconMetadata, iconPath, iconFileName, "image/png", "icon");
|
sendUploadProgress("icon", 0, "正在上传图标...");
|
||||||
|
iconUrl = await uploadFileToOss(iconMetadata, iconPath, iconFileName, "image/png", "icon", (progress) => {
|
||||||
|
sendUploadProgress("icon", progress, `正在上传图标... ${Math.floor(progress)}%`);
|
||||||
|
});
|
||||||
|
sendUploadProgress("icon", 100, "图标上传完成");
|
||||||
logger.info({ iconUrl }, "[Submitter] Icon upload completed");
|
logger.info({ iconUrl }, "[Submitter] Icon upload completed");
|
||||||
} else {
|
} else {
|
||||||
logger.error({ iconPath }, "[Submitter] Icon file does not exist");
|
logger.error({ iconPath }, "[Submitter] Icon file does not exist");
|
||||||
@@ -1044,7 +1140,11 @@ ipcMain.handle("submit-app", async (_event, formData: unknown) => {
|
|||||||
logger.info(`[Submitter] ============== STEP 2: UPLOAD SCREENSHOT ${i + 1} ==============`);
|
logger.info(`[Submitter] ============== STEP 2: UPLOAD SCREENSHOT ${i + 1} ==============`);
|
||||||
const picMetadata = await getOssUploadMetadata("pic");
|
const picMetadata = await getOssUploadMetadata("pic");
|
||||||
const picFileName = getUUIDFileNameSuggestIcoPic(screenshot);
|
const picFileName = getUUIDFileNameSuggestIcoPic(screenshot);
|
||||||
const picUrl = await uploadFileToOss(picMetadata, screenshot, picFileName, "image/png", `screenshot ${i + 1}`);
|
sendUploadProgress(`screenshot-${i}`, 0, `正在上传截图 ${i + 1}...`);
|
||||||
|
const picUrl = await uploadFileToOss(picMetadata, screenshot, picFileName, "image/png", `screenshot ${i + 1}`, (progress) => {
|
||||||
|
sendUploadProgress(`screenshot-${i}`, progress, `正在上传截图 ${i + 1}... ${Math.floor(progress)}%`);
|
||||||
|
});
|
||||||
|
sendUploadProgress(`screenshot-${i}`, 100, `截图 ${i + 1} 上传完成`);
|
||||||
screenshotUrls.push(picUrl);
|
screenshotUrls.push(picUrl);
|
||||||
logger.info({ picUrl }, `[Submitter] Screenshot ${i + 1} upload completed`);
|
logger.info({ picUrl }, `[Submitter] Screenshot ${i + 1} upload completed`);
|
||||||
} else {
|
} else {
|
||||||
@@ -1057,7 +1157,11 @@ ipcMain.handle("submit-app", async (_event, formData: unknown) => {
|
|||||||
logger.info({ debFilePath, debFileName: getUUIDFileNameSuggestDeb(debFilePath) }, "[Submitter] Starting deb upload");
|
logger.info({ debFilePath, debFileName: getUUIDFileNameSuggestDeb(debFilePath) }, "[Submitter] Starting deb upload");
|
||||||
const debMetadata = await getOssUploadMetadata("deb");
|
const debMetadata = await getOssUploadMetadata("deb");
|
||||||
const debFileName = getUUIDFileNameSuggestDeb(debFilePath);
|
const debFileName = getUUIDFileNameSuggestDeb(debFilePath);
|
||||||
const debUrl = await uploadFileToOss(debMetadata, debFilePath, debFileName, "application/vnd.debian.binary-package", "deb");
|
sendUploadProgress("deb", 0, "正在上传安装包...");
|
||||||
|
const debUrl = await uploadFileToOss(debMetadata, debFilePath, debFileName, "application/vnd.debian.binary-package", "deb", (progress) => {
|
||||||
|
sendUploadProgress("deb", progress, `正在上传安装包... ${Math.floor(progress)}%`);
|
||||||
|
});
|
||||||
|
sendUploadProgress("deb", 100, "安装包上传完成");
|
||||||
logger.info("[Submitter] ============== DEB UPLOAD SUCCESSFUL ==============");
|
logger.info("[Submitter] ============== DEB UPLOAD SUCCESSFUL ==============");
|
||||||
logger.info({ debUrl, debFileName }, "[Submitter] Deb upload completed successfully");
|
logger.info({ debUrl, debFileName }, "[Submitter] Deb upload completed successfully");
|
||||||
|
|
||||||
|
|||||||
@@ -1063,6 +1063,5 @@ import { onMounted, nextTick } from "vue";
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log("[Submitter] Component mounted, loading categories and tags");
|
console.log("[Submitter] Component mounted, loading categories and tags");
|
||||||
loadCategoriesList();
|
loadCategoriesList();
|
||||||
loadTagsList();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user