238 lines
5.8 KiB
TypeScript
238 lines
5.8 KiB
TypeScript
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
|
import { onBeforeRouteLeave } from 'vue-router'
|
|
|
|
import {
|
|
injectAppBackup,
|
|
injectModrinthClient,
|
|
injectModrinthServerContext,
|
|
injectNotificationManager,
|
|
} from '#ui/providers/'
|
|
|
|
export function useInlineBackup(backupName: string | (() => string)) {
|
|
const serverCtx = injectModrinthServerContext(null)
|
|
const appBackup = injectAppBackup(null)
|
|
|
|
if (!serverCtx) {
|
|
if (appBackup) {
|
|
const isBackingUp = ref(false)
|
|
const backupFailed = ref(false)
|
|
const backupComplete = ref(false)
|
|
|
|
return {
|
|
available: true as const,
|
|
isServer: false as const,
|
|
isBackingUp,
|
|
isCancelling: ref(false),
|
|
backupFailed,
|
|
backupComplete,
|
|
backupCancelled: ref(false),
|
|
externalBackupInProgress: computed(() => false),
|
|
startBackup: async () => {
|
|
isBackingUp.value = true
|
|
backupFailed.value = false
|
|
backupComplete.value = false
|
|
try {
|
|
await appBackup.createBackup()
|
|
backupComplete.value = true
|
|
} catch {
|
|
backupFailed.value = true
|
|
} finally {
|
|
isBackingUp.value = false
|
|
}
|
|
},
|
|
cancelBackup: async () => {},
|
|
}
|
|
}
|
|
|
|
return {
|
|
available: false as const,
|
|
isServer: false as const,
|
|
isBackingUp: ref(false),
|
|
isCancelling: ref(false),
|
|
backupFailed: ref(false),
|
|
backupComplete: ref(false),
|
|
backupCancelled: ref(false),
|
|
externalBackupInProgress: ref(false),
|
|
startBackup: async () => {},
|
|
cancelBackup: async () => {},
|
|
}
|
|
}
|
|
|
|
const client = injectModrinthClient()
|
|
const { addNotification } = injectNotificationManager()
|
|
const { serverId, worldId, backupsState, markBackupCancelled } = serverCtx
|
|
|
|
const isBackingUp = ref(false)
|
|
const backupFailed = ref(false)
|
|
const backupComplete = ref(false)
|
|
const backupCancelled = ref(false)
|
|
const isCancelling = ref(false)
|
|
const createdBackupId = ref<string | null>(null)
|
|
|
|
const externalBackupInProgress = computed(() => {
|
|
for (const [id, entry] of backupsState.entries()) {
|
|
if (id !== createdBackupId.value && entry.create?.state === 'ongoing') return true
|
|
}
|
|
return false
|
|
})
|
|
|
|
// Watch backupsState for websocket progress events from Kyros
|
|
watch(
|
|
() => {
|
|
if (!createdBackupId.value) return null
|
|
return backupsState.get(createdBackupId.value)
|
|
},
|
|
(entry) => {
|
|
if (!entry?.create) return
|
|
|
|
if (entry.create.state === 'done') {
|
|
isBackingUp.value = false
|
|
backupComplete.value = true
|
|
} else if (entry.create.state === 'cancelled') {
|
|
isBackingUp.value = false
|
|
isCancelling.value = false
|
|
backupCancelled.value = true
|
|
} else if (entry.create.state === 'failed') {
|
|
isBackingUp.value = false
|
|
backupFailed.value = true
|
|
}
|
|
},
|
|
{ deep: true },
|
|
)
|
|
|
|
// Fallback: poll the REST API in case websocket events don't arrive
|
|
let pollTimer: ReturnType<typeof setInterval> | null = null
|
|
|
|
function stopPolling() {
|
|
if (pollTimer !== null) {
|
|
clearInterval(pollTimer)
|
|
pollTimer = null
|
|
}
|
|
}
|
|
|
|
async function pollBackupStatus(backupId: string) {
|
|
if (!isBackingUp.value) {
|
|
stopPolling()
|
|
return
|
|
}
|
|
|
|
try {
|
|
const backup = await client.archon.backups_v1.get(serverId, worldId.value!, backupId)
|
|
|
|
if (!backup.ongoing) {
|
|
stopPolling()
|
|
|
|
if (backup.interrupted) {
|
|
isBackingUp.value = false
|
|
backupFailed.value = true
|
|
} else {
|
|
isBackingUp.value = false
|
|
backupComplete.value = true
|
|
}
|
|
}
|
|
} catch {
|
|
stopPolling()
|
|
isBackingUp.value = false
|
|
backupFailed.value = true
|
|
}
|
|
}
|
|
|
|
async function startBackup() {
|
|
if (!worldId.value) return
|
|
|
|
const name = typeof backupName === 'function' ? backupName() : backupName
|
|
|
|
isBackingUp.value = true
|
|
backupFailed.value = false
|
|
backupComplete.value = false
|
|
backupCancelled.value = false
|
|
isCancelling.value = false
|
|
createdBackupId.value = null
|
|
|
|
try {
|
|
const { id } = await client.archon.backups_v1.create(serverId, worldId.value, { name })
|
|
createdBackupId.value = id
|
|
|
|
stopPolling()
|
|
pollTimer = setInterval(() => pollBackupStatus(id), 3000)
|
|
} catch (error) {
|
|
isBackingUp.value = false
|
|
backupFailed.value = true
|
|
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
const isRateLimit = message.includes('429')
|
|
addNotification({
|
|
type: 'error',
|
|
title: 'Error creating backup',
|
|
text: isRateLimit ? "You're creating backups too fast." : message,
|
|
})
|
|
}
|
|
}
|
|
|
|
async function cancelBackup() {
|
|
if (!worldId.value || !createdBackupId.value || !isBackingUp.value) return
|
|
|
|
isCancelling.value = true
|
|
stopPolling()
|
|
markBackupCancelled(createdBackupId.value)
|
|
|
|
try {
|
|
await client.archon.backups_v1.delete(serverId, worldId.value, createdBackupId.value)
|
|
isBackingUp.value = false
|
|
isCancelling.value = false
|
|
backupCancelled.value = true
|
|
addNotification({
|
|
type: 'info',
|
|
title: 'Backup cancelled',
|
|
text: 'The backup has been cancelled. You can create a new one or proceed without a backup.',
|
|
})
|
|
} catch {
|
|
isBackingUp.value = false
|
|
isCancelling.value = false
|
|
backupFailed.value = true
|
|
}
|
|
}
|
|
|
|
function handleBeforeUnload(e: BeforeUnloadEvent) {
|
|
if (isBackingUp.value) {
|
|
e.preventDefault()
|
|
return ''
|
|
}
|
|
}
|
|
|
|
if (typeof window !== 'undefined') {
|
|
watch(isBackingUp, (operating) => {
|
|
if (operating) {
|
|
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
} else {
|
|
window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
}
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
stopPolling()
|
|
})
|
|
|
|
onBeforeRouteLeave(() => {
|
|
if (isBackingUp.value) {
|
|
return window.confirm('A backup is being created. Are you sure you want to leave?')
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
return {
|
|
available: true as const,
|
|
isServer: true as const,
|
|
isBackingUp,
|
|
isCancelling,
|
|
backupFailed,
|
|
backupComplete,
|
|
backupCancelled,
|
|
externalBackupInProgress,
|
|
startBackup,
|
|
cancelBackup,
|
|
}
|
|
}
|