From b1f9e525ab790fef160afcd0505a50a6006c5401 Mon Sep 17 00:00:00 2001 From: lyswhut Date: Tue, 5 Sep 2023 23:11:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=90=8C=E6=AD=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=AF=B9=E2=80=9C=E4=B8=8D=E5=96=9C=E6=AC=A2=E6=AD=8C?= =?UTF-8?q?=E6=9B=B2=E2=80=9D=E5=88=97=E8=A1=A8=E7=9A=84=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- publish/changeLog.md | 1 + src/common/constants_sync.ts | 4 + src/common/ipcNames.ts | 11 +- src/common/types/dislike_list.d.ts | 4 +- src/common/types/dislike_list_sync.d.ts | 30 +++ src/common/types/sync.d.ts | 13 +- src/lang/en-us.json | 15 +- src/lang/zh-cn.json | 15 +- src/lang/zh-tw.json | 15 +- src/main/app.ts | 3 +- src/main/event/DislikeEvent.ts | 56 +++++ src/main/event/index.ts | 6 + .../modules/commonRenderers/dislike/index.ts | 3 + .../commonRenderers/dislike/rendererEvent.ts | 18 ++ .../dislike/winRendererEvent.ts | 24 ++ src/main/modules/commonRenderers/index.ts | 2 + src/main/modules/sync/client/client.ts | 2 + .../sync/client/modules/dislike/handler.ts | 68 +++++ .../sync/client/modules/dislike/index.ts | 4 + .../sync/client/modules/dislike/localEvent.ts | 21 ++ src/main/modules/sync/client/modules/index.ts | 8 +- .../sync/client/modules/list/handler.ts | 77 +++--- .../modules/sync/client/modules/list/index.ts | 2 +- .../sync/client/modules/list/localEvent.ts | 2 +- src/main/modules/sync/client/sync/handler.ts | 2 + src/main/modules/sync/dislikeEvent.ts | 50 ++++ src/main/modules/sync/listEvent.ts | 144 +++++++++++ .../sync/server/modules/dislike/index.ts | 3 + .../sync/server/modules/dislike/manage.ts | 55 +++++ .../modules/dislike/snapshotDataManage.ts | 143 +++++++++++ .../server/modules/dislike/sync/handler.ts | 35 +++ .../sync/server/modules/dislike/sync/index.ts | 3 + .../server/modules/dislike/sync/localEvent.ts | 43 ++++ .../sync/server/modules/dislike/sync/sync.ts | 232 ++++++++++++++++++ .../sync/server/modules/dislike/utils.ts | 23 ++ src/main/modules/sync/server/modules/index.ts | 12 +- .../sync/server/modules/list/manage.ts | 2 +- .../sync/server/modules/list/sync/handler.ts | 42 ++-- .../sync/server/modules/list/sync/index.ts | 2 +- .../server/modules/list/sync/localEvent.ts | 2 +- .../sync/server/modules/list/sync/sync.ts | 4 +- src/main/modules/sync/server/server/server.ts | 3 + src/main/modules/sync/server/user/index.ts | 4 + src/main/modules/sync/utils.ts | 144 ----------- .../modules/winMain/rendererEvent/index.ts | 2 + .../modules/winMain/rendererEvent/music.ts | 10 - .../modules/winMain/rendererEvent/sync.ts | 11 +- src/main/types/app.d.ts | 3 +- src/main/types/common.d.ts | 1 + src/main/types/sync.d.ts | 4 + src/main/types/sync_common.d.ts | 25 ++ .../dbService/modules/dislike_list/index.ts | 2 +- .../components/layout/SyncModeModal.vue | 58 ++++- src/renderer/core/dislikeList.ts | 75 +++--- src/renderer/core/useApp/useDataInit.ts | 5 +- src/renderer/core/useApp/useSync.ts | 3 +- src/renderer/store/dislikeList/action.ts | 12 +- src/renderer/store/dislikeList/state.ts | 4 +- src/renderer/store/index.ts | 2 + src/renderer/utils/ipc.ts | 9 - .../Setting/components/DislikeListModal.vue | 8 +- .../views/Setting/components/SettingOther.vue | 9 +- 62 files changed, 1278 insertions(+), 317 deletions(-) create mode 100644 src/common/types/dislike_list_sync.d.ts create mode 100644 src/main/event/DislikeEvent.ts create mode 100644 src/main/modules/commonRenderers/dislike/index.ts create mode 100644 src/main/modules/commonRenderers/dislike/rendererEvent.ts create mode 100644 src/main/modules/commonRenderers/dislike/winRendererEvent.ts create mode 100644 src/main/modules/sync/client/modules/dislike/handler.ts create mode 100644 src/main/modules/sync/client/modules/dislike/index.ts create mode 100644 src/main/modules/sync/client/modules/dislike/localEvent.ts create mode 100644 src/main/modules/sync/dislikeEvent.ts create mode 100644 src/main/modules/sync/listEvent.ts create mode 100644 src/main/modules/sync/server/modules/dislike/index.ts create mode 100644 src/main/modules/sync/server/modules/dislike/manage.ts create mode 100644 src/main/modules/sync/server/modules/dislike/snapshotDataManage.ts create mode 100644 src/main/modules/sync/server/modules/dislike/sync/handler.ts create mode 100644 src/main/modules/sync/server/modules/dislike/sync/index.ts create mode 100644 src/main/modules/sync/server/modules/dislike/sync/localEvent.ts create mode 100644 src/main/modules/sync/server/modules/dislike/sync/sync.ts create mode 100644 src/main/modules/sync/server/modules/dislike/utils.ts diff --git a/publish/changeLog.md b/publish/changeLog.md index eadb1f55..e1fd79ca 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -10,6 +10,7 @@ - 新增我的列表名右键菜单-排序歌曲-随机乱序功能,使用它可以对选中列表内歌曲进行随机重排(#1440) - 新增数据同步服务端模式已认证设备列表管理,该功能位置:设置-数据同步-服务端模式-已认证设备列表 - 新增“不喜欢歌曲”功能,可以在我的列表或者在线列表内歌曲的右击菜单使用,还可以去“设置-其他”手动编辑不喜欢规则,注:“上一曲”、“下一曲”功能将跳过符合“不喜欢歌曲”规则的歌曲,但你仍可以手动播放这些歌曲 +- 新增同步功能对“不喜欢歌曲”列表的同步 - 新增软件内快捷键“不喜欢该歌曲”设置,全局快捷键“收藏歌曲”、“取消收藏”、“不喜欢该歌曲”设置 - 新增设置-播放设置-点击相同列表内的歌曲切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)选项,默认关闭 diff --git a/src/common/constants_sync.ts b/src/common/constants_sync.ts index 17dd5d40..bb28093a 100644 --- a/src/common/constants_sync.ts +++ b/src/common/constants_sync.ts @@ -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 diff --git a/src/common/ipcNames.ts b/src/common/ipcNames.ts index 3f0a3b2d..80a7fda2 100644 --- a/src/common/ipcNames.ts +++ b/src/common/ipcNames.ts @@ -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) { 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 diff --git a/src/common/types/dislike_list.d.ts b/src/common/types/dislike_list.d.ts index 50361daa..153b8338 100644 --- a/src/common/types/dislike_list.d.ts +++ b/src/common/types/dislike_list.d.ts @@ -23,13 +23,15 @@ declare namespace LX { singer: string } + type DislikeRules = string + interface DislikeInfo { // musicIds: Set names: Set musicNames: Set singerNames: Set // list: LX.Dislike.ListItem[] - rules: string + rules: DislikeRules } } } diff --git a/src/common/types/dislike_list_sync.d.ts b/src/common/types/dislike_list_sync.d.ts new file mode 100644 index 00000000..5a080996 --- /dev/null +++ b/src/common/types/dislike_list_sync.d.ts @@ -0,0 +1,30 @@ +declare namespace LX { + + namespace Sync { + namespace Dislike { + interface ListInfo { + lastSyncDate?: number + snapshotKey: string + } + + interface SyncActionBase { + action: A + } + interface SyncActionData extends SyncActionBase { + data: D + } + type SyncAction = D extends undefined ? SyncActionBase : SyncActionData + 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' + } + + } +} diff --git a/src/common/types/sync.d.ts b/src/common/types/sync.d.ts index 981beb77..f8f70730 100644 --- a/src/common/types/sync.d.ts +++ b/src/common/types/sync.d.ts @@ -19,12 +19,20 @@ declare namespace LX { } type SyncAction = D extends undefined ? SyncActionBase : SyncActionData - 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 }> } diff --git a/src/lang/en-us.json b/src/lang/en-us.json index f4529501..46e56603 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -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: @\n3. Specify a song: \n4. Specify a certain 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", diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index a04b0890..69d0bc93 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -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", diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index d89a83ed..1fb07363 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -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", diff --git a/src/main/app.ts b/src/main/app.ts index 3ae9f3eb..321d9155 100644 --- a/src/main/app.ts +++ b/src/main/app.ts @@ -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: { diff --git a/src/main/event/DislikeEvent.ts b/src/main/event/DislikeEvent.ts new file mode 100644 index 00000000..c2273499 --- /dev/null +++ b/src/main/event/DislikeEvent.ts @@ -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 +declare class EventType extends Event { + on(event: K, listener: EventMethods[K]): this + once(event: K, listener: EventMethods[K]): this + off(event: K, listener: EventMethods[K]): this +} +export type Type = Omit> diff --git a/src/main/event/index.ts b/src/main/event/index.ts index b6c656e7..63a36f6f 100644 --- a/src/main/event/index.ts +++ b/src/main/event/index.ts @@ -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() +} + diff --git a/src/main/modules/commonRenderers/dislike/index.ts b/src/main/modules/commonRenderers/dislike/index.ts new file mode 100644 index 00000000..e70df146 --- /dev/null +++ b/src/main/modules/commonRenderers/dislike/index.ts @@ -0,0 +1,3 @@ +export { registerRendererEvents } from './winRendererEvent' + +export { default } from './rendererEvent' diff --git a/src/main/modules/commonRenderers/dislike/rendererEvent.ts b/src/main/modules/commonRenderers/dislike/rendererEvent.ts new file mode 100644 index 00000000..19bc3ef2 --- /dev/null +++ b/src/main/modules/commonRenderers/dislike/rendererEvent.ts @@ -0,0 +1,18 @@ +import { mainHandle } from '@common/mainIpc' +import { DISLIKE_EVENT_NAME } from '@common/ipcNames' + +// 列表操作事件(公共,只注册一次) +export default () => { + mainHandle(DISLIKE_EVENT_NAME.get_dislike_music_infos, async() => { + return global.lx.worker.dbService.getDislikeListInfo() + }) + mainHandle(DISLIKE_EVENT_NAME.add_dislike_music_infos, async({ params: listData }) => { + await global.lx.event_dislike.dislike_music_add(listData, false) + }) + mainHandle(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) + }) +} diff --git a/src/main/modules/commonRenderers/dislike/winRendererEvent.ts b/src/main/modules/commonRenderers/dislike/winRendererEvent.ts new file mode 100644 index 00000000..c477b19c --- /dev/null +++ b/src/main/modules/commonRenderers/dislike/winRendererEvent.ts @@ -0,0 +1,24 @@ +import { DISLIKE_EVENT_NAME } from '@common/ipcNames' + +// 发送列表操作事件到渲染进程的注册方法 +// 哪个渲染进程需要接收则引入此方法注册 +export const registerRendererEvents = (sendEvent: (name: string, params?: T | undefined) => void) => { + const dislike_music_add = async(listData: LX.Dislike.DislikeMusicInfo[]) => { + sendEvent(DISLIKE_EVENT_NAME.add_dislike_music_infos, listData) + } + const dislike_data_overwrite = async(rules: LX.Dislike.DislikeRules) => { + sendEvent(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) + } +} diff --git a/src/main/modules/commonRenderers/index.ts b/src/main/modules/commonRenderers/index.ts index acba20ab..f2eadbde 100644 --- a/src/main/modules/commonRenderers/index.ts +++ b/src/main/modules/commonRenderers/index.ts @@ -1,7 +1,9 @@ import common from './common' import list from './list' +import dislike from './dislike' export default () => { common() list() + dislike() } diff --git a/src/main/modules/sync/client/client.ts b/src/main/modules/sync/client/client.ts index 7421f8d9..ddc88382 100644 --- a/src/main/modules/sync/client/client.ts +++ b/src/main/modules/sync/client/client.ts @@ -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({ diff --git a/src/main/modules/sync/client/modules/dislike/handler.ts b/src/main/modules/sync/client/modules/dislike/handler.ts new file mode 100644 index 00000000..03859441 --- /dev/null +++ b/src/main/modules/sync/client/modules/dislike/handler.ts @@ -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 => 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 = { + 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 + diff --git a/src/main/modules/sync/client/modules/dislike/index.ts b/src/main/modules/sync/client/modules/dislike/index.ts new file mode 100644 index 00000000..ae2f0814 --- /dev/null +++ b/src/main/modules/sync/client/modules/dislike/index.ts @@ -0,0 +1,4 @@ + +export { default as handler } from './handler' + +export * from './localEvent' diff --git a/src/main/modules/sync/client/modules/dislike/localEvent.ts b/src/main/modules/sync/client/modules/dislike/localEvent.ts new file mode 100644 index 00000000..1e6f73f1 --- /dev/null +++ b/src/main/modules/sync/client/modules/dislike/localEvent.ts @@ -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 +} diff --git a/src/main/modules/sync/client/modules/index.ts b/src/main/modules/sync/client/modules/index.ts index 04eece93..5a548e3d 100644 --- a/src/main/modules/sync/client/modules/index.ts +++ b/src/main/modules/sync/client/modules/index.ts @@ -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 diff --git a/src/main/modules/sync/client/modules/list/handler.ts b/src/main/modules/sync/client/modules/list/handler.ts index a0b1881e..4a90452a 100644 --- a/src/main/modules/sync/client/modules/list/handler.ts +++ b/src/main/modules/sync/client/modules/list/handler.ts @@ -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 => new Promise((resolve, reject) => { const handleDisconnect = (err: Error) => { sendCloseSelectMode() @@ -30,7 +22,7 @@ const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise { + 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 { - 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 = { + 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 diff --git a/src/main/modules/sync/client/modules/list/index.ts b/src/main/modules/sync/client/modules/list/index.ts index 273dfea2..ae2f0814 100644 --- a/src/main/modules/sync/client/modules/list/index.ts +++ b/src/main/modules/sync/client/modules/list/index.ts @@ -1,4 +1,4 @@ -export * as handler from './handler' +export { default as handler } from './handler' export * from './localEvent' diff --git a/src/main/modules/sync/client/modules/list/localEvent.ts b/src/main/modules/sync/client/modules/list/localEvent.ts index d2b1c535..9ba543f6 100644 --- a/src/main/modules/sync/client/modules/list/localEvent.ts +++ b/src/main/modules/sync/client/modules/list/localEvent.ts @@ -1,4 +1,4 @@ -import { registerListActionEvent } from '@main/modules/sync/utils' +import { registerListActionEvent } from '@main/modules/sync/listEvent' let unregisterLocalListAction: (() => void) | null diff --git a/src/main/modules/sync/client/sync/handler.ts b/src/main/modules/sync/client/sync/handler.ts index f1240eeb..4ddd7661 100644 --- a/src/main/modules/sync/client/sync/handler.ts +++ b/src/main/modules/sync/client/sync/handler.ts @@ -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, } } } diff --git a/src/main/modules/sync/dislikeEvent.ts b/src/main/modules/sync/dislikeEvent.ts new file mode 100644 index 00000000..65f13c82 --- /dev/null +++ b/src/main/modules/sync/dislikeEvent.ts @@ -0,0 +1,50 @@ + +export const getLocalDislikeData = async(): Promise => { + 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)) => { + 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') + } +} diff --git a/src/main/modules/sync/listEvent.ts b/src/main/modules/sync/listEvent.ts new file mode 100644 index 00000000..94c1bd64 --- /dev/null +++ b/src/main/modules/sync/listEvent.ts @@ -0,0 +1,144 @@ +import { LIST_IDS } from '@common/constants' + +export const getLocalListData = async(): Promise => { + 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)) => { + const list_data_overwrite = async(listData: MakeOptional, 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') + } +} diff --git a/src/main/modules/sync/server/modules/dislike/index.ts b/src/main/modules/sync/server/modules/dislike/index.ts new file mode 100644 index 00000000..648ebf24 --- /dev/null +++ b/src/main/modules/sync/server/modules/dislike/index.ts @@ -0,0 +1,3 @@ +export * as sync from './sync' +export { DislikeManage } from './manage' + diff --git a/src/main/modules/sync/server/modules/dislike/manage.ts b/src/main/modules/sync/server/modules/dislike/manage.ts new file mode 100644 index 00000000..618061a0 --- /dev/null +++ b/src/main/modules/sync/server/modules/dislike/manage.ts @@ -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() + } +} + diff --git a/src/main/modules/sync/server/modules/dislike/snapshotDataManage.ts b/src/main/modules/sync/server/modules/dislike/snapshotDataManage.ts new file mode 100644 index 00000000..e60bbdba --- /dev/null +++ b/src/main/modules/sync/server/modules/dislike/snapshotDataManage.ts @@ -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 +} +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 => { + 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 + +// export const createUserDataManage = (user: LX.UserConfig) => { +// const manage = Object.create(userDataManage) as typeof userDataManage +// manage.userDir = user.dataPath +// } diff --git a/src/main/modules/sync/server/modules/dislike/sync/handler.ts b/src/main/modules/sync/server/modules/dislike/sync/handler.ts new file mode 100644 index 00000000..2807c6fd --- /dev/null +++ b/src/main/modules/sync/server/modules/dislike/sync/handler.ts @@ -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 = { + 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 diff --git a/src/main/modules/sync/server/modules/dislike/sync/index.ts b/src/main/modules/sync/server/modules/dislike/sync/index.ts new file mode 100644 index 00000000..0dbd6bef --- /dev/null +++ b/src/main/modules/sync/server/modules/dislike/sync/index.ts @@ -0,0 +1,3 @@ +export { default as handler } from './handler' +export { sync } from './sync' +export * from './localEvent' diff --git a/src/main/modules/sync/server/modules/dislike/sync/localEvent.ts b/src/main/modules/sync/server/modules/dislike/sync/localEvent.ts new file mode 100644 index 00000000..e542e193 --- /dev/null +++ b/src/main/modules/sync/server/modules/dislike/sync/localEvent.ts @@ -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 +} diff --git a/src/main/modules/sync/server/modules/dislike/sync/sync.ts b/src/main/modules/sync/server/modules/dislike/sync/sync.ts new file mode 100644 index 00000000..319b0eec --- /dev/null +++ b/src/main/modules/sync/server/modules/dislike/sync/sync.ts @@ -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 => { + console.log('getRemoteListData') + return (await socket.remoteQueueDislike.dislike_sync_get_list_data()) ?? '' +} + +const getRemoteDataMD5 = async(socket: LX.Sync.Server.Socket): Promise => { + return socket.remoteQueueDislike.dislike_sync_get_md5() +} + +// const getLocalDislikeData async(socket: LX.Sync.Server.Socket): Promise => { +// return getUserSpace(socket.userInfo.name).listManage.getListData() +// } +const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise => 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 => { +// 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> = [] + 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 => { + 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() + 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 + }) +} diff --git a/src/main/modules/sync/server/modules/dislike/utils.ts b/src/main/modules/sync/server/modules/dislike/utils.ts new file mode 100644 index 00000000..3dd37247 --- /dev/null +++ b/src/main/modules/sync/server/modules/dislike/utils.ts @@ -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) +} diff --git a/src/main/modules/sync/server/modules/index.ts b/src/main/modules/sync/server/modules/index.ts index d24414c8..c61c8ec1 100644 --- a/src/main/modules/sync/server/modules/index.ts +++ b/src/main/modules/sync/server/modules/index.ts @@ -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 diff --git a/src/main/modules/sync/server/modules/list/manage.ts b/src/main/modules/sync/server/modules/list/manage.ts index d1c4cc93..6b338163 100644 --- a/src/main/modules/sync/server/modules/list/manage.ts +++ b/src/main/modules/sync/server/modules/list/manage.ts @@ -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 diff --git a/src/main/modules/sync/server/modules/list/sync/handler.ts b/src/main/modules/sync/server/modules/list/sync/handler.ts index bf8a2877..24ac6256 100644 --- a/src/main/modules/sync/server/modules/list/sync/handler.ts +++ b/src/main/modules/sync/server/modules/list/sync/handler.ts @@ -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 = { + 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 diff --git a/src/main/modules/sync/server/modules/list/sync/index.ts b/src/main/modules/sync/server/modules/list/sync/index.ts index 78dd3db7..0dbd6bef 100644 --- a/src/main/modules/sync/server/modules/list/sync/index.ts +++ b/src/main/modules/sync/server/modules/list/sync/index.ts @@ -1,3 +1,3 @@ -export * as handler from './handler' +export { default as handler } from './handler' export { sync } from './sync' export * from './localEvent' diff --git a/src/main/modules/sync/server/modules/list/sync/localEvent.ts b/src/main/modules/sync/server/modules/list/sync/localEvent.ts index c55df3f6..b996c979 100644 --- a/src/main/modules/sync/server/modules/list/sync/localEvent.ts +++ b/src/main/modules/sync/server/modules/list/sync/localEvent.ts @@ -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 diff --git a/src/main/modules/sync/server/modules/list/sync/sync.ts b/src/main/modules/sync/server/modules/list/sync/sync.ts index ddd2cf9b..6d962b4f 100644 --- a/src/main/modules/sync/server/modules/list/sync/sync.ts +++ b/src/main/modules/sync/server/modules/list/sync/sync.ts @@ -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 { + sendSelectMode(socket.keyInfo.deviceName, 'list', (mode) => { if (mode == null) { reject(new Error('cancel')) return diff --git a/src/main/modules/sync/server/server/server.ts b/src/main/modules/sync/server/server/server.ts index bd08b01a..6a01cd4b 100644 --- a/src/main/modules/sync/server/server/server.ts +++ b/src/main/modules/sync/server/server/server.ts @@ -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) => { diff --git a/src/main/modules/sync/server/user/index.ts b/src/main/modules/sync/server/user/index.ts index d8f93b32..f98c8ea4 100644 --- a/src/main/modules/sync/server/user/index.ts +++ b/src/main/modules/sync/server/user/index.ts @@ -1,11 +1,13 @@ import { UserDataManage } from './data' import { ListManage, + DislikeManage, } from '../modules' export interface UserSpace { dataManage: UserDataManage listManage: ListManage + dislikeManage: DislikeManage getDecices: () => Promise removeDevice: (clientId: string) => Promise } @@ -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() }, diff --git a/src/main/modules/sync/utils.ts b/src/main/modules/sync/utils.ts index b98d87c8..139c2344 100644 --- a/src/main/modules/sync/utils.ts +++ b/src/main/modules/sync/utils.ts @@ -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 => { - 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)) => { - const list_data_overwrite = async(listData: MakeOptional, 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') - } -} diff --git a/src/main/modules/winMain/rendererEvent/index.ts b/src/main/modules/winMain/rendererEvent/index.ts index 240f8a0e..869bb71c 100644 --- a/src/main/modules/winMain/rendererEvent/index.ts +++ b/src/main/modules/winMain/rendererEvent/index.ts @@ -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() diff --git a/src/main/modules/winMain/rendererEvent/music.ts b/src/main/modules/winMain/rendererEvent/music.ts index ee8dc611..df1cc856 100644 --- a/src/main/modules/winMain/rendererEvent/music.ts +++ b/src/main/modules/winMain/rendererEvent/music.ts @@ -70,16 +70,6 @@ export default () => { return global.lx.worker.dbService.musicInfoOtherSourceCount() }) - // =========================不喜欢的歌曲========================= - mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_dislike_music_infos, async() => { - return global.lx.worker.dbService.getDislikeListInfo() - }) - mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.add_dislike_music_infos, async({ params: infos }) => { - await global.lx.worker.dbService.dislikeInfoAdd(infos) - }) - mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.overwrite_dislike_music_infos, async({ params: rules }) => { - await global.lx.worker.dbService.dislikeInfoOverwrite(rules) - }) // mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.remove_dislike_music_infos, async({ params: ids }) => { // await global.lx.worker.dbService.dislikeInfoRemove(ids) // }) diff --git a/src/main/modules/winMain/rendererEvent/sync.ts b/src/main/modules/winMain/rendererEvent/sync.ts index 93bff17e..58e7f723 100644 --- a/src/main/modules/winMain/rendererEvent/sync.ts +++ b/src/main/modules/winMain/rendererEvent/sync.ts @@ -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(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 = (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) diff --git a/src/main/types/app.d.ts b/src/main/types/app.d.ts index bd88fd77..13c87adc 100644 --- a/src/main/types/app.d.ts +++ b/src/main/types/app.d.ts @@ -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 } diff --git a/src/main/types/common.d.ts b/src/main/types/common.d.ts index a21fa28d..f139f1e5 100644 --- a/src/main/types/common.d.ts +++ b/src/main/types/common.d.ts @@ -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' diff --git a/src/main/types/sync.d.ts b/src/main/types/sync.d.ts index f99f0a4b..25879c52 100644 --- a/src/main/types/sync.d.ts +++ b/src/main/types/sync.d.ts @@ -15,11 +15,13 @@ declare global { } moduleReadys: { list: boolean + dislike: boolean } onClose: (handler: (err: Error) => (void | Promise)) => () => 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 @@ -45,6 +48,7 @@ declare global { remote: LX.Sync.ClientSyncActions remoteQueueList: LX.Sync.ClientSyncListActions + remoteQueueDislike: LX.Sync.ClientSyncDislikeActions } type SocketServer = WS.Server } diff --git a/src/main/types/sync_common.d.ts b/src/main/types/sync_common.d.ts index 560e7690..d6916e24 100644 --- a/src/main/types/sync_common.d.ts +++ b/src/main/types/sync_common.d.ts @@ -1,16 +1,30 @@ +type WarpSyncHandlerActions = { + [K in keyof Actions]: (...args: [Socket, ...Parameters]) => ReturnType +} + declare namespace LX { namespace Sync { type ServerSyncActions = WarpPromiseRecord<{ onFeatureChanged: (feature: EnabledFeatures) => void }> + type ServerSyncHandlerActions = WarpSyncHandlerActions + type ServerSyncListActions = WarpPromiseRecord<{ onListSyncAction: (action: LX.Sync.List.ActionList) => void }> + type ServerSyncHandlerListActions = WarpSyncHandlerActions + + type ServerSyncDislikeActions = WarpPromiseRecord<{ + onDislikeSyncAction: (action: LX.Sync.Dislike.ActionList) => void + }> + type ServerSyncHandlerDislikeActions = WarpSyncHandlerActions type ClientSyncActions = WarpPromiseRecord<{ getEnabledFeatures: (serverType: ServerType, supportedFeatures: SupportedFeatures) => EnabledFeatures finished: () => void }> + type ClientSyncHandlerActions = WarpSyncHandlerActions + 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 = WarpSyncHandlerActions + + 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 = WarpSyncHandlerActions } } diff --git a/src/main/worker/dbService/modules/dislike_list/index.ts b/src/main/worker/dbService/modules/dislike_list/index.ts index f32c9351..37332b22 100644 --- a/src/main/worker/dbService/modules/dislike_list/index.ts +++ b/src/main/worker/dbService/modules/dislike_list/index.ts @@ -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 } diff --git a/src/renderer/components/layout/SyncModeModal.vue b/src/renderer/components/layout/SyncModeModal.vue index 6633e5a2..5ed86f9d 100644 --- a/src/renderer/components/layout/SyncModeModal.vue +++ b/src/renderer/components/layout/SyncModeModal.vue @@ -1,7 +1,7 @@