修复master重构后的AGC发布接线
将分片上传与校验进度迁入主线runtime模块 移除commands旧层重复发布命令并保留desktop入口 补齐合并后的Rust编译与上传回归
This commit is contained in:
@@ -4576,149 +4576,6 @@ pub(crate) fn checkpoint_with_capture_for_test(
|
||||
create_local_project_checkpoint_with_capture(project_path, capture)
|
||||
}
|
||||
|
||||
/// 为发布导出试玩包:项目还没有可玩入口时先跑项目自己的 `npm run build`。
|
||||
///
|
||||
/// 作者只点一次「发布」:已有 `game/index.html` 或 `dist/index.html` 直接打包;只有源码时
|
||||
/// 走 `project.verify` 的受控 npm 运行器构建后再打包,失败信息带构建日志尾部。
|
||||
#[tauri::command]
|
||||
pub(crate) async fn export_local_project_package(
|
||||
project_path: String,
|
||||
) -> Result<LocalProjectExportPackageResult, String> {
|
||||
let root = Path::new(project_path.trim());
|
||||
enforce_project_permission_policy(root, "project.export_package")?;
|
||||
let _lock = acquire_project_write_lock(root, "project.export_package")?;
|
||||
advance_agent_runtime_project_revision_locked(root)?;
|
||||
export_local_project_package_for_publish_at(root).await
|
||||
}
|
||||
|
||||
/// 把归一化后的发行包落到内容寻址的暂存文件,返回分片续传所需的元数据。
|
||||
///
|
||||
/// 发布链路从此只把「暂存路径 + 摘要 + 体积」交给渲染进程:整包字节不再经过
|
||||
/// WebView IPC,续传时也复用同一个暂存文件(同名同内容)。
|
||||
#[tauri::command]
|
||||
pub(crate) fn prepare_local_project_game_package(
|
||||
app: tauri::AppHandle,
|
||||
project_path: String,
|
||||
package_relative_path: String,
|
||||
) -> Result<crate::game_package_upload::StagedGamePackage, String> {
|
||||
let root = Path::new(project_path.trim());
|
||||
enforce_project_permission_policy(root, "project.export_package")?;
|
||||
let payload = read_local_project_export_package_at(root, package_relative_path.trim())?;
|
||||
let staging_dir = game_package_upload_staging_dir(&app)?;
|
||||
let mut staged = crate::game_package_upload::stage_game_package_bytes(
|
||||
&staging_dir,
|
||||
&payload.package_sha256,
|
||||
&payload.package_bytes,
|
||||
)?;
|
||||
staged.package_file_count = u32::try_from(payload.files.len()).unwrap_or(u32::MAX);
|
||||
Ok(staged)
|
||||
}
|
||||
|
||||
/// 分片续传上传暂存的发行包;进度通过 `game-package-upload-progress` 事件回传。
|
||||
#[tauri::command]
|
||||
pub(crate) async fn upload_local_project_game_package(
|
||||
app: tauri::AppHandle,
|
||||
staging_path: String,
|
||||
version_id: String,
|
||||
api_base_url: String,
|
||||
access_token: String,
|
||||
idempotency_key: String,
|
||||
) -> Result<crate::game_package_upload::GamePackageUploadOutcome, String> {
|
||||
let staging_dir = game_package_upload_staging_dir(&app)?;
|
||||
let resolved_path =
|
||||
crate::game_package_upload::ensure_staging_path_in_dir(&staging_dir, &staging_path)?;
|
||||
let client = reqwest::Client::builder()
|
||||
.build()
|
||||
.map_err(|error| format!("创建上传客户端失败:{error}"))?;
|
||||
let version_id = version_id.trim().to_string();
|
||||
if version_id.is_empty() {
|
||||
return Err("缺少发行版本标识".to_string());
|
||||
}
|
||||
let emit_handle = app.clone();
|
||||
let progress_version_id = version_id.clone();
|
||||
let progress_publish_id = idempotency_key.trim().to_string();
|
||||
let verify_handle = app.clone();
|
||||
let verify_version_id = version_id.clone();
|
||||
let verify_publish_id = progress_publish_id.clone();
|
||||
crate::game_package_upload::upload_staged_game_package(
|
||||
&client,
|
||||
crate::game_package_upload::GamePackageUploadRequest {
|
||||
staging_path: &resolved_path,
|
||||
version_id: &version_id,
|
||||
api_base_url: api_base_url.trim(),
|
||||
access_token: access_token.trim(),
|
||||
idempotency_key: idempotency_key.trim(),
|
||||
session_identity: None,
|
||||
},
|
||||
move |received_bytes, total_bytes| {
|
||||
let _ = emit_handle.emit(
|
||||
crate::game_package_upload::GAME_PACKAGE_UPLOAD_PROGRESS_EVENT,
|
||||
crate::game_package_upload::progress_event_payload(
|
||||
&progress_version_id,
|
||||
&progress_publish_id,
|
||||
"upload",
|
||||
"正在上传发行包…",
|
||||
received_bytes,
|
||||
total_bytes,
|
||||
),
|
||||
);
|
||||
},
|
||||
move || {
|
||||
let _ = verify_handle.emit(
|
||||
crate::game_package_upload::GAME_PACKAGE_UPLOAD_PROGRESS_EVENT,
|
||||
crate::game_package_upload::progress_event_payload(
|
||||
&verify_version_id,
|
||||
&verify_publish_id,
|
||||
"verify",
|
||||
"正在校验发行包…",
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn game_package_upload_staging_dir(app: &tauri::AppHandle) -> Result<PathBuf, String> {
|
||||
app.path()
|
||||
.app_data_dir()
|
||||
.map(|app_data_root| {
|
||||
crate::game_package_upload::game_package_upload_staging_dir(&app_data_root)
|
||||
})
|
||||
.map_err(|error| format!("无法读取 AGC 应用数据目录:{error}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn list_local_project_export_packages(
|
||||
project_path: String,
|
||||
) -> Result<LocalProjectExportPackagesResult, String> {
|
||||
let root = Path::new(project_path.trim());
|
||||
enforce_project_permission_policy(root, "project.export_list")?;
|
||||
list_local_project_export_packages_at(root)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn diff_local_project_checkpoint(
|
||||
project_path: String,
|
||||
checkpoint_id: String,
|
||||
) -> Result<LocalProjectDiffResult, String> {
|
||||
let root = Path::new(project_path.trim());
|
||||
enforce_project_permission_policy(root, "project.diff")?;
|
||||
diff_local_project_checkpoint_at(root, checkpoint_id.trim())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn restore_local_project_checkpoint(
|
||||
project_path: String,
|
||||
checkpoint_id: String,
|
||||
) -> Result<LocalProjectRestoreResult, String> {
|
||||
let root = Path::new(project_path.trim());
|
||||
enforce_project_permission_policy(root, "project.restore")?;
|
||||
let _lock = acquire_project_write_lock(root, "project.restore")?;
|
||||
advance_agent_runtime_project_revision_locked(root)?;
|
||||
restore_local_project_checkpoint_at(root, checkpoint_id.trim())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub(crate) fn read_project_permission_policy(
|
||||
project_path: String,
|
||||
|
||||
@@ -1509,6 +1509,10 @@ pub(crate) async fn upload_local_project_game_package(
|
||||
}
|
||||
let emit_handle = app.clone();
|
||||
let progress_version_id = version_id.clone();
|
||||
let progress_publish_id = idempotency_key.trim().to_string();
|
||||
let verify_handle = app.clone();
|
||||
let verify_version_id = version_id.clone();
|
||||
let verify_publish_id = progress_publish_id.clone();
|
||||
crate::game_package_upload::upload_staged_game_package(
|
||||
&client,
|
||||
crate::game_package_upload::GamePackageUploadRequest {
|
||||
@@ -1524,11 +1528,27 @@ pub(crate) async fn upload_local_project_game_package(
|
||||
crate::game_package_upload::GAME_PACKAGE_UPLOAD_PROGRESS_EVENT,
|
||||
crate::game_package_upload::progress_event_payload(
|
||||
&progress_version_id,
|
||||
&progress_publish_id,
|
||||
"upload",
|
||||
"正在上传发行包…",
|
||||
received_bytes,
|
||||
total_bytes,
|
||||
),
|
||||
);
|
||||
},
|
||||
move || {
|
||||
let _ = verify_handle.emit(
|
||||
crate::game_package_upload::GAME_PACKAGE_UPLOAD_PROGRESS_EVENT,
|
||||
crate::game_package_upload::progress_event_payload(
|
||||
&verify_version_id,
|
||||
&verify_publish_id,
|
||||
"verify",
|
||||
"正在校验发行包…",
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -145,330 +145,10 @@ fn parse_received_bytes(body: &str) -> Option<u64> {
|
||||
.and_then(Value::as_u64)
|
||||
}
|
||||
|
||||
fn platform_request(
|
||||
client: &reqwest::Client,
|
||||
access_token: &str,
|
||||
method: reqwest::Method,
|
||||
url: &str,
|
||||
) -> reqwest::RequestBuilder {
|
||||
client
|
||||
.request(method, url)
|
||||
.header(AGC_CLIENT_MARKER_HEADER, AGC_CLIENT_MARKER_VALUE)
|
||||
.header(
|
||||
reqwest::header::AUTHORIZATION,
|
||||
format!("Bearer {access_token}"),
|
||||
)
|
||||
}
|
||||
|
||||
async fn read_upload_state(
|
||||
client: &reqwest::Client,
|
||||
base_url: &str,
|
||||
version_id: &str,
|
||||
access_token: &str,
|
||||
) -> Result<PackageUploadState, String> {
|
||||
let response = platform_request(
|
||||
client,
|
||||
access_token,
|
||||
reqwest::Method::GET,
|
||||
&upload_url(base_url, version_id, "/upload-state"),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("无法连接登录服务,请确认配套后端或 API 代理已启动后重试:{error}"))?;
|
||||
let status = response.status().as_u16();
|
||||
let body = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(|error| format!("读取上传状态失败:{error}"))?;
|
||||
if status >= 400 {
|
||||
let (_, message) = parse_server_error(status, &body);
|
||||
return Err(message.unwrap_or_else(|| format!("读取上传状态失败(HTTP {status})")));
|
||||
}
|
||||
let parsed: Value = serde_json::from_str(&body)
|
||||
.map_err(|error| format!("上传状态响应不是合法 JSON:{error}"))?;
|
||||
let payload = parsed.get("data").unwrap_or(&parsed);
|
||||
serde_json::from_value(payload.clone())
|
||||
.map_err(|error| format!("上传状态响应缺少字段:{error}"))
|
||||
}
|
||||
|
||||
/// 上传一个分片;返回服务端确认后的已收字节。
|
||||
async fn upload_chunk(
|
||||
client: &reqwest::Client,
|
||||
base_url: &str,
|
||||
version_id: &str,
|
||||
access_token: &str,
|
||||
idempotency_key: &str,
|
||||
plan: ChunkPlan,
|
||||
body: Vec<u8>,
|
||||
) -> Result<u64, ChunkUploadError> {
|
||||
let response = platform_request(
|
||||
client,
|
||||
access_token,
|
||||
reqwest::Method::PUT,
|
||||
&upload_url(base_url, version_id, "/chunk"),
|
||||
)
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/octet-stream")
|
||||
.header(UPLOAD_OFFSET_HEADER, plan.offset.to_string())
|
||||
.header("Idempotency-Key", format!("{idempotency_key}:chunk"))
|
||||
.body(body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| ChunkUploadError::Retryable(format!("发行包分片发送失败:{error}")))?;
|
||||
let status = response.status().as_u16();
|
||||
let text = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(|error| ChunkUploadError::Retryable(format!("读取分片响应失败:{error}")))?;
|
||||
if status == 409 {
|
||||
let (code, message) = parse_server_error(status, &text);
|
||||
if code.as_deref() == Some("PACKAGE_UPLOAD_OFFSET_MISMATCH") {
|
||||
let received = parse_received_bytes(&text).ok_or_else(|| {
|
||||
ChunkUploadError::Fatal("分片偏移不一致,但服务端未返回权威偏移".to_string())
|
||||
})?;
|
||||
return Ok(received);
|
||||
}
|
||||
return Err(ChunkUploadError::Fatal(
|
||||
message.unwrap_or_else(|| "发行包分片被拒绝".to_string()),
|
||||
));
|
||||
}
|
||||
if status >= 500 || status == 408 || status == 429 {
|
||||
let (_, message) = parse_server_error(status, &text);
|
||||
return Err(ChunkUploadError::Retryable(
|
||||
message.unwrap_or_else(|| format!("发行包分片上传失败(HTTP {status})")),
|
||||
));
|
||||
}
|
||||
if status >= 400 {
|
||||
let (_, message) = parse_server_error(status, &text);
|
||||
return Err(ChunkUploadError::Fatal(
|
||||
message.unwrap_or_else(|| format!("发行包分片被拒绝(HTTP {status})")),
|
||||
));
|
||||
}
|
||||
let parsed: Value = serde_json::from_str(&text)
|
||||
.map_err(|error| ChunkUploadError::Retryable(format!("分片响应不是合法 JSON:{error}")))?;
|
||||
let payload = parsed.get("data").unwrap_or(&parsed);
|
||||
payload
|
||||
.get("receivedBytes")
|
||||
.and_then(Value::as_u64)
|
||||
.ok_or_else(|| ChunkUploadError::Fatal("分片响应缺少 receivedBytes".to_string()))
|
||||
}
|
||||
|
||||
enum ChunkUploadError {
|
||||
Retryable(String),
|
||||
Fatal(String),
|
||||
}
|
||||
|
||||
async fn complete_upload(
|
||||
client: &reqwest::Client,
|
||||
base_url: &str,
|
||||
version_id: &str,
|
||||
access_token: &str,
|
||||
idempotency_key: &str,
|
||||
) -> Result<GamePackageUploadOutcome, String> {
|
||||
let response = platform_request(
|
||||
client,
|
||||
access_token,
|
||||
reqwest::Method::POST,
|
||||
&upload_url(base_url, version_id, "/complete"),
|
||||
)
|
||||
.header("Idempotency-Key", format!("{idempotency_key}:complete"))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("完成发行包上传失败:{error}"))?;
|
||||
let status = response.status().as_u16();
|
||||
let text = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(|error| format!("读取完成响应失败:{error}"))?;
|
||||
if status >= 400 {
|
||||
let (_, message) = parse_server_error(status, &text);
|
||||
return Err(message.unwrap_or_else(|| format!("完成发行包上传失败(HTTP {status})")));
|
||||
}
|
||||
let parsed: Value =
|
||||
serde_json::from_str(&text).map_err(|error| format!("完成响应不是合法 JSON:{error}"))?;
|
||||
let payload = parsed.get("data").unwrap_or(&parsed);
|
||||
Ok(GamePackageUploadOutcome {
|
||||
version_id: payload
|
||||
.get("versionId")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or(version_id)
|
||||
.to_string(),
|
||||
status: payload
|
||||
.get("status")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("uploaded")
|
||||
.to_string(),
|
||||
uploaded_bytes: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) struct GamePackageUploadRequest<'a> {
|
||||
pub(crate) staging_path: &'a Path,
|
||||
pub(crate) version_id: &'a str,
|
||||
pub(crate) api_base_url: &'a str,
|
||||
pub(crate) access_token: &'a str,
|
||||
pub(crate) idempotency_key: &'a str,
|
||||
pub(crate) session_identity: Option<&'a PlatformSessionIdentity>,
|
||||
}
|
||||
|
||||
impl<'a> GamePackageUploadRequest<'a> {
|
||||
fn current_access_token(&self) -> Result<String, String> {
|
||||
let Some(expected) = self.session_identity else {
|
||||
return Ok(self.access_token.to_string());
|
||||
};
|
||||
let session = current_platform_session().ok_or_else(|| {
|
||||
"authentication-required: 陶泥儿登录态缺失,请重新登录后重试".to_string()
|
||||
})?;
|
||||
validate_platform_session_identity(expected)?;
|
||||
Ok(session.access_token)
|
||||
}
|
||||
}
|
||||
|
||||
/// 分片续传主循环:权威偏移来自服务端,失败按可重试分类退避,偏移不符立即按权威偏移继续。
|
||||
///
|
||||
/// `on_verify` 在整包收齐、调用服务端 `/complete`(重算摘要 + 展开清单 + 确认完整对象)
|
||||
/// 之前触发:那一步是真正的「校验」,渲染层据此把进度切到校验阶段。
|
||||
pub(crate) async fn upload_staged_game_package(
|
||||
client: &reqwest::Client,
|
||||
request: GamePackageUploadRequest<'_>,
|
||||
mut on_progress: impl FnMut(u64, u64),
|
||||
mut on_verify: impl FnMut(),
|
||||
) -> Result<GamePackageUploadOutcome, String> {
|
||||
let total_bytes = fs::metadata(request.staging_path)
|
||||
.map_err(|error| format!("读取发行包暂存文件失败:{error}"))?
|
||||
.len();
|
||||
if total_bytes == 0 {
|
||||
return Err("发行包暂存文件为空,请重新导出试玩包".to_string());
|
||||
}
|
||||
let access_token = request.current_access_token()?;
|
||||
let state = read_upload_state(
|
||||
client,
|
||||
request.api_base_url,
|
||||
request.version_id,
|
||||
&access_token,
|
||||
)
|
||||
.await?;
|
||||
if state.declared_package_bytes != 0 && state.declared_package_bytes != total_bytes {
|
||||
return Err(format!(
|
||||
"本地发行包与版本声明的体积不一致(本地 {} 字节,声明 {} 字节),请重新导出后再发布",
|
||||
total_bytes, state.declared_package_bytes
|
||||
));
|
||||
}
|
||||
let chunk_bytes = if state.chunk_bytes == 0 {
|
||||
MAX_CHUNK_BYTES
|
||||
} else {
|
||||
state.chunk_bytes.min(MAX_CHUNK_BYTES)
|
||||
};
|
||||
let mut received = state.received_bytes.min(total_bytes);
|
||||
on_progress(received, total_bytes);
|
||||
let mut file = File::open(request.staging_path)
|
||||
.map_err(|error| format!("打开发行包暂存文件失败:{error}"))?;
|
||||
while let Some(plan) = next_chunk_plan(received, total_bytes, chunk_bytes) {
|
||||
let body = read_chunk(&mut file, plan)?;
|
||||
let mut attempt = 1_usize;
|
||||
loop {
|
||||
let access_token = request.current_access_token()?;
|
||||
match upload_chunk(
|
||||
client,
|
||||
request.api_base_url,
|
||||
request.version_id,
|
||||
&access_token,
|
||||
request.idempotency_key,
|
||||
plan,
|
||||
body.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(next_received) => {
|
||||
received = next_received.min(total_bytes);
|
||||
on_progress(received, total_bytes);
|
||||
break;
|
||||
}
|
||||
Err(ChunkUploadError::Fatal(error)) => return Err(error),
|
||||
Err(ChunkUploadError::Retryable(error)) => {
|
||||
if attempt >= CHUNK_MAX_ATTEMPTS {
|
||||
return Err(format!("{error}(已尝试 {attempt} 次,可重新发布续传)"));
|
||||
}
|
||||
attempt += 1;
|
||||
tokio::time::sleep(CHUNK_RETRY_DELAY).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 权威偏移可能在重试期间前进(例如响应丢失后服务端已写入),按服务端口径对齐。
|
||||
let access_token = request.current_access_token()?;
|
||||
let authoritative = read_upload_state(
|
||||
client,
|
||||
request.api_base_url,
|
||||
request.version_id,
|
||||
&access_token,
|
||||
)
|
||||
.await?;
|
||||
received = authoritative.received_bytes.min(total_bytes);
|
||||
on_progress(received, total_bytes);
|
||||
}
|
||||
let access_token = request.current_access_token()?;
|
||||
on_verify();
|
||||
let mut outcome = complete_upload(
|
||||
client,
|
||||
request.api_base_url,
|
||||
request.version_id,
|
||||
&access_token,
|
||||
request.idempotency_key,
|
||||
)
|
||||
.await?;
|
||||
outcome.uploaded_bytes = total_bytes;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
/// 进度事件的载荷形状(渲染进程按它显示阶段与百分比)。
|
||||
///
|
||||
/// `phase` 是发布链路的稳定阶段标识(`prepare` / `upload` / `verify` / `submit`),
|
||||
/// 渲染层据此点亮步骤条;`upload` 之外的阶段 `receivedBytes` / `totalBytes` 为 0,
|
||||
/// 表示该阶段没有可换算的百分比而不是 0% 进度。`publishId` 是本次发布尝试的根幂等键,
|
||||
/// 只用于把事件归到同一次发布,不含 token 或任何凭据。
|
||||
pub(crate) fn progress_event_payload(
|
||||
version_id: &str,
|
||||
publish_id: &str,
|
||||
phase: &str,
|
||||
message: &str,
|
||||
received_bytes: u64,
|
||||
total_bytes: u64,
|
||||
) -> Value {
|
||||
json!({
|
||||
"versionId": version_id,
|
||||
"publishId": publish_id,
|
||||
"phase": phase,
|
||||
"message": message,
|
||||
"receivedBytes": received_bytes,
|
||||
"totalBytes": total_bytes,
|
||||
})
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn progress_payload_carries_publish_stage_and_percent_inputs() {
|
||||
let payload = progress_event_payload(
|
||||
"ver-1",
|
||||
"agc-publish-key",
|
||||
"upload",
|
||||
"正在上传发行包…",
|
||||
8 * 1024 * 1024,
|
||||
12 * 1024 * 1024,
|
||||
);
|
||||
assert_eq!(payload["versionId"], "ver-1");
|
||||
assert_eq!(payload["publishId"], "agc-publish-key");
|
||||
assert_eq!(payload["phase"], "upload");
|
||||
assert_eq!(payload["message"], "正在上传发行包…");
|
||||
assert_eq!(payload["receivedBytes"], 8 * 1024 * 1024);
|
||||
assert_eq!(payload["totalBytes"], 12 * 1024 * 1024);
|
||||
// 载荷只带本地标识与计数,不带 token、路径或包字节。
|
||||
let serialized = payload.to_string();
|
||||
for forbidden in ["token", "Bearer", "stagingPath", ":\\"] {
|
||||
assert!(!serialized.contains(forbidden), "{serialized}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chunk_plan_covers_whole_package_without_gaps_or_overlap() {
|
||||
let total = 20 * 1024 * 1024 + 3;
|
||||
|
||||
@@ -235,6 +235,7 @@ pub(crate) async fn upload_staged_game_package(
|
||||
client: &reqwest::Client,
|
||||
request: GamePackageUploadRequest<'_>,
|
||||
mut on_progress: impl FnMut(u64, u64),
|
||||
mut on_verify: impl FnMut(),
|
||||
) -> Result<GamePackageUploadOutcome, String> {
|
||||
let total_bytes = fs::metadata(request.staging_path)
|
||||
.map_err(|error| format!("读取发行包暂存文件失败:{error}"))?
|
||||
@@ -309,6 +310,7 @@ pub(crate) async fn upload_staged_game_package(
|
||||
on_progress(received, total_bytes);
|
||||
}
|
||||
let access_token = request.current_access_token()?;
|
||||
on_verify();
|
||||
let mut outcome = complete_upload(
|
||||
client,
|
||||
request.api_base_url,
|
||||
@@ -321,14 +323,20 @@ pub(crate) async fn upload_staged_game_package(
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
/// 进度事件的载荷形状(渲染进程按它显示进度)。
|
||||
/// 发布进度载荷;上传阶段带字节进度,其他阶段按阶段文案展示。
|
||||
pub(crate) fn progress_event_payload(
|
||||
version_id: &str,
|
||||
publish_id: &str,
|
||||
phase: &str,
|
||||
message: &str,
|
||||
received_bytes: u64,
|
||||
total_bytes: u64,
|
||||
) -> Value {
|
||||
json!({
|
||||
"versionId": version_id,
|
||||
"publishId": publish_id,
|
||||
"phase": phase,
|
||||
"message": message,
|
||||
"receivedBytes": received_bytes,
|
||||
"totalBytes": total_bytes,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user