新增同步功能对“不喜欢歌曲”列表的同步

pull/1583/head
lyswhut 2023-09-05 23:11:09 +08:00
parent fc03cd77fc
commit b1f9e525ab
62 changed files with 1278 additions and 317 deletions

View File

@ -10,6 +10,7 @@
- 新增我的列表名右键菜单-排序歌曲-随机乱序功能,使用它可以对选中列表内歌曲进行随机重排(#1440
- 新增数据同步服务端模式已认证设备列表管理,该功能位置:设置-数据同步-服务端模式-已认证设备列表
- 新增“不喜欢歌曲”功能,可以在我的列表或者在线列表内歌曲的右击菜单使用,还可以去“设置-其他”手动编辑不喜欢规则,注:“上一曲”、“下一曲”功能将跳过符合“不喜欢歌曲”规则的歌曲,但你仍可以手动播放这些歌曲
- 新增同步功能对“不喜欢歌曲”列表的同步
- 新增软件内快捷键“不喜欢该歌曲”设置,全局快捷键“收藏歌曲”、“取消收藏”、“不喜欢该歌曲”设置
- 新增设置-播放设置-点击相同列表内的歌曲切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)选项,默认关闭

View File

@ -61,10 +61,14 @@ export const File = {
listDir: 'list',
listSnapshotDir: 'snapshot',
listSnapshotInfoJSON: 'snapshotInfo.json',
dislikeDir: 'dislike',
dislikeSnapshotDir: 'snapshot',
dislikeSnapshotInfoJSON: 'snapshotInfo.json',
syncAuthKeysJSON: 'syncAuthKey.json',
} as const
export const FeaturesList = [
'list',
'dislike',
] as const

View File

@ -36,6 +36,12 @@ const modules = {
list_music_check_exist: 'list_music_check_exist',
list_music_get_list_ids: 'list_music_get_list_ids',
},
dislike: {
get_dislike_music_infos: 'get_dislike_music_infos',
add_dislike_music_infos: 'add_dislike_music_infos',
overwrite_dislike_music_infos: 'overwrite_dislike_music_infos',
clear_dislike_music_infos: 'clear_dislike_music_infos',
},
winMain: {
focus: 'focus',
close: 'close',
@ -126,10 +132,6 @@ const modules = {
clear_music_url: 'clear_music_url',
get_music_url_count: 'get_music_url_count',
get_dislike_music_infos: 'get_dislike_music_infos',
add_dislike_music_infos: 'add_dislike_music_infos',
overwrite_dislike_music_infos: 'overwrite_dislike_music_infos',
sync_action: 'sync_action',
sync_get_server_devices: 'sync_get_server_devices',
sync_remove_server_device: 'sync_remove_server_device',
@ -186,6 +188,7 @@ for (const moduleName of Object.keys(modules) as Array<keyof typeof modules>) {
export const CMMON_EVENT_NAME = modules.common
export const PLAYER_EVENT_NAME = modules.player
export const DISLIKE_EVENT_NAME = modules.dislike
export const WIN_MAIN_RENDERER_EVENT_NAME = modules.winMain
export const WIN_LYRIC_RENDERER_EVENT_NAME = modules.winLyric
export const HOTKEY_RENDERER_EVENT_NAME = modules.hotKey

View File

@ -23,13 +23,15 @@ declare namespace LX {
singer: string
}
type DislikeRules = string
interface DislikeInfo {
// musicIds: Set<string>
names: Set<string>
musicNames: Set<string>
singerNames: Set<string>
// list: LX.Dislike.ListItem[]
rules: string
rules: DislikeRules
}
}
}

30
src/common/types/dislike_list_sync.d.ts vendored Normal file
View File

@ -0,0 +1,30 @@
declare namespace LX {
namespace Sync {
namespace Dislike {
interface ListInfo {
lastSyncDate?: number
snapshotKey: string
}
interface SyncActionBase <A> {
action: A
}
interface SyncActionData<A, D> extends SyncActionBase<A> {
data: D
}
type SyncAction<A, D = undefined> = D extends undefined ? SyncActionBase<A> : SyncActionData<A, D>
type ActionList = SyncAction<'dislike_data_overwrite', LX.Dislike.DislikeRules>
| SyncAction<'dislike_music_add', LX.Dislike.DislikeMusicInfo[]>
| SyncAction<'dislike_music_clear'>
type SyncMode = 'merge_local_remote'
| 'merge_remote_local'
| 'overwrite_local_remote'
| 'overwrite_remote_local'
// | 'none'
| 'cancel'
}
}
}

View File

@ -19,12 +19,20 @@ declare namespace LX {
}
type SyncAction<A, D = undefined> = D extends undefined ? SyncActionBase<A> : SyncActionData<A, D>
type SyncMainWindowActions = SyncAction<'select_mode', string>
interface ModeTypes {
list: LX.Sync.List.SyncMode
dislike: LX.Sync.Dislike.SyncMode
}
type ModeType = { [K in keyof ModeTypes]: { type: K, mode: ModeTypes[K] } }[keyof ModeTypes]
type SyncMainWindowActions = SyncAction<'select_mode', { deviceName: string, type: keyof ModeTypes }>
| SyncAction<'close_select_mode'>
| SyncAction<'client_status', ClientStatus>
| SyncAction<'server_status', ServerStatus>
type SyncServiceActions = SyncAction<'select_mode', LX.Sync.List.SyncMode>
type SyncServiceActions = SyncAction<'select_mode', ModeType>
| SyncAction<'get_server_status'>
| SyncAction<'get_client_status'>
| SyncAction<'generate_code'>
@ -64,6 +72,7 @@ declare namespace LX {
type ServerType = 'desktop-app' | 'server'
interface EnabledFeatures {
list: boolean
dislike: boolean
}
type SupportedFeatures = Partial<{ [k in keyof EnabledFeatures]: number }>
}

View File

@ -383,8 +383,9 @@
"setting__desktop_lyric_shadow_color": "Shadow color",
"setting__desktop_lyric_show_taskbar": "Display lyrics progress on the taskbar (this setting is used as a workaround when the screen recording software cannot capture the lyrics window)",
"setting__desktop_lyric_unplay_color": "Color not playing",
"setting__dislike_list_input_tip": "song name@artist name\nSong name\n@ singer name",
"setting__dislike_list_save_btn": "Save",
"setting__dislike_list_tips": "1. If there is a \"@\" symbol in the song or singer's name, you need to replace it with \"#\"\n2. Specify a song of a singer: <Name>@<Singer>\n3. Specify a song: <Name>\n4. Specify a certain singer:@<Singer>",
"setting__dislike_list_tips": "1. If there is a \"@\" symbol in the song or singer's name, you need to replace it with \"#\"\n2. Specify a song of a singer: Name@Singer\n3. Specify a song: Name\n4. Specify a certain singer: @Singer",
"setting__dislike_list_title": "List of Disliked Song Rules",
"setting__download": "Download",
"setting__download_data_embed": "Whether to embed the following content in the audio file",
@ -588,14 +589,20 @@
"source_xm": "Xiami",
"sync__auth_code_input_tip": "Please enter the connection code",
"sync__auth_code_title": "Need to enter the connection code",
"sync__dislike_merge_tip_desc": "Merge the content of the two lists and remove the duplicates",
"sync__dislike_other_tip_desc": "\"Cancel sync\" will not use the dislike list sync feature",
"sync__dislike_overwrite_tip_desc": "The list of overriddens will be replaced with the list of overriders",
"sync__dislike_title": "Choose how to sync with {name}'s dislike list",
"sync__list_merge_tip_desc": "Merge the two lists together, the same song will be removed (the song of the merged person is removed), and different songs will be added.",
"sync__list_other_tip_desc": "\"Cancel Sync\" will not use list sync.",
"sync__list_overwrite_tip_desc": "The list with the same ID of the covered person and the covered list will be deleted and replaced with the list of the covered person (lists with different list IDs will be merged together). If you check Complete coverage, all lists of the covered person will be moved. \nDivide, and then replace with a list of overriders.",
"sync__list_title": "Choose how to synchronize the list with {name}",
"sync__merge_btn_local_remote": "Local list merge remote list",
"sync__merge_btn_remote_local": "Remote list merge local list",
"sync__merge_label": "Merge",
"sync__merge_tip": "Merge:",
"sync__merge_tip_desc": "Merge the two lists together, the same song will be removed (the song of the merged person is removed), and different songs will be added.",
"sync__other_label": "Other",
"sync__other_tip": "Other: ",
"sync__other_tip_desc": "\"Cancel Sync\" will not use list sync.",
"sync__overwrite": "Full coverage",
"sync__overwrite_btn_cancel": "Cancel sync",
"sync__overwrite_btn_local_remote": "Local list Overwrite remote list",
@ -603,8 +610,6 @@
"sync__overwrite_btn_remote_local": "Remote list Overwrite local list",
"sync__overwrite_label": "Cover",
"sync__overwrite_tip": "Over: ",
"sync__overwrite_tip_desc": "The list with the same ID of the covered person and the covered list will be deleted and replaced with the list of the covered person (lists with different list IDs will be merged together). If you check Complete coverage, all lists of the covered person will be moved. \nDivide, and then replace with a list of overriders.",
"sync__title": "Choose how to synchronize the list with {name}",
"sync_status_disabled": "not connected",
"tag__high_quality": "HQ",
"tag__lossless": "SQ",

View File

@ -382,8 +382,9 @@
"setting__desktop_lyric_shadow_color": "阴影颜色",
"setting__desktop_lyric_show_taskbar": "在任务栏显示歌词进程(此设置用于在录屏软件无法捕获歌词窗口时的变通解决方法)",
"setting__desktop_lyric_unplay_color": "未播放颜色",
"setting__dislike_list_input_tip": "歌曲名@歌手名\n歌曲名\n@歌手名",
"setting__dislike_list_save_btn": "保存",
"setting__dislike_list_tips": "1. 每条一行,若歌曲或者歌手名字中存在“@”符号,需要将其替换成“#”\n2. 指定某歌手的某首歌:<歌曲名>@<歌手名>\n3. 指定某首歌:<歌曲名>\n4. 指定某歌手:@<歌手名>",
"setting__dislike_list_tips": "1. 每条一行,若歌曲或者歌手名字中存在“@”符号,需要将其替换成“#”\n2. 指定某歌手的某首歌:歌曲名@歌手名\n3. 指定某首歌:歌曲名\n4. 指定某歌手:@歌手名",
"setting__dislike_list_title": "不喜欢的歌曲规则列表",
"setting__download": "下载设置",
"setting__download_data_embed": "是否将以下内容嵌入到音频文件中",
@ -587,14 +588,20 @@
"source_xm": "虾米音乐",
"sync__auth_code_input_tip": "请输入连接码",
"sync__auth_code_title": "需要输入连接码",
"sync__dislike_merge_tip_desc": "合并两边列表内容并去重",
"sync__dislike_other_tip_desc": "“取消同步”将不使用不喜欢列表同步功能",
"sync__dislike_overwrite_tip_desc": "被覆盖者的列表将被替换成覆盖者的列表",
"sync__dislike_title": "选择与 {name} 的不喜欢列表同步方式",
"sync__list_merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。",
"sync__list_other_tip_desc": "“取消同步”将不使用列表同步功能。",
"sync__list_overwrite_tip_desc": "被覆盖者与覆盖者列表ID相同的列表将被删除后替换成覆盖者的列表列表ID不同的列表将被合并到一起若勾选完全覆盖则被覆盖者的所有列表将被移除然后替换成覆盖者的列表。",
"sync__list_title": "选择与 {name} 的列表同步方式",
"sync__merge_btn_local_remote": "本机列表 合并 远程列表",
"sync__merge_btn_remote_local": "远程列表 合并 本机列表",
"sync__merge_label": "合并",
"sync__merge_tip": "合并:",
"sync__merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。",
"sync__other_label": "其他",
"sync__other_tip": "其他:",
"sync__other_tip_desc": "“取消同步”将不使用列表同步功能。",
"sync__overwrite": "完全覆盖",
"sync__overwrite_btn_cancel": "取消同步",
"sync__overwrite_btn_local_remote": "本机列表 覆盖 远程列表",
@ -602,8 +609,6 @@
"sync__overwrite_btn_remote_local": "远程列表 覆盖 本机列表",
"sync__overwrite_label": "覆盖",
"sync__overwrite_tip": "覆盖:",
"sync__overwrite_tip_desc": "被覆盖者与覆盖者列表ID相同的列表将被删除后替换成覆盖者的列表列表ID不同的列表将被合并到一起若勾选完全覆盖则被覆盖者的所有列表将被移除然后替换成覆盖者的列表。",
"sync__title": "选择与 {name} 的列表同步方式",
"sync_status_disabled": "未连接",
"tag__high_quality": "HQ",
"tag__lossless": "SQ",

View File

@ -383,8 +383,9 @@
"setting__desktop_lyric_shadow_color": "陰影顏色",
"setting__desktop_lyric_show_taskbar": "在任務欄顯示歌詞進程(此設置用於在錄屏軟件無法捕獲歌詞窗口時的變通解決方法)",
"setting__desktop_lyric_unplay_color": "未播放顏色",
"setting__dislike_list_input_tip": "歌曲名@歌手名\n歌曲名\n@歌手名",
"setting__dislike_list_save_btn": "保存",
"setting__dislike_list_tips": "1. 每條一行,若歌曲或者歌手名字中存在“@”符號,需要將其替換成“#”\n2. 指定某歌手的某首歌:<歌曲名>@<歌手名>\n3. 指定某首歌:<歌曲名>\n4. 指定某歌手:@<歌手名>",
"setting__dislike_list_tips": "1. 每條一行,若歌曲或者歌手名字中存在“@”符號,需要將其替換成“#”\n2. 指定某歌手的某首歌:歌曲名@歌手名\n3. 指定某首歌:歌曲名\n4. 指定某歌手:@歌手名",
"setting__dislike_list_title": "不喜歡的歌曲規則列表",
"setting__download": "下載設置",
"setting__download_data_embed": "是否將以下內容嵌入到音頻文件中",
@ -587,14 +588,20 @@
"source_xm": "蝦米音樂",
"sync__auth_code_input_tip": "請輸入連接碼",
"sync__auth_code_title": "需要輸入連接碼",
"sync__dislike_merge_tip_desc": "合併兩邊列表內容並去重",
"sync__dislike_other_tip_desc": "“取消同步”將不使用不喜歡列表同步功能",
"sync__dislike_overwrite_tip_desc": "被覆蓋者的列表將被替換成覆蓋者的列表",
"sync__dislike_title": "選擇與 {name} 的不喜歡列表同步方式",
"sync__list_merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
"sync__list_other_tip_desc": "“取消同步”將不使用列表同步功能。",
"sync__list_overwrite_tip_desc": "被覆蓋者與覆蓋者列表ID相同的列表將被刪除後替換成覆蓋者的列表列表ID不同的列表將被合併到一起若勾選完全覆蓋則被覆蓋者的所有列表將被移除然後替換成覆蓋者的列表。",
"sync__list_title": "選擇與 {name} 的列表同步方式",
"sync__merge_btn_local_remote": "本機列表 合併 遠程列表",
"sync__merge_btn_remote_local": "遠程列表 合併 本機列表",
"sync__merge_label": "合併",
"sync__merge_tip": "合併:",
"sync__merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
"sync__other_label": "其他",
"sync__other_tip": "其他:",
"sync__other_tip_desc": "“取消同步”將不使用列表同步功能。",
"sync__overwrite": "完全覆蓋",
"sync__overwrite_btn_cancel": "取消同步",
"sync__overwrite_btn_local_remote": "本機列表 覆蓋 遠程列表",
@ -602,8 +609,6 @@
"sync__overwrite_btn_remote_local": "遠程列表 覆蓋 本機列表",
"sync__overwrite_label": "覆蓋",
"sync__overwrite_tip": "覆蓋:",
"sync__overwrite_tip_desc": "被覆蓋者與覆蓋者列表ID相同的列表將被刪除後替換成覆蓋者的列表列表ID不同的列表將被合併到一起若勾選完全覆蓋則被覆蓋者的所有列表將被移除然後替換成覆蓋者的列表。",
"sync__title": "選擇與 {name} 的列表同步方式",
"sync_status_disabled": "未連接",
"tag__high_quality": "HQ",
"tag__lossless": "SQ",

View File

@ -6,7 +6,7 @@ import { getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
import { navigationUrlWhiteList } from '@common/config'
import defaultSetting from '@common/defaultSetting'
import { closeWindow, isExistWindow as isExistMainWindow, showWindow as showMainWindow } from './modules/winMain'
import { createAppEvent, createListEvent } from '@main/event'
import { createAppEvent, createDislikeEvent, createListEvent } from '@main/event'
import { isMac, log } from '@common/utils'
import createWorkers from './worker'
import { migrateDBData } from './utils/migrate'
@ -212,6 +212,7 @@ export const initAppSetting = async() => {
// mainWindowClosed: true,
event_app: createAppEvent(),
event_list: createListEvent(),
event_dislike: createDislikeEvent(),
appSetting: defaultSetting,
worker: createWorkers(),
hotKey: {

View File

@ -0,0 +1,56 @@
import { EventEmitter } from 'events'
export class Event extends EventEmitter {
dislike_changed() {
this.emit('dislike_changed')
}
/**
*
* @param dislikeData
* @param isRemote
*/
async dislike_data_overwrite(dislikeData: LX.Dislike.DislikeRules, isRemote: boolean = false) {
await global.lx.worker.dbService.dislikeInfoOverwrite(dislikeData)
this.emit('dislike_data_overwrite', dislikeData, isRemote)
this.dislike_changed()
}
/**
*
* @param dislikeId id
* @param musicInfos
* @param addMusicLocationType
* @param isRemote
*/
async dislike_music_add(musicInfo: LX.Dislike.DislikeMusicInfo[], isRemote: boolean = false) {
// const changedIds =
await global.lx.worker.dbService.dislikeInfoAdd(musicInfo)
// await checkUpdateDislike(changedIds)
this.emit('dislike_music_add', musicInfo, isRemote)
this.dislike_changed()
}
/**
*
* @param ids Id
* @param isRemote
*/
async dislike_music_clear(isRemote: boolean = false) {
// const changedIds =
await global.lx.worker.dbService.dislikeInfoOverwrite('')
// await checkUpdateDislike(changedIds)
this.emit('dislike_music_clear', isRemote)
this.dislike_changed()
}
}
type EventMethods = Omit<EventType, keyof EventEmitter>
declare class EventType extends Event {
on<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
once<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
off<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
}
export type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>

View File

@ -1,9 +1,11 @@
import { Event as App, type Type as AppType } from './AppEvent'
import { Event as List, type Type as ListType } from './ListEvent'
import { Event as Dislike, type Type as DislikeType } from './DislikeEvent'
export type {
AppType,
ListType,
DislikeType,
}
export const createAppEvent = (): AppType => {
@ -14,3 +16,7 @@ export const createListEvent = (): ListType => {
return new List()
}
export const createDislikeEvent = (): DislikeType => {
return new Dislike()
}

View File

@ -0,0 +1,3 @@
export { registerRendererEvents } from './winRendererEvent'
export { default } from './rendererEvent'

View File

@ -0,0 +1,18 @@
import { mainHandle } from '@common/mainIpc'
import { DISLIKE_EVENT_NAME } from '@common/ipcNames'
// 列表操作事件(公共,只注册一次)
export default () => {
mainHandle<LX.Dislike.DislikeInfo>(DISLIKE_EVENT_NAME.get_dislike_music_infos, async() => {
return global.lx.worker.dbService.getDislikeListInfo()
})
mainHandle<LX.Dislike.DislikeMusicInfo[]>(DISLIKE_EVENT_NAME.add_dislike_music_infos, async({ params: listData }) => {
await global.lx.event_dislike.dislike_music_add(listData, false)
})
mainHandle<LX.Dislike.DislikeRules>(DISLIKE_EVENT_NAME.overwrite_dislike_music_infos, async({ params: rules }) => {
await global.lx.event_dislike.dislike_data_overwrite(rules, false)
})
mainHandle(DISLIKE_EVENT_NAME.clear_dislike_music_infos, async() => {
await global.lx.event_dislike.dislike_music_clear(false)
})
}

View File

@ -0,0 +1,24 @@
import { DISLIKE_EVENT_NAME } from '@common/ipcNames'
// 发送列表操作事件到渲染进程的注册方法
// 哪个渲染进程需要接收则引入此方法注册
export const registerRendererEvents = (sendEvent: <T = any>(name: string, params?: T | undefined) => void) => {
const dislike_music_add = async(listData: LX.Dislike.DislikeMusicInfo[]) => {
sendEvent<LX.Dislike.DislikeMusicInfo[]>(DISLIKE_EVENT_NAME.add_dislike_music_infos, listData)
}
const dislike_data_overwrite = async(rules: LX.Dislike.DislikeRules) => {
sendEvent<LX.Dislike.DislikeRules>(DISLIKE_EVENT_NAME.overwrite_dislike_music_infos, rules)
}
const dislike_music_clear = async() => {
sendEvent(DISLIKE_EVENT_NAME.clear_dislike_music_infos)
}
global.lx.event_dislike.on('dislike_music_add', dislike_music_add)
global.lx.event_dislike.on('dislike_data_overwrite', dislike_data_overwrite)
global.lx.event_dislike.on('dislike_music_clear', dislike_music_clear)
return () => {
global.lx.event_dislike.off('dislike_music_add', dislike_music_add)
global.lx.event_dislike.off('dislike_data_overwrite', dislike_data_overwrite)
global.lx.event_dislike.off('dislike_music_clear', dislike_music_clear)
}
}

View File

@ -1,7 +1,9 @@
import common from './common'
import list from './list'
import dislike from './dislike'
export default () => {
common()
list()
dislike()
}

View File

@ -200,6 +200,7 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client
client.remote = message2read.remote
client.remoteQueueList = message2read.createQueueRemote('list')
client.remoteQueueDislike = message2read.createQueueRemote('dislike')
client.addEventListener('message', ({ data }) => {
if (data == 'ping') return
@ -235,6 +236,7 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client
client!.isReady = false
client!.moduleReadys = {
list: false,
dislike: false,
}
disconnected = false
sendSyncStatus({

View File

@ -0,0 +1,68 @@
// 这个文件导出的方法将暴露给服务端调用,第一个参数固定为当前 socket 对象
import { handleRemoteDislikeAction, getLocalDislikeData, setLocalDislikeData } from '@main/modules/sync/dislikeEvent'
import { toMD5 } from '@common/utils/nodejs'
import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
import log from '@main/modules/sync/log'
import { registerEvent, unregisterEvent } from './localEvent'
const logInfo = (eventName: string, success = false) => {
log.info(`[${eventName}]${eventName.replace('dislike:sync:dislike_sync_', '').replaceAll('_', ' ')}${success ? ' success' : ''}`)
}
// const logError = (eventName: string, err: Error) => {
// log.error(`[${eventName}]${eventName.replace('dislike:sync:dislike_sync_', '').replaceAll('_', ' ')} error: ${err.message}`)
// }
const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise<LX.Sync.Dislike.SyncMode> => new Promise((resolve, reject) => {
const handleDisconnect = (err: Error) => {
sendCloseSelectMode()
removeSelectModeListener()
reject(err)
}
let removeEventClose = socket.onClose(handleDisconnect)
sendSelectMode(socket.data.keyInfo.serverName, 'dislike', (mode) => {
if (mode == null) {
reject(new Error('cancel'))
return
}
resolve(mode)
removeSelectModeListener()
removeEventClose()
})
})
const handler: LX.Sync.ClientSyncHandlerDislikeActions<LX.Sync.Client.Socket> = {
async onDislikeSyncAction(socket, action) {
if (!socket.moduleReadys?.dislike) return
await handleRemoteDislikeAction(action)
},
async dislike_sync_get_md5(socket) {
logInfo('dislike:sync:dislike_sync_get_md5')
return toMD5((await getLocalDislikeData()).trim())
},
async dislike_sync_get_sync_mode(socket) {
return getSyncMode(socket)
},
async dislike_sync_get_list_data(socket) {
logInfo('dislike:sync:dislike_sync_get_list_data')
return getLocalDislikeData()
},
async dislike_sync_set_list_data(socket, data) {
logInfo('dislike:sync:dislike_sync_set_list_data')
await setLocalDislikeData(data)
},
async dislike_sync_finished(socket) {
logInfo('dislike:sync:finished')
socket.moduleReadys.dislike = true
registerEvent(socket)
socket.onClose(() => {
unregisterEvent()
})
},
}
export default handler

View File

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

View File

@ -0,0 +1,21 @@
import { registerDislikeActionEvent } from '@main/modules/sync/dislikeEvent'
let unregisterLocalListAction: (() => void) | null
export const registerEvent = (socket: LX.Sync.Client.Socket) => {
// socket = _socket
// socket.onClose(() => {
// unregisterLocalListAction?.()
// unregisterLocalListAction = null
// })
unregisterEvent()
unregisterLocalListAction = registerDislikeActionEvent((action) => {
if (!socket.moduleReadys?.dislike) return
void socket.remoteQueueDislike.onDislikeSyncAction(action)
})
}
export const unregisterEvent = () => {
unregisterLocalListAction?.()
unregisterLocalListAction = null
}

View File

@ -1,14 +1,20 @@
import * as list from './list'
import * as dislike from './dislike'
// export * as theme from './theme'
export const callObj = Object.assign({}, list.handler)
export const callObj = Object.assign({},
list.handler,
dislike.handler,
)
export const modules = {
list,
dislike,
}
export const featureVersion = {
list: 1,
dislike: 1,
} as const

View File

@ -1,6 +1,9 @@
// 这个文件导出的方法将暴露给服务端调用,第一个参数固定为当前 socket 对象
import { handleRemoteListAction } from '@main/modules/sync/utils'
import { getLocalListData, setLocalListData } from '../../../utils'
import {
handleRemoteListAction,
getLocalListData,
setLocalListData,
} from '@main/modules/sync/listEvent'
import { toMD5 } from '@common/utils/nodejs'
import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
import log from '@main/modules/sync/log'
@ -12,17 +15,6 @@ const logInfo = (eventName: string, success = false) => {
// const logError = (eventName: string, err: Error) => {
// log.error(`[${eventName}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')} error: ${err.message}`)
// }
export const onListSyncAction = async(socket: LX.Sync.Client.Socket, action: LX.Sync.List.ActionList) => {
if (!socket.moduleReadys?.list) return
await handleRemoteListAction(action)
}
export const list_sync_get_md5 = async(socket: LX.Sync.Client.Socket) => {
logInfo('list:sync:list_sync_get_md5')
return toMD5(JSON.stringify(await getLocalListData()))
}
const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise<LX.Sync.List.SyncMode> => new Promise((resolve, reject) => {
const handleDisconnect = (err: Error) => {
sendCloseSelectMode()
@ -30,7 +22,7 @@ const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise<LX.Sync.List.S
reject(err)
}
let removeEventClose = socket.onClose(handleDisconnect)
sendSelectMode(socket.data.keyInfo.serverName, (mode) => {
sendSelectMode(socket.data.keyInfo.serverName, 'list', (mode) => {
if (mode == null) {
reject(new Error('cancel'))
return
@ -40,26 +32,41 @@ const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise<LX.Sync.List.S
removeEventClose()
})
})
export const list_sync_get_sync_mode = async(socket: LX.Sync.Client.Socket) => {
return getSyncMode(socket)
}
export const list_sync_get_list_data = async(socket: LX.Sync.Client.Socket) => {
logInfo('list:sync:list_sync_get_list_data')
return getLocalListData()
}
export const list_sync_set_list_data = async(socket: LX.Sync.Client.Socket, data: LX.Sync.List.ListData) => {
logInfo('list:sync:list_sync_set_list_data')
await setLocalListData(data)
}
export const list_sync_finished = async(socket: LX.Sync.Client.Socket) => {
logInfo('list:sync:finished')
socket.moduleReadys.list = true
registerEvent(socket)
socket.onClose(() => {
unregisterEvent()
})
const handler: LX.Sync.ClientSyncHandlerListActions<LX.Sync.Client.Socket> = {
async onListSyncAction(socket, action) {
if (!socket.moduleReadys?.list) return
await handleRemoteListAction(action)
},
async list_sync_get_md5(socket) {
logInfo('list:sync:list_sync_get_md5')
return toMD5(JSON.stringify(await getLocalListData()))
},
async list_sync_get_sync_mode(socket) {
return getSyncMode(socket)
},
async list_sync_get_list_data(socket) {
logInfo('list:sync:list_sync_get_list_data')
return getLocalListData()
},
async list_sync_set_list_data(socket, data) {
logInfo('list:sync:list_sync_set_list_data')
await setLocalListData(data)
},
async list_sync_finished(socket) {
logInfo('list:sync:finished')
socket.moduleReadys.list = true
registerEvent(socket)
socket.onClose(() => {
unregisterEvent()
})
},
}
export default handler

View File

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

View File

@ -1,4 +1,4 @@
import { registerListActionEvent } from '@main/modules/sync/utils'
import { registerListActionEvent } from '@main/modules/sync/listEvent'
let unregisterLocalListAction: (() => void) | null

View File

@ -12,11 +12,13 @@ export const getEnabledFeatures = async(socket: LX.Sync.Client.Socket, serverTyp
case 'server':
return {
list: featureVersion.list == supportedFeatures.list,
dislike: featureVersion.dislike == supportedFeatures.dislike,
}
case 'desktop-app':
default:
return {
list: featureVersion.list == supportedFeatures.list,
dislike: featureVersion.dislike == supportedFeatures.dislike,
}
}
}

View File

@ -0,0 +1,50 @@
export const getLocalDislikeData = async(): Promise<LX.Dislike.DislikeRules> => {
return (await global.lx.worker.dbService.getDislikeListInfo()).rules
}
export const setLocalDislikeData = async(listData: LX.Dislike.DislikeRules) => {
await global.lx.event_dislike.dislike_data_overwrite(listData, true)
}
export const registerDislikeActionEvent = (sendDislikeAction: (action: LX.Sync.Dislike.ActionList) => (void | Promise<void>)) => {
const dislike_music_add = async(listData: LX.Dislike.DislikeMusicInfo[], isRemote: boolean = false) => {
if (isRemote) return
await sendDislikeAction({ action: 'dislike_music_add', data: listData })
}
const dislike_data_overwrite = async(listInfos: LX.Dislike.DislikeRules, isRemote: boolean = false) => {
if (isRemote) return
await sendDislikeAction({ action: 'dislike_data_overwrite', data: listInfos })
}
const dislike_music_clear = async(isRemote: boolean = false) => {
if (isRemote) return
await sendDislikeAction({ action: 'dislike_music_clear' })
}
global.lx.event_dislike.on('dislike_music_add', dislike_music_add)
global.lx.event_dislike.on('dislike_data_overwrite', dislike_data_overwrite)
global.lx.event_dislike.on('dislike_music_clear', dislike_music_clear)
return () => {
global.lx.event_dislike.off('dislike_music_add', dislike_music_add)
global.lx.event_dislike.off('dislike_data_overwrite', dislike_data_overwrite)
global.lx.event_dislike.off('dislike_music_clear', dislike_music_clear)
}
}
export const handleRemoteDislikeAction = async(event: LX.Sync.Dislike.ActionList) => {
// console.log('handleRemoteDislikeAction', event)
switch (event.action) {
case 'dislike_music_add':
await global.lx.event_dislike.dislike_music_add(event.data, true)
break
case 'dislike_data_overwrite':
await global.lx.event_dislike.dislike_data_overwrite(event.data, true)
break
case 'dislike_music_clear':
await global.lx.event_dislike.dislike_music_clear(true)
break
default:
throw new Error('unknown list sync action')
}
}

View File

@ -0,0 +1,144 @@
import { LIST_IDS } from '@common/constants'
export const getLocalListData = async(): Promise<LX.Sync.List.ListData> => {
const lists: LX.Sync.List.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
}
export const setLocalListData = async(listData: LX.Sync.List.ListData) => {
await global.lx.event_list.list_data_overwrite(listData, true)
}
export const registerListActionEvent = (sendListAction: (action: LX.Sync.List.ActionList) => (void | Promise<void>)) => {
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.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)
}
}
export const handleRemoteListAction = async({ action, data }: LX.Sync.List.ActionList) => {
// console.log('handleRemoteListAction', action)
switch (action) {
case 'list_data_overwrite':
await global.lx.event_list.list_data_overwrite(data, true)
break
case 'list_create':
await global.lx.event_list.list_create(data.position, data.listInfos, true)
break
case 'list_remove':
await global.lx.event_list.list_remove(data, true)
break
case 'list_update':
await global.lx.event_list.list_update(data, true)
break
case 'list_update_position':
await global.lx.event_list.list_update_position(data.position, data.ids, true)
break
case 'list_music_add':
await global.lx.event_list.list_music_add(data.id, data.musicInfos, data.addMusicLocationType, true)
break
case 'list_music_move':
await global.lx.event_list.list_music_move(data.fromId, data.toId, data.musicInfos, data.addMusicLocationType, true)
break
case 'list_music_remove':
await global.lx.event_list.list_music_remove(data.listId, data.ids, true)
break
case 'list_music_update':
await global.lx.event_list.list_music_update(data, true)
break
case 'list_music_update_position':
await global.lx.event_list.list_music_update_position(data.listId, data.position, data.ids, true)
break
case 'list_music_overwrite':
await global.lx.event_list.list_music_overwrite(data.listId, data.musicInfos, true)
break
case 'list_music_clear':
await global.lx.event_list.list_music_clear(data, true)
break
default:
throw new Error('unknown list sync action')
}
}

View File

@ -0,0 +1,3 @@
export * as sync from './sync'
export { DislikeManage } from './manage'

View File

@ -0,0 +1,55 @@
import { type UserDataManage } from '../../user'
import { SnapshotDataManage } from './snapshotDataManage'
import { toMD5 } from '../../utils'
import { getLocalDislikeData } from '@main/modules/sync/dislikeEvent'
export class DislikeManage {
snapshotDataManage: SnapshotDataManage
constructor(userDataManage: UserDataManage) {
this.snapshotDataManage = new SnapshotDataManage(userDataManage)
}
createSnapshot = async() => {
const listData = await this.getDislikeRules()
const md5 = toMD5(listData.trim())
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((await this.getDislikeRules()).trim())
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)
}
removeDevice = async(clientId: string) => {
this.snapshotDataManage.removeSnapshotInfo(clientId)
}
getDislikeRules = async() => {
return getLocalDislikeData()
}
}

View File

@ -0,0 +1,143 @@
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 '../../../../../../common/constants_sync'
import { checkAndCreateDirSync } from '../../utils'
interface SnapshotInfo {
latest: string | null
time: number
list: string[]
clients: Record<string, LX.Sync.Dislike.ListInfo>
}
export class SnapshotDataManage {
userDataManage: UserDataManage
dislikeDir: 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()
}
removeSnapshotInfo = (clientId: string) => {
let client = this.snapshotInfo.clients[clientId]
if (!client) return
if (client.snapshotKey) this.clientSnapshotKeys.splice(this.clientSnapshotKeys.indexOf(client.snapshotKey), 1)
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete this.snapshotInfo.clients[clientId]
this.saveSnapshotInfoThrottle()
}
getSnapshot = async(name: string) => {
const filePath = path.join(this.snapshotDir, `snapshot_${name}`)
let listData: LX.Dislike.DislikeRules
try {
listData = (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 {
fs.writeFileSync(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 {
fs.unlinkSync(filePath)
} catch (err) {
syncLog.error(err)
}
}
constructor(userDataManage: UserDataManage) {
this.userDataManage = userDataManage
this.dislikeDir = path.join(userDataManage.userDir, File.dislikeDir)
checkAndCreateDirSync(this.dislikeDir)
this.snapshotDir = path.join(this.dislikeDir, File.dislikeSnapshotDir)
checkAndCreateDirSync(this.snapshotDir)
this.snapshotInfoFilePath = path.join(this.dislikeDir, File.dislikeSnapshotInfoJSON)
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,35 @@
// 这个文件导出的方法将暴露给客户端调用,第一个参数固定为当前 socket 对象
// import { throttle } from '@common/utils/common'
// import { sendSyncActionList } from '@main/modules/winMain'
// import { SYNC_CLOSE_CODE } from '@/constants'
// import { SYNC_CLOSE_CODE } from '@common/constants_sync'
import { SYNC_CLOSE_CODE } from '@common/constants_sync'
import { getUserSpace } from '@main/modules/sync/server/user'
import { handleRemoteDislikeAction } from '@main/modules/sync/dislikeEvent'
// import { encryptMsg } from '@/utils/tools'
const handler: LX.Sync.ServerSyncHandlerDislikeActions<LX.Sync.Server.Socket> = {
async onDislikeSyncAction(socket, action) {
if (!socket.moduleReadys.dislike) return
await handleRemoteDislikeAction(action)
const userSpace = getUserSpace(socket.userInfo.name)
const key = await userSpace.dislikeManage.createSnapshot()
userSpace.dislikeManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, key)
const currentUserName = socket.userInfo.name
const currentId = socket.keyInfo.clientId
socket.broadcast((client) => {
if (client.keyInfo.clientId == currentId || !client.moduleReadys?.dislike || client.userInfo.name != currentUserName) return
void client.remoteQueueDislike.onDislikeSyncAction(action).then(async() => {
return userSpace.dislikeManage.updateDeviceSnapshotKey(client.keyInfo.clientId, key)
}).catch(err => {
// TODO send status
client.close(SYNC_CLOSE_CODE.failed)
// client.moduleReadys.dislike = false
console.log(err.message)
})
})
},
}
export default handler

View File

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

View File

@ -0,0 +1,43 @@
import { SYNC_CLOSE_CODE } from '@common/constants_sync'
import { registerDislikeActionEvent } from '../../../../dislikeEvent'
import { getUserSpace } from '../../../user'
// let socket: LX.Sync.Server.Socket | null
let unregisterLocalListAction: (() => void) | null
const sendListAction = async(wss: LX.Sync.Server.SocketServer, action: LX.Sync.Dislike.ActionList) => {
// console.log('sendListAction', action.action)
const userSpace = getUserSpace()
let key = ''
for (const client of wss.clients) {
if (!client.moduleReadys?.dislike) continue
// eslint-disable-next-line require-atomic-updates
if (!key) key = await userSpace.dislikeManage.createSnapshot()
void client.remoteQueueDislike.onDislikeSyncAction(action).then(async() => {
return userSpace.dislikeManage.updateDeviceSnapshotKey(client.keyInfo.clientId, key)
}).catch(err => {
// TODO send status
client.close(SYNC_CLOSE_CODE.failed)
// client.moduleReadys.dislike = false
console.log(err.message)
})
}
}
export const registerEvent = (wss: LX.Sync.Server.SocketServer) => {
// socket = _socket
// socket.onClose(() => {
// unregisterLocalListAction?.()
// unregisterLocalListAction = null
// })
unregisterEvent()
unregisterLocalListAction = registerDislikeActionEvent((action) => {
void sendListAction(wss, action)
})
}
export const unregisterEvent = () => {
unregisterLocalListAction?.()
unregisterLocalListAction = null
}

View File

@ -0,0 +1,232 @@
// import { SYNC_CLOSE_CODE } from '../../../../constants'
import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
import { getUserSpace } from '../../../user'
import { getLocalDislikeData, setLocalDislikeData } from '@main/modules/sync/dislikeEvent'
import { SYNC_CLOSE_CODE } from '@common/constants_sync'
import { filterRules } from '../utils'
// import { LIST_IDS } from '@common/constants'
// type ListInfoType = LX.Dislike.UserListInfoFull | LX.Dislike.MyDefaultListInfoFull | LX.Dislike.MyLoveListInfoFull
// let wss: LX.Sync.Server.SocketServer | null
let syncingId: string | null = null
const wait = async(time = 1000) => await new Promise((resolve, reject) => setTimeout(resolve, time))
const getRemoteListData = async(socket: LX.Sync.Server.Socket): Promise<LX.Dislike.DislikeRules> => {
console.log('getRemoteListData')
return (await socket.remoteQueueDislike.dislike_sync_get_list_data()) ?? ''
}
const getRemoteDataMD5 = async(socket: LX.Sync.Server.Socket): Promise<string> => {
return socket.remoteQueueDislike.dislike_sync_get_md5()
}
// const getLocalDislikeData async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.Dislike.ListData> => {
// return getUserSpace(socket.userInfo.name).listManage.getListData()
// }
const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.Dislike.SyncMode> => new Promise((resolve, reject) => {
const handleDisconnect = (err: Error) => {
sendCloseSelectMode()
removeSelectModeListener()
reject(err)
}
let removeEventClose = socket.onClose(handleDisconnect)
sendSelectMode(socket.keyInfo.deviceName, 'dislike', (mode) => {
if (mode == null) {
reject(new Error('cancel'))
return
}
resolve(mode)
removeSelectModeListener()
removeEventClose()
})
})
// const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.Dislike.SyncMode> => {
// return socket.remoteQueueDislike.list_sync_get_sync_mode()
// }
const finishedSync = async(socket: LX.Sync.Server.Socket) => {
await socket.remoteQueueDislike.dislike_sync_finished()
}
const setLocalList = async(socket: LX.Sync.Server.Socket, listData: LX.Dislike.DislikeRules) => {
await setLocalDislikeData(listData)
const userSpace = getUserSpace(socket.userInfo.name)
return userSpace.listManage.createSnapshot()
}
const overwriteRemoteListData = async(socket: LX.Sync.Server.Socket, listData: LX.Dislike.DislikeRules, key: string, excludeIds: string[] = []) => {
const action = { action: 'dislike_data_overwrite', data: listData } as const
const tasks: Array<Promise<void>> = []
const userSpace = getUserSpace(socket.userInfo.name)
socket.broadcast((client) => {
if (excludeIds.includes(client.keyInfo.clientId) || client.userInfo?.name != socket.userInfo.name || !client.moduleReadys?.dislike) return
tasks.push(client.remoteQueueDislike.onDislikeSyncAction(action).then(async() => {
return userSpace.dislikeManage.updateDeviceSnapshotKey(client.keyInfo.clientId, key)
}).catch(err => {
// TODO send status
client.close(SYNC_CLOSE_CODE.failed)
// client.moduleReadys.list = false
console.log(err.message)
}))
})
if (!tasks.length) return
await Promise.all(tasks)
}
const setRemotelList = async(socket: LX.Sync.Server.Socket, listData: LX.Dislike.DislikeRules, key: string): Promise<void> => {
await socket.remoteQueueDislike.dislike_sync_set_list_data(listData)
const userSpace = getUserSpace(socket.userInfo.name)
await userSpace.dislikeManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, key)
}
const mergeList = (socket: LX.Sync.Server.Socket, sourceListData: LX.Dislike.DislikeRules, targetListData: LX.Dislike.DislikeRules): LX.Dislike.DislikeRules => {
return Array.from(filterRules(sourceListData + '\n' + targetListData)).join('\n')
}
const handleMergeListData = async(socket: LX.Sync.Server.Socket): Promise<[LX.Dislike.DislikeRules, boolean, boolean]> => {
const mode: LX.Sync.Dislike.SyncMode = await getSyncMode(socket)
if (mode == 'cancel') throw new Error('cancel')
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalDislikeData()])
console.log('handleMergeListData', 'remoteListData, localListData')
let listData: LX.Dislike.DislikeRules
let requiredUpdateLocalListData = true
let requiredUpdateRemoteListData = true
switch (mode) {
case 'merge_local_remote':
listData = mergeList(socket, localListData, remoteListData)
break
case 'merge_remote_local':
listData = mergeList(socket, remoteListData, localListData)
break
case 'overwrite_local_remote':
listData = localListData
requiredUpdateLocalListData = false
break
case 'overwrite_remote_local':
listData = remoteListData
requiredUpdateRemoteListData = false
break
// case 'none': return null
// case 'cancel':
default: throw new Error('cancel')
}
return [listData, requiredUpdateLocalListData, requiredUpdateRemoteListData]
}
const handleSyncList = async(socket: LX.Sync.Server.Socket) => {
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalDislikeData()])
console.log('handleSyncList', 'remoteListData, localListData')
console.log('localListData', localListData.length)
console.log('remoteListData', remoteListData.length)
const userSpace = getUserSpace(socket.userInfo.name)
const clientId = socket.keyInfo.clientId
if (localListData.length) {
if (remoteListData.length) {
const [mergedList, requiredUpdateLocalListData, requiredUpdateRemoteListData] = await handleMergeListData(socket)
console.log('handleMergeListData', 'mergedList', requiredUpdateLocalListData, requiredUpdateRemoteListData)
let key
if (requiredUpdateLocalListData) {
key = await setLocalList(socket, mergedList)
await overwriteRemoteListData(socket, mergedList, key, [clientId])
if (!requiredUpdateRemoteListData) await userSpace.dislikeManage.updateDeviceSnapshotKey(clientId, key)
}
if (requiredUpdateRemoteListData) {
if (!key) key = await userSpace.dislikeManage.getCurrentListInfoKey()
await setRemotelList(socket, mergedList, key)
}
} else {
await setRemotelList(socket, localListData, await userSpace.dislikeManage.getCurrentListInfoKey())
}
} else {
let key: string
if (remoteListData.length) {
key = await setLocalList(socket, remoteListData)
await overwriteRemoteListData(socket, remoteListData, key, [clientId])
}
key ??= await userSpace.dislikeManage.getCurrentListInfoKey()
await userSpace.dislikeManage.updateDeviceSnapshotKey(clientId, key)
}
}
const mergeDataFromSnapshot = (
sourceList: LX.Dislike.DislikeRules,
targetList: LX.Dislike.DislikeRules,
snapshotList: LX.Dislike.DislikeRules,
): LX.Dislike.DislikeRules => {
const removedRules = new Set<string>()
const sourceRules = filterRules(sourceList)
const targetRules = filterRules(targetList)
if (snapshotList) {
const snapshotRules = filterRules(snapshotList)
for (const m of snapshotRules.values()) {
if (!sourceRules.has(m) || !targetRules.has(m)) removedRules.add(m)
}
}
return Array.from(new Set(Array.from([...sourceRules, ...targetRules]).filter((rule) => {
return !removedRules.has(rule)
}))).join('\n')
}
const checkListLatest = async(socket: LX.Sync.Server.Socket) => {
const remoteListMD5 = await getRemoteDataMD5(socket)
const userSpace = getUserSpace(socket.userInfo.name)
const userCurrentListInfoKey = await userSpace.dislikeManage.getDeviceCurrentSnapshotKey(socket.keyInfo.clientId)
const currentListInfoKey = await userSpace.dislikeManage.getCurrentListInfoKey()
const latest = remoteListMD5 == currentListInfoKey
if (latest && userCurrentListInfoKey != currentListInfoKey) await userSpace.dislikeManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, currentListInfoKey)
return latest
}
const handleMergeListDataFromSnapshot = async(socket: LX.Sync.Server.Socket, snapshot: LX.Dislike.DislikeRules) => {
if (await checkListLatest(socket)) return
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalDislikeData()])
const newDislikeData = mergeDataFromSnapshot(localListData, remoteListData, snapshot)
const key = await setLocalList(socket, newDislikeData)
const err = await setRemotelList(socket, newDislikeData, key).catch(err => err)
await overwriteRemoteListData(socket, newDislikeData, key, [socket.keyInfo.clientId])
if (err) throw err
}
const syncDislike = async(socket: LX.Sync.Server.Socket) => {
// socket.data.snapshotFilePath = getSnapshotFilePath(socket.keyInfo)
// console.log(socket.keyInfo)
const user = getUserSpace(socket.userInfo.name)
const userCurrentDislikeInfoKey = await user.dislikeManage.getDeviceCurrentSnapshotKey(socket.keyInfo.clientId)
if (userCurrentDislikeInfoKey) {
const listData = await user.dislikeManage.snapshotDataManage.getSnapshot(userCurrentDislikeInfoKey)
if (listData) {
console.log('handleMergeDislikeDataFromSnapshot')
await handleMergeListDataFromSnapshot(socket, listData)
return
}
}
await handleSyncList(socket)
}
export const sync = async(socket: LX.Sync.Server.Socket) => {
let disconnected = false
socket.onClose(() => {
disconnected = true
if (syncingId == socket.keyInfo.clientId) syncingId = null
})
while (true) {
if (disconnected) throw new Error('disconnected')
if (!syncingId) break
await wait()
}
syncingId = socket.keyInfo.clientId
await syncDislike(socket).then(async() => {
await finishedSync(socket)
socket.moduleReadys.dislike = true
}).finally(() => {
syncingId = null
})
}

View File

@ -0,0 +1,23 @@
import { SPLIT_CHAR } from '@common/constants'
export const filterRules = (rules: string) => {
const list: string[] = []
for (const item of rules.split('\n')) {
if (!item) continue
let [name, singer] = item.split(SPLIT_CHAR.DISLIKE_NAME)
if (name) {
name = name.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim()
if (singer) {
singer = singer.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim()
list.push(`${name}${SPLIT_CHAR.DISLIKE_NAME}${singer}`)
} else {
list.push(name)
}
} else if (singer) {
singer = singer.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim()
list.push(`${SPLIT_CHAR.DISLIKE_NAME}${singer}`)
}
}
return new Set(list)
}

View File

@ -1,16 +1,22 @@
import { sync } from './list'
import { sync as listSync } from './list'
import { sync as dislikeSync } from './dislike'
export const callObj = Object.assign({},
sync.handler,
listSync.handler,
dislikeSync.handler,
)
export const modules = {
list: sync,
list: listSync,
dislike: dislikeSync,
}
export { ListManage } from './list'
export { DislikeManage } from './dislike'
export const featureVersion = {
list: 1,
dislike: 1,
} as const

View File

@ -1,7 +1,7 @@
import { type UserDataManage } from '../../user'
import { SnapshotDataManage } from './snapshotDataManage'
import { toMD5 } from '../../utils'
import { getLocalListData } from '@main/modules/sync/utils'
import { getLocalListData } from '@main/modules/sync/listEvent'
export class ListManage {
snapshotDataManage: SnapshotDataManage

View File

@ -5,7 +5,7 @@
// import { SYNC_CLOSE_CODE } from '@common/constants_sync'
import { SYNC_CLOSE_CODE } from '@common/constants_sync'
import { getUserSpace } from '@main/modules/sync/server/user'
import { handleRemoteListAction } from '@main/modules/sync/utils'
import { handleRemoteListAction } from '@main/modules/sync/listEvent'
// import { encryptMsg } from '@/utils/tools'
// let wss: LX.SocketServer | null
@ -146,23 +146,27 @@ import { handleRemoteListAction } from '@main/modules/sync/utils'
// // }
// }
export const onListSyncAction = async(socket: LX.Sync.Server.Socket, action: LX.Sync.List.ActionList) => {
if (!socket.moduleReadys.list) return
await handleRemoteListAction(action)
const userSpace = getUserSpace(socket.userInfo.name)
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.moduleReadys?.list || client.userInfo.name != currentUserName) return
void client.remoteQueueList.onListSyncAction(action).then(async() => {
return userSpace.listManage.updateDeviceSnapshotKey(client.keyInfo.clientId, key)
}).catch(err => {
// TODO send status
client.close(SYNC_CLOSE_CODE.failed)
// client.moduleReadys.list = false
console.log(err.message)
const handler: LX.Sync.ServerSyncHandlerListActions<LX.Sync.Server.Socket> = {
async onListSyncAction(socket, action) {
if (!socket.moduleReadys.list) return
await handleRemoteListAction(action)
const userSpace = getUserSpace(socket.userInfo.name)
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.moduleReadys?.list || client.userInfo.name != currentUserName) return
void client.remoteQueueList.onListSyncAction(action).then(async() => {
return userSpace.listManage.updateDeviceSnapshotKey(client.keyInfo.clientId, key)
}).catch(err => {
// TODO send status
client.close(SYNC_CLOSE_CODE.failed)
// client.moduleReadys.list = false
console.log(err.message)
})
})
})
},
}
export default handler

View File

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

View File

@ -1,5 +1,5 @@
import { SYNC_CLOSE_CODE } from '@common/constants_sync'
import { registerListActionEvent } from '../../../../utils'
import { registerListActionEvent } from '../../../../listEvent'
import { getUserSpace } from '../../../user'
// let socket: LX.Sync.Server.Socket | null

View File

@ -1,7 +1,7 @@
// import { SYNC_CLOSE_CODE } from '../../../../constants'
import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
import { getUserSpace, getUserConfig } from '../../../user'
import { getLocalListData, setLocalListData } from '@main/modules/sync/utils'
import { getLocalListData, setLocalListData } from '@main/modules/sync/listEvent'
import { SYNC_CLOSE_CODE } from '@common/constants_sync'
// import { LIST_IDS } from '@common/constants'
@ -38,7 +38,7 @@ const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.List.S
reject(err)
}
let removeEventClose = socket.onClose(handleDisconnect)
sendSelectMode(socket.keyInfo.deviceName, (mode) => {
sendSelectMode(socket.keyInfo.deviceName, 'list', (mode) => {
if (mode == null) {
reject(new Error('cancel'))
return

View File

@ -156,9 +156,11 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
socket.isReady = false
socket.moduleReadys = {
list: false,
dislike: false,
}
socket.feature = {
list: false,
dislike: false,
}
socket.on('pong', () => {
socket.isAlive = true
@ -196,6 +198,7 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
})
socket.remote = msg2call.remote
socket.remoteQueueList = msg2call.createQueueRemote('list')
socket.remoteQueueDislike = msg2call.createQueueRemote('dislike')
socket.addEventListener('message', ({ data }) => {
if (typeof data != 'string') return
void decryptMsg(socket.keyInfo, data).then((data) => {

View File

@ -1,11 +1,13 @@
import { UserDataManage } from './data'
import {
ListManage,
DislikeManage,
} from '../modules'
export interface UserSpace {
dataManage: UserDataManage
listManage: ListManage
dislikeManage: DislikeManage
getDecices: () => Promise<LX.Sync.ServerKeyInfo[]>
removeDevice: (clientId: string) => Promise<void>
}
@ -34,9 +36,11 @@ export const getUserSpace = (userName = 'default') => {
console.log('new user data manage:', userName)
const dataManage = new UserDataManage(userName)
const listManage = new ListManage(dataManage)
const dislikeManage = new DislikeManage(dataManage)
users.set(userName, user = {
dataManage,
listManage,
dislikeManage,
async getDecices() {
return this.dataManage.getAllClientKeyInfo()
},

View File

@ -2,7 +2,6 @@ import { createCipheriv, createDecipheriv, publicEncrypt, privateDecrypt, consta
import os, { networkInterfaces } from 'node:os'
import zlib from 'node:zlib'
import cp from 'node:child_process'
import { LIST_IDS } from '@common/constants'
export const getAddress = (): string[] => {
@ -89,146 +88,3 @@ export const rsaEncrypt = (buffer: Buffer, key: string): string => {
export const rsaDecrypt = (buffer: Buffer, key: string): Buffer => {
return privateDecrypt({ key, padding: constants.RSA_PKCS1_OAEP_PADDING }, buffer)
}
export const getLocalListData = async(): Promise<LX.Sync.List.ListData> => {
const lists: LX.Sync.List.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
}
export const setLocalListData = async(listData: LX.Sync.List.ListData) => {
await global.lx.event_list.list_data_overwrite(listData, true)
}
export const registerListActionEvent = (sendListAction: (action: LX.Sync.List.ActionList) => (void | Promise<void>)) => {
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.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)
}
}
export const handleRemoteListAction = async({ action, data }: LX.Sync.List.ActionList) => {
// console.log('handleRemoteListAction', action)
switch (action) {
case 'list_data_overwrite':
await global.lx.event_list.list_data_overwrite(data, true)
break
case 'list_create':
await global.lx.event_list.list_create(data.position, data.listInfos, true)
break
case 'list_remove':
await global.lx.event_list.list_remove(data, true)
break
case 'list_update':
await global.lx.event_list.list_update(data, true)
break
case 'list_update_position':
await global.lx.event_list.list_update_position(data.position, data.ids, true)
break
case 'list_music_add':
await global.lx.event_list.list_music_add(data.id, data.musicInfos, data.addMusicLocationType, true)
break
case 'list_music_move':
await global.lx.event_list.list_music_move(data.fromId, data.toId, data.musicInfos, data.addMusicLocationType, true)
break
case 'list_music_remove':
await global.lx.event_list.list_music_remove(data.listId, data.ids, true)
break
case 'list_music_update':
await global.lx.event_list.list_music_update(data, true)
break
case 'list_music_update_position':
await global.lx.event_list.list_music_update_position(data.listId, data.position, data.ids, true)
break
case 'list_music_overwrite':
await global.lx.event_list.list_music_overwrite(data.listId, data.musicInfos, true)
break
case 'list_music_clear':
await global.lx.event_list.list_music_clear(data, true)
break
default:
throw new Error('unknown list sync action')
}
}

View File

@ -1,5 +1,6 @@
import { registerRendererEvents as common } from '@main/modules/commonRenderers/common'
import { registerRendererEvents as list } from '@main/modules/commonRenderers/list'
import { registerRendererEvents as dislike } from '@main/modules/commonRenderers/dislike'
import app, { sendConfigChange } from './app'
import hotKey from './hotKey'
import kw_decodeLyric from './kw_decodeLyric'
@ -25,6 +26,7 @@ export default () => {
common(sendEvent)
list(sendEvent)
dislike(sendEvent)
app()
hotKey()
kw_decodeLyric()

View File

@ -70,16 +70,6 @@ export default () => {
return global.lx.worker.dbService.musicInfoOtherSourceCount()
})
// =========================不喜欢的歌曲=========================
mainHandle<LX.Dislike.DislikeInfo>(WIN_MAIN_RENDERER_EVENT_NAME.get_dislike_music_infos, async() => {
return global.lx.worker.dbService.getDislikeListInfo()
})
mainHandle<LX.Dislike.DislikeMusicInfo[]>(WIN_MAIN_RENDERER_EVENT_NAME.add_dislike_music_infos, async({ params: infos }) => {
await global.lx.worker.dbService.dislikeInfoAdd(infos)
})
mainHandle<string>(WIN_MAIN_RENDERER_EVENT_NAME.overwrite_dislike_music_infos, async({ params: rules }) => {
await global.lx.worker.dbService.dislikeInfoOverwrite(rules)
})
// mainHandle<string[]>(WIN_MAIN_RENDERER_EVENT_NAME.remove_dislike_music_infos, async({ params: ids }) => {
// await global.lx.worker.dbService.dislikeInfoRemove(ids)
// })

View File

@ -13,7 +13,8 @@ import {
} from '@main/modules/sync'
import { sendEvent } from '../main'
let selectModeListenr: ((mode: LX.Sync.List.SyncMode | null) => void) | null = null
let selectModeListenr: ((mode: LX.Sync.ModeTypes[keyof LX.Sync.ModeTypes] | null) => void) | null = null
export default () => {
mainHandle<LX.Sync.SyncServiceActions, any>(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, async({ params: data }) => {
@ -29,7 +30,7 @@ export default () => {
case 'generate_code': return generateCode()
case 'select_mode':
if (selectModeListenr) {
selectModeListenr(data.data)
selectModeListenr(data.data.mode)
selectModeListenr = null
}
break
@ -62,9 +63,9 @@ export const sendServerStatus = (status: LX.Sync.ServerStatus) => {
data: status,
})
}
export const sendSelectMode = (deviceName: string, listener: (mode: LX.Sync.List.SyncMode | null) => void) => {
selectModeListenr = listener
sendSyncAction({ action: 'select_mode', data: deviceName })
export const sendSelectMode = <T extends keyof LX.Sync.ModeTypes>(deviceName: string, type: T, listener: (mode: LX.Sync.ModeTypes[T] | null) => void) => {
selectModeListenr = listener as typeof selectModeListenr
sendSyncAction({ action: 'select_mode', data: { deviceName, type } })
}
export const removeSelectModeListener = () => {
if (selectModeListenr) selectModeListenr(null)

View File

@ -1,7 +1,7 @@
/* eslint-disable no-var */
// import { Event as WinMainEvent } from '@main/modules/winMain/event'
// import { Event as WinLyricEvent } from '@main/modules/winLyric/event'
import { type AppType, type ListType } from '@main/event'
import { type DislikeType, type AppType, type ListType } from '@main/event'
import { type DBSeriveTypes } from '@main/worker/utils'
interface Lx {
@ -25,6 +25,7 @@ interface Lx {
// mainWindowClosed: boolean
event_app: AppType
event_list: ListType
event_dislike: DislikeType
worker: {
dbService: DBSeriveTypes
}

View File

@ -13,3 +13,4 @@ import '@common/types/theme'
import '@common/types/ipc_main'
import '@common/types/sound_effect'
import '@common/types/dislike_list'
import '@common/types/dislike_list_sync'

View File

@ -15,11 +15,13 @@ declare global {
}
moduleReadys: {
list: boolean
dislike: boolean
}
onClose: (handler: (err: Error) => (void | Promise<void>)) => () => void
remote: LX.Sync.ServerSyncActions
remoteQueueList: LX.Sync.ServerSyncListActions
remoteQueueDislike: LX.Sync.ServerSyncDislikeActions
}
interface UrlInfo {
@ -38,6 +40,7 @@ declare global {
feature: LX.Sync.EnabledFeatures
moduleReadys: {
list: boolean
dislike: boolean
}
onClose: (handler: (err: Error) => (void | Promise<void>)) => () => void
@ -45,6 +48,7 @@ declare global {
remote: LX.Sync.ClientSyncActions
remoteQueueList: LX.Sync.ClientSyncListActions
remoteQueueDislike: LX.Sync.ClientSyncDislikeActions
}
type SocketServer = WS.Server<Socket>
}

View File

@ -1,16 +1,30 @@
type WarpSyncHandlerActions<Socket, Actions> = {
[K in keyof Actions]: (...args: [Socket, ...Parameters<Actions[K]>]) => ReturnType<Actions[K]>
}
declare namespace LX {
namespace Sync {
type ServerSyncActions = WarpPromiseRecord<{
onFeatureChanged: (feature: EnabledFeatures) => void
}>
type ServerSyncHandlerActions<Socket> = WarpSyncHandlerActions<Socket, ServerSyncActions>
type ServerSyncListActions = WarpPromiseRecord<{
onListSyncAction: (action: LX.Sync.List.ActionList) => void
}>
type ServerSyncHandlerListActions<Socket> = WarpSyncHandlerActions<Socket, ServerSyncListActions>
type ServerSyncDislikeActions = WarpPromiseRecord<{
onDislikeSyncAction: (action: LX.Sync.Dislike.ActionList) => void
}>
type ServerSyncHandlerDislikeActions<Socket> = WarpSyncHandlerActions<Socket, ServerSyncDislikeActions>
type ClientSyncActions = WarpPromiseRecord<{
getEnabledFeatures: (serverType: ServerType, supportedFeatures: SupportedFeatures) => EnabledFeatures
finished: () => void
}>
type ClientSyncHandlerActions<Socket> = WarpSyncHandlerActions<Socket, ClientSyncActions>
type ClientSyncListActions = WarpPromiseRecord<{
onListSyncAction: (action: LX.Sync.List.ActionList) => void
list_sync_get_md5: () => string
@ -19,6 +33,17 @@ declare namespace LX {
list_sync_set_list_data: (data: LX.Sync.List.ListData) => void
list_sync_finished: () => void
}>
type ClientSyncHandlerListActions<Socket> = WarpSyncHandlerActions<Socket, ClientSyncListActions>
type ClientSyncDislikeActions = WarpPromiseRecord<{
onDislikeSyncAction: (action: LX.Sync.Dislike.ActionList) => void
dislike_sync_get_md5: () => string
dislike_sync_get_sync_mode: () => LX.Sync.Dislike.SyncMode
dislike_sync_get_list_data: () => LX.Dislike.DislikeRules
dislike_sync_set_list_data: (data: LX.Dislike.DislikeRules) => void
dislike_sync_finished: () => void
}>
type ClientSyncHandlerDislikeActions<Socket> = WarpSyncHandlerActions<Socket, ClientSyncDislikeActions>
}
}

View File

@ -51,7 +51,7 @@ const initDislikeList = () => {
}
}
dislikeInfo.rules = Array.from(new Set(list)).join('\n') + '\n'
dislikeInfo.rules = Array.from(new Set(list)).join('\n')
return dislikeInfo
}

View File

@ -1,7 +1,7 @@
<template>
<material-modal :show="sync.isShowSyncMode" :bg-close="false" :close-btn="false" @close="handleClose(false)">
<main :class="$style.main">
<h2>{{ $t('sync__title', { name: sync.deviceName }) }}</h2>
<main v-if="sync.type == 'list'" :class="$style.main">
<h2>{{ $t('sync__list_title', { name: sync.deviceName }) }}</h2>
<div class="scroll" :class="$style.content">
<dl :class="$style.btnGroup">
<dt :class="$style.label">{{ $t('sync__merge_label') }}</dt>
@ -31,15 +31,57 @@
<dd>
<section :class="$style.tipGroup">
<h3 :class="$style.title">{{ $t('sync__merge_tip') }}</h3>
<p :class="$style.tip">{{ $t('sync__merge_tip_desc') }}</p>
<p :class="$style.tip">{{ $t('sync__list_merge_tip_desc') }}</p>
</section>
<section :class="$style.tipGroup">
<h3 :class="$style.title">{{ $t('sync__overwrite_tip') }}</h3>
<p :class="$style.tip">{{ $t('sync__overwrite_tip_desc') }}</p>
<p :class="$style.tip">{{ $t('sync__list_overwrite_tip_desc') }}</p>
</section>
<section :class="$style.tipGroup">
<h3 :class="$style.title">{{ $t('sync__other_tip') }}</h3>
<p :class="$style.tip">{{ $t('sync__other_tip_desc') }}</p>
<p :class="$style.tip">{{ $t('sync__list_other_tip_desc') }}</p>
</section>
</dd>
</dl>
</div>
</main>
<main v-else-if="sync.type == 'dislike'" :class="$style.main">
<h2>{{ $t('sync__dislike_title', { name: sync.deviceName }) }}</h2>
<div class="scroll" :class="$style.content">
<dl :class="$style.btnGroup">
<dt :class="$style.label">{{ $t('sync__merge_label') }}</dt>
<dd :class="$style.btns">
<base-btn :class="$style.btn" @click="handleSelectMode('merge_local_remote')">{{ $t('sync__merge_btn_local_remote') }}</base-btn>
<base-btn :class="$style.btn" @click="handleSelectMode('merge_remote_local')">{{ $t('sync__merge_btn_remote_local') }}</base-btn>
</dd>
</dl>
<dl :class="$style.btnGroup">
<dt :class="$style.label">{{ $t('sync__overwrite_label') }}</dt>
<dd :class="$style.btns">
<base-btn :class="$style.btn" @click="handleSelectMode('overwrite_local_remote')">{{ $t('sync__overwrite_btn_local_remote') }}</base-btn>
<base-btn :class="$style.btn" @click="handleSelectMode('overwrite_remote_local')">{{ $t('sync__overwrite_btn_remote_local') }}</base-btn>
</dd>
</dl>
<dl :class="$style.btnGroup">
<dt :class="$style.label">{{ $t('sync__other_label') }}</dt>
<dd :class="$style.btns">
<!-- <base-btn :class="$style.btn" @click="handleSelectMode('none')">{{ $t('sync__overwrite_btn_none') }}</base-btn> -->
<base-btn :class="$style.btn" @click="handleSelectMode('cancel')">{{ $t('sync__overwrite_btn_cancel') }}</base-btn>
</dd>
</dl>
<dl :class="$style.btnGroup">
<dd>
<section :class="$style.tipGroup">
<h3 :class="$style.title">{{ $t('sync__merge_tip') }}</h3>
<p :class="$style.tip">{{ $t('sync__dislike_merge_tip_desc') }}</p>
</section>
<section :class="$style.tipGroup">
<h3 :class="$style.title">{{ $t('sync__overwrite_tip') }}</h3>
<p :class="$style.tip">{{ $t('sync__dislike_overwrite_tip_desc') }}</p>
</section>
<section :class="$style.tipGroup">
<h3 :class="$style.title">{{ $t('sync__other_tip') }}</h3>
<p :class="$style.tip">{{ $t('sync__dislike_other_tip_desc') }}</p>
</section>
</dd>
</dl>
@ -60,8 +102,10 @@ export default {
sync.isShowSyncMode = false
}
const handleSelectMode = (mode) => {
if (mode.startsWith('overwrite') && isOverwrite.value) mode += '_full'
void sendSyncAction({ action: 'select_mode', data: mode })
if (sync.type == 'list') {
if (mode.startsWith('overwrite') && isOverwrite.value) mode += '_full'
}
void sendSyncAction({ action: 'select_mode', data: { type: sync.type, mode } })
handleClose()
}
return {

View File

@ -1,48 +1,51 @@
// import { toRaw } from '@common/utils/vueTools'
import { DISLIKE_EVENT_NAME } from '@common/ipcNames'
import { rendererInvoke, rendererOff, rendererOn } from '@common/rendererIpc'
import { action } from '@renderer/store/dislikeList'
import {
getDislikeListInfo,
addDislikeInfo as addDislikeInfoRemote,
overwirteDislikeInfo as overwirteDislikeInfoRemote,
// updateDislikeInfo as updateDislikeInfoRemote,
// removeDislikeInfo as removeDislikeInfoRemote,
// clearDislikeInfo as clearDislikeInfoRemote,
} from '@renderer/utils/ipc'
export const initDislikeInfo = async() => {
action.initDislikeInfo(await getDislikeListInfo())
action.initDislikeInfo(await rendererInvoke<LX.Dislike.DislikeInfo>(DISLIKE_EVENT_NAME.get_dislike_music_infos))
}
export const addDislikeInfo = async(infos: LX.Dislike.DislikeMusicInfo[]) => {
await addDislikeInfoRemote(infos)
return action.addDislikeInfo(infos)
}
export const overwirteDislikeInfo = async(rules: string) => {
await overwirteDislikeInfoRemote(rules)
return action.overwirteDislikeInfo(rules)
}
// export const updateDislikeInfo = async(info: LX.Dislike.ListItem) => {
// await updateDislikeInfoRemote([toRaw(info)])
// action.updateDislikeInfo(info)
// }
// export const removeDislikeInfo = async(ids: string[]) => {
// await removeDislikeInfoRemote(toRaw(ids))
// action.removeDislikeInfo(ids)
// }
// export const clearDislikeInfo = async() => {
// await clearDislikeInfoRemote()
// action.clearDislikeInfo()
// }
export const hasDislike = (info: LX.Music.MusicInfo | LX.Download.ListItem | null) => {
if (!info) return false
return action.hasDislike(info)
}
export const addDislikeInfo = async(infos: LX.Dislike.DislikeMusicInfo[]) => {
await rendererInvoke<LX.Dislike.DislikeMusicInfo[]>(DISLIKE_EVENT_NAME.add_dislike_music_infos, infos)
}
export const overwirteDislikeInfo = async(rules: string) => {
await rendererInvoke<string>(DISLIKE_EVENT_NAME.overwrite_dislike_music_infos, rules)
}
export const clearDislikeInfo = async() => {
await rendererInvoke(DISLIKE_EVENT_NAME.clear_dislike_music_infos)
}
const noop = () => {}
export const registerRemoteDislikeAction = (onListChanged: (listIds: string[]) => void = noop) => {
const add_dislike_music_infos = ({ params: datas }: LX.IpcRendererEventParams<LX.Dislike.DislikeMusicInfo[]>) => {
action.addDislikeInfo(datas)
}
const overwrite_dislike_music_infos = ({ params: datas }: LX.IpcRendererEventParams<LX.Dislike.DislikeRules>) => {
action.overwirteDislikeInfo(datas)
}
const clear_dislike_music_infos = () => {
return action.clearDislikeInfo()
}
rendererOn(DISLIKE_EVENT_NAME.add_dislike_music_infos, add_dislike_music_infos)
rendererOn(DISLIKE_EVENT_NAME.overwrite_dislike_music_infos, overwrite_dislike_music_infos)
rendererOn(DISLIKE_EVENT_NAME.clear_dislike_music_infos, clear_dislike_music_infos)
return () => {
rendererOff(DISLIKE_EVENT_NAME.add_dislike_music_infos, add_dislike_music_infos)
rendererOff(DISLIKE_EVENT_NAME.overwrite_dislike_music_infos, overwrite_dislike_music_infos)
rendererOff(DISLIKE_EVENT_NAME.clear_dislike_music_infos, clear_dislike_music_infos)
}
}

View File

@ -9,7 +9,7 @@ import { play, playList } from '@renderer/core/player'
import { onBeforeUnmount } from '@common/utils/vueTools'
import { appSetting } from '@renderer/store/setting'
import { playMusicInfo } from '@renderer/store/player/state'
import { initDislikeInfo } from '@renderer/core/dislikeList'
import { initDislikeInfo, registerRemoteDislikeAction } from '@renderer/core/dislikeList'
const initPrevPlayInfo = async() => {
const info = await getPlayInfo()
@ -33,9 +33,11 @@ export default () => {
const initUserApi = useInitUserApi()
let unregister: null | (() => void) = null
let unregisterDislikeEvent: null | (() => void) = null
onBeforeUnmount(() => {
if (unregister) unregister()
if (unregisterDislikeEvent) unregisterDislikeEvent()
})
return async() => {
@ -49,6 +51,7 @@ export default () => {
window.app_event.myListUpdate(ids)
})
window.lxData.userLists = await getUserLists() // 获取用户列表
unregisterDislikeEvent = registerRemoteDislikeAction()
await initDislikeInfo() // 获取不喜欢列表
await initPrevPlayInfo().catch(err => {
log.error(err)

View File

@ -9,7 +9,8 @@ export default () => {
// console.log(event)
switch (event.action) {
case 'select_mode':
sync.deviceName = event.data
sync.deviceName = event.data.deviceName
sync.type = event.data.type
sync.isShowSyncMode = true
break
case 'close_select_mode':

View File

@ -1,7 +1,7 @@
import { markRaw } from '@common/utils/vueTools'
import { dislikeInfo } from './state'
import { dislikeInfo, dislikeRuleCount } from './state'
import { SPLIT_CHAR } from '@common/constants'
@ -19,6 +19,7 @@ export const initDislikeInfo = ({ musicNames, rules, names, singerNames }: LX.Di
dislikeInfo.singerNames = markRaw(singerNames)
dislikeInfo.musicNames = markRaw(musicNames)
dislikeInfo.rules = rules
dislikeRuleCount.value = dislikeInfo.musicNames.size + dislikeInfo.singerNames.size + dislikeInfo.names.size
}
const initNameSet = () => {
@ -46,7 +47,8 @@ const initNameSet = () => {
list.push(`${SPLIT_CHAR.DISLIKE_NAME}${singer}`)
}
}
dislikeInfo.rules = Array.from(new Set(list)).join('\n') + '\n'
dislikeInfo.rules = Array.from(new Set(list)).join('\n')
dislikeRuleCount.value = dislikeInfo.musicNames.size + dislikeInfo.singerNames.size + dislikeInfo.names.size
}
export const addDislikeInfo = (infos: LX.Dislike.DislikeMusicInfo[]) => {
@ -61,6 +63,12 @@ export const overwirteDislikeInfo = (rules: string) => {
return dislikeInfo.rules
}
export const clearDislikeInfo = () => {
dislikeInfo.rules = ''
initNameSet()
return dislikeInfo.rules
}
// export const updateDislikeInfo = (info: LX.Dislike.ListItem) => {
// const targetInfo = dislikeInfo.list.find(i => i.id == info.id)

View File

@ -1,4 +1,4 @@
import { markRaw } from '@common/utils/vueTools'
import { markRaw, ref } from '@common/utils/vueTools'
// import { deduplicationList } from '@common/utils/renderer'
@ -9,3 +9,5 @@ export const dislikeInfo: LX.Dislike.DislikeInfo = markRaw({
singerNames: markRaw(new Set()),
rules: '',
})
export const dislikeRuleCount = ref(0)

View File

@ -31,6 +31,7 @@ export const sync: {
isShowSyncMode: boolean
isShowAuthCodeModal: boolean
deviceName: string
type: keyof LX.Sync.ModeTypes
server: {
port: string
status: {
@ -55,6 +56,7 @@ export const sync: {
isShowSyncMode: false,
isShowAuthCodeModal: false,
deviceName: '',
type: 'list',
server: {
port: '',
status: {

View File

@ -40,15 +40,6 @@ export const getOtherSourceCount = async() => {
return rendererInvoke<number>(WIN_MAIN_RENDERER_EVENT_NAME.get_other_source_count)
}
export const getDislikeListInfo = async(): Promise<LX.Dislike.DislikeInfo> => {
return rendererInvoke<LX.Dislike.DislikeInfo>(WIN_MAIN_RENDERER_EVENT_NAME.get_dislike_music_infos)
}
export const addDislikeInfo = async(dislikeInfo: LX.Dislike.DislikeMusicInfo[]) => {
return rendererInvoke<LX.Dislike.DislikeMusicInfo[]>(WIN_MAIN_RENDERER_EVENT_NAME.add_dislike_music_infos, dislikeInfo)
}
export const overwirteDislikeInfo = async(dislikeInfo: string) => {
return rendererInvoke<string>(WIN_MAIN_RENDERER_EVENT_NAME.overwrite_dislike_music_infos, dislikeInfo)
}
// export const updateDislikeInfo = async(dislikeInfo: LX.Dislike.ListItem[]) => {
// await rendererInvoke<LX.Dislike.ListItem[]>(WIN_MAIN_RENDERER_EVENT_NAME.update_dislike_music_infos, dislikeInfo)
// }

View File

@ -3,7 +3,7 @@ material-modal(:show="modelValue" teleport="#view" height="80%" width="80%" @clo
main(:class="$style.main")
h2 {{ $t('setting__dislike_list_title') }}
div(:class="$style.content")
textarea(v-model="rules" :class="$style.textarea")
textarea(v-model="rules" :class="$style.textarea" :placeholder="$t('setting__dislike_list_input_tip')")
div(:class="$style.footer")
div(:class="$style.tips") {{ $t('setting__dislike_list_tips') }}
base-btn(:class="$style.btn" @click="handleSave") {{ $t('setting__dislike_list_save_btn') }}
@ -21,21 +21,20 @@ export default {
default: false,
},
},
emits: ['update:modelValue', 'onRuleUpdate'],
emits: ['update:modelValue'],
setup(props, { emit }) {
const rules = ref('')
const handleSave = async() => {
if (rules.value.trim() != dislikeInfo.rules.trim()) {
await overwirteDislikeInfo(rules.value)
emit('onRuleUpdate')
}
emit('update:modelValue', false)
}
watch(() => props.modelValue, (visible) => {
if (!visible) return
rules.value = dislikeInfo.rules
rules.value = dislikeInfo.rules.length ? dislikeInfo.rules + '\n' : dislikeInfo.rules
})
return {
@ -84,6 +83,7 @@ export default {
border: none;
outline: none;
border-radius: 4px;
padding: 5px;
background-color: var(--color-primary-light-200-alpha-900);
box-sizing: border-box;
font-family: inherit;

View File

@ -42,7 +42,7 @@ dd
span.auto-hidden {{ dislikeRuleCount }}
.p
base-btn.btn(min @click="isShowDislikeList = true") {{ $t('setting__other_dislike_list_show_btn') }}
DislikeListModal(v-model="isShowDislikeList" @on-rule-update="handleCountRules")
DislikeListModal(v-model="isShowDislikeList")
dd
h3#other_lyric_edited {{ $t('setting__other_lyric_edited_cache') }}
@ -75,7 +75,7 @@ import { dialog } from '@renderer/plugins/Dialog'
import { useI18n } from '@renderer/plugins/i18n'
import { appSetting, updateSetting } from '@renderer/store/setting'
import { overwriteListFull } from '@renderer/store/list/listManage'
import { dislikeInfo } from '@renderer/store/dislikeList'
import { dislikeRuleCount } from '@renderer/store/dislikeList'
import DislikeListModal from './DislikeListModal.vue'
export default {
@ -150,11 +150,7 @@ export default {
}
refreshMusicUrlCount()
const dislikeRuleCount = ref(dislikeInfo.musicNames.size + dislikeInfo.singerNames.size + dislikeInfo.names.size)
const isShowDislikeList = ref(false)
const handleCountRules = () => {
dislikeRuleCount.value = dislikeInfo.musicNames.size + dislikeInfo.singerNames.size + dislikeInfo.names.size
}
const lyricRawCount = ref(0)
const isDisabledLyricRawCacheClear = ref(false)
@ -226,7 +222,6 @@ export default {
dislikeRuleCount,
isShowDislikeList,
handleCountRules,
lyricRawCount,
isDisabledLyricRawCacheClear,