完善同步

pull/1229/head
lyswhut 2023-03-01 18:44:51 +08:00
parent e75ab3a7d7
commit 3399333360
14 changed files with 89 additions and 50 deletions

View File

@ -523,14 +523,14 @@
"sync__merge_tip_desc": "Merge the two lists together, the same song will be removed (the song of the merged person is removed), and different songs will be added.", "sync__merge_tip_desc": "Merge the two lists together, the same song will be removed (the song of the merged person is removed), and different songs will be added.",
"sync__other_label": "Other", "sync__other_label": "Other",
"sync__other_tip": "Other: ", "sync__other_tip": "Other: ",
"sync__other_tip_desc": "\"Only use real-time synchronization function\" will not modify the lists of both parties, only real-time synchronization operations; \"Cancel synchronization\" will directly disconnect the two parties.", "sync__other_tip_desc": "\"Cancel Sync\" will directly disconnect the two parties.",
"sync__overwrite": "Full coverage", "sync__overwrite": "Full coverage",
"sync__overwrite_btn_cancel": "Cancel sync", "sync__overwrite_btn_cancel": "Cancel sync",
"sync__overwrite_btn_local_remote": "Local list Overwrite remote list", "sync__overwrite_btn_local_remote": "Local list Overwrite remote list",
"sync__overwrite_btn_none": "Only use real-time synchronization", "sync__overwrite_btn_none": "Only use real-time synchronization",
"sync__overwrite_btn_remote_local": "Remote list Overwrite local list", "sync__overwrite_btn_remote_local": "Remote list Overwrite local list",
"sync__overwrite_label": "Cover", "sync__overwrite_label": "Cover",
"sync__overwrite_tip": "Cover: ", "sync__overwrite_tip": "Over: ",
"sync__overwrite_tip_desc": "The list with the same ID of the covered person and the covered list will be deleted and replaced with the list of the covered person (lists with different list IDs will be merged together). If you check Complete coverage, all lists of the covered person will be moved. \nDivide, and then replace with a list of overriders.", "sync__overwrite_tip_desc": "The list with the same ID of the covered person and the covered list will be deleted and replaced with the list of the covered person (lists with different list IDs will be merged together). If you check Complete coverage, all lists of the covered person will be moved. \nDivide, and then replace with a list of overriders.",
"sync__title": "Choose how to synchronize the list with {name}", "sync__title": "Choose how to synchronize the list with {name}",
"sync_status_disabled": "not connected", "sync_status_disabled": "not connected",

View File

@ -526,7 +526,7 @@
"sync__merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。", "sync__merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。",
"sync__other_label": "其他", "sync__other_label": "其他",
"sync__other_tip": "其他:", "sync__other_tip": "其他:",
"sync__other_tip_desc": "“仅使用实时同步功能”将不修改双方的列表,仅实时同步操作;“取消同步”将直接断开双方的连接。", "sync__other_tip_desc": "“取消同步”将直接断开双方的连接。",
"sync__overwrite": "完全覆盖", "sync__overwrite": "完全覆盖",
"sync__overwrite_btn_cancel": "取消同步", "sync__overwrite_btn_cancel": "取消同步",
"sync__overwrite_btn_local_remote": "本机列表 覆盖 远程列表", "sync__overwrite_btn_local_remote": "本机列表 覆盖 远程列表",

View File

@ -523,7 +523,7 @@
"sync__merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。", "sync__merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
"sync__other_label": "其他", "sync__other_label": "其他",
"sync__other_tip": "其他:", "sync__other_tip": "其他:",
"sync__other_tip_desc": "“僅使用實時同步功能”將不修改雙方的列表,僅實時同步操作;“取消同步”將直接斷開雙方的連接。", "sync__other_tip_desc": "“取消同步”將直接斷開雙方的連接。",
"sync__overwrite": "完全覆蓋", "sync__overwrite": "完全覆蓋",
"sync__overwrite_btn_cancel": "取消同步", "sync__overwrite_btn_cancel": "取消同步",
"sync__overwrite_btn_local_remote": "本機列表 覆蓋 遠程列表", "sync__overwrite_btn_local_remote": "本機列表 覆蓋 遠程列表",

View File

@ -38,6 +38,7 @@ const handleConnection = (socket: LX.Sync.Client.Socket) => {
const heartbeatTools = { const heartbeatTools = {
failedNum: 0, failedNum: 0,
maxTryNum: 3,
pingTimeout: null as NodeJS.Timeout | null, pingTimeout: null as NodeJS.Timeout | null,
delayRetryTimeout: null as NodeJS.Timeout | null, delayRetryTimeout: null as NodeJS.Timeout | null,
handleOpen() { handleOpen() {
@ -64,16 +65,21 @@ const heartbeatTools = {
// client = null // client = null
if (!client) return if (!client) return
if (this.failedNum > 3) throw new Error('connect error') if (++this.failedNum > this.maxTryNum) {
this.failedNum = 0
throw new Error('connect error')
}
this.delayRetryTimeout = setTimeout(() => { this.delayRetryTimeout = setTimeout(() => {
this.delayRetryTimeout = null this.delayRetryTimeout = null
if (!client) return if (!client) return
console.log(dateFormat(new Date()), 'reconnnect...') console.log(dateFormat(new Date()), 'reconnnect...')
sendSyncStatus({
status: false,
message: `Try reconnnect... (${this.failedNum}/${this.maxTryNum})`,
})
connect(client.data.urlInfo, client.data.keyInfo) connect(client.data.urlInfo, client.data.keyInfo)
}, 2000) }, 2000)
this.failedNum++
}, },
clearTimeout() { clearTimeout() {
if (this.delayRetryTimeout) { if (this.delayRetryTimeout) {
@ -176,23 +182,40 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client
message: '', message: '',
}) })
}).catch(err => { }).catch(err => {
if (err.message == 'closed') {
sendSyncStatus({
status: false,
message: '',
})
} else {
console.log(err) console.log(err)
log.r_error(err.stack) log.r_error(err.stack)
sendSyncStatus({ sendSyncStatus({
status: false, status: false,
message: err.message, message: err.message,
}) })
}
}) })
}) })
client.addEventListener('close', () => { client.addEventListener('close', ({ code }) => {
sendSyncStatus({
status: false,
message: '',
})
const err = new Error('closed') const err = new Error('closed')
for (const handler of closeEvents) void handler(err) for (const handler of closeEvents) void handler(err)
closeEvents = [] closeEvents = []
events = {} events = {}
switch (code) {
case SYNC_CLOSE_CODE.normal:
// case SYNC_CLOSE_CODE.failed:
sendSyncStatus({
status: false,
message: '',
})
}
})
client.addEventListener('error', ({ message }) => {
sendSyncStatus({
status: false,
message,
})
}) })
} }
@ -202,6 +225,7 @@ export const disconnect = async() => {
client.close(SYNC_CLOSE_CODE.normal) client.close(SYNC_CLOSE_CODE.normal)
client = null client = null
heartbeatTools.clearTimeout() heartbeatTools.clearTimeout()
heartbeatTools.failedNum = 0
} }
export const getStatus = (): LX.Sync.ClientStatus => status export const getStatus = (): LX.Sync.ClientStatus => status

View File

@ -5,13 +5,18 @@ import { SYNC_CODE } from '@common/constants'
import log from '../log' import log from '../log'
import { parseUrl } from './utils' import { parseUrl } from './utils'
let connectId = 0
const handleConnect = async(host: string, authCode?: string) => { const handleConnect = async(host: string, authCode?: string) => {
// const hostInfo = await getSyncHost() // const hostInfo = await getSyncHost()
// console.log(hostInfo) // console.log(hostInfo)
// if (!hostInfo || !hostInfo.host || !hostInfo.port) throw new Error(SYNC_CODE.unknownServiceAddress) // if (!hostInfo || !hostInfo.host || !hostInfo.port) throw new Error(SYNC_CODE.unknownServiceAddress)
const id = connectId
const urlInfo = parseUrl(host) const urlInfo = parseUrl(host)
await disconnectServer(false) await disconnectServer(false)
if (id != connectId) return
const keyInfo = await handleAuth(urlInfo, authCode) const keyInfo = await handleAuth(urlInfo, authCode)
if (id != connectId) return
socketConnect(urlInfo, keyInfo) socketConnect(urlInfo, keyInfo)
} }
const handleDisconnect = async() => { const handleDisconnect = async() => {
@ -23,12 +28,9 @@ const connectServer = async(host: string, authCode?: string) => {
status: false, status: false,
message: SYNC_CODE.connecting, message: SYNC_CODE.connecting,
}) })
return handleConnect(host, authCode).then(() => { const id = connectId
sendSyncStatus({ return handleConnect(host, authCode).catch(async err => {
status: true, if (id != connectId) return
message: '',
})
}).catch(async err => {
sendSyncStatus({ sendSyncStatus({
status: false, status: false,
message: err.message, message: err.message,
@ -49,6 +51,7 @@ const connectServer = async(host: string, authCode?: string) => {
const disconnectServer = async(isResetStatus = true) => handleDisconnect().then(() => { const disconnectServer = async(isResetStatus = true) => handleDisconnect().then(() => {
log.info('disconnect...') log.info('disconnect...')
if (isResetStatus) { if (isResetStatus) {
connectId++
sendSyncStatus({ sendSyncStatus({
status: false, status: false,
message: '', message: '',

View File

@ -9,15 +9,15 @@ import log from './log'
export const getSyncAuthKey = async(serverId: string) => { export const getSyncAuthKey = async(serverId: string) => {
const store = getStore(STORE_NAMES.SYNC) const store = getStore(STORE_NAMES.SYNC)
const keys = store.get('syncAuthKey') as Record<string, LX.Sync.ClientKeyInfo> | null const keys = store.get('syncAuthKey_v3') as Record<string, LX.Sync.ClientKeyInfo> | null
if (!keys) return null if (!keys) return null
return keys[serverId] ?? null return keys[serverId] ?? null
} }
export const setSyncAuthKey = async(serverId: string, info: LX.Sync.ClientKeyInfo) => { export const setSyncAuthKey = async(serverId: string, info: LX.Sync.ClientKeyInfo) => {
const store = getStore(STORE_NAMES.SYNC) const store = getStore(STORE_NAMES.SYNC)
let keys: Record<string, LX.Sync.ClientKeyInfo> = (store.get('syncAuthKey') as Record<string, LX.Sync.ClientKeyInfo> | null) ?? {} let keys: Record<string, LX.Sync.ClientKeyInfo> = (store.get('syncAuthKey_v3') as Record<string, LX.Sync.ClientKeyInfo> | null) ?? {}
keys[serverId] = info keys[serverId] = info
store.set('syncAuthKey', keys) store.set('syncAuthKey_v3', keys)
} }
let syncHost: string let syncHost: string
@ -73,7 +73,7 @@ const devicesInfo: DevicesInfo = { serverId: '', clients: {}, snapshotInfo: { la
let deviceKeys: string[] = [] let deviceKeys: string[] = []
const saveDevicesInfoThrottle = throttle(() => { const saveDevicesInfoThrottle = throttle(() => {
const store = getStore(STORE_NAMES.SYNC) const store = getStore(STORE_NAMES.SYNC)
store.set('keys', devicesInfo.clients) store.set('clients', devicesInfo.clients)
}) })
const initDeviceInfo = () => { const initDeviceInfo = () => {
@ -82,7 +82,8 @@ const initDeviceInfo = () => {
if (serverId) devicesInfo.serverId = serverId if (serverId) devicesInfo.serverId = serverId
else { else {
devicesInfo.serverId = randomBytes(4 * 4).toString('base64') devicesInfo.serverId = randomBytes(4 * 4).toString('base64')
saveDevicesInfoThrottle() const store = getStore(STORE_NAMES.SYNC)
store.set('serverId', devicesInfo.serverId)
} }
const devices = store.get('clients') as DevicesInfo['clients'] | undefined const devices = store.get('clients') as DevicesInfo['clients'] | undefined
if (devices) devicesInfo.clients = devices if (devices) devicesInfo.clients = devices
@ -155,6 +156,7 @@ export const saveSnapshotInfo = (info: SnapshotInfo) => {
} }
export const getSnapshot = async(name: string) => { export const getSnapshot = async(name: string) => {
console.log('getSnapshot', name)
const filePath = path.join(global.lxDataPath, `snapshot_${name}`) const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
let listData: LX.Sync.ListData let listData: LX.Sync.ListData
try { try {
@ -166,6 +168,7 @@ export const getSnapshot = async(name: string) => {
return listData return listData
} }
export const saveSnapshot = async(name: string, data: string) => { export const saveSnapshot = async(name: string, data: string) => {
console.log('saveSnapshot', name)
const filePath = path.join(global.lxDataPath, `snapshot_${name}`) const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
return fs.promises.writeFile(filePath, data).catch((err) => { return fs.promises.writeFile(filePath, data).catch((err) => {
log.error(err) log.error(err)
@ -173,6 +176,7 @@ export const saveSnapshot = async(name: string, data: string) => {
}) })
} }
export const removeSnapshot = async(name: string) => { export const removeSnapshot = async(name: string) => {
console.log('removeSnapshot', name)
const filePath = path.join(global.lxDataPath, `snapshot_${name}`) const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
return fs.promises.unlink(filePath).catch((err) => { return fs.promises.unlink(filePath).catch((err) => {
log.error(err) log.error(err)

View File

@ -2,8 +2,8 @@
// import { sendSyncActionList } from '@main/modules/winMain' // import { sendSyncActionList } from '@main/modules/winMain'
import { SYNC_CLOSE_CODE } from '@common/constants' import { SYNC_CLOSE_CODE } from '@common/constants'
import { updateDeviceSnapshotKey } from '../../data' import { updateDeviceSnapshotKey } from '../../data'
import { handleRemoteListAction } from '../../utils' import { handleRemoteListAction, registerListActionEvent } from '../../utils'
import { createSnapshot, encryptMsg } from '../utils' import { createSnapshot, encryptMsg, getCurrentListInfoKey } from '../utils'
let wss: LX.Sync.Server.SocketServer | null let wss: LX.Sync.Server.SocketServer | null
let removeListener: (() => void) | null let removeListener: (() => void) | null
@ -29,16 +29,16 @@ const broadcast = async(key: string, data: any, excludeIds: string[] = []) => {
} }
} }
export const sendListAction = async(action: LX.Sync.ActionList) => { const sendListAction = async(action: LX.Sync.ActionList) => {
console.log('sendListAction', action.action) console.log('sendListAction', action.action)
// io.sockets // io.sockets
await broadcast('list:sync:action', action) await broadcast(await getCurrentListInfoKey(), action)
} }
export const registerListHandler = (_wss: LX.Sync.Server.SocketServer, socket: LX.Sync.Server.Socket) => { export const registerListHandler = (_wss: LX.Sync.Server.SocketServer, socket: LX.Sync.Server.Socket) => {
if (!wss) { if (!wss) {
wss = _wss wss = _wss
// removeListener = registerListActionEvent() removeListener = registerListActionEvent(sendListAction)
} }
socket.onRemoteEvent('list:sync:action', (action) => { socket.onRemoteEvent('list:sync:action', (action) => {

View File

@ -19,6 +19,7 @@ let status: LX.Sync.ServerStatus = {
code: '', code: '',
devices: [], devices: [],
} }
let stopingServer = false
const codeTools: { const codeTools: {
timeout: NodeJS.Timer | null timeout: NodeJS.Timer | null
@ -148,6 +149,7 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
// const events = new Map<keyof ActionsType, Array<(err: Error | null, data: LX.Sync.ActionSyncType[keyof LX.Sync.ActionSyncType]) => void>>() // const events = new Map<keyof ActionsType, Array<(err: Error | null, data: LX.Sync.ActionSyncType[keyof LX.Sync.ActionSyncType]) => void>>()
// const events = new Map<keyof LX.Sync.ActionSyncType, Array<(err: Error | null, data: LX.Sync.ActionSyncType[keyof LX.Sync.ActionSyncType]) => void>>() // const events = new Map<keyof LX.Sync.ActionSyncType, Array<(err: Error | null, data: LX.Sync.ActionSyncType[keyof LX.Sync.ActionSyncType]) => void>>()
let events: Partial<{ [K in keyof LX.Sync.ActionSyncType]: Array<(data: LX.Sync.ActionSyncType[K]) => void> }> = {} let events: Partial<{ [K in keyof LX.Sync.ActionSyncType]: Array<(data: LX.Sync.ActionSyncType[K]) => void> }> = {}
let closeEvents: Array<(err: Error) => (void | Promise<void>)> = []
socket.addEventListener('message', ({ data }) => { socket.addEventListener('message', ({ data }) => {
if (typeof data === 'string') { if (typeof data === 'string') {
let syncData: LX.Sync.ActionSync let syncData: LX.Sync.ActionSync
@ -167,11 +169,9 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
}) })
socket.addEventListener('close', () => { socket.addEventListener('close', () => {
const err = new Error('closed') const err = new Error('closed')
for (const handler of Object.values(events).flat()) { for (const handler of closeEvents) void handler(err)
// @ts-expect-error
handler(err, null)
}
events = {} events = {}
closeEvents = []
if (!status.devices.length) handleUnconnection() if (!status.devices.length) handleUnconnection()
log.info('deconnection', socket.keyInfo.deviceName) log.info('deconnection', socket.keyInfo.deviceName)
}) })
@ -186,6 +186,12 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
eventArr!.splice(eventArr!.indexOf(handler), 1) eventArr!.splice(eventArr!.indexOf(handler), 1)
} }
} }
socket.onClose = function(handler: typeof closeEvents[number]) {
closeEvents.push(handler)
return () => {
closeEvents.splice(closeEvents.indexOf(handler), 1)
}
}
socket.sendData = function(eventName, data, callback) { socket.sendData = function(eventName, data, callback) {
socket.send(encryptMsg(socket.keyInfo, JSON.stringify({ action: eventName, data })), callback) socket.send(encryptMsg(socket.keyInfo, JSON.stringify({ action: eventName, data })), callback)
} }
@ -248,6 +254,7 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
const handleStopServer = async() => new Promise<void>((resolve, reject) => { const handleStopServer = async() => new Promise<void>((resolve, reject) => {
if (!wss) return if (!wss) return
for (const client of wss.clients) client.close(SYNC_CLOSE_CODE.normal)
wss.close() wss.close()
wss = null wss = null
httpServer.close((err) => { httpServer.close((err) => {
@ -271,6 +278,9 @@ export const stopServer = async() => {
return return
} }
console.log('stoping sync server...') console.log('stoping sync server...')
status.message = 'stoping...'
sendServerStatus(status)
stopingServer = true
await handleStopServer().then(() => { await handleStopServer().then(() => {
console.log('sync server stoped') console.log('sync server stoped')
status.status = false status.status = false
@ -281,12 +291,14 @@ export const stopServer = async() => {
console.log(err) console.log(err)
status.message = err.message status.message = err.message
}).finally(() => { }).finally(() => {
stopingServer = false
sendServerStatus(status) sendServerStatus(status)
}) })
} }
export const startServer = async(port: number) => { export const startServer = async(port: number) => {
console.log('status.status', status.status) console.log('status.status', status.status)
if (stopingServer) return
if (status.status) await handleStopServer() if (status.status) await handleStopServer()
log.info('starting sync server') log.info('starting sync server')

View File

@ -52,11 +52,6 @@ export const createSnapshot = async() => {
export const getCurrentListInfoKey = async() => { export const getCurrentListInfoKey = async() => {
if (!snapshotInfo) snapshotInfo = getSnapshotInfo() // if (!snapshotInfo) snapshotInfo = getSnapshotInfo()
if (snapshotInfo.latest) { return createSnapshot()
return snapshotInfo.latest
}
snapshotInfo.latest = toMD5(JSON.stringify(await getLocalListData()))
saveSnapshotInfo(snapshotInfo)
return snapshotInfo.latest
} }

View File

@ -28,7 +28,7 @@ export default (name: string, isIgnoredError = true, isShowErrorAlert = true): S
const backPath = join(global.lxDataPath, name + '.json.bak') const backPath = join(global.lxDataPath, name + '.json.bak')
fs.copyFileSync(join(global.lxDataPath, name + '.json'), backPath) fs.renameSync(join(global.lxDataPath, name + '.json'), backPath)
if (isShowErrorAlert) { if (isShowErrorAlert) {
dialog.showMessageBoxSync({ dialog.showMessageBoxSync({
type: 'error', type: 'error',

View File

@ -35,6 +35,7 @@ export default {
const handleSubmit = () => { const handleSubmit = () => {
let code = verify() let code = verify()
if (code == '') return if (code == '') return
authCode.value = ''
handleClose() handleClose()
sendSyncAction({ sendSyncAction({
action: 'enable_client', action: 'enable_client',

View File

@ -6,7 +6,7 @@ import { SYNC_CODE } from '@common/constants'
export default () => { export default () => {
const handleSyncList = (event: LX.Sync.SyncMainWindowActions) => { const handleSyncList = (event: LX.Sync.SyncMainWindowActions) => {
console.log(event) // console.log(event)
switch (event.action) { switch (event.action) {
case 'select_mode': case 'select_mode':
sync.deviceName = event.data sync.deviceName = event.data

View File

@ -21,7 +21,7 @@ export const mergeSetting = (newSetting: Partial<LX.AppSetting>) => {
} }
export const updateSetting = window.lxData.updateSetting = (setting: Partial<LX.AppSetting>) => { export const updateSetting = window.lxData.updateSetting = (setting: Partial<LX.AppSetting>) => {
console.warn(setting) // console.warn(setting)
void saveSetting(setting) void saveSetting(setting)
} }

View File

@ -140,10 +140,10 @@ export default {
scrollIndex = _scrollIndex scrollIndex = _scrollIndex
isAnimation = _isAnimation isAnimation = _isAnimation
if (isAnimation) restoreScroll(scrollIndex, isAnimation) if (isAnimation) restoreScroll(scrollIndex, isAnimation)
console.log('handleRestoreScroll', scrollIndex, isAnimation) // console.log('handleRestoreScroll', scrollIndex, isAnimation)
} }
const onLoadedList = () => { const onLoadedList = () => {
console.log('restoreScroll', scrollIndex, isAnimation) // console.log('restoreScroll', scrollIndex, isAnimation)
restoreScroll(scrollIndex, isAnimation) restoreScroll(scrollIndex, isAnimation)
} }