This commit is contained in:
lyswhut
2022-10-29 11:36:35 +08:00
parent 66b965d9f7
commit e45cee6596
715 changed files with 57096 additions and 31771 deletions
-15
View File
@@ -1,15 +0,0 @@
const Event = require('./event/event')
const eventNames = require('./event/name')
const modules = require('./modules')
const { startServer, stopServer, getStatus, generateCode } = require('./server/server')
module.exports = {
startServer,
stopServer,
getStatus,
generateCode,
Event,
eventNames,
modules,
}
+21
View File
@@ -0,0 +1,21 @@
// import Event from './event/event'
// import eventNames from './event/name'
import * as modules from './modules'
import { startServer, stopServer, getStatus, generateCode } from './server/server'
export {
startServer,
stopServer,
getStatus,
generateCode,
// Event,
// eventNames,
modules,
}
export default () => {
global.lx.event_app.on('main_window_close', () => {
void stopServer()
})
}
-1
View File
@@ -1 +0,0 @@
exports.list = require('./list')
+1
View File
@@ -0,0 +1 @@
export * as list from './list'
-41
View File
@@ -1,41 +0,0 @@
const { encryptMsg, decryptMsg } = require('../server/utils')
let io
const handleListAction = ({ action, data }) => {
// console.log(action, data)
global.lx_event.sync.action_list({ action, data })
}
// const addMusic = (orderId, callback) => {
// // ...
// }
const broadcast = async(action, data, excludeIds = []) => {
if (!io) return
const sockets = await io.fetchSockets()
for (const socket of sockets) {
if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
socket.emit(action, encryptMsg(socket.data.keyInfo, data))
}
}
exports.sendListAction = (action, data) => {
// io.sockets
return broadcast('list:action', JSON.stringify({ action, data }))
}
exports.registerListHandler = (_io, socket) => {
io = _io
socket.on('list:action', msg => {
// console.log(msg)
msg = decryptMsg(socket.data.keyInfo, msg)
if (!msg) return
handleListAction(JSON.parse(msg))
broadcast('list:action', msg, [socket.data.keyInfo.clientId])
// socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } })
})
// socket.on('list:add', addMusic)
}
exports.unregisterListHandler = () => {
io = null
}
+172
View File
@@ -0,0 +1,172 @@
// import { throttle } from '@common/utils/common'
import { Server } from 'socket.io'
// import { sendSyncActionList } from '@main/modules/winMain'
import { encryptMsg, decryptMsg } from '../server/utils'
let io: Server | null
let removeListener: (() => void) | null
type listAction = 'list:action'
const handleListAction = ({ action, data }: LX.Sync.ActionList) => {
// console.log(action, data)
switch (action) {
case 'list_data_overwrite':
void 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)
break
case 'list_remove':
void global.lx.event_list.list_remove(data, true)
break
case 'list_update':
void 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)
break
case 'list_music_add':
void 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)
break
case 'list_music_remove':
void 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)
break
case 'list_music_update_position':
void 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)
break
case 'list_music_clear':
void global.lx.event_list.list_music_clear(data, true)
break
default:
break
}
}
const registerListActionEvent = () => {
const list_data_overwrite = async(listData: MakeOptional<LX.List.ListDataFull, 'tempList'>, isRemote: boolean = false) => {
if (isRemote) return
await sendListAction('list_data_overwrite', listData)
}
const list_create = async(position: number, lists: LX.List.UserListInfo[], isRemote: boolean = false) => {
if (isRemote) return
await sendListAction('list_create', { position, lists })
}
const list_remove = async(ids: string[], isRemote: boolean = false) => {
if (isRemote) return
await sendListAction('list_remove', ids)
}
const list_update = async(lists: LX.List.UserListInfo[], isRemote: boolean = false) => {
if (isRemote) return
await sendListAction('list_update', lists)
}
const list_update_position = async(position: number, ids: string[], isRemote: boolean = false) => {
if (isRemote) return
await sendListAction('list_update_position', { position, ids })
}
const list_music_overwrite = async(listId: string, musicInfos: LX.Music.MusicInfo[], isRemote: boolean = false) => {
if (isRemote) return
await sendListAction('list_music_overwrite', { listId, musicInfos })
}
const list_music_add = async(listId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType, isRemote: boolean = false) => {
if (isRemote) return
await sendListAction('list_music_add', { listId, 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('list_music_move', { fromId, toId, musicInfos, addMusicLocationType })
}
const list_music_remove = async(listId: string, ids: string[], isRemote: boolean = false) => {
if (isRemote) return
await sendListAction('list_music_remove', { listId, ids })
}
const list_music_update = async(musicInfos: LX.List.ListActionMusicUpdate, isRemote: boolean = false) => {
if (isRemote) return
await sendListAction('list_music_update', musicInfos)
}
const list_music_clear = async(listId: string, isRemote: boolean = false) => {
if (isRemote) return
await sendListAction('list_music_clear', listId)
}
const list_music_update_position = async(listId: string, position: number, ids: string[], isRemote: boolean = false) => {
if (isRemote) return
await sendListAction('list_music_update_position', { listId, position, ids })
}
global.lx.event_list.on('list_data_overwrite', list_data_overwrite)
global.lx.event_list.on('list_create', list_create)
global.lx.event_list.on('list_remove', list_remove)
global.lx.event_list.on('list_update', list_update)
global.lx.event_list.on('list_update_position', list_update_position)
global.lx.event_list.on('list_music_overwrite', list_music_overwrite)
global.lx.event_list.on('list_music_add', list_music_add)
global.lx.event_list.on('list_music_move', list_music_move)
global.lx.event_list.on('list_music_remove', list_music_remove)
global.lx.event_list.on('list_music_update', list_music_update)
global.lx.event_list.on('list_music_clear', list_music_clear)
global.lx.event_list.on('list_music_update_position', list_music_update_position)
return () => {
global.lx.event_list.off('list_data_overwrite', list_data_overwrite)
global.lx.event_list.off('list_create', list_create)
global.lx.event_list.off('list_remove', list_remove)
global.lx.event_list.off('list_update', list_update)
global.lx.event_list.off('list_update_position', list_update_position)
global.lx.event_list.off('list_music_overwrite', list_music_overwrite)
global.lx.event_list.off('list_music_add', list_music_add)
global.lx.event_list.off('list_music_move', list_music_move)
global.lx.event_list.off('list_music_remove', list_music_remove)
global.lx.event_list.off('list_music_update', list_music_update)
global.lx.event_list.off('list_music_clear', list_music_clear)
global.lx.event_list.off('list_music_update_position', list_music_update_position)
}
}
// const addMusic = (orderId, callback) => {
// // ...
// }
const broadcast = async(action: listAction, data: any, excludeIds: string[] = []) => {
if (!io) return
const sockets: LX.Sync.RemoteSocket[] = await io.fetchSockets()
for (const socket of sockets) {
if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
socket.emit(action, encryptMsg(socket.data.keyInfo, data))
}
}
export const sendListAction = async(action: string, data: any) => {
// io.sockets
return await broadcast('list:action', JSON.stringify({ action, data }))
}
export const registerListHandler = (_io: Server, socket: LX.Sync.Socket) => {
io = _io
socket.on('list:action', msg => {
// console.log(msg)
msg = decryptMsg(socket.data.keyInfo, msg)
if (!msg) return
handleListAction(JSON.parse(msg))
void broadcast('list:action', msg, [socket.data.keyInfo.clientId])
// socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } })
})
removeListener = registerListActionEvent()
// socket.on('list:add', addMusic)
}
export const unregisterListHandler = () => {
io = null
if (removeListener) {
removeListener()
removeListener = null
}
}
-69
View File
@@ -1,69 +0,0 @@
const { aesEncrypt, aesDecrypt, createClientKeyInfo, getClientKeyInfo, setClientKeyInfo } = require('./utils')
const authMsg = 'lx-music auth::'
const helloMsg = 'Hello~::^-^::'
exports.authCode = async(req, res, authCode) => {
let code = 401
let msg = 'Forbidden'
// console.log(req.headers)
if (req.headers.m) {
label:
if (req.headers.i) {
const keyInfo = getClientKeyInfo(req.headers.i)
if (!keyInfo) break label
let text
try {
text = aesDecrypt(req.headers.m, keyInfo.key, keyInfo.iv)
} catch (err) {
break label
}
console.log(text)
if (text.startsWith(authMsg)) {
code = 200
const deviceName = text.replace(authMsg, '') || 'Unknown'
if (deviceName != keyInfo.deviceName) {
keyInfo.deviceName = deviceName
setClientKeyInfo(keyInfo)
}
msg = aesEncrypt(helloMsg, keyInfo.key, keyInfo.iv)
}
} else {
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
key = Buffer.from(key).toString('base64')
// console.log(authCode, key, iv)
let text
try {
text = aesDecrypt(req.headers.m, key, iv)
} catch (err) {
break label
}
console.log(text)
if (text.startsWith(authMsg)) {
code = 200
const deviceName = text.replace(authMsg, '') || 'Unknown'
msg = aesEncrypt(JSON.stringify(createClientKeyInfo(deviceName)), key, iv)
}
}
}
res.writeHead(code)
res.end(msg)
}
exports.authConnect = async req => {
const { i, t } = req._query
label:
if (i && t) {
const keyInfo = getClientKeyInfo(i)
if (!keyInfo) break label
let text
try {
text = aesDecrypt(t, keyInfo.key, keyInfo.iv)
} catch (err) {
break label
}
if (text == 'lx-music connect') return
}
throw new Error('failed')
}
+92
View File
@@ -0,0 +1,92 @@
import http from 'http'
import {
aesEncrypt,
aesDecrypt,
createClientKeyInfo,
getClientKeyInfo,
setClientKeyInfo,
} from './utils'
const authMsg = 'lx-music auth::'
const helloMsg = 'Hello~::^-^::~v2'
const requestIps = new Map<string, number>()
export const authCode = async(req: http.IncomingMessage, res: http.ServerResponse, authCode: string) => {
let code = 401
let msg = 'Forbidden'
let ip = req.socket.remoteAddress
// console.log(req.headers)
if (typeof req.headers.m == 'string' && ip && (requestIps.get(ip) ?? 0) > 10) {
if (req.headers.m) {
label:
if (req.headers.i) {
if (typeof req.headers.i != 'string') break label
const keyInfo = getClientKeyInfo(req.headers.i)
if (!keyInfo) break label
let text
try {
text = aesDecrypt(req.headers.m, keyInfo.key, keyInfo.iv)
} catch (err) {
break label
}
console.log(text)
if (text.startsWith(authMsg)) {
code = 200
const deviceName = text.replace(authMsg, '') || 'Unknown'
if (deviceName != keyInfo.deviceName) {
keyInfo.deviceName = deviceName
setClientKeyInfo(keyInfo)
}
msg = aesEncrypt(helloMsg, keyInfo.key, keyInfo.iv)
}
} else {
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
key = Buffer.from(key).toString('base64')
// console.log(authCode, key, iv)
let text
try {
text = aesDecrypt(req.headers.m, key, iv)
} catch (err) {
break label
}
console.log(text)
if (text.startsWith(authMsg)) {
code = 200
const deviceName = text.replace(authMsg, '') || 'Unknown'
msg = aesEncrypt(JSON.stringify(createClientKeyInfo(deviceName)), key, iv)
}
}
}
}
res.writeHead(code)
res.end(msg)
if (ip && code != 200) {
const num = requestIps.get(ip) ?? 0
if (num > 20) return
requestIps.set(ip, num + 1)
}
}
export const authConnect = async(req: http.IncomingMessage) => {
const url = new URL(req.url as string)
const i = url.searchParams.get('i')
const t = url.searchParams.get('t')
label:
if (i && t) {
const keyInfo = getClientKeyInfo(i)
if (!keyInfo) break label
let text
try {
text = aesDecrypt(t, keyInfo.key, keyInfo.iv)
} catch (err) {
break label
}
if (text == 'lx-music connect') return
}
throw new Error('failed')
}
-7
View File
@@ -1,7 +0,0 @@
const { startServer, stopServer, getStatus } = require('./server')
module.exports = {
startServer,
stopServer,
getStatus,
}
+7
View File
@@ -0,0 +1,7 @@
import { startServer, stopServer, getStatus } from './server'
export {
startServer,
stopServer,
getStatus,
}
@@ -1,14 +1,15 @@
const http = require('http')
const sio = require('socket.io')
const { createHttpTerminator } = require('http-terminator')
const modules = require('../modules')
const { authCode, authConnect } = require('./auth')
const { getAddress, getServerId, generateCode, getClientKeyInfo, setClientKeyInfo } = require('./utils')
const { syncList, removeSnapshot } = require('./syncList')
const { log } = require('@common/utils')
import http from 'http'
import { Server, Socket } from 'socket.io'
import { createHttpTerminator, HttpTerminator } from 'http-terminator'
import * as modules from '../modules'
import { authCode, authConnect } from './auth'
import { getAddress, getServerId, generateCode as handleGenerateCode, getClientKeyInfo, setClientKeyInfo } from './utils'
import { syncList, removeSnapshot } from './syncList'
import { log } from '@common/utils'
import { sendStatus } from '@main/modules/winMain'
let status = {
let status: LX.Sync.Status = {
status: false,
message: '',
address: [],
@@ -16,7 +17,26 @@ let status = {
devices: [],
}
const handleConnection = (io, socket) => {
const codeTools: {
timeout: NodeJS.Timer | null
start: () => void
stop: () => void
} = {
timeout: null,
start() {
this.stop()
this.timeout = setInterval(() => {
void generateCode()
}, 60 * 3 * 1000)
},
stop() {
if (!this.timeout) return
clearInterval(this.timeout)
this.timeout = null
},
}
const handleConnection = (io: Server, socket: LX.Sync.Socket) => {
console.log('connection')
// console.log(socket.handshake.query)
for (const module of Object.values(modules)) {
@@ -24,7 +44,7 @@ const handleConnection = (io, socket) => {
}
}
const authConnection = (req, callback) => {
const authConnection = (req: http.IncomingMessage, callback: (err: string | null | undefined, success: boolean) => void) => {
// console.log(req.headers)
// // console.log(req.auth)
// console.log(req._query.authCode)
@@ -35,10 +55,10 @@ const authConnection = (req, callback) => {
})
}
let httpTerminator = null
let io = null
let httpTerminator: HttpTerminator | null = null
let io: Server | null = null
const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
const handleStartServer = async(port = 9527) => await new Promise((resolve, reject) => {
const httpServer = http.createServer((req, res) => {
// console.log(req.url)
let code
@@ -46,14 +66,14 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
switch (req.url) {
case '/hello':
code = 200
msg = 'Hello~::^-^::'
msg = 'Hello~::^-^::~v2'
break
case '/id':
code = 200
msg = 'OjppZDo6' + getServerId()
break
case '/ah':
authCode(req, res, status.code)
void authCode(req, res, status.code)
break
default:
code = 401
@@ -67,7 +87,7 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
httpTerminator = createHttpTerminator({
server: httpServer,
})
io = sio(httpServer, {
io = new Server(httpServer, {
path: '/sync',
serveClient: false,
connectTimeout: 10000,
@@ -77,13 +97,16 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
transports: ['websocket'],
})
io.on('connection', async socket => {
io.on('connection', async(_socket: Socket) => {
const socket = _socket as LX.Sync.Socket
socket.on('disconnect', reason => {
console.log('disconnect', reason)
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo.clientId), 1)
global.lx_event.sync.status(status)
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo?.clientId), 1)
sendStatus(status)
})
if (typeof socket.handshake.query.i != 'string') return socket.disconnect(true)
const keyInfo = getClientKeyInfo(socket.handshake.query.i)
if (!keyInfo || !io) return socket.disconnect(true)
keyInfo.connectionTime = Date.now()
setClientKeyInfo(keyInfo)
// socket.lx_keyInfo = keyInfo
@@ -96,8 +119,8 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
return
}
status.devices.push(keyInfo)
handleConnection(io, socket, keyInfo)
global.lx_event.sync.status(status)
handleConnection(io, socket)
sendStatus(status)
})
httpServer.on('error', error => {
@@ -107,9 +130,10 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
httpServer.on('listening', () => {
const addr = httpServer.address()
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
if (!addr) return reject(new Error('address is null'))
const bind = typeof addr == 'string' ? `pipe ${addr}` : `port ${addr.port}`
console.info(`Listening on ${bind}`)
resolve()
resolve(null)
})
httpServer.listen(port)
@@ -117,23 +141,25 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
const handleStopServer = async() => {
if (!httpTerminator) return
await io.close()
if (!io) return
io.close()
await httpTerminator.terminate().catch(() => {})
io = null
httpTerminator = null
}
exports.stopServer = async() => {
export const stopServer = async() => {
codeTools.stop()
if (!status.status) {
status.status = false
status.message = ''
status.address = []
status.code = ''
global.lx_event.sync.status(status)
sendStatus(status)
return
}
console.log('stoping sync server...')
return handleStopServer().then(() => {
return await handleStopServer().then(() => {
console.log('sync server stoped')
status.status = false
status.message = ''
@@ -143,19 +169,22 @@ exports.stopServer = async() => {
console.log(err)
status.message = err.message
}).finally(() => {
global.lx_event.sync.status(status)
sendStatus(status)
})
}
exports.startServer = async port => {
export const startServer = async(port: number) => {
if (status.status) await handleStopServer()
console.log('starting sync server...')
return handleStartServer(port).then(() => {
return await handleStartServer(port).then(() => {
console.log('sync server started')
status.status = true
status.message = ''
status.address = getAddress()
status.code = generateCode()
void generateCode()
codeTools.start()
}).catch(err => {
console.log(err)
status.status = false
@@ -163,16 +192,18 @@ exports.startServer = async port => {
status.address = []
status.code = ''
}).finally(() => {
global.lx_event.sync.status(status)
sendStatus(status)
})
}
exports.getStatus = () => status
export const getStatus = (): LX.Sync.Status => status
exports.generateCode = async() => {
status.code = generateCode()
global.lx_event.sync.status(status)
export const generateCode = async() => {
status.code = handleGenerateCode()
sendStatus(status)
return status.code
}
exports.removeSnapshot = removeSnapshot
export {
removeSnapshot,
}
-438
View File
@@ -1,438 +0,0 @@
const fs = require('fs')
const fsPromises = fs.promises
const { encryptMsg, decryptMsg, getSnapshotFilePath } = require('./utils')
const SYNC_EVENT_NAMES = require('../event/name')
const { common: COMMON_EVENT_NAME } = require('@main/events/_name')
const { throttle } = require('@common/utils')
let io
let syncingId = null
const wait = (time = 1000) => new Promise((resolve, reject) => setTimeout(resolve, time))
const patchListData = listData => {
return Object.assign({}, {
defaultList: {
id: 'default',
name: '试听列表',
list: [],
},
loveList: {
id: 'love',
name: '我的收藏',
list: [],
},
userList: [],
}, listData)
}
const getRemoteListData = socket => new Promise((resolve, reject) => {
console.log('getRemoteListData')
const handleError = reason => {
reject(new Error(reason))
}
const handleSuccess = enData => {
socket.removeListener('disconnect', handleError)
socket.removeListener('list:sync', handleSuccess)
console.log('getRemoteListData', 'handleSuccess')
const data = JSON.parse(decryptMsg(socket.data.keyInfo, enData))
if (!data) return reject(new Error('Get remote list data failed'))
if (data.action != 'getData') return
resolve(patchListData(data.data))
}
socket.on('disconnect', handleError)
socket.on('list:sync', handleSuccess)
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'getData', data: 'all' })))
})
const getLocalListData = () => new Promise((resolve, reject) => {
const handleSuccess = ({ action, data }) => {
if (action !== 'getData') return
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
resolve(patchListData(data))
}
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
global.lx_event.sync.sync_list({
action: 'getData',
})
})
const getSyncMode = keyInfo => new Promise((resolve, reject) => {
const handleSuccess = ({ action, data }) => {
if (action !== 'selectMode') return
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
resolve(data)
}
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
global.lx_event.sync.sync_list({
action: 'selectMode',
data: keyInfo,
})
})
const finishedSync = socket => {
return socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({
action: 'finished',
})))
}
const setLocalList = listData => {
global.lx_event.sync.sync_list({
action: 'setData',
data: listData,
})
}
const setRemotelList = async(socket, listData) => {
if (!io) return
const sockets = await io.fetchSockets()
for (const socket of sockets) {
// if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'setData', data: listData })))
}
}
let writeFilePromises = {}
const updateSnapshot = (path, data) => {
console.log('updateSnapshot', path)
let writeFilePromise = writeFilePromises[path] || Promise.resolve()
return writeFilePromise.then(() => {
writeFilePromise = writeFilePromises[path] = fsPromises.writeFile(path, data)
return writeFilePromise
})
}
const createUserListDataObj = listData => {
const userListDataObj = {}
for (const list of listData.userList) userListDataObj[list.id] = list
return userListDataObj
}
const handleMergeList = (sourceList, targetList, addMusicLocationType) => {
let newList
switch (addMusicLocationType) {
case 'top':
newList = [...targetList.list, ...sourceList.list]
break
case 'bottom':
default:
newList = [...sourceList.list, ...targetList.list]
break
}
const map = {}
const ids = []
switch (addMusicLocationType) {
case 'top':
newList = [...targetList.list, ...sourceList.list]
for (let i = newList.length - 1; i > -1; i--) {
const item = newList[i]
if (map[item.songmid]) continue
ids.unshift(item.songmid)
map[item.songmid] = item
}
break
case 'bottom':
default:
newList = [...sourceList.list, ...targetList.list]
for (const item of newList) {
if (map[item.songmid]) continue
ids.push(item.songmid)
map[item.songmid] = item
}
break
}
return {
...sourceList,
list: ids.map(id => map[id]),
}
}
const mergeList = (sourceListData, targetListData) => {
const addMusicLocationType = global.appSetting.list.addMusicLocationType
const newListData = {}
newListData.defaultList = handleMergeList(sourceListData.defaultList, targetListData.defaultList, addMusicLocationType)
newListData.loveList = handleMergeList(sourceListData.loveList, targetListData.loveList, addMusicLocationType)
const userListDataObj = createUserListDataObj(sourceListData)
newListData.userList = [...sourceListData.userList]
targetListData.userList.forEach((list, index) => {
const targetUpdateTime = list?.locationUpdateTime ?? 0
const sourceList = userListDataObj[list.id]
if (sourceList) {
sourceList.list = handleMergeList(sourceList, list, addMusicLocationType).list
const sourceUpdateTime = sourceList?.locationUpdateTime ?? 0
if (targetUpdateTime >= sourceUpdateTime) return
// 调整位置
const [newList] = newListData.userList.splice(newListData.userList.findIndex(l => l.id == list.id), 1)
newList.locationUpdateTime = targetUpdateTime
newListData.userList.splice(index, 0, newList)
} else {
if (targetUpdateTime) {
newListData.userList.splice(index, 0, list)
} else {
newListData.userList.push(list)
}
}
})
return newListData
}
const overwriteList = (sourceListData, targetListData) => {
const newListData = {}
newListData.defaultList = sourceListData.defaultList
newListData.loveList = sourceListData.loveList
const userListDataObj = createUserListDataObj(sourceListData)
newListData.userList = [...sourceListData.userList]
targetListData.userList.forEach((list, index) => {
if (userListDataObj[list.id]) return
if (list?.locationUpdateTime) {
newListData.userList.splice(index, 0, list)
} else {
newListData.userList.push(list)
}
})
return newListData
}
const handleMergeListData = async socket => {
let isSelectingMode = false
const handleDisconnect = () => {
if (!isSelectingMode) return
global.lx_event.sync.sync_list({
action: 'closeSelectMode',
})
}
socket.on('disconnect', handleDisconnect)
isSelectingMode = true
const mode = await getSyncMode(socket.data.keyInfo)
isSelectingMode = false
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
console.log('handleMergeListData', 'remoteListData, localListData')
let listData
switch (mode) {
case 'merge_local_remote':
listData = mergeList(localListData, remoteListData)
break
case 'merge_remote_local':
listData = mergeList(remoteListData, localListData)
break
case 'overwrite_local_remote':
listData = overwriteList(localListData, remoteListData)
break
case 'overwrite_remote_local':
listData = overwriteList(remoteListData, localListData)
break
case 'overwrite_local_remote_full':
listData = localListData
break
case 'overwrite_remote_local_full':
listData = remoteListData
break
case 'none': return
case 'cancel':
socket.disconnect(true)
throw new Error('cancel')
}
return listData
}
const handleSyncList = async socket => {
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
console.log('handleSyncList', 'remoteListData, localListData')
const listData = {}
if (localListData.defaultList.list.length || localListData.loveList.list.length || localListData.userList.length) {
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
const mergedList = await handleMergeListData(socket)
console.log('handleMergeListData', 'mergedList')
console.log(mergedList)
if (!mergedList) return
listData.defaultList = mergedList.defaultList
listData.loveList = mergedList.loveList
listData.userList = mergedList.userList
setLocalList(mergedList)
setRemotelList(socket, mergedList)
} else {
setRemotelList(socket, localListData)
listData.defaultList = localListData.defaultList
listData.loveList = localListData.loveList
listData.userList = localListData.userList
}
} else {
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
setLocalList(remoteListData)
listData.defaultList = remoteListData.defaultList
listData.loveList = remoteListData.loveList
listData.userList = remoteListData.userList
} else {
listData.defaultList = localListData.defaultList
listData.loveList = localListData.loveList
listData.userList = localListData.userList
}
}
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
defaultList: listData.defaultList,
loveList: listData.loveList,
userList: listData.userList,
})).then(() => {
socket.data.isCreatedSnapshot = true
return listData
})
}
const mergeListDataFromSnapshot = (sourceList, targetList, snapshotList, addMusicLocationType) => {
const removedListIds = new Set()
const sourceListItemIds = new Set()
const targetListItemIds = new Set()
for (const m of sourceList.list) sourceListItemIds.add(m.songmid)
for (const m of targetList.list) targetListItemIds.add(m.songmid)
if (snapshotList) {
for (const m of snapshotList.list) {
if (!sourceListItemIds.has(m.songmid) || !targetListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
}
}
let newList
const map = {}
const ids = []
switch (addMusicLocationType) {
case 'top':
newList = [...targetList.list, ...sourceList.list]
for (let i = newList.length - 1; i > -1; i--) {
const item = newList[i]
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
ids.unshift(item.songmid)
map[item.songmid] = item
}
break
case 'bottom':
default:
newList = [...sourceList.list, ...targetList.list]
for (const item of newList) {
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
ids.push(item.songmid)
map[item.songmid] = item
}
break
}
return {
...sourceList,
list: ids.map(id => map[id]),
}
}
const handleMergeListDataFromSnapshot = async(socket, snapshot) => {
const addMusicLocationType = global.appSetting.list.addMusicLocationType
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
const newListData = {}
newListData.defaultList = mergeListDataFromSnapshot(localListData.defaultList, remoteListData.defaultList, snapshot.defaultList, addMusicLocationType)
newListData.loveList = mergeListDataFromSnapshot(localListData.loveList, remoteListData.loveList, snapshot.loveList, addMusicLocationType)
const localUserListData = createUserListDataObj(localListData)
const remoteUserListData = createUserListDataObj(remoteListData)
const snapshotUserListData = createUserListDataObj(snapshot)
const removedListIds = new Set()
const localUserListIds = new Set()
const remoteUserListIds = new Set()
for (const l of localListData.userList) localUserListIds.add(l.id)
for (const l of remoteListData.userList) remoteUserListIds.add(l.id)
for (const l of snapshot.userList) {
if (!localUserListIds.has(l.id) || !remoteUserListIds.has(l.id)) removedListIds.add(l.id)
}
let newUserList = []
for (const list of localListData.userList) {
if (removedListIds.has(list.id)) continue
const remoteList = remoteUserListData[list.id]
let newList
if (remoteList) {
newList = mergeListDataFromSnapshot(list, remoteList, snapshotUserListData[list.id], addMusicLocationType)
} else {
newList = { ...list }
}
newUserList.push(newList)
}
remoteListData.userList.forEach((list, index) => {
if (removedListIds.has(list.id)) return
const remoteUpdateTime = list?.locationUpdateTime ?? 0
if (localUserListData[list.id]) {
const localUpdateTime = localUserListData[list.id]?.locationUpdateTime ?? 0
if (localUpdateTime >= remoteUpdateTime) return
// 调整位置
const [newList] = newUserList.splice(newUserList.findIndex(l => l.id == list.id), 1)
newList.locationUpdateTime = localUpdateTime
newUserList.splice(index, 0, newList)
} else {
if (remoteUpdateTime) {
newUserList.splice(index, 0, { ...list })
} else {
newUserList.push({ ...list })
}
}
})
newListData.userList = newUserList
setLocalList(newListData)
setRemotelList(socket, newListData)
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
defaultList: newListData.defaultList,
loveList: newListData.loveList,
userList: newListData.userList,
})).then(() => {
socket.data.isCreatedSnapshot = true
return newListData
})
}
const registerUpdateSnapshotTask = (socket, snapshot) => {
if (!socket.data.isCreatedSnapshot) return
const handleUpdateSnapshot = throttle(({ defaultList, loveList, userList }) => {
if (defaultList != null) snapshot.defaultList = defaultList
if (loveList != null) snapshot.loveList = loveList
if (userList != null) snapshot.userList = userList
updateSnapshot(socket.data.snapshotFilePath, JSON.stringify(snapshot))
}, 2000)
global.lx_event.common.on(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
socket.on('disconnect', () => {
global.lx_event.common.off(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
})
}
const syncList = async socket => {
socket.data.snapshotFilePath = getSnapshotFilePath(socket.data.keyInfo)
let fileData
let isSyncRequired = false
try {
fileData = await fsPromises.readFile(socket.data.snapshotFilePath)
fileData = JSON.parse(fileData)
} catch (err) {
if (err.code !== 'ENOENT') throw err
isSyncRequired = true
}
console.log('isSyncRequired', isSyncRequired)
if (isSyncRequired) return handleSyncList(socket)
return handleMergeListDataFromSnapshot(socket, patchListData(fileData))
}
const checkSyncQueue = async() => {
if (!syncingId) return
await wait()
return checkSyncQueue()
}
exports.syncList = async(_io, socket) => {
io = _io
await checkSyncQueue()
syncingId = socket.data.keyInfo.clientId
return syncList(socket).then(newListData => {
registerUpdateSnapshotTask(socket, { ...newListData })
return finishedSync(socket)
}).finally(() => {
syncingId = null
})
}
exports.removeSnapshot = keyInfo => {
const filePath = getSnapshotFilePath(keyInfo)
return fsPromises.unlink(filePath)
}
+492
View File
@@ -0,0 +1,492 @@
import { promises as fsPromises } from 'fs'
import { encryptMsg, decryptMsg, getSnapshotFilePath } from './utils'
import { throttle } from '@common/utils'
import { Server } from 'socket.io'
import { sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
import { LIST_IDS } from '@common/constants'
// type ListInfoType = LX.List.UserListInfoFull | LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull
let io: Server | null
let syncingId: string | null = null
const wait = async(time = 1000) => await new Promise((resolve, reject) => setTimeout(resolve, time))
const patchListData = (listData: Partial<LX.Sync.ListData>): LX.Sync.ListData => {
return Object.assign({
defaultList: [],
loveList: [],
userList: [],
}, listData)
}
const getRemoteListData = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData> => await new Promise((resolve, reject) => {
console.log('getRemoteListData')
const handleError = (reason: string) => {
reject(new Error(reason))
}
const handleSuccess = (enData: string) => {
socket.removeListener('disconnect', handleError)
socket.removeListener('list:sync', handleSuccess)
console.log('getRemoteListData', 'handleSuccess')
const data: LX.Sync.Data | null = JSON.parse(decryptMsg(socket.data.keyInfo, enData))
if (!data) return reject(new Error('Get remote list data failed'))
if (data.action != 'getData') return
resolve(patchListData(data.data))
}
socket.on('disconnect', handleError)
socket.on('list:sync', handleSuccess)
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'getData', data: 'all' })))
})
// const getAllLists = async() => {
// const lists = []
// lists.push(await getListMusics(defaultList.id).then(musics => ({ ...defaultList, list: toRaw(musics).map(m => toOldMusicInfo(m)) })))
// lists.push(await getListMusics(loveList.id).then(musics => ({ ...loveList, list: toRaw(musics).map(m => toOldMusicInfo(m)) })))
// for await (const list of userLists) {
// lists.push(await getListMusics(list.id).then(musics => ({ ...toRaw(list), list: toRaw(musics).map(m => toOldMusicInfo(m)) })))
// }
// return lists
// }
const getLocalListData = async(): Promise<LX.Sync.ListData> => {
const lists: LX.Sync.ListData = {
defaultList: await global.lx.worker.dbService.getListMusics(LIST_IDS.DEFAULT),
loveList: await global.lx.worker.dbService.getListMusics(LIST_IDS.LOVE),
userList: [],
}
const userListInfos = await global.lx.worker.dbService.getAllUserList()
for await (const list of userListInfos) {
lists.userList.push(await global.lx.worker.dbService.getListMusics(list.id)
.then(musics => ({ ...list, list: musics })))
}
return lists
}
const getSyncMode = async(keyInfo: LX.Sync.KeyInfo): Promise<LX.Sync.Mode> => await new Promise((resolve, reject) => {
let removeListener = sendSelectMode(keyInfo, (mode) => {
removeListener()
resolve(mode)
})
})
const finishedSync = (socket: LX.Sync.Socket) => {
return socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({
action: 'finished',
})))
}
const setLocalList = (listData: LX.Sync.ListData) => {
void global.lx.event_list.list_data_overwrite(listData, true)
}
const setRemotelList = async(socket: LX.Sync.Socket, listData: LX.Sync.ListData) => {
if (!io) return
const sockets: LX.Sync.RemoteSocket[] = await io.fetchSockets()
for (const socket of sockets) {
// if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'setData', data: listData })))
}
}
const writeFilePromises: Map<string, Promise<void>> = new Map()
const updateSnapshot = async(path: string, data: string) => {
console.log('updateSnapshot', path)
let writeFilePromise = writeFilePromises.get(path) ?? Promise.resolve()
writeFilePromise = writeFilePromise.then(async() => {
if (writeFilePromise !== writeFilePromises.get(path)) return
return await fsPromises.writeFile(path, data)
})
writeFilePromises.set(path, writeFilePromise)
return await writeFilePromise.finally(() => {
if (writeFilePromise !== writeFilePromises.get(path)) return
writeFilePromises.delete(path)
})
}
interface UserDataObj {
[listId: string]: LX.List.UserListInfoFull
}
const createUserListDataObj = (listData: LX.Sync.ListData): UserDataObj => {
const userListDataObj: UserDataObj = {}
for (const list of listData.userList) userListDataObj[list.id] = list
return userListDataObj
}
const handleMergeList = (
sourceList: LX.Music.MusicInfo[],
targetList: LX.Music.MusicInfo[],
addMusicLocationType: LX.AddMusicLocationType,
): LX.Music.MusicInfo[] => {
let newList
switch (addMusicLocationType) {
case 'top':
newList = [...targetList, ...sourceList]
break
case 'bottom':
default:
newList = [...sourceList, ...targetList]
break
}
const map: Map<string | number, LX.Music.MusicInfo> = new Map()
const ids: Array<string | number> = []
switch (addMusicLocationType) {
case 'top':
newList = [...targetList, ...sourceList]
for (let i = newList.length - 1; i > -1; i--) {
const item = newList[i]
if (map.has(item.id)) continue
ids.unshift(item.id)
map.set(item.id, item)
}
break
case 'bottom':
default:
newList = [...sourceList, ...targetList]
for (const item of newList) {
if (map.has(item.id)) continue
ids.push(item.id)
map.set(item.id, item)
}
break
}
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 newListData: LX.Sync.ListData = {
defaultList: [],
loveList: [],
userList: [],
}
newListData.defaultList = handleMergeList(sourceListData.defaultList, targetListData.defaultList, addMusicLocationType)
newListData.loveList = handleMergeList(sourceListData.loveList, targetListData.loveList, addMusicLocationType)
const userListDataObj = createUserListDataObj(sourceListData)
newListData.userList = [...sourceListData.userList]
targetListData.userList.forEach((list, index) => {
const targetUpdateTime = list?.locationUpdateTime ?? 0
const sourceList = userListDataObj[list.id]
if (sourceList) {
sourceList.list = handleMergeList(sourceList.list, list.list, addMusicLocationType)
const sourceUpdateTime = sourceList?.locationUpdateTime ?? 0
if (targetUpdateTime >= sourceUpdateTime) return
// 调整位置
const [newList] = newListData.userList.splice(newListData.userList.findIndex(l => l.id == list.id), 1)
newList.locationUpdateTime = targetUpdateTime
newListData.userList.splice(index, 0, newList)
} else {
if (targetUpdateTime) {
newListData.userList.splice(index, 0, list)
} else {
newListData.userList.push(list)
}
}
})
return newListData
}
const overwriteList = (sourceListData: LX.Sync.ListData, targetListData: LX.Sync.ListData): LX.Sync.ListData => {
const newListData: LX.Sync.ListData = {
defaultList: [],
loveList: [],
userList: [],
}
newListData.defaultList = sourceListData.defaultList
newListData.loveList = sourceListData.loveList
const userListDataObj = createUserListDataObj(sourceListData)
newListData.userList = [...sourceListData.userList]
targetListData.userList.forEach((list, index) => {
if (userListDataObj[list.id]) return
if (list?.locationUpdateTime) {
newListData.userList.splice(index, 0, list)
} else {
newListData.userList.push(list)
}
})
return newListData
}
const handleMergeListData = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData | null> => {
let isSelectingMode = false
const handleDisconnect = () => {
if (!isSelectingMode) return
sendCloseSelectMode()
}
socket.on('disconnect', handleDisconnect)
isSelectingMode = true
const mode: LX.Sync.Mode = await getSyncMode(socket.data.keyInfo)
isSelectingMode = false
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
console.log('handleMergeListData', 'remoteListData, localListData')
let listData: LX.Sync.ListData
switch (mode) {
case 'merge_local_remote':
listData = mergeList(localListData, remoteListData)
break
case 'merge_remote_local':
listData = mergeList(remoteListData, localListData)
break
case 'overwrite_local_remote':
listData = overwriteList(localListData, remoteListData)
break
case 'overwrite_remote_local':
listData = overwriteList(remoteListData, localListData)
break
case 'overwrite_local_remote_full':
listData = localListData
break
case 'overwrite_remote_local_full':
listData = remoteListData
break
case 'none': return null
case 'cancel':
socket.disconnect(true)
throw new Error('cancel')
}
return listData
}
const handleSyncList = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData | null> => {
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
console.log('handleSyncList', 'remoteListData, localListData')
const listData: LX.Sync.ListData = {
defaultList: [],
loveList: [],
userList: [],
}
if (localListData.defaultList.length || localListData.loveList.length || localListData.userList.length) {
if (remoteListData.defaultList.length || remoteListData.loveList.length || remoteListData.userList.length) {
const mergedList = await handleMergeListData(socket)
console.log('handleMergeListData', 'mergedList')
console.log(mergedList)
if (!mergedList) return null
listData.defaultList = mergedList.defaultList
listData.loveList = mergedList.loveList
listData.userList = mergedList.userList
setLocalList(mergedList)
void setRemotelList(socket, mergedList)
} else {
void setRemotelList(socket, localListData)
listData.defaultList = localListData.defaultList
listData.loveList = localListData.loveList
listData.userList = localListData.userList
}
} else {
if (remoteListData.defaultList.length || remoteListData.loveList.length || remoteListData.userList.length) {
setLocalList(remoteListData)
listData.defaultList = remoteListData.defaultList
listData.loveList = remoteListData.loveList
listData.userList = remoteListData.userList
} else {
listData.defaultList = localListData.defaultList
listData.loveList = localListData.loveList
listData.userList = localListData.userList
}
}
return await updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
defaultList: listData.defaultList,
loveList: listData.loveList,
userList: listData.userList,
})).then(() => {
socket.data.isCreatedSnapshot = true
return listData
})
}
const mergeListDataFromSnapshot = (
sourceList: LX.Music.MusicInfo[],
targetList: LX.Music.MusicInfo[],
snapshotList: LX.Music.MusicInfo[],
addMusicLocationType: LX.AddMusicLocationType,
): LX.Music.MusicInfo[] => {
const removedListIds: Set<string | number> = new Set()
const sourceListItemIds: Set<string | number> = new Set()
const targetListItemIds: Set<string | number> = new Set()
for (const m of sourceList) sourceListItemIds.add(m.id)
for (const m of targetList) targetListItemIds.add(m.id)
if (snapshotList) {
for (const m of snapshotList) {
if (!sourceListItemIds.has(m.id) || !targetListItemIds.has(m.id)) removedListIds.add(m.id)
}
}
let newList
const map: Map<string | number, LX.Music.MusicInfo> = new Map()
const ids = []
switch (addMusicLocationType) {
case 'top':
newList = [...targetList, ...sourceList]
for (let i = newList.length - 1; i > -1; i--) {
const item = newList[i]
if (map.has(item.id) || removedListIds.has(item.id)) continue
ids.unshift(item.id)
map.set(item.id, item)
}
break
case 'bottom':
default:
newList = [...sourceList, ...targetList]
for (const item of newList) {
if (map.has(item.id) || removedListIds.has(item.id)) continue
ids.push(item.id)
map.set(item.id, item)
}
break
}
return ids.map(id => map.get(id)) as LX.Music.MusicInfo[]
}
const handleMergeListDataFromSnapshot = async(socket: LX.Sync.Socket, snapshot: LX.Sync.ListData): Promise<LX.Sync.ListData> => {
const addMusicLocationType = global.lx.appSetting['list.addMusicLocationType']
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
const newListData: LX.Sync.ListData = {
defaultList: [],
loveList: [],
userList: [],
}
newListData.defaultList = mergeListDataFromSnapshot(localListData.defaultList, remoteListData.defaultList, snapshot.defaultList, addMusicLocationType)
newListData.loveList = mergeListDataFromSnapshot(localListData.loveList, remoteListData.loveList, snapshot.loveList, addMusicLocationType)
const localUserListData = createUserListDataObj(localListData)
const remoteUserListData = createUserListDataObj(remoteListData)
const snapshotUserListData = createUserListDataObj(snapshot)
const removedListIds: Set<string | number> = new Set()
const localUserListIds: Set<string | number> = new Set()
const remoteUserListIds: Set<string | number> = new Set()
for (const l of localListData.userList) localUserListIds.add(l.id)
for (const l of remoteListData.userList) remoteUserListIds.add(l.id)
for (const l of snapshot.userList) {
if (!localUserListIds.has(l.id) || !remoteUserListIds.has(l.id)) removedListIds.add(l.id)
}
let newUserList: LX.List.UserListInfoFull[] = []
for (const list of localListData.userList) {
if (removedListIds.has(list.id)) continue
const remoteList = remoteUserListData[list.id]
let newList: LX.List.UserListInfoFull
if (remoteList) {
newList = { ...list, list: mergeListDataFromSnapshot(list.list, remoteList.list, snapshotUserListData[list.id].list, addMusicLocationType) }
} else {
newList = { ...list }
}
newUserList.push(newList)
}
remoteListData.userList.forEach((list, index) => {
if (removedListIds.has(list.id)) return
const remoteUpdateTime = list?.locationUpdateTime ?? 0
if (localUserListData[list.id]) {
const localUpdateTime = localUserListData[list.id]?.locationUpdateTime ?? 0
if (localUpdateTime >= remoteUpdateTime) return
// 调整位置
const [newList] = newUserList.splice(newUserList.findIndex(l => l.id == list.id), 1)
newList.locationUpdateTime = localUpdateTime
newUserList.splice(index, 0, newList)
} else {
if (remoteUpdateTime) {
newUserList.splice(index, 0, { ...list })
} else {
newUserList.push({ ...list })
}
}
})
newListData.userList = newUserList
setLocalList(newListData)
void setRemotelList(socket, newListData)
return await updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
defaultList: newListData.defaultList,
loveList: newListData.loveList,
userList: newListData.userList,
})).then(() => {
socket.data.isCreatedSnapshot = true
return newListData
})
}
const registerUpdateSnapshotTask = (socket: LX.Sync.Socket, snapshot: LX.Sync.ListData) => {
if (!socket.data.isCreatedSnapshot) return
const handleUpdateSnapshot = throttle(() => {
// TODO: 同步成功后再保存快照
void getLocalListData().then(({ defaultList, loveList, userList }) => {
if (defaultList != null) snapshot.defaultList = defaultList
if (loveList != null) snapshot.loveList = loveList
if (userList != null) snapshot.userList = userList
void updateSnapshot(socket.data.snapshotFilePath, JSON.stringify(snapshot))
})
}, 2000)
global.lx.event_list.on('list_changed', handleUpdateSnapshot)
socket.on('disconnect', () => {
global.lx.event_list.off('list_changed', handleUpdateSnapshot)
})
}
const syncList = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData | null> => {
socket.data.snapshotFilePath = getSnapshotFilePath(socket.data.keyInfo)
let fileData: any
let isSyncRequired = false
try {
fileData = (await fsPromises.readFile(socket.data.snapshotFilePath)).toString()
fileData = JSON.parse(fileData)
} catch (error) {
const err = error as NodeJS.ErrnoException
if (err.code != 'ENOENT') throw err
isSyncRequired = true
}
console.log('isSyncRequired', isSyncRequired)
if (isSyncRequired) return await handleSyncList(socket)
return await handleMergeListDataFromSnapshot(socket, patchListData(fileData))
}
const checkSyncQueue = async(): Promise<void> => {
if (!syncingId) return
await wait()
return await checkSyncQueue()
}
// export {
// syncList = async(_io: Server, socket: LX.Sync.Socket) => {
// io = _io
// await checkSyncQueue()
// syncingId = socket.data.keyInfo.clientId
// return await syncList(socket).then(newListData => {
// registerUpdateSnapshotTask(socket, { ...newListData })
// return finishedSync(socket)
// }).finally(() => {
// syncingId = null
// })
// }
// }
const _syncList = async(_io: Server, socket: LX.Sync.Socket) => {
io = _io
await checkSyncQueue()
syncingId = socket.data.keyInfo.clientId
return await syncList(socket).then(newListData => {
if (newListData) registerUpdateSnapshotTask(socket, { ...newListData })
return finishedSync(socket)
}).finally(() => {
syncingId = null
})
}
const removeSnapshot = async(keyInfo: LX.Sync.KeyInfo) => {
const filePath = getSnapshotFilePath(keyInfo)
return await fsPromises.unlink(filePath)
}
export {
_syncList as syncList,
removeSnapshot,
}
-99
View File
@@ -1,99 +0,0 @@
const { app } = require('electron')
const { networkInterfaces } = require('os')
const { randomBytes, createCipheriv, createDecipheriv } = require('crypto')
const path = require('path')
const getStore = require('@common/store')
const STORE_NAME = 'sync'
exports.getAddress = () => {
const nets = networkInterfaces()
const results = []
// console.log(nets)
for (const name of Object.keys(nets)) {
for (const net of nets[name]) {
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
if (net.family === 'IPv4' && !net.internal) {
results.push(net.address)
}
}
}
return results
}
let serverId
exports.getServerId = () => {
if (serverId) return serverId
const store = getStore(STORE_NAME)
serverId = store.get('serverId')
if (!serverId) {
serverId = randomBytes(4 * 4).toString('base64')
store.set('serverId', serverId)
}
return serverId
}
let keyInfos
exports.createClientKeyInfo = deviceName => {
const keyInfo = {
clientId: randomBytes(4 * 4).toString('base64'),
key: randomBytes(16).toString('base64'),
iv: randomBytes(16).toString('base64'),
deviceName,
}
const store = getStore(STORE_NAME)
if (!keyInfos) keyInfos = store.get('keys') || {}
if (Object.keys(keyInfos).length > 101) throw new Error('max keys')
keyInfos[keyInfo.clientId] = keyInfo
store.set('keys', keyInfos)
return keyInfo
}
exports.setClientKeyInfo = keyInfo => {
keyInfos[keyInfo.clientId] = keyInfo
const store = getStore(STORE_NAME)
store.set('keys', keyInfos)
}
exports.getClientKeyInfo = clientId => {
if (!keyInfos) {
const store = getStore(STORE_NAME)
keyInfos = store.get('keys') || {}
}
return keyInfos[clientId] || null
}
exports.generateCode = () => {
return Math.random().toString().substring(2, 8)
}
exports.aesEncrypt = (buffer, key, iv) => {
const cipher = createCipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
return Buffer.concat([cipher.update(buffer), cipher.final()]).toString('base64')
}
exports.aesDecrypt = (text, key, iv) => {
const decipher = createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
}
exports.encryptMsg = (keyInfo, msg) => {
return msg
// if (!keyInfo) return ''
// return exports.aesEncrypt(msg, keyInfo.key, keyInfo.iv)
}
exports.decryptMsg = (keyInfo, enMsg) => {
return enMsg
// if (!keyInfo) return ''
// let msg = ''
// try {
// msg = exports.aesDecrypt(enMsg, keyInfo.key, keyInfo.iv)
// } catch (err) {
// console.log(err)
// }
// return msg
}
exports.getSnapshotFilePath = keyInfo => {
return path.join(app.getPath('userData'), `snapshot-${Buffer.from(keyInfo.clientId).toString('hex').substring(0, 10)}.json`)
}
+104
View File
@@ -0,0 +1,104 @@
import { networkInterfaces } from 'os'
import { randomBytes, createCipheriv, createDecipheriv } from 'crypto'
import { join } from 'path'
import getStore from '@main/utils/store'
const STORE_NAME = 'sync'
interface KeyInfos {
[key: string]: LX.Sync.KeyInfo
}
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
}
let serverId: string | undefined
export const getServerId = (): string => {
if (serverId) return serverId
const store = getStore(STORE_NAME)
serverId = store.get('serverId') as string | undefined
if (!serverId) {
serverId = randomBytes(4 * 4).toString('base64')
store.set('serverId', serverId)
}
return serverId
}
let keyInfos: KeyInfos
export const createClientKeyInfo = (deviceName: string): LX.Sync.KeyInfo => {
const keyInfo: LX.Sync.KeyInfo = {
clientId: randomBytes(4 * 4).toString('base64'),
key: randomBytes(16).toString('base64'),
iv: randomBytes(16).toString('base64'),
deviceName,
}
const store = getStore(STORE_NAME)
if (!keyInfos) keyInfos = store.get('keys') as KeyInfos || {}
if (Object.keys(keyInfos).length > 101) throw new Error('max keys')
keyInfos[keyInfo.clientId] = keyInfo
store.set('keys', keyInfos)
return keyInfo
}
export const setClientKeyInfo = (keyInfo: LX.Sync.KeyInfo) => {
keyInfos[keyInfo.clientId] = keyInfo
const store = getStore(STORE_NAME)
store.set('keys', keyInfos)
}
export const getClientKeyInfo = (clientId: string): LX.Sync.KeyInfo | null => {
if (!keyInfos) {
const store = getStore(STORE_NAME)
keyInfos = store.get('keys') as KeyInfos || {}
}
return keyInfos[clientId] ?? null
}
export const generateCode = (): string => {
return Math.random().toString().substring(2, 8)
}
export const aesEncrypt = (buffer: string | Buffer, key: string, iv: string): string => {
const cipher = createCipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
return Buffer.concat([cipher.update(buffer), cipher.final()]).toString('base64')
}
export const aesDecrypt = (text: string, key: string, iv: string): string => {
const decipher = createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
}
export const encryptMsg = (keyInfo: LX.Sync.KeyInfo, msg: string): string => {
return msg
// if (!keyInfo) return ''
// return aesEncrypt(msg, keyInfo.key, keyInfo.iv)
}
export const decryptMsg = (keyInfo: LX.Sync.KeyInfo, enMsg: string): string => {
return enMsg
// 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.lxDataPath, `snapshot-${Buffer.from(keyInfo.clientId).toString('hex').substring(0, 10)}.json`)
}