重构同步服务端功能部分代码,使其更易扩展新功能

pull/1583/head
lyswhut 2023-08-25 19:13:50 +08:00
parent 70815e276e
commit 0d76c9ccb4
35 changed files with 1089 additions and 473 deletions

View File

@ -31,3 +31,4 @@
### 其他
- 更新 electron 到 v22.3.21
- 重构同步服务端功能部分代码,使其更易扩展新功能

View File

@ -140,5 +140,18 @@ declare namespace LX {
tempList: LX.Music.MusicInfo[]
}
type ActionList = SyncAction<'list_data_overwrite', LX.List.ListActionDataOverwrite>
| SyncAction<'list_create', LX.List.ListActionAdd>
| SyncAction<'list_remove', LX.List.ListActionRemove>
| SyncAction<'list_update', LX.List.ListActionUpdate>
| SyncAction<'list_update_position', LX.List.ListActionUpdatePosition>
| SyncAction<'list_music_add', LX.List.ListActionMusicAdd>
| SyncAction<'list_music_move', LX.List.ListActionMusicMove>
| SyncAction<'list_music_remove', LX.List.ListActionMusicRemove>
| SyncAction<'list_music_update', LX.List.ListActionMusicUpdate>
| SyncAction<'list_music_update_position', LX.List.ListActionMusicUpdatePosition>
| SyncAction<'list_music_overwrite', LX.List.ListActionMusicOverwrite>
| SyncAction<'list_music_clear', LX.List.ListActionMusicClear>
}
}

View File

@ -24,7 +24,7 @@ declare namespace LX {
| SyncAction<'client_status', ClientStatus>
| SyncAction<'server_status', ServerStatus>
type SyncServiceActions = SyncAction<'select_mode', Mode>
type SyncServiceActions = SyncAction<'select_mode', ListSyncMode>
| SyncAction<'get_server_status'>
| SyncAction<'get_client_status'>
| SyncAction<'generate_code'>
@ -90,14 +90,17 @@ declare namespace LX {
clientId: string
key: string
deviceName: string
lastSyncDate?: number
snapshotKey: string
lastConnectDate?: number
isMobile: boolean
}
interface ListInfo {
lastSyncDate?: number
snapshotKey: string
}
type ListData = Omit<LX.List.ListDataFull, 'tempList'>
type Mode = 'merge_local_remote'
type ListSyncMode = 'merge_local_remote'
| 'merge_remote_local'
| 'overwrite_local_remote'
| 'overwrite_remote_local'

View File

@ -1,26 +1,13 @@
declare namespace LX {
namespace Sync {
type ActionList = SyncAction<'list_data_overwrite', LX.List.ListActionDataOverwrite>
| SyncAction<'list_create', LX.List.ListActionAdd>
| SyncAction<'list_remove', LX.List.ListActionRemove>
| SyncAction<'list_update', LX.List.ListActionUpdate>
| SyncAction<'list_update_position', LX.List.ListActionUpdatePosition>
| SyncAction<'list_music_add', LX.List.ListActionMusicAdd>
| SyncAction<'list_music_move', LX.List.ListActionMusicMove>
| SyncAction<'list_music_remove', LX.List.ListActionMusicRemove>
| SyncAction<'list_music_update', LX.List.ListActionMusicUpdate>
| SyncAction<'list_music_update_position', LX.List.ListActionMusicUpdatePosition>
| SyncAction<'list_music_overwrite', LX.List.ListActionMusicOverwrite>
| SyncAction<'list_music_clear', LX.List.ListActionMusicClear>
type ServerActions = WarpPromiseRecord<{
onListSyncAction: (action: LX.Sync.ActionList) => void
onListSyncAction: (action: LX.List.ActionList) => void
}>
type ClientActions = WarpPromiseRecord<{
onListSyncAction: (action: LX.Sync.ActionList) => void
onListSyncAction: (action: LX.List.ActionList) => void
list_sync_get_md5: () => string
list_sync_get_sync_mode: () => Mode
list_sync_get_sync_mode: () => ListSyncMode
list_sync_get_list_data: () => ListData
list_sync_set_list_data: (data: ListData) => void
list_sync_finished: () => void

View File

@ -1,5 +1,5 @@
import { request, generateRsaKey } from './utils'
import { getSyncAuthKey, setSyncAuthKey } from '../data'
import { getSyncAuthKey, setSyncAuthKey } from './data'
import { SYNC_CODE } from '@common/constants'
import log from '../log'
import { aesDecrypt, aesEncrypt, getComputerName, rsaDecrypt } from '../utils'

View File

@ -0,0 +1,76 @@
import fs from 'node:fs'
import path from 'node:path'
import { File } from '../constants'
let syncAuthKeys: Record<string, LX.Sync.ClientKeyInfo>
const saveSyncAuthKeys = async() => {
const syncAuthKeysFilePath = path.join(global.lxDataPath, File.clientDataPath, File.syncAuthKeysJSON)
return fs.promises.writeFile(syncAuthKeysFilePath, JSON.stringify(syncAuthKeys), 'utf8')
}
const exists = async(path: string) => fs.promises.stat(path).then(() => true).catch(() => false)
export const initClientInfo = async() => {
if (syncAuthKeys != null) return
const syncAuthKeysFilePath = path.join(global.lxDataPath, File.clientDataPath, File.syncAuthKeysJSON)
if (await fs.promises.stat(syncAuthKeysFilePath).then(() => true).catch(() => false)) {
syncAuthKeys = JSON.parse((await fs.promises.readFile(syncAuthKeysFilePath)).toString())
} else {
syncAuthKeys = {}
const syncDataPath = path.join(global.lxDataPath, File.clientDataPath)
if (!await exists(syncDataPath)) {
await fs.promises.mkdir(syncDataPath, { recursive: true })
}
void saveSyncAuthKeys()
}
}
export const getSyncAuthKey = async(serverId: string) => {
await initClientInfo()
return syncAuthKeys[serverId] ?? null
}
export const setSyncAuthKey = async(serverId: string, info: LX.Sync.ClientKeyInfo) => {
await initClientInfo()
syncAuthKeys[serverId] = info
void saveSyncAuthKeys()
}
// let syncHost: string
// export const getSyncHost = async() => {
// if (syncHost === undefined) {
// const store = getStore(STORE_NAMES.SYNC)
// syncHost = (store.get('syncHost') as typeof syncHost | null) ?? ''
// }
// return syncHost
// }
// export const setSyncHost = async(host: string) => {
// // let hostInfo = await getData(syncHostPrefix) || {}
// // hostInfo.host = host
// // hostInfo.port = port
// syncHost = host
// const store = getStore(STORE_NAMES.SYNC)
// store.set('syncHost', syncHost)
// }
// let syncHostHistory: string[]
// export const getSyncHostHistory = async() => {
// if (syncHostHistory === undefined) {
// const store = getStore(STORE_NAMES.SYNC)
// syncHostHistory = (store.get('syncHostHistory') as string[]) ?? []
// }
// return syncHostHistory
// }
// export const addSyncHostHistory = async(host: string) => {
// let syncHostHistory = await getSyncHostHistory()
// if (syncHostHistory.some(h => h == host)) return
// syncHostHistory.unshift(host)
// if (syncHostHistory.length > 20) syncHostHistory = syncHostHistory.slice(0, 20) // 最多存储20个
// const store = getStore(STORE_NAMES.SYNC)
// store.set('syncHostHistory', syncHostHistory)
// }
// export const removeSyncHostHistory = async(index: number) => {
// syncHostHistory.splice(index, 1)
// const store = getStore(STORE_NAMES.SYNC)
// store.set('syncHostHistory', syncHostHistory)
// }

View File

@ -4,6 +4,7 @@ import { connect as socketConnect, disconnect as socketDisconnect, sendSyncStatu
import { SYNC_CODE } from '@common/constants'
import log from '../log'
import { parseUrl } from './utils'
import migrateData from '../migrate'
let connectId = 0
@ -29,6 +30,8 @@ const connectServer = async(host: string, authCode?: string) => {
message: SYNC_CODE.connecting,
})
const id = connectId
await migrateData(global.lxDataPath)
return handleConnect(host, authCode).catch(async err => {
if (id != connectId) return
sendSyncStatus({

View File

@ -21,7 +21,7 @@ export const list_sync_get_md5 = async(socket: LX.Sync.Client.Socket) => {
return toMD5(JSON.stringify(await getLocalListData()))
}
const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise<LX.Sync.Mode> => new Promise((resolve, reject) => {
const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise<LX.Sync.ListSyncMode> => new Promise((resolve, reject) => {
const handleDisconnect = (err: Error) => {
sendCloseSelectMode()
removeSelectModeListener()

View File

@ -0,0 +1,66 @@
export const ENV_PARAMS = [
'PORT',
'BIND_IP',
'CONFIG_PATH',
'LOG_PATH',
'DATA_PATH',
'PROXY_HEADER',
'MAX_SNAPSHOT_NUM',
'LIST_ADD_MUSIC_LOCATION_TYPE',
'LX_USER_',
] as const
export const LIST_IDS = {
DEFAULT: 'default',
LOVE: 'love',
TEMP: 'temp',
DOWNLOAD: 'download',
PLAY_LATER: null,
} as const
export const SYNC_CODE = {
helloMsg: 'Hello~::^-^::~v4~',
idPrefix: 'OjppZDo6',
authMsg: 'lx-music auth::',
msgAuthFailed: 'Auth failed',
msgBlockedIp: 'Blocked IP',
msgConnect: 'lx-music connect',
authFailed: 'Auth failed',
missingAuthCode: 'Missing auth code',
getServiceIdFailed: 'Get service id failed',
connectServiceFailed: 'Connect service failed',
connecting: 'Connecting...',
unknownServiceAddress: 'Unknown service address',
} as const
export const SYNC_CLOSE_CODE = {
normal: 1000,
failed: 4100,
} as const
export const TRANS_MODE: Readonly<Record<LX.Sync.ListSyncMode, LX.Sync.ListSyncMode>> = {
merge_local_remote: 'merge_remote_local',
merge_remote_local: 'merge_local_remote',
overwrite_local_remote: 'overwrite_remote_local',
overwrite_remote_local: 'overwrite_local_remote',
overwrite_local_remote_full: 'overwrite_remote_local_full',
overwrite_remote_local_full: 'overwrite_local_remote_full',
cancel: 'cancel',
} as const
export const File = {
serverDataPath: 'sync/server',
clientDataPath: 'sync/client',
serverInfoJSON: 'serverInfo.json',
userDir: 'users',
userDevicesJSON: 'devices.json',
listDir: 'list',
listSnapshotDir: 'snapshot',
listSnapshotInfoJSON: 'snapshotInfo.json',
syncAuthKeysJSON: 'syncAuthKey.json',
} as const

View File

@ -1,185 +0,0 @@
import { randomBytes } from 'node:crypto'
import { STORE_NAMES } from '@common/constants'
import getStore from '@main/utils/store'
import { throttle } from '@common/utils/common'
import path from 'node:path'
import fs from 'node:fs'
import log from './log'
export const getSyncAuthKey = async(serverId: string) => {
const store = getStore(STORE_NAMES.SYNC)
const keys = store.get('syncAuthKey') as Record<string, LX.Sync.ClientKeyInfo> | null
if (!keys) return null
return keys[serverId] ?? null
}
export const setSyncAuthKey = async(serverId: string, info: LX.Sync.ClientKeyInfo) => {
const store = getStore(STORE_NAMES.SYNC)
let keys: Record<string, LX.Sync.ClientKeyInfo> = (store.get('syncAuthKey') as Record<string, LX.Sync.ClientKeyInfo> | null) ?? {}
keys[serverId] = info
store.set('syncAuthKey', keys)
}
let syncHost: string
export const getSyncHost = async() => {
if (syncHost === undefined) {
const store = getStore(STORE_NAMES.SYNC)
syncHost = (store.get('syncHost') as typeof syncHost | null) ?? ''
}
return syncHost
}
export const setSyncHost = async(host: string) => {
// let hostInfo = await getData(syncHostPrefix) || {}
// hostInfo.host = host
// hostInfo.port = port
syncHost = host
const store = getStore(STORE_NAMES.SYNC)
store.set('syncHost', syncHost)
}
let syncHostHistory: string[]
export const getSyncHostHistory = async() => {
if (syncHostHistory === undefined) {
const store = getStore(STORE_NAMES.SYNC)
syncHostHistory = (store.get('syncHostHistory') as string[]) ?? []
}
return syncHostHistory
}
export const addSyncHostHistory = async(host: string) => {
let syncHostHistory = await getSyncHostHistory()
if (syncHostHistory.some(h => h == host)) return
syncHostHistory.unshift(host)
if (syncHostHistory.length > 20) syncHostHistory = syncHostHistory.slice(0, 20) // 最多存储20个
const store = getStore(STORE_NAMES.SYNC)
store.set('syncHostHistory', syncHostHistory)
}
export const removeSyncHostHistory = async(index: number) => {
syncHostHistory.splice(index, 1)
const store = getStore(STORE_NAMES.SYNC)
store.set('syncHostHistory', syncHostHistory)
}
export interface SnapshotInfo {
latest: string | null
time: number
list: string[]
}
interface DevicesInfo {
serverId: string
clients: Record<string, LX.Sync.ServerKeyInfo>
snapshotInfo: SnapshotInfo
}
// const devicesFilePath = path.join(global.lx.dataPath, 'devices.json')
const devicesInfo: DevicesInfo = { serverId: '', clients: {}, snapshotInfo: { latest: null, time: 0, list: [] } }
let deviceKeys: string[] = []
const saveDevicesInfoThrottle = throttle(() => {
const store = getStore(STORE_NAMES.SYNC)
store.set('clients', devicesInfo.clients)
})
const initDeviceInfo = () => {
const store = getStore(STORE_NAMES.SYNC)
const serverId = store.get('serverId') as string | undefined
if (serverId) devicesInfo.serverId = serverId
else {
devicesInfo.serverId = randomBytes(4 * 4).toString('base64')
const store = getStore(STORE_NAMES.SYNC)
store.set('serverId', devicesInfo.serverId)
}
const devices = store.get('clients') as DevicesInfo['clients'] | undefined
if (devices) devicesInfo.clients = devices
deviceKeys = Object.values(devicesInfo.clients).map(device => device.snapshotKey).filter(k => k)
const snapshotInfo = store.get('snapshotInfo') as DevicesInfo['snapshotInfo'] | undefined
if (snapshotInfo) devicesInfo.snapshotInfo = snapshotInfo
}
export const createClientKeyInfo = (deviceName: string, isMobile: boolean): LX.Sync.ServerKeyInfo => {
const keyInfo: LX.Sync.ServerKeyInfo = {
clientId: randomBytes(4 * 4).toString('base64'),
key: randomBytes(16).toString('base64'),
deviceName,
isMobile,
snapshotKey: '',
lastSyncDate: 0,
}
saveClientKeyInfo(keyInfo)
return keyInfo
}
export const saveClientKeyInfo = (keyInfo: LX.Sync.ServerKeyInfo) => {
if (devicesInfo.clients[keyInfo.clientId] == null && Object.keys(devicesInfo.clients).length > 101) throw new Error('max keys')
devicesInfo.clients[keyInfo.clientId] = keyInfo
saveDevicesInfoThrottle()
}
export const getClientKeyInfo = (clientId?: string): LX.Sync.ServerKeyInfo | null => {
if (!clientId) return null
if (!devicesInfo.serverId) initDeviceInfo()
return devicesInfo.clients[clientId] ?? null
}
export const getServerId = (): string => {
if (!devicesInfo.serverId) initDeviceInfo()
return devicesInfo.serverId
}
export const isIncluedsDevice = (name: string) => {
return deviceKeys.includes(name)
}
export const clearOldSnapshot = async() => {
if (!devicesInfo.snapshotInfo) return
const snapshotList = devicesInfo.snapshotInfo.list.filter(name => !isIncluedsDevice(name))
let requiredSave = snapshotList.length > global.lx.appSetting['sync.server.maxSsnapshotNum']
while (snapshotList.length > global.lx.appSetting['sync.server.maxSsnapshotNum']) {
const name = snapshotList.pop()
if (name) {
await removeSnapshot(name)
devicesInfo.snapshotInfo.list.splice(devicesInfo.snapshotInfo.list.indexOf(name), 1)
} else break
}
if (requiredSave) saveSnapshotInfo(devicesInfo.snapshotInfo)
}
export const updateDeviceSnapshotKey = (keyInfo: LX.Sync.ServerKeyInfo, key: string) => {
if (keyInfo.snapshotKey) deviceKeys.splice(deviceKeys.indexOf(keyInfo.snapshotKey), 1)
keyInfo.snapshotKey = key
keyInfo.lastSyncDate = Date.now()
saveClientKeyInfo(keyInfo)
deviceKeys.push(key)
saveDevicesInfoThrottle()
void clearOldSnapshot()
}
const saveSnapshotInfoThrottle = throttle(() => {
const store = getStore(STORE_NAMES.SYNC)
store.set('snapshotInfo', devicesInfo.snapshotInfo)
})
export const getSnapshotInfo = (): SnapshotInfo => {
return devicesInfo.snapshotInfo
}
export const saveSnapshotInfo = (info: SnapshotInfo) => {
devicesInfo.snapshotInfo = info
saveSnapshotInfoThrottle()
}
export const getSnapshot = async(name: string) => {
console.log('getSnapshot', name)
const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
let listData: LX.Sync.ListData
try {
listData = JSON.parse((await fs.promises.readFile(filePath)).toString('utf-8'))
} catch (err) {
log.warn(err)
return null
}
return listData
}
export const saveSnapshot = async(name: string, data: string) => {
console.log('saveSnapshot', name)
const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
return fs.promises.writeFile(filePath, data).catch((err) => {
log.error(err)
throw err
})
}
export const removeSnapshot = async(name: string) => {
console.log('removeSnapshot', name)
const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
return fs.promises.unlink(filePath).catch((err) => {
log.error(err)
})
}

View File

@ -0,0 +1,77 @@
import { File } from './constants'
import fs from 'node:fs'
import path from 'node:path'
interface ServerKeyInfo {
clientId: string
key: string
deviceName: string
lastSyncDate?: number
snapshotKey?: string
lastConnectDate?: number
isMobile: boolean
}
const exists = async(path: string) => fs.promises.stat(path).then(() => true).catch(() => false)
// 迁移 v2 sync 数据
export default async(dataPath: string) => {
const syncDataPath = path.join(dataPath, 'sync')
// console.log(syncDataPath)
if (await exists(syncDataPath)) return
const oldInfoPath = path.join(dataPath, 'sync.json')
// console.log(oldInfoPath)
if (!await exists(oldInfoPath)) return
const serverSyncDataPath = path.join(dataPath, File.serverDataPath)
const clientSyncDataPath = path.join(dataPath, File.clientDataPath)
await fs.promises.mkdir(serverSyncDataPath, { recursive: true })
await fs.promises.mkdir(clientSyncDataPath, { recursive: true })
const info = JSON.parse((await fs.promises.readFile(oldInfoPath)).toString())
const serverInfoPath = path.join(serverSyncDataPath, File.serverInfoJSON)
const devicesInfoPath = path.join(serverSyncDataPath, File.userDevicesJSON)
const listDir = path.join(serverSyncDataPath, File.listDir)
await fs.promises.mkdir(listDir)
const snapshotInfo = info.snapshotInfo
delete info.snapshotInfo
snapshotInfo.clients = {}
for (const device of Object.values<ServerKeyInfo>(info.clients)) {
snapshotInfo.clients[device.clientId] = {
snapshotKey: device.snapshotKey,
lastSyncDate: device.lastSyncDate,
}
device.lastConnectDate = device.lastSyncDate
delete device.lastSyncDate
delete device.snapshotKey
}
const devicesInfo = {
userName: 'default',
clients: info.clients,
}
await fs.promises.writeFile(serverInfoPath, JSON.stringify({ serverId: info.serverId, version: 2 }))
await fs.promises.writeFile(devicesInfoPath, JSON.stringify(devicesInfo))
await fs.promises.writeFile(path.join(listDir, File.listSnapshotInfoJSON), JSON.stringify(snapshotInfo))
const snapshotPath = path.join(listDir, File.listSnapshotDir)
await fs.promises.mkdir(snapshotPath)
const snapshots = (await fs.promises.readdir(dataPath)).filter(name => name.startsWith('snapshot_'))
if (snapshots.length) {
for (const file of snapshots) {
await fs.promises.copyFile(path.join(dataPath, file), path.join(snapshotPath, file))
}
}
await fs.promises.writeFile(path.join(clientSyncDataPath, File.syncAuthKeysJSON), JSON.stringify(info.syncAuthKey))
for (const file of snapshots) {
await fs.promises.unlink(path.join(dataPath, file))
}
await fs.promises.unlink(oldInfoPath)
}

View File

@ -1,4 +1,3 @@
import * as modules from './modules'
import {
startServer,
stopServer,
@ -12,5 +11,4 @@ export {
stopServer,
getStatus,
generateCode,
modules,
}

View File

@ -1,10 +1,12 @@
import * as list from './list'
// export * as theme from './theme'
export const callObj = Object.assign({}, list.handler)
import { sync } from './list'
export const callObj = Object.assign({},
sync.handler,
)
export const modules = {
list,
list: sync,
}
export { ListManage } from './list'

View File

@ -1,86 +0,0 @@
// import { throttle } from '@common/utils/common'
// import { sendSyncActionList } from '@main/modules/winMain'
// import { SYNC_CLOSE_CODE } from '@common/constants'
import { updateDeviceSnapshotKey } from '@main/modules/sync/data'
import { handleRemoteListAction } from '../../../utils'
import { createSnapshot } from '../../utils'
// let wss: LX.Sync.Server.SocketServer | null
// let removeListener: (() => void) | null
// type listAction = 'list:action'
// const addMusic = (orderId, callback) => {
// // ...
// }
// const broadcast = async(key: string, data: any, excludeIds: string[] = []) => {
// if (!wss) return
// const dataStr = JSON.stringify({ action: 'list:sync:action', data })
// const clients = Array.from(wss.clients).filter(socket => !excludeIds.includes(socket.keyInfo.clientId) && socket.isReady)
// if (!clients.length) return
// const enData = await encryptMsg(null, dataStr)
// for (const socket of clients) {
// if (excludeIds.includes(socket.keyInfo.clientId) || !socket.isReady) continue
// socket.send(enData, (err) => {
// if (err) {
// socket.close(SYNC_CLOSE_CODE.failed)
// return
// }
// updateDeviceSnapshotKey(socket.keyInfo, key)
// })
// }
// }
// const sendListAction = async(action: LX.Sync.ActionList) => {
// console.log('sendListAction', action.action)
// // io.sockets
// await broadcast(await getCurrentListInfoKey(), action)
// }
// export const registerListHandler = (_wss: LX.Sync.Server.SocketServer, socket: LX.Sync.Server.Socket) => {
// if (!wss) {
// wss = _wss
// removeListener = registerListActionEvent(sendListAction)
// }
// socket.onRemoteEvent('list:sync:action', (action) => {
// if (!socket.isReady) return
// // console.log(msg)
// void handleRemoteListAction(action).then(updated => {
// if (!updated) return
// void createSnapshot().then(key => {
// if (!key) return
// updateDeviceSnapshotKey(socket.keyInfo, key)
// void broadcast(key, action, [socket.keyInfo.clientId])
// })
// })
// // socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } })
// })
// // socket.on('list:add', addMusic)
// }
// export const unregisterListHandler = () => {
// wss = null
// if (removeListener) {
// removeListener()
// removeListener = null
// }
// }
export const onListSyncAction = async(socket: LX.Sync.Server.Socket, action: LX.Sync.ActionList) => {
await handleRemoteListAction(action).then(updated => {
if (!updated) return
console.log(updated)
void createSnapshot().then(key => {
if (!key) return
updateDeviceSnapshotKey(socket.keyInfo, key)
const currentId = socket.keyInfo.clientId
socket.broadcast((client) => {
if (client.keyInfo.clientId == currentId || !client.isReady) return
void client.remoteSyncList.onListSyncAction(action)
})
})
})
}

View File

@ -1,4 +1,3 @@
export * as handler from './handler'
export { default as sync } from './sync'
export * as sync from './sync'
export { ListManage } from './manage'
export * from './localEvent'

View File

@ -0,0 +1,51 @@
import { type UserDataManage } from '../../user'
import { SnapshotDataManage } from './snapshotDataManage'
import { toMD5 } from '../../utils'
import { getLocalListData } from '@main/modules/sync/utils'
export class ListManage {
snapshotDataManage: SnapshotDataManage
constructor(userDataManage: UserDataManage) {
this.snapshotDataManage = new SnapshotDataManage(userDataManage)
}
createSnapshot = async() => {
const listData = JSON.stringify(await this.getListData())
const md5 = toMD5(listData)
const snapshotInfo = await this.snapshotDataManage.getSnapshotInfo()
console.log(md5, snapshotInfo.latest)
if (snapshotInfo.latest == md5) return md5
if (snapshotInfo.list.includes(md5)) {
snapshotInfo.list.splice(snapshotInfo.list.indexOf(md5), 1)
} else await this.snapshotDataManage.saveSnapshot(md5, listData)
if (snapshotInfo.latest) snapshotInfo.list.unshift(snapshotInfo.latest)
snapshotInfo.latest = md5
snapshotInfo.time = Date.now()
this.snapshotDataManage.saveSnapshotInfo(snapshotInfo)
return md5
}
getCurrentListInfoKey = async() => {
const snapshotInfo = await this.snapshotDataManage.getSnapshotInfo()
if (snapshotInfo.latest) {
return snapshotInfo.latest
}
snapshotInfo.latest = toMD5(JSON.stringify(await this.getListData()))
this.snapshotDataManage.saveSnapshotInfo(snapshotInfo)
return snapshotInfo.latest
}
getDeviceCurrentSnapshotKey = async(clientId: string) => {
return this.snapshotDataManage.getDeviceCurrentSnapshotKey(clientId)
}
updateDeviceSnapshotKey = async(clientId: string, key: string) => {
await this.snapshotDataManage.updateDeviceSnapshotKey(clientId, key)
}
getListData = async(): Promise<LX.Sync.ListData> => {
return getLocalListData()
}
}

View File

@ -0,0 +1,134 @@
import { throttle } from '@common/utils/common'
import fs from 'node:fs'
import path from 'node:path'
import syncLog from '../../../log'
import { getUserConfig, type UserDataManage } from '../../user/data'
import { File } from '../../../constants'
import { checkAndCreateDirSync } from '../../utils'
interface SnapshotInfo {
latest: string | null
time: number
list: string[]
clients: Record<string, LX.Sync.ListInfo>
}
export class SnapshotDataManage {
userDataManage: UserDataManage
listDir: string
snapshotDir: string
snapshotInfoFilePath: string
snapshotInfo: SnapshotInfo
clientSnapshotKeys: string[]
private readonly saveSnapshotInfoThrottle: () => void
isIncluedsDevice = (key: string) => {
return this.clientSnapshotKeys.includes(key)
}
clearOldSnapshot = async() => {
if (!this.snapshotInfo) return
const snapshotList = this.snapshotInfo.list.filter(key => !this.isIncluedsDevice(key))
// console.log(snapshotList.length, lx.config.maxSnapshotNum)
const userMaxSnapshotNum = getUserConfig(this.userDataManage.userName).maxSnapshotNum
let requiredSave = snapshotList.length > userMaxSnapshotNum
while (snapshotList.length > userMaxSnapshotNum) {
const name = snapshotList.pop()
if (name) {
await this.removeSnapshot(name)
this.snapshotInfo.list.splice(this.snapshotInfo.list.indexOf(name), 1)
} else break
}
if (requiredSave) this.saveSnapshotInfo(this.snapshotInfo)
}
updateDeviceSnapshotKey = async(clientId: string, key: string) => {
// console.log('updateDeviceSnapshotKey', key)
let client = this.snapshotInfo.clients[clientId]
if (!client) client = this.snapshotInfo.clients[clientId] = { snapshotKey: '', lastSyncDate: 0 }
if (client.snapshotKey) this.clientSnapshotKeys.splice(this.clientSnapshotKeys.indexOf(client.snapshotKey), 1)
client.snapshotKey = key
client.lastSyncDate = Date.now()
this.clientSnapshotKeys.push(key)
this.saveSnapshotInfoThrottle()
}
getDeviceCurrentSnapshotKey = async(clientId: string) => {
// console.log('updateDeviceSnapshotKey', key)
const client = this.snapshotInfo.clients[clientId]
return client?.snapshotKey
}
getSnapshotInfo = async(): Promise<SnapshotInfo> => {
return this.snapshotInfo
}
saveSnapshotInfo = (info: SnapshotInfo) => {
this.snapshotInfo = info
this.saveSnapshotInfoThrottle()
}
getSnapshot = async(name: string) => {
const filePath = path.join(this.snapshotDir, `snapshot_${name}`)
let listData: LX.Sync.ListData
try {
listData = JSON.parse((await fs.promises.readFile(filePath)).toString('utf-8'))
} catch (err) {
syncLog.warn(err)
return null
}
return listData
}
saveSnapshot = async(name: string, data: string) => {
syncLog.info('saveSnapshot', this.userDataManage.userName, name)
const filePath = path.join(this.snapshotDir, `snapshot_${name}`)
try {
await fs.promises.writeFile(filePath, data)
} catch (err) {
syncLog.error(err)
throw err
}
}
removeSnapshot = async(name: string) => {
syncLog.info('removeSnapshot', this.userDataManage.userName, name)
const filePath = path.join(this.snapshotDir, `snapshot_${name}`)
try {
await fs.promises.unlink(filePath)
} catch (err) {
syncLog.error(err)
}
}
constructor(userDataManage: UserDataManage) {
this.userDataManage = userDataManage
this.listDir = path.join(userDataManage.userDir, File.listDir)
checkAndCreateDirSync(this.listDir)
this.snapshotDir = path.join(this.listDir, File.listSnapshotDir)
checkAndCreateDirSync(this.snapshotDir)
this.snapshotInfoFilePath = path.join(this.listDir, File.listSnapshotInfoJSON)
this.snapshotInfo = fs.existsSync(this.snapshotInfoFilePath)
? JSON.parse(fs.readFileSync(this.snapshotInfoFilePath).toString())
: { latest: null, time: 0, list: [], clients: {} }
this.saveSnapshotInfoThrottle = throttle(() => {
fs.writeFile(this.snapshotInfoFilePath, JSON.stringify(this.snapshotInfo), 'utf8', (err) => {
if (err) console.error(err)
void this.clearOldSnapshot()
})
})
this.clientSnapshotKeys = Object.values(this.snapshotInfo.clients).map(device => device.snapshotKey).filter(k => k)
}
}
// type UserDataManages = Map<string, UserDataManage>
// export const createUserDataManage = (user: LX.UserConfig) => {
// const manage = Object.create(userDataManage) as typeof userDataManage
// manage.userDir = user.dataPath
// }

View File

@ -0,0 +1,163 @@
// import { throttle } from '@common/utils/common'
// import { sendSyncActionList } from '@main/modules/winMain'
// import { SYNC_CLOSE_CODE } from '@/constants'
import { SYNC_CLOSE_CODE } from '@main/modules/sync/constants'
import { getUserSpace } from '@main/modules/sync/server/user'
import { handleRemoteListAction } from '@main/modules/sync/utils'
// import { encryptMsg } from '@/utils/tools'
// let wss: LX.SocketServer | null
// let removeListener: (() => void) | null
// type listAction = 'list:action'
// const registerListActionEvent = () => {
// const list_data_overwrite = async(listData: MakeOptional<LX.List.ListDataFull, 'tempList'>, isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_data_overwrite', data: listData })
// }
// const list_create = async(position: number, listInfos: LX.List.UserListInfo[], isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_create', data: { position, listInfos } })
// }
// const list_remove = async(ids: string[], isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_remove', data: ids })
// }
// const list_update = async(lists: LX.List.UserListInfo[], isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_update', data: lists })
// }
// const list_update_position = async(position: number, ids: string[], isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_update_position', data: { position, ids } })
// }
// const list_music_overwrite = async(listId: string, musicInfos: LX.Music.MusicInfo[], isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_music_overwrite', data: { listId, musicInfos } })
// }
// const list_music_add = async(id: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType, isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_music_add', data: { id, musicInfos, addMusicLocationType } })
// }
// const list_music_move = async(fromId: string, toId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType, isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_music_move', data: { fromId, toId, musicInfos, addMusicLocationType } })
// }
// const list_music_remove = async(listId: string, ids: string[], isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_music_remove', data: { listId, ids } })
// }
// const list_music_update = async(musicInfos: LX.List.ListActionMusicUpdate, isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_music_update', data: musicInfos })
// }
// const list_music_clear = async(ids: string[], isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_music_clear', data: ids })
// }
// const list_music_update_position = async(listId: string, position: number, ids: string[], isRemote: boolean = false) => {
// if (isRemote) return
// await sendListAction({ action: 'list_music_update_position', data: { listId, position, ids } })
// }
// global.event_list.on('list_data_overwrite', list_data_overwrite)
// global.event_list.on('list_create', list_create)
// global.event_list.on('list_remove', list_remove)
// global.event_list.on('list_update', list_update)
// global.event_list.on('list_update_position', list_update_position)
// global.event_list.on('list_music_overwrite', list_music_overwrite)
// global.event_list.on('list_music_add', list_music_add)
// global.event_list.on('list_music_move', list_music_move)
// global.event_list.on('list_music_remove', list_music_remove)
// global.event_list.on('list_music_update', list_music_update)
// global.event_list.on('list_music_clear', list_music_clear)
// global.event_list.on('list_music_update_position', list_music_update_position)
// return () => {
// global.event_list.off('list_data_overwrite', list_data_overwrite)
// global.event_list.off('list_create', list_create)
// global.event_list.off('list_remove', list_remove)
// global.event_list.off('list_update', list_update)
// global.event_list.off('list_update_position', list_update_position)
// global.event_list.off('list_music_overwrite', list_music_overwrite)
// global.event_list.off('list_music_add', list_music_add)
// global.event_list.off('list_music_move', list_music_move)
// global.event_list.off('list_music_remove', list_music_remove)
// global.event_list.off('list_music_update', list_music_update)
// global.event_list.off('list_music_clear', list_music_clear)
// global.event_list.off('list_music_update_position', list_music_update_position)
// }
// }
// const addMusic = (orderId, callback) => {
// // ...
// }
// const broadcast = async(socket: LX.Socket, key: string, data: any, excludeIds: string[] = []) => {
// if (!wss) return
// const dataStr = JSON.stringify({ action: 'list:sync:action', data })
// const userSpace = getUserSpace(socket.userInfo.name)
// for (const client of wss.clients) {
// if (excludeIds.includes(client.keyInfo.clientId) || !client.isReady || client.userInfo.name != socket.userInfo.name) continue
// client.send(encryptMsg(client.keyInfo, dataStr), (err) => {
// if (err) {
// client.close(SYNC_CLOSE_CODE.failed)
// return
// }
// userSpace.dataManage.updateDeviceSnapshotKey(client.keyInfo, key)
// })
// }
// }
// export const sendListAction = async(action: LX.Sync.ActionList) => {
// console.log('sendListAction', action.action)
// // io.sockets
// await broadcast('list:sync:action', action)
// }
// export const registerListHandler = (_wss: LX.SocketServer, socket: LX.Socket) => {
// if (!wss) {
// wss = _wss
// // removeListener = registerListActionEvent()
// }
// const userSpace = getUserSpace(socket.userInfo.name)
// socket.onRemoteEvent('list:sync:action', (action) => {
// if (!socket.isReady) return
// // console.log(msg)
// void handleListAction(socket.userInfo.name, action).then(key => {
// if (!key) return
// console.log(key)
// userSpace.dataManage.updateDeviceSnapshotKey(socket.keyInfo, key)
// void broadcast(socket, key, action, [socket.keyInfo.clientId])
// })
// // socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } })
// })
// // socket.on('list:add', addMusic)
// }
// export const unregisterListHandler = () => {
// wss = null
// // if (removeListener) {
// // removeListener()
// // removeListener = null
// // }
// }
export const onListSyncAction = async(socket: LX.Sync.Server.Socket, action: LX.Sync.ActionList) => {
const userSpace = getUserSpace(socket.userInfo.name)
await handleRemoteListAction(action).then(async updated => {
if (!updated) {
socket.close(SYNC_CLOSE_CODE.failed)
return
}
const key = await userSpace.listManage.createSnapshot()
userSpace.listManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, key)
const currentUserName = socket.userInfo.name
const currentId = socket.keyInfo.clientId
socket.broadcast((client) => {
if (client.keyInfo.clientId == currentId || !client.isReady || client.userInfo.name != currentUserName) return
void client.remoteSyncList.onListSyncAction(action)
})
})
}

View File

@ -0,0 +1,3 @@
export * as handler from './handler'
export { default as sync } from './sync'
export * from './localEvent'

View File

@ -1,6 +1,5 @@
import { updateDeviceSnapshotKey } from '@main/modules/sync/data'
import { registerListActionEvent } from '../../../utils'
import { getCurrentListInfoKey } from '../../utils'
import { registerListActionEvent } from '../../../../utils'
import { getUserSpace } from '../../../user'
// let socket: LX.Sync.Server.Socket | null
let unregisterLocalListAction: (() => void) | null
@ -8,11 +7,12 @@ let unregisterLocalListAction: (() => void) | null
const sendListAction = async(wss: LX.Sync.Server.SocketServer, action: LX.Sync.ActionList) => {
// console.log('sendListAction', action.action)
const key = await getCurrentListInfoKey()
const userSpace = getUserSpace()
const key = await userSpace.listManage.createSnapshot()
for (const client of wss.clients) {
if (!client.isReady) return
void client.remoteSyncList.onListSyncAction(action).then(() => {
updateDeviceSnapshotKey(client.keyInfo, key)
void userSpace.listManage.updateDeviceSnapshotKey(client.keyInfo.clientId, key)
})
}
}

View File

@ -1,14 +1,7 @@
import { SYNC_CLOSE_CODE } from '@common/constants'
import { SYNC_CLOSE_CODE } from '../../../../constants'
import { getUserSpace, getUserConfig } from '../../../user'
import { getLocalListData, setLocalListData } from '@main/modules/sync/utils'
import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
import { createSnapshot, getCurrentListInfoKey } from '../../utils'
import { getSnapshot, updateDeviceSnapshotKey } from '@main/modules/sync/data'
const handleSetLocalListData = async(listData: LX.Sync.ListData) => {
await setLocalListData(listData)
return createSnapshot()
}
// import { LIST_IDS } from '@common/constants'
// type ListInfoType = LX.List.UserListInfoFull | LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull
@ -33,35 +26,32 @@ const getRemoteListMD5 = async(socket: LX.Sync.Server.Socket): Promise<string> =
return socket.remoteSyncList.list_sync_get_md5()
}
const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.Mode> => new Promise((resolve, reject) => {
const handleDisconnect = (err: Error) => {
sendCloseSelectMode()
removeSelectModeListener()
reject(err)
}
let removeEventClose = socket.onClose(handleDisconnect)
sendSelectMode(socket.keyInfo.deviceName, (mode) => {
if (mode == null) {
reject(new Error('cancel'))
return
}
resolve(mode)
removeSelectModeListener()
removeEventClose()
})
})
// const getLocalListData = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.ListData> => {
// return getUserSpace(socket.userInfo.name).listManage.getListData()
// }
const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.ListSyncMode> => {
return socket.remoteSyncList.list_sync_get_sync_mode()
}
const finishedSync = async(socket: LX.Sync.Server.Socket) => {
await socket.remoteSyncList.list_sync_finished()
}
const setLocalList = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.ListData) => {
await setLocalListData(listData)
const userSpace = getUserSpace(socket.userInfo.name)
return userSpace.listManage.createSnapshot()
}
const overwriteRemoteListData = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.ListData, key: string, excludeIds: string[] = []) => {
const action = { action: 'list_data_overwrite', data: listData } as const
const tasks: Array<Promise<void>> = []
socket.broadcast((client) => {
if (excludeIds.includes(client.keyInfo.clientId) || !client.isReady) return
tasks.push(client.remoteSyncList.onListSyncAction(action).then(() => {
updateDeviceSnapshotKey(socket.keyInfo, key)
if (excludeIds.includes(client.keyInfo.clientId) || client.userInfo.name != socket.userInfo.name || !client.isReady) return
tasks.push(client.remoteSyncList.onListSyncAction(action).then(async() => {
const userSpace = getUserSpace(socket.userInfo.name)
return userSpace.listManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, key)
}).catch(err => {
console.log(err.message)
}))
@ -71,7 +61,8 @@ const overwriteRemoteListData = async(socket: LX.Sync.Server.Socket, listData: L
}
const setRemotelList = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.ListData, key: string): Promise<void> => {
await socket.remoteSyncList.list_sync_set_list_data(listData)
updateDeviceSnapshotKey(socket.keyInfo, key)
const userSpace = getUserSpace(socket.userInfo.name)
await userSpace.listManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, key)
}
type UserDataObj = Map<string, LX.List.UserListInfoFull>
@ -120,8 +111,8 @@ const handleMergeList = (
}
return ids.map(id => map.get(id)) as LX.Music.MusicInfo[]
}
const mergeList = (sourceListData: LX.Sync.ListData, targetListData: LX.Sync.ListData): LX.Sync.ListData => {
const addMusicLocationType = global.lx.appSetting['list.addMusicLocationType']
const mergeList = (socket: LX.Sync.Server.Socket, sourceListData: LX.Sync.ListData, targetListData: LX.Sync.ListData): LX.Sync.ListData => {
const addMusicLocationType = getUserConfig(socket.userInfo.name)['list.addMusicLocationType']
const newListData: LX.Sync.ListData = {
defaultList: [],
loveList: [],
@ -181,7 +172,7 @@ const overwriteList = (sourceListData: LX.Sync.ListData, targetListData: LX.Sync
}
const handleMergeListData = async(socket: LX.Sync.Server.Socket): Promise<[LX.Sync.ListData, boolean, boolean]> => {
const mode: LX.Sync.Mode = await getSyncMode(socket)
const mode: LX.Sync.ListSyncMode = await getSyncMode(socket)
if (mode == 'cancel') {
socket.close(SYNC_CLOSE_CODE.normal)
@ -194,10 +185,10 @@ const handleMergeListData = async(socket: LX.Sync.Server.Socket): Promise<[LX.Sy
let requiredUpdateRemoteListData = true
switch (mode) {
case 'merge_local_remote':
listData = mergeList(localListData, remoteListData)
listData = mergeList(socket, localListData, remoteListData)
break
case 'merge_remote_local':
listData = mergeList(remoteListData, localListData)
listData = mergeList(socket, remoteListData, localListData)
break
case 'overwrite_local_remote':
listData = overwriteList(localListData, remoteListData)
@ -225,31 +216,35 @@ const handleMergeListData = async(socket: LX.Sync.Server.Socket): Promise<[LX.Sy
const handleSyncList = async(socket: LX.Sync.Server.Socket) => {
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
console.log('handleSyncList', 'remoteListData, localListData')
console.log('localListData', localListData.defaultList.length || localListData.loveList.length || localListData.userList.length)
console.log('remoteListData', remoteListData.defaultList.length || remoteListData.loveList.length || remoteListData.userList.length)
const userSpace = getUserSpace(socket.userInfo.name)
const clientId = socket.keyInfo.clientId
if (localListData.defaultList.length || localListData.loveList.length || localListData.userList.length) {
if (remoteListData.defaultList.length || remoteListData.loveList.length || remoteListData.userList.length) {
const [mergedList, requiredUpdateLocalListData, requiredUpdateRemoteListData] = await handleMergeListData(socket)
console.log('handleMergeListData', 'mergedList')
console.log('handleMergeListData', 'mergedList', requiredUpdateLocalListData, requiredUpdateRemoteListData)
let key
if (requiredUpdateLocalListData) {
key = await handleSetLocalListData(mergedList)
await overwriteRemoteListData(socket, mergedList, key, [socket.keyInfo.clientId])
if (!requiredUpdateRemoteListData) updateDeviceSnapshotKey(socket.keyInfo, key)
key = await setLocalList(socket, mergedList)
await overwriteRemoteListData(socket, mergedList, key, [clientId])
if (!requiredUpdateRemoteListData) await userSpace.listManage.updateDeviceSnapshotKey(clientId, key)
}
if (requiredUpdateRemoteListData) {
if (!key) key = await getCurrentListInfoKey()
if (!key) key = await userSpace.listManage.getCurrentListInfoKey()
await setRemotelList(socket, mergedList, key)
}
} else {
await setRemotelList(socket, localListData, await getCurrentListInfoKey())
await setRemotelList(socket, localListData, await userSpace.listManage.getCurrentListInfoKey())
}
} else {
let key: string
if (remoteListData.defaultList.length || remoteListData.loveList.length || remoteListData.userList.length) {
key = await handleSetLocalListData(remoteListData)
await overwriteRemoteListData(socket, remoteListData, key, [socket.keyInfo.clientId])
key = await setLocalList(socket, remoteListData)
await overwriteRemoteListData(socket, remoteListData, key, [clientId])
}
key ??= await getCurrentListInfoKey()
updateDeviceSnapshotKey(socket.keyInfo, key)
key ??= await userSpace.listManage.getCurrentListInfoKey()
await userSpace.listManage.updateDeviceSnapshotKey(clientId, key)
}
}
@ -297,15 +292,17 @@ const mergeListDataFromSnapshot = (
}
const checkListLatest = async(socket: LX.Sync.Server.Socket) => {
const remoteListMD5 = await getRemoteListMD5(socket)
const currentListInfoKey = await getCurrentListInfoKey()
const userSpace = getUserSpace(socket.userInfo.name)
const userCurrentListInfoKey = await userSpace.listManage.getDeviceCurrentSnapshotKey(socket.keyInfo.clientId)
const currentListInfoKey = await userSpace.listManage.getCurrentListInfoKey()
const latest = remoteListMD5 == currentListInfoKey
if (latest && socket.keyInfo.snapshotKey != currentListInfoKey) updateDeviceSnapshotKey(socket.keyInfo, currentListInfoKey)
if (latest && userCurrentListInfoKey != currentListInfoKey) await userSpace.listManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, currentListInfoKey)
return latest
}
const handleMergeListDataFromSnapshot = async(socket: LX.Sync.Server.Socket, snapshot: LX.Sync.ListData) => {
if (await checkListLatest(socket)) return
const addMusicLocationType = global.lx.appSetting['list.addMusicLocationType']
const addMusicLocationType = getUserConfig(socket.userInfo.name)['list.addMusicLocationType']
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
const newListData: LX.Sync.ListData = {
defaultList: [],
@ -361,17 +358,21 @@ const handleMergeListDataFromSnapshot = async(socket: LX.Sync.Server.Socket, sna
})
newListData.userList = newUserList
const key = await handleSetLocalListData(newListData)
await setRemotelList(socket, newListData, key)
const key = await setLocalList(socket, newListData)
const err = await setRemotelList(socket, newListData, key).catch(err => err)
await overwriteRemoteListData(socket, newListData, key, [socket.keyInfo.clientId])
if (err) throw err
}
const syncList = async(socket: LX.Sync.Server.Socket) => {
// socket.data.snapshotFilePath = getSnapshotFilePath(socket.keyInfo)
if (socket.keyInfo.snapshotKey) {
const listData = await getSnapshot(socket.keyInfo.snapshotKey)
// console.log(socket.keyInfo)
const user = getUserSpace(socket.userInfo.name)
const userCurrentListInfoKey = await user.listManage.getDeviceCurrentSnapshotKey(socket.keyInfo.clientId)
if (userCurrentListInfoKey) {
const listData = await user.listManage.snapshotDataManage.getSnapshot(userCurrentListInfoKey)
if (listData) {
console.log('handleMergeListDataFromSnapshot')
await handleMergeListDataFromSnapshot(socket, listData)
return
}
@ -379,7 +380,6 @@ const syncList = async(socket: LX.Sync.Server.Socket) => {
await handleSyncList(socket)
}
// export default async(_wss: LX.Sync.Server.SocketServer, socket: LX.Sync.Server.Socket) => {
// if (!wss) {
// wss = _wss
@ -402,7 +402,6 @@ const syncList = async(socket: LX.Sync.Server.Socket) => {
// syncingId = socket.keyInfo.clientId
// await syncList(socket).then(async() => {
// // if (newListData) registerUpdateSnapshotTask(socket, { ...newListData })
// return finishedSync(socket)
// }).finally(() => {
// syncingId = null

View File

@ -1,21 +1,26 @@
import type http from 'http'
import { SYNC_CODE } from '@common/constants'
import {
aesEncrypt,
aesDecrypt,
rsaEncrypt,
getIP,
} from '../utils/tools'
import querystring from 'node:querystring'
import { getIP } from './utils'
import { createClientKeyInfo, getClientKeyInfo, saveClientKeyInfo } from '../data'
import { aesDecrypt, aesEncrypt, getComputerName, rsaEncrypt } from '../utils'
import { toMD5 } from '@common/utils/nodejs'
import { getUserSpace, createClientKeyInfo } from '../user'
import { toMD5 } from '../utils'
import { getComputerName } from '../../utils'
const requestIps = new Map<string, number>()
const getAvailableIP = (req: http.IncomingMessage) => {
let ip = getIP(req)
return ip && (requestIps.get(ip) ?? 0) < 10 ? ip : null
}
const verifyByKey = (encryptMsg: string, userId: string) => {
const keyInfo = getClientKeyInfo(userId)
const userSpace = getUserSpace()
const keyInfo = userSpace.dataManage.getClientKeyInfo(userId)
if (!keyInfo) return null
let text
try {
@ -28,7 +33,7 @@ const verifyByKey = (encryptMsg: string, userId: string) => {
const deviceName = text.replace(SYNC_CODE.authMsg, '') || 'Unknown'
if (deviceName != keyInfo.deviceName) {
keyInfo.deviceName = deviceName
saveClientKeyInfo(keyInfo)
userSpace.dataManage.saveClientKeyInfo(keyInfo)
}
return aesEncrypt(SYNC_CODE.helloMsg, keyInfo.key)
}
@ -43,7 +48,7 @@ const verifyByCode = (encryptMsg: string, password: string) => {
let text
try {
text = aesDecrypt(encryptMsg, key)
} catch (err) {
} catch {
return null
}
// console.log(text)
@ -53,6 +58,8 @@ const verifyByCode = (encryptMsg: string, password: string) => {
const deviceName = data[2] || 'Unknown'
const isMobile = data[3] == 'lx_music_mobile'
const keyInfo = createClientKeyInfo(deviceName, isMobile)
const userSpace = getUserSpace()
userSpace.dataManage.saveClientKeyInfo(keyInfo)
return rsaEncrypt(Buffer.from(JSON.stringify({
clientId: keyInfo.clientId,
key: keyInfo.key,
@ -66,7 +73,6 @@ export const authCode = async(req: http.IncomingMessage, res: http.ServerRespons
let code = 401
let msg: string = SYNC_CODE.msgAuthFailed
// console.log(req.headers)
let ip = getAvailableIP(req)
if (ip) {
if (typeof req.headers.m == 'string' && req.headers.m) {
@ -89,13 +95,15 @@ export const authCode = async(req: http.IncomingMessage, res: http.ServerRespons
code = 403
msg = SYNC_CODE.msgBlockedIp
}
// console.log(req.headers)
res.writeHead(code)
res.end(msg)
}
const verifyConnection = (encryptMsg: string, userId: string) => {
const keyInfo = getClientKeyInfo(userId)
const userSpace = getUserSpace()
const keyInfo = userSpace.dataManage.getClientKeyInfo(userId)
if (!keyInfo) return false
let text
try {

View File

@ -0,0 +1,14 @@
import {
startServer,
stopServer,
getStatus,
generateCode,
} from './server'
export {
startServer,
stopServer,
getStatus,
generateCode,
}

View File

@ -1,15 +1,16 @@
import http, { type IncomingMessage } from 'node:http'
import url from 'node:url'
import { WebSocketServer } from 'ws'
import { modules, callObj } from './modules'
import { modules, callObj } from '../modules'
import { authCode, authConnect } from './auth'
import log from '../log'
import { SYNC_CLOSE_CODE, SYNC_CODE } from '@common/constants'
import { decryptMsg, encryptMsg, generateCode as handleGenerateCode } from './utils'
import { getAddress } from '../utils'
import { sendServerStatus } from '@main/modules/winMain/index'
import { getClientKeyInfo, getServerId, saveClientKeyInfo } from '../data'
import { getAddress } from '../../utils'
import { SYNC_CLOSE_CODE, SYNC_CODE } from '../../constants'
import { getUserSpace, releaseUserSpace, getServerId, initServerInfo } from '../user'
import { createMsg2call } from 'message2call'
import log from '../../log'
import { sendServerStatus } from '@main/modules/winMain'
import { decryptMsg, encryptMsg, generateCode as handleGenerateCode } from '../utils/tools'
import migrateData from '../../migrate'
let status: LX.Sync.ServerStatus = {
@ -19,6 +20,7 @@ let status: LX.Sync.ServerStatus = {
code: '',
devices: [],
}
let stopingServer = false
const codeTools: {
@ -51,6 +53,7 @@ const syncData = async(socket: LX.Sync.Server.Socket) => {
}
}
const registerLocalSyncEvent = async(wss: LX.Sync.Server.SocketServer) => {
for (const module of Object.values(modules)) {
module.registerEvent(wss)
@ -63,10 +66,11 @@ const unregisterLocalSyncEvent = () => {
}
}
const checkDuplicateClient = (newSocket: LX.Sync.Server.Socket) => {
for (const client of [...wss!.clients]) {
if (client === newSocket || client.keyInfo.clientId != newSocket.keyInfo.clientId) continue
console.log('duplicate client', client.keyInfo.deviceName)
log.info('duplicate client', client.userInfo.name, client.keyInfo.deviceName)
client.isReady = false
client.close(SYNC_CLOSE_CODE.normal)
}
@ -76,15 +80,18 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM
const queryData = url.parse(request.url as string, true).query as Record<string, string>
// // if (typeof socket.handshake.query.i != 'string') return socket.disconnect(true)
const keyInfo = getClientKeyInfo(queryData.i)
const userSpace = getUserSpace()
const keyInfo = userSpace.dataManage.getClientKeyInfo(queryData.i)
if (!keyInfo) {
socket.close(SYNC_CLOSE_CODE.failed)
return
}
keyInfo.lastSyncDate = Date.now()
saveClientKeyInfo(keyInfo)
keyInfo.lastConnectDate = Date.now()
userSpace.dataManage.saveClientKeyInfo(keyInfo)
// // socket.lx_keyInfo = keyInfo
socket.keyInfo = keyInfo
socket.userInfo = { name: 'default' }
checkDuplicateClient(socket)
try {
@ -95,13 +102,12 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM
return
}
status.devices.push(keyInfo)
socket.onClose(() => {
// console.log('disconnect', reason)
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo?.clientId), 1)
sendServerStatus(status)
})
// handleConnection(io, socket)
sendServerStatus(status)
socket.onClose(() => {
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo.clientId), 1)
sendServerStatus(status)
})
// console.log('connection', keyInfo.deviceName)
log.info('connection', keyInfo.deviceName)
@ -111,8 +117,8 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM
}
const handleUnconnection = () => {
console.log('unconnection')
// console.log(socket.handshake.query)
// console.log('unconnection')
releaseUserSpace()
}
const authConnection = (req: http.IncomingMessage, callback: (err: string | null | undefined, success: boolean) => void) => {
@ -229,7 +235,7 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
closeEvents = []
msg2call.onDestroy()
if (socket.isReady) {
log.info('deconnection', socket.keyInfo.deviceName)
log.info('deconnection', socket.userInfo.name, socket.keyInfo.deviceName)
// events = {}
if (!status.devices.length) handleUnconnection()
} else {
@ -247,14 +253,16 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
if (!wss) return
for (const client of wss.clients) handler(client)
}
void handleConnection(socket, request)
})
httpServer.on('upgrade', function upgrade(request, socket, head) {
socket.on('error', onSocketError)
socket.addListener('error', onSocketError)
// This function is not defined on purpose. Implement it with your own logic.
authConnection(request, err => {
if (err) {
console.log(err)
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n')
socket.destroy()
return
@ -270,6 +278,7 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
const interval = setInterval(() => {
wss?.clients.forEach(socket => {
if (socket.isAlive == false) {
log.info('alive check false:', socket.userInfo.name, socket.keyInfo.deviceName)
socket.terminate()
return
}
@ -321,7 +330,6 @@ const handleStopServer = async() => new Promise<void>((resolve, reject) => {
})
export const stopServer = async() => {
console.log('stop')
codeTools.stop()
if (!status.status) {
status.status = false
@ -345,16 +353,20 @@ export const stopServer = async() => {
console.log(err)
status.message = err.message
}).finally(() => {
stopingServer = false
sendServerStatus(status)
stopingServer = false
})
}
export const startServer = async(port: number) => {
console.log('status.status', status.status)
// if (status.status) await handleStopServer()
console.log('status.status', status.status, stopingServer)
if (stopingServer) return
if (status.status) await handleStopServer()
await migrateData(global.lxDataPath)
await initServerInfo()
log.info('starting sync server')
await handleStartServer(port).then(() => {
console.log('sync server started')

View File

@ -0,0 +1,142 @@
import fs from 'node:fs'
import path from 'node:path'
import { randomBytes } from 'node:crypto'
import { throttle } from '@common/utils/common'
import { filterFileName, toMD5 } from '../utils'
import { File } from '../../constants'
interface ServerInfo {
serverId: string
version: number
}
interface DevicesInfo {
userName: string
clients: Record<string, LX.Sync.ServerKeyInfo>
}
const saveServerInfoThrottle = throttle(() => {
fs.writeFile(path.join(global.lxDataPath, File.serverDataPath, File.serverInfoJSON), JSON.stringify(serverInfo), (err) => {
if (err) console.error(err)
})
})
let serverInfo: ServerInfo
const exists = async(path: string) => fs.promises.stat(path).then(() => true).catch(() => false)
export const initServerInfo = async() => {
if (serverInfo != null) return
const serverInfoFilePath = path.join(global.lxDataPath, File.serverDataPath, File.serverInfoJSON)
if (await exists(serverInfoFilePath)) {
serverInfo = JSON.parse((await fs.promises.readFile(serverInfoFilePath)).toString())
} else {
serverInfo = {
serverId: randomBytes(4 * 4).toString('base64'),
version: 2,
}
const syncDataPath = path.join(global.lxDataPath, File.serverDataPath)
if (!await exists(syncDataPath)) {
await fs.promises.mkdir(syncDataPath, { recursive: true })
}
saveServerInfoThrottle()
}
}
export const getServerId = () => {
return serverInfo.serverId
}
export const getVersion = async() => {
await initServerInfo()
return serverInfo.version ?? 1
}
export const setVersion = async(version: number) => {
await initServerInfo()
serverInfo.version = version
saveServerInfoThrottle()
}
export const getUserDirname = (userName: string) => `${filterFileName(userName)}_${toMD5(userName).substring(0, 6)}`
export const getUserConfig = (userName: string) => {
return {
maxSnapshotNum: global.lx.appSetting['sync.server.maxSsnapshotNum'],
'list.addMusicLocationType': global.lx.appSetting['list.addMusicLocationType'],
}
}
// 读取所有用户目录下的devicesInfo信息建立clientId与用户的对应关系用于非首次连接
// let deviceUserMap: Map<string, string> = new Map<string, string>()
// const init
// for (const deviceInfo of fs.readdirSync(syncDataPath).map(dirname => {
// const devicesFilePath = path.join(syncDataPath, dirname, File.userDevicesJSON)
// if (fs.existsSync(devicesFilePath)) {
// const devicesInfo = JSON.parse(fs.readFileSync(devicesFilePath).toString()) as DevicesInfo
// if (getUserDirname(devicesInfo.userName) == dirname) return { userName: devicesInfo.userName, devices: devicesInfo.clients }
// }
// return { userName: '', devices: {} }
// })) {
// for (const device of Object.values(deviceInfo.devices)) {
// if (deviceInfo.userName) deviceUserMap.set(device.clientId, deviceInfo.userName)
// }
// }
// export const getUserName = (clientId: string): string | null => {
// if (!clientId) return null
// return deviceUserMap.get(clientId) ?? null
// }
// export const setUserName = (clientId: string, dir: string) => {
// deviceUserMap.set(clientId, dir)
// }
// export const deleteUserName = (clientId: string) => {
// deviceUserMap.delete(clientId)
// }
export const createClientKeyInfo = (deviceName: string, isMobile: boolean): LX.Sync.ServerKeyInfo => {
const keyInfo: LX.Sync.ServerKeyInfo = {
clientId: randomBytes(4 * 4).toString('base64'),
key: randomBytes(16).toString('base64'),
deviceName,
isMobile,
lastConnectDate: 0,
}
return keyInfo
}
export class UserDataManage {
userName: string
userDir: string
devicesFilePath: string
devicesInfo: DevicesInfo
private readonly saveDevicesInfoThrottle: () => void
saveClientKeyInfo = (keyInfo: LX.Sync.ServerKeyInfo) => {
if (this.devicesInfo.clients[keyInfo.clientId] == null && Object.keys(this.devicesInfo.clients).length > 101) throw new Error('max keys')
this.devicesInfo.clients[keyInfo.clientId] = keyInfo
this.saveDevicesInfoThrottle()
}
getClientKeyInfo = (clientId?: string): LX.Sync.ServerKeyInfo | null => {
if (!clientId) return null
return this.devicesInfo.clients[clientId] ?? null
}
isIncluedsClient = (clientId: string) => {
return Object.values(this.devicesInfo.clients).some(client => client.clientId == clientId)
}
constructor(userName: string) {
this.userName = userName
const syncDataPath = path.join(global.lxDataPath, File.serverDataPath)
this.userDir = syncDataPath
this.devicesFilePath = path.join(this.userDir, File.userDevicesJSON)
this.devicesInfo = fs.existsSync(this.devicesFilePath) ? JSON.parse(fs.readFileSync(this.devicesFilePath).toString()) : { userName, clients: {} }
this.saveDevicesInfoThrottle = throttle(() => {
fs.writeFile(this.devicesFilePath, JSON.stringify(this.devicesInfo), 'utf8', (err) => {
if (err) console.error(err)
})
})
}
}
// type UserDataManages = Map<string, UserDataManage>
// export const createUserDataManage = (user: LX.UserConfig) => {
// const manage = Object.create(userDataManage) as typeof userDataManage
// manage.userDir = user.dataPath
// }

View File

@ -0,0 +1,50 @@
import { UserDataManage } from './data'
import {
ListManage,
} from '../modules'
export interface UserSpace {
dataManage: UserDataManage
listManage: ListManage
}
const users = new Map<string, UserSpace>()
const delayTime = 10 * 1000
const delayReleaseTimeouts = new Map<string, NodeJS.Timeout>()
const clearDelayReleaseTimeout = (userName: string) => {
if (!delayReleaseTimeouts.has(userName)) return
clearTimeout(delayReleaseTimeouts.get(userName))
delayReleaseTimeouts.delete(userName)
}
const seartDelayReleaseTimeout = (userName: string) => {
clearDelayReleaseTimeout(userName)
delayReleaseTimeouts.set(userName, setTimeout(() => {
users.delete(userName)
}, delayTime))
}
export const getUserSpace = (userName = 'default') => {
clearDelayReleaseTimeout(userName)
let user = users.get(userName)
if (!user) {
console.log('new user data manage:', userName)
const dataManage = new UserDataManage(userName)
users.set(userName, user = {
dataManage,
listManage: new ListManage(dataManage),
})
}
return user
}
export const releaseUserSpace = (userName = 'default', force = false) => {
if (force) {
clearDelayReleaseTimeout(userName)
users.delete(userName)
} else seartDelayReleaseTimeout(userName)
}
export * from './data'

View File

@ -1,61 +0,0 @@
import { toMD5 } from '@common/utils/nodejs'
import type http from 'node:http'
import {
getSnapshotInfo,
saveSnapshot,
saveSnapshotInfo,
type SnapshotInfo,
} from '../data'
import { decodeData, encodeData, getLocalListData } from '../utils'
export const generateCode = (): string => {
return Math.random().toString().substring(2, 8)
}
export const getIP = (request: http.IncomingMessage) => {
return request.socket.remoteAddress
}
export const encryptMsg = async(keyInfo: LX.Sync.ServerKeyInfo | null, msg: string): Promise<string> => {
return encodeData(msg)
// console.log('enmsg raw: ', msg.length, 'en: ', len.length)
// return len
// if (!keyInfo) return ''
// return aesEncrypt(msg, keyInfo.key, keyInfo.iv)
}
export const decryptMsg = async(keyInfo: LX.Sync.ServerKeyInfo | null, enMsg: string): Promise<string> => {
return decodeData(enMsg)
// console.log('decmsg raw: ', len.length, 'en: ', enMsg.length)
// return len
// if (!keyInfo) return ''
// let msg = ''
// try {
// msg = aesDecrypt(enMsg, keyInfo.key, keyInfo.iv)
// } catch (err) {
// console.log(err)
// }
// return msg
}
let snapshotInfo: SnapshotInfo
export const createSnapshot = async() => {
if (!snapshotInfo) snapshotInfo = getSnapshotInfo()
const listData = JSON.stringify(await getLocalListData())
const md5 = toMD5(listData)
if (snapshotInfo.latest == md5) return md5
if (snapshotInfo.list.includes(md5)) {
snapshotInfo.list.splice(snapshotInfo.list.indexOf(md5), 1)
} else await saveSnapshot(md5, listData)
if (snapshotInfo.latest) snapshotInfo.list.unshift(snapshotInfo.latest)
snapshotInfo.latest = md5
snapshotInfo.time = Date.now()
saveSnapshotInfo(snapshotInfo)
return md5
}
export const getCurrentListInfoKey = async() => {
// if (!snapshotInfo) snapshotInfo = getSnapshotInfo()
return createSnapshot()
}

View File

@ -0,0 +1,31 @@
import fs from 'node:fs'
import crypto from 'node:crypto'
export const createDirSync = (path: string) => {
if (!fs.existsSync(path)) {
try {
fs.mkdirSync(path, { recursive: true })
} catch (e: any) {
if (e.code !== 'EEXIST') {
console.error('Could not set up log directory, error was: ', e)
process.exit(1)
}
}
}
}
const fileNameRxp = /[\\/:*?#"<>|]/g
export const filterFileName = (name: string): string => name.replace(fileNameRxp, '')
/**
* MD5 hash
* @param {*} str
*/
export const toMD5 = (str: string) => crypto.createHash('md5').update(str).digest('hex')
export const checkAndCreateDirSync = (path: string) => {
if (!fs.existsSync(path)) {
fs.mkdirSync(path, { recursive: true })
}
}

View File

@ -0,0 +1,105 @@
import { networkInterfaces } from 'node:os'
import { createCipheriv, createDecipheriv, publicEncrypt, privateDecrypt, constants } from 'node:crypto'
// import { join } from 'node:path'
import zlib from 'node:zlib'
import type http from 'node:http'
// import getStore from '@/utils/store'
// import syncLog from '../../log'
// import { getUserName } from '../user/data'
// import { saveClientKeyInfo } from './data'
export const getAddress = (): string[] => {
const nets = networkInterfaces()
const results: string[] = []
// console.log(nets)
for (const interfaceInfos of Object.values(nets)) {
if (!interfaceInfos) continue
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
for (const interfaceInfo of interfaceInfos) {
if (interfaceInfo.family === 'IPv4' && !interfaceInfo.internal) {
results.push(interfaceInfo.address)
}
}
}
return results
}
export const generateCode = (): string => {
return Math.random().toString().substring(2, 8)
}
export const getIP = (request: http.IncomingMessage) => {
return request.socket.remoteAddress
}
export const aesEncrypt = (buffer: string | Buffer, key: string): string => {
const cipher = createCipheriv('aes-128-ecb', Buffer.from(key, 'base64'), '')
return Buffer.concat([cipher.update(buffer), cipher.final()]).toString('base64')
}
export const aesDecrypt = (text: string, key: string): string => {
const decipher = createDecipheriv('aes-128-ecb', Buffer.from(key, 'base64'), '')
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
}
export const rsaEncrypt = (buffer: Buffer, key: string): string => {
return publicEncrypt({ key, padding: constants.RSA_PKCS1_OAEP_PADDING }, buffer).toString('base64')
}
export const rsaDecrypt = (buffer: Buffer, key: string): Buffer => {
return privateDecrypt({ key, padding: constants.RSA_PKCS1_OAEP_PADDING }, buffer)
}
const gzip = async(data: string) => new Promise<string>((resolve, reject) => {
zlib.gzip(data, (err, buf) => {
if (err) {
reject(err)
return
}
resolve(buf.toString('base64'))
})
})
const unGzip = async(data: string) => new Promise<string>((resolve, reject) => {
zlib.gunzip(Buffer.from(data, 'base64'), (err, buf) => {
if (err) {
reject(err)
return
}
resolve(buf.toString())
})
})
export const encryptMsg = async(keyInfo: LX.Sync.ServerKeyInfo | null, msg: string): Promise<string> => {
return msg.length > 1024
? 'cg_' + await gzip(msg)
: msg
// if (!keyInfo) return ''
// return aesEncrypt(msg, keyInfo.key, keyInfo.iv)
}
export const decryptMsg = async(keyInfo: LX.Sync.ServerKeyInfo | null, enMsg: string): Promise<string> => {
return enMsg.substring(0, 3) == 'cg_'
? await unGzip(enMsg.replace('cg_', ''))
: enMsg
// console.log('decmsg raw: ', len.length, 'en: ', enMsg.length)
// if (!keyInfo) return ''
// let msg = ''
// try {
// msg = aesDecrypt(enMsg, keyInfo.key, keyInfo.iv)
// } catch (err) {
// console.log(err)
// }
// return msg
}
// export const getSnapshotFilePath = (keyInfo: LX.Sync.KeyInfo): string => {
// return join(global.lx.snapshotPath, `snapshot_${keyInfo.snapshotKey}.json`)
// }
// export const sendStatus = (status: LX.Sync.ServerStatus) => {
// syncLog.info('status', status.devices.map(d => `${getUserName(d.clientId) ?? ''} ${d.deviceName}`))
// }

View File

@ -193,40 +193,40 @@ export const handleRemoteListAction = async({ action, data }: LX.Sync.ActionList
switch (action) {
case 'list_data_overwrite':
void global.lx.event_list.list_data_overwrite(data, true)
await global.lx.event_list.list_data_overwrite(data, true)
break
case 'list_create':
void global.lx.event_list.list_create(data.position, data.listInfos, true)
await global.lx.event_list.list_create(data.position, data.listInfos, true)
break
case 'list_remove':
void global.lx.event_list.list_remove(data, true)
await global.lx.event_list.list_remove(data, true)
break
case 'list_update':
void global.lx.event_list.list_update(data, true)
await global.lx.event_list.list_update(data, true)
break
case 'list_update_position':
void global.lx.event_list.list_update_position(data.position, data.ids, true)
await global.lx.event_list.list_update_position(data.position, data.ids, true)
break
case 'list_music_add':
void global.lx.event_list.list_music_add(data.id, data.musicInfos, data.addMusicLocationType, true)
await global.lx.event_list.list_music_add(data.id, data.musicInfos, data.addMusicLocationType, true)
break
case 'list_music_move':
void global.lx.event_list.list_music_move(data.fromId, data.toId, data.musicInfos, data.addMusicLocationType, true)
await global.lx.event_list.list_music_move(data.fromId, data.toId, data.musicInfos, data.addMusicLocationType, true)
break
case 'list_music_remove':
void global.lx.event_list.list_music_remove(data.listId, data.ids, true)
await global.lx.event_list.list_music_remove(data.listId, data.ids, true)
break
case 'list_music_update':
void global.lx.event_list.list_music_update(data, true)
await global.lx.event_list.list_music_update(data, true)
break
case 'list_music_update_position':
void global.lx.event_list.list_music_update_position(data.listId, data.position, data.ids, true)
await global.lx.event_list.list_music_update_position(data.listId, data.position, data.ids, true)
break
case 'list_music_overwrite':
void global.lx.event_list.list_music_overwrite(data.listId, data.musicInfos, true)
await global.lx.event_list.list_music_overwrite(data.listId, data.musicInfos, true)
break
case 'list_music_clear':
void global.lx.event_list.list_music_clear(data, true)
await global.lx.event_list.list_music_clear(data, true)
break
default:
return false

View File

@ -3,7 +3,7 @@ import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
import { startServer, stopServer, getServerStatus, generateCode, connectServer, disconnectServer, getClientStatus } from '@main/modules/sync'
import { sendEvent } from '../main'
let selectModeListenr: ((mode: LX.Sync.Mode | null) => void) | null = null
let selectModeListenr: ((mode: LX.Sync.ListSyncMode | null) => void) | null = null
export default () => {
mainHandle<LX.Sync.SyncServiceActions, any>(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, async({ params: data }) => {
@ -46,7 +46,7 @@ export const sendServerStatus = (status: LX.Sync.ServerStatus) => {
data: status,
})
}
export const sendSelectMode = (deviceName: string, listener: (mode: LX.Sync.Mode | null) => void) => {
export const sendSelectMode = (deviceName: string, listener: (mode: LX.Sync.ListSyncMode | null) => void) => {
selectModeListenr = listener
sendSyncAction({ action: 'select_mode', data: deviceName })
}

View File

@ -29,6 +29,7 @@ declare global {
interface Socket extends WS.WebSocket {
isAlive?: boolean
isReady: boolean
userInfo: { name: 'default' }
keyInfo: ServerKeyInfo
onClose: (handler: (err: Error) => (void | Promise<void>)) => () => void
broadcast: (handler: (client: Socket) => void) => void

View File

@ -44,6 +44,8 @@ export default {
host: appSetting['sync.client.host'],
authCode: code,
},
}).catch(err => {
console.log(err)
})
}
return {

View File

@ -46,6 +46,8 @@ export default () => {
enable: appSetting['sync.enable'],
port: appSetting['sync.server.port'],
},
}).catch(err => {
console.log(err)
})
}
break
@ -57,6 +59,8 @@ export default () => {
enable: appSetting['sync.enable'],
host: appSetting['sync.client.host'],
},
}).catch(err => {
console.log(err)
})
}
break

View File

@ -56,6 +56,8 @@ export default () => {
enable: appSetting['sync.enable'],
port: appSetting['sync.server.port'],
},
}).catch(err => {
console.log(err)
})
}
break
@ -67,6 +69,8 @@ export default () => {
enable: appSetting['sync.enable'],
host: appSetting['sync.client.host'],
},
}).catch(err => {
console.log(err)
})
}
break