feat(desktop): refresh publish tasks instantly on lease activation
Emit a 'publish-task-lease' runtime invalidation event whenever the scheduler activates a publish task, and have PublishManagementView listen for it to trigger an immediate refresh. The view also switches to a 5s active-task poll only while a task is in_progress, replacing the unconditional 20s timer.
This commit is contained in:
@@ -84,7 +84,7 @@ import {
|
||||
notePublishTaskLeaseMiss,
|
||||
selectNextPublishTask,
|
||||
} from './publish-scheduler'
|
||||
import { onRuntimeInvalidated } from './runtime-events'
|
||||
import { emitRuntimeInvalidated, onRuntimeInvalidated } from './runtime-events'
|
||||
import {
|
||||
initScheduler,
|
||||
noteSchedulerAccountsSync,
|
||||
@@ -2426,6 +2426,7 @@ async function executeLeasedTask(
|
||||
noteMonitorTaskLeased(task, routing)
|
||||
} else {
|
||||
notePublishTaskActivated(taskRecord.id, taskRecord.platform)
|
||||
emitRuntimeInvalidated('publish-task-lease', { taskId: taskRecord.id })
|
||||
}
|
||||
syncSchedulerSurface()
|
||||
queueMicrotask(() => pumpExecutionLoop())
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
type RuntimeInvalidationReason = 'account-health'
|
||||
type RuntimeInvalidationReason = 'account-health' | 'publish-task-lease'
|
||||
|
||||
type RuntimeInvalidationListener = (event: {
|
||||
reason: RuntimeInvalidationReason
|
||||
at: number
|
||||
accountId?: string
|
||||
taskId?: string
|
||||
}) => void
|
||||
|
||||
const listeners = new Set<RuntimeInvalidationListener>()
|
||||
|
||||
export function emitRuntimeInvalidated(
|
||||
reason: RuntimeInvalidationReason,
|
||||
details: { accountId?: string } = {},
|
||||
details: { accountId?: string; taskId?: string } = {},
|
||||
): void {
|
||||
const event = {
|
||||
reason,
|
||||
|
||||
@@ -145,11 +145,21 @@ const desktopBridge = {
|
||||
setWindowMode: (mode: 'login' | 'main', request?: WindowModeRequest) =>
|
||||
ipcRenderer.invoke('desktop:set-window-mode', mode, request) as Promise<null>,
|
||||
onRuntimeInvalidated: (
|
||||
listener: (event: { reason: 'account-health'; at: number; accountId?: string }) => void,
|
||||
listener: (event: {
|
||||
reason: 'account-health' | 'publish-task-lease'
|
||||
at: number
|
||||
accountId?: string
|
||||
taskId?: string
|
||||
}) => void,
|
||||
) => {
|
||||
const wrapped = (
|
||||
_event: IpcRendererEvent,
|
||||
payload: { reason: 'account-health'; at: number; accountId?: string },
|
||||
payload: {
|
||||
reason: 'account-health' | 'publish-task-lease'
|
||||
at: number
|
||||
accountId?: string
|
||||
taskId?: string
|
||||
},
|
||||
) => {
|
||||
listener(payload)
|
||||
}
|
||||
|
||||
+6
-1
@@ -64,7 +64,12 @@ declare global {
|
||||
},
|
||||
): Promise<null>
|
||||
onRuntimeInvalidated(
|
||||
listener: (event: { reason: 'account-health'; at: number; accountId?: string }) => void,
|
||||
listener: (event: {
|
||||
reason: 'account-health' | 'publish-task-lease'
|
||||
at: number
|
||||
accountId?: string
|
||||
taskId?: string
|
||||
}) => void,
|
||||
): () => void
|
||||
onNetworkObserved(listener: (event: DesktopObservedRequest) => void): () => void
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
const PAGE_SIZE = 10
|
||||
const INLINE_ERROR_HEAD_CHARS = 48
|
||||
const INLINE_ERROR_TAIL_CHARS = 10
|
||||
const ACTIVE_TASK_POLL_INTERVAL_MS = 5_000
|
||||
|
||||
const publishPlatformIndex: Record<string, (typeof desktopPublishMediaCatalog)[number]> =
|
||||
Object.fromEntries(desktopPublishMediaCatalog.map((item) => [item.id, item]))
|
||||
@@ -52,6 +53,7 @@ interface PublishTaskItem {
|
||||
}
|
||||
|
||||
let refreshTimer: ReturnType<typeof setInterval> | null = null
|
||||
let runtimeInvalidationUnsubscribe: (() => void) | null = null
|
||||
|
||||
function platformMeta(platform: string) {
|
||||
const key = (platform ?? '').toLowerCase()
|
||||
@@ -104,6 +106,12 @@ function isPendingStatus(status: DesktopTaskInfo['status']): boolean {
|
||||
return status === 'queued' || status === 'in_progress'
|
||||
}
|
||||
|
||||
function hasActivePublishingTask(page: DesktopPublishTaskListResponse | null): boolean {
|
||||
return Boolean(
|
||||
page?.items.some((task) => normalizePublishTaskStatus(task.status) === 'in_progress'),
|
||||
)
|
||||
}
|
||||
|
||||
function statusTone(status: DesktopTaskInfo['status']): 'info' | 'warn' | 'success' | 'danger' {
|
||||
switch (status) {
|
||||
case 'queued':
|
||||
@@ -359,13 +367,61 @@ async function refreshTasks(page = currentPage.value) {
|
||||
|
||||
taskPage.value = response
|
||||
currentPage.value = response.page
|
||||
syncActiveTaskPolling(response)
|
||||
} catch (err) {
|
||||
error.value = normalizeTaskErrorMessage(unwrapClientErrorMessage(err, '发布任务暂时不可用'))
|
||||
if (!hasActivePublishingTask(taskPage.value)) {
|
||||
stopActiveTaskPolling()
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function startActiveTaskPolling() {
|
||||
if (refreshTimer) {
|
||||
return
|
||||
}
|
||||
refreshTimer = setInterval(() => {
|
||||
void refreshTasks()
|
||||
}, ACTIVE_TASK_POLL_INTERVAL_MS)
|
||||
}
|
||||
|
||||
function stopActiveTaskPolling() {
|
||||
if (!refreshTimer) {
|
||||
return
|
||||
}
|
||||
clearInterval(refreshTimer)
|
||||
refreshTimer = null
|
||||
}
|
||||
|
||||
function syncActiveTaskPolling(page = taskPage.value) {
|
||||
if (hasActivePublishingTask(page)) {
|
||||
startActiveTaskPolling()
|
||||
return
|
||||
}
|
||||
stopActiveTaskPolling()
|
||||
}
|
||||
|
||||
function bindPublishLeaseListener() {
|
||||
if (runtimeInvalidationUnsubscribe || !window.desktopBridge?.app?.onRuntimeInvalidated) {
|
||||
return
|
||||
}
|
||||
|
||||
runtimeInvalidationUnsubscribe = window.desktopBridge.app.onRuntimeInvalidated((event) => {
|
||||
if (event.reason !== 'publish-task-lease') {
|
||||
return
|
||||
}
|
||||
|
||||
void refreshTasks(1)
|
||||
})
|
||||
}
|
||||
|
||||
function unbindPublishLeaseListener() {
|
||||
runtimeInvalidationUnsubscribe?.()
|
||||
runtimeInvalidationUnsubscribe = null
|
||||
}
|
||||
|
||||
async function retryTask(taskId: string) {
|
||||
actionPendingTaskId.value = taskId
|
||||
|
||||
@@ -480,17 +536,13 @@ watch(searchTitle, (value) => {
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
bindPublishLeaseListener()
|
||||
void refreshTasks()
|
||||
refreshTimer = setInterval(() => {
|
||||
void refreshTasks()
|
||||
}, 20_000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (refreshTimer) {
|
||||
clearInterval(refreshTimer)
|
||||
refreshTimer = null
|
||||
}
|
||||
unbindPublishLeaseListener()
|
||||
stopActiveTaskPolling()
|
||||
})
|
||||
|
||||
const accountNameMap = computed(() => {
|
||||
|
||||
Reference in New Issue
Block a user