mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-02-11 04:00:32 +08:00
fix: use atomic update in changeCurrentProfile
This commit is contained in:
parent
fbde5c3f09
commit
3d6d545a93
@ -15,9 +15,8 @@ import { mihomoUpgradeConfig } from '../core/mihomoApi'
|
|||||||
|
|
||||||
import i18next from 'i18next'
|
import i18next from 'i18next'
|
||||||
|
|
||||||
let profileConfig: IProfileConfig // profile.yaml
|
let profileConfig: IProfileConfig
|
||||||
let profileConfigWriteQueue: Promise<void> = Promise.resolve()
|
let profileConfigWriteQueue: Promise<void> = Promise.resolve()
|
||||||
// 最终选中订阅ID
|
|
||||||
let targetProfileId: string | null = null
|
let targetProfileId: string | null = null
|
||||||
|
|
||||||
export async function getProfileConfig(force = false): Promise<IProfileConfig> {
|
export async function getProfileConfig(force = false): Promise<IProfileConfig> {
|
||||||
@ -26,7 +25,7 @@ export async function getProfileConfig(force = false): Promise<IProfileConfig> {
|
|||||||
profileConfig = parse(data) || { items: [] }
|
profileConfig = parse(data) || { items: [] }
|
||||||
}
|
}
|
||||||
if (typeof profileConfig !== 'object') profileConfig = { items: [] }
|
if (typeof profileConfig !== 'object') profileConfig = { items: [] }
|
||||||
return profileConfig
|
return structuredClone(profileConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setProfileConfig(config: IProfileConfig): Promise<void> {
|
export async function setProfileConfig(config: IProfileConfig): Promise<void> {
|
||||||
@ -37,6 +36,22 @@ export async function setProfileConfig(config: IProfileConfig): Promise<void> {
|
|||||||
await profileConfigWriteQueue
|
await profileConfigWriteQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateProfileConfig(
|
||||||
|
updater: (config: IProfileConfig) => IProfileConfig | Promise<IProfileConfig>
|
||||||
|
): Promise<IProfileConfig> {
|
||||||
|
let result: IProfileConfig
|
||||||
|
profileConfigWriteQueue = profileConfigWriteQueue.then(async () => {
|
||||||
|
const data = await readFile(profileConfigPath(), 'utf-8')
|
||||||
|
profileConfig = parse(data) || { items: [] }
|
||||||
|
if (typeof profileConfig !== 'object') profileConfig = { items: [] }
|
||||||
|
profileConfig = await updater(structuredClone(profileConfig))
|
||||||
|
result = profileConfig
|
||||||
|
await writeFile(profileConfigPath(), stringify(profileConfig), 'utf-8')
|
||||||
|
})
|
||||||
|
await profileConfigWriteQueue
|
||||||
|
return structuredClone(result!)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getProfileItem(id: string | undefined): Promise<IProfileItem | undefined> {
|
export async function getProfileItem(id: string | undefined): Promise<IProfileItem | undefined> {
|
||||||
const { items } = await getProfileConfig()
|
const { items } = await getProfileConfig()
|
||||||
if (!id || id === 'default')
|
if (!id || id === 'default')
|
||||||
@ -45,8 +60,7 @@ export async function getProfileItem(id: string | undefined): Promise<IProfileIt
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function changeCurrentProfile(id: string): Promise<void> {
|
export async function changeCurrentProfile(id: string): Promise<void> {
|
||||||
const config = await getProfileConfig()
|
const { current } = await getProfileConfig()
|
||||||
const current = config.current
|
|
||||||
|
|
||||||
if (current === id && targetProfileId !== id) {
|
if (current === id && targetProfileId !== id) {
|
||||||
return
|
return
|
||||||
@ -54,13 +68,12 @@ export async function changeCurrentProfile(id: string): Promise<void> {
|
|||||||
|
|
||||||
targetProfileId = id
|
targetProfileId = id
|
||||||
|
|
||||||
config.current = id
|
|
||||||
const configSavePromise = setProfileConfig(config)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await configSavePromise
|
await updateProfileConfig((config) => {
|
||||||
|
config.current = id
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
// 检查订阅切换是否中断
|
|
||||||
if (targetProfileId !== id) {
|
if (targetProfileId !== id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -70,8 +83,10 @@ export async function changeCurrentProfile(id: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (targetProfileId === id) {
|
if (targetProfileId === id) {
|
||||||
|
await updateProfileConfig((config) => {
|
||||||
config.current = current
|
config.current = current
|
||||||
await setProfileConfig(config)
|
return config
|
||||||
|
})
|
||||||
targetProfileId = null
|
targetProfileId = null
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
@ -79,47 +94,51 @@ export async function changeCurrentProfile(id: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateProfileItem(item: IProfileItem): Promise<void> {
|
export async function updateProfileItem(item: IProfileItem): Promise<void> {
|
||||||
const config = await getProfileConfig()
|
await updateProfileConfig((config) => {
|
||||||
const index = config.items.findIndex((i) => i.id === item.id)
|
const index = config.items.findIndex((i) => i.id === item.id)
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
throw new Error('Profile not found')
|
throw new Error('Profile not found')
|
||||||
}
|
}
|
||||||
config.items[index] = item
|
config.items[index] = item
|
||||||
await setProfileConfig(config)
|
return config
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addProfileItem(item: Partial<IProfileItem>): Promise<void> {
|
export async function addProfileItem(item: Partial<IProfileItem>): Promise<void> {
|
||||||
const newItem = await createProfile(item)
|
const newItem = await createProfile(item)
|
||||||
const config = await getProfileConfig()
|
let shouldChangeCurrent = false
|
||||||
if (await getProfileItem(newItem.id)) {
|
await updateProfileConfig((config) => {
|
||||||
await updateProfileItem(newItem)
|
const existingIndex = config.items.findIndex((i) => i.id === newItem.id)
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
config.items[existingIndex] = newItem
|
||||||
} else {
|
} else {
|
||||||
config.items.push(newItem)
|
config.items.push(newItem)
|
||||||
}
|
}
|
||||||
await setProfileConfig(config)
|
|
||||||
|
|
||||||
if (!config.current) {
|
if (!config.current) {
|
||||||
|
shouldChangeCurrent = true
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
if (shouldChangeCurrent) {
|
||||||
await changeCurrentProfile(newItem.id)
|
await changeCurrentProfile(newItem.id)
|
||||||
}
|
}
|
||||||
await addProfileUpdater(newItem)
|
await addProfileUpdater(newItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeProfileItem(id: string): Promise<void> {
|
export async function removeProfileItem(id: string): Promise<void> {
|
||||||
// 先清理自动更新定时器,防止已删除的订阅重新出现
|
|
||||||
await removeProfileUpdater(id)
|
await removeProfileUpdater(id)
|
||||||
|
|
||||||
const config = await getProfileConfig()
|
|
||||||
config.items = config.items?.filter((item) => item.id !== id)
|
|
||||||
let shouldRestart = false
|
let shouldRestart = false
|
||||||
|
await updateProfileConfig((config) => {
|
||||||
|
config.items = config.items?.filter((item) => item.id !== id)
|
||||||
if (config.current === id) {
|
if (config.current === id) {
|
||||||
shouldRestart = true
|
shouldRestart = true
|
||||||
if (config.items.length > 0) {
|
config.current = config.items.length > 0 ? config.items[0].id : undefined
|
||||||
config.current = config.items[0].id
|
|
||||||
} else {
|
|
||||||
config.current = undefined
|
|
||||||
}
|
}
|
||||||
}
|
return config
|
||||||
await setProfileConfig(config)
|
})
|
||||||
|
|
||||||
if (existsSync(profilePath(id))) {
|
if (existsSync(profilePath(id))) {
|
||||||
await rm(profilePath(id))
|
await rm(profilePath(id))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,34 +1,40 @@
|
|||||||
import { addProfileItem, getCurrentProfileItem, getProfileConfig } from '../config'
|
import { addProfileItem, getCurrentProfileItem, getProfileConfig, getProfileItem } from '../config'
|
||||||
import { Cron } from 'croner'
|
import { Cron } from 'croner'
|
||||||
import { logger } from '../utils/logger'
|
import { logger } from '../utils/logger'
|
||||||
|
|
||||||
const intervalPool: Record<string, Cron | NodeJS.Timeout> = {}
|
const intervalPool: Record<string, Cron | NodeJS.Timeout> = {}
|
||||||
|
|
||||||
|
async function updateProfile(id: string): Promise<void> {
|
||||||
|
const item = await getProfileItem(id)
|
||||||
|
if (item && item.type === 'remote') {
|
||||||
|
await addProfileItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function initProfileUpdater(): Promise<void> {
|
export async function initProfileUpdater(): Promise<void> {
|
||||||
const { items, current } = await getProfileConfig()
|
const { items, current } = await getProfileConfig()
|
||||||
const currentItem = await getCurrentProfileItem()
|
const currentItem = await getCurrentProfileItem()
|
||||||
|
|
||||||
for (const item of items.filter((i) => i.id !== current)) {
|
for (const item of items.filter((i) => i.id !== current)) {
|
||||||
if (item.type === 'remote' && item.autoUpdate && item.interval) {
|
if (item.type === 'remote' && item.autoUpdate && item.interval) {
|
||||||
|
const itemId = item.id
|
||||||
if (typeof item.interval === 'number') {
|
if (typeof item.interval === 'number') {
|
||||||
// 数字间隔使用 setInterval
|
intervalPool[itemId] = setInterval(
|
||||||
intervalPool[item.id] = setInterval(
|
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(item)
|
await updateProfile(itemId)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await logger.warn(`[ProfileUpdater] Failed to update profile ${item.name}:`, e)
|
await logger.warn(`[ProfileUpdater] Failed to update profile ${itemId}:`, e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
item.interval * 60 * 1000
|
item.interval * 60 * 1000
|
||||||
)
|
)
|
||||||
} else if (typeof item.interval === 'string') {
|
} else if (typeof item.interval === 'string') {
|
||||||
// 字符串间隔使用 Cron
|
intervalPool[itemId] = new Cron(item.interval, async () => {
|
||||||
intervalPool[item.id] = new Cron(item.interval, async () => {
|
|
||||||
try {
|
try {
|
||||||
await addProfileItem(item)
|
await updateProfile(itemId)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await logger.warn(`[ProfileUpdater] Failed to update profile ${item.name}:`, e)
|
await logger.warn(`[ProfileUpdater] Failed to update profile ${itemId}:`, e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -42,11 +48,12 @@ export async function initProfileUpdater(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentItem?.type === 'remote' && currentItem.autoUpdate && currentItem.interval) {
|
if (currentItem?.type === 'remote' && currentItem.autoUpdate && currentItem.interval) {
|
||||||
|
const currentId = currentItem.id
|
||||||
if (typeof currentItem.interval === 'number') {
|
if (typeof currentItem.interval === 'number') {
|
||||||
intervalPool[currentItem.id] = setInterval(
|
intervalPool[currentId] = setInterval(
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(currentItem)
|
await updateProfile(currentId)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await logger.warn(`[ProfileUpdater] Failed to update current profile:`, e)
|
await logger.warn(`[ProfileUpdater] Failed to update current profile:`, e)
|
||||||
}
|
}
|
||||||
@ -57,17 +64,17 @@ export async function initProfileUpdater(): Promise<void> {
|
|||||||
setTimeout(
|
setTimeout(
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(currentItem)
|
await updateProfile(currentId)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await logger.warn(`[ProfileUpdater] Failed to update current profile:`, e)
|
await logger.warn(`[ProfileUpdater] Failed to update current profile:`, e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
currentItem.interval * 60 * 1000 + 10000 // +10s
|
currentItem.interval * 60 * 1000 + 10000
|
||||||
)
|
)
|
||||||
} else if (typeof currentItem.interval === 'string') {
|
} else if (typeof currentItem.interval === 'string') {
|
||||||
intervalPool[currentItem.id] = new Cron(currentItem.interval, async () => {
|
intervalPool[currentId] = new Cron(currentItem.interval, async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(currentItem)
|
await updateProfile(currentId)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await logger.warn(`[ProfileUpdater] Failed to update current profile:`, e)
|
await logger.warn(`[ProfileUpdater] Failed to update current profile:`, e)
|
||||||
}
|
}
|
||||||
@ -92,23 +99,24 @@ export async function addProfileUpdater(item: IProfileItem): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const itemId = item.id
|
||||||
if (typeof item.interval === 'number') {
|
if (typeof item.interval === 'number') {
|
||||||
intervalPool[item.id] = setInterval(
|
intervalPool[itemId] = setInterval(
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(item)
|
await updateProfile(itemId)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await logger.warn(`[ProfileUpdater] Failed to update profile ${item.name}:`, e)
|
await logger.warn(`[ProfileUpdater] Failed to update profile ${itemId}:`, e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
item.interval * 60 * 1000
|
item.interval * 60 * 1000
|
||||||
)
|
)
|
||||||
} else if (typeof item.interval === 'string') {
|
} else if (typeof item.interval === 'string') {
|
||||||
intervalPool[item.id] = new Cron(item.interval, async () => {
|
intervalPool[itemId] = new Cron(item.interval, async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(item)
|
await updateProfile(itemId)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await logger.warn(`[ProfileUpdater] Failed to update profile ${item.name}:`, e)
|
await logger.warn(`[ProfileUpdater] Failed to update profile ${itemId}:`, e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user