11import LoggerCore from "@App/app/logger/core" ;
22import Logger from "@App/app/logger/logger" ;
3- import { ScriptDAO } from "@App/app/repo/scripts" ;
3+ import { ScriptDAO , type Script } from "@App/app/repo/scripts" ;
44import type { SCMetadata , Subscribe , SubscribeScript } from "@App/app/repo/subscribe" ;
5- import { SUBSCRIBE_STATUS_DISABLE , SUBSCRIBE_STATUS_ENABLE , SubscribeDAO } from "@App/app/repo/subscribe" ;
5+ import { SubscribeDAO , SubscribeStatusType } from "@App/app/repo/subscribe" ;
66import { type IMessageQueue } from "@Packages/message/message_queue" ;
77import { type Group } from "@Packages/message/server" ;
88import { type ScriptService } from "./script" ;
@@ -31,13 +31,17 @@ export class SubscribeService {
3131 }
3232
3333 async install ( param : { subscribe : Subscribe } ) {
34+ // 1)由安装页呼叫,进行 user.sub.js 的安装
35+ // 2)静默更新启动状态下,Subscribe 列表自动更新
3436 const logger = this . logger . with ( {
3537 subscribeUrl : param . subscribe . url ,
3638 name : param . subscribe . name ,
3739 } ) ;
3840 try {
39- await this . subscribeDAO . save ( param . subscribe ) ;
41+ await this . subscribeDAO . save ( param . subscribe ) ; // 所谓的安装,仅储存脚本资源。
4042 logger . info ( "upsert subscribe success" ) ;
43+ // 广播后才会根据 subscribe.scripts 的 url 取得/更新脚本
44+ // 注:installSubscribe 的广播是自己和自己对话。(不等待回应)
4145 this . mq . publish < TInstallSubscribe > ( "installSubscribe" , {
4246 subscribe : param . subscribe ,
4347 } ) ;
@@ -83,85 +87,121 @@ export class SubscribeService {
8387 }
8488 }
8589
86- // 更新订阅的脚本
90+ // 更新订阅的脚本( installSubscribe )
91+ // 已订阅的脚本则根据 Script脚本 本身的更新逻辑更新,与 Subscribe脚本 的更新无关
8792 async upsertScript ( url : string ) {
8893 const subscribe = await this . subscribeDAO . get ( url ) ;
89- if ( ! subscribe ) return ;
94+ if ( ! subscribe || ! subscribe . metadata . usersubscribe ) return ; // 有效的 Subscribe 必定有 usersubscribe
9095 const logger = this . logger . with ( {
9196 url : subscribe . url ,
9297 name : subscribe . name ,
9398 } ) ;
9499 // 对比脚本是否有变化
95- const addScript : string [ ] = [ ] ;
96- const removeScript : SubscribeScript [ ] = [ ] ;
97- const scriptUrl = subscribe . metadata . scripturl || [ ] ;
98- const scripts = Object . keys ( subscribe . scripts ) ;
99- for ( const url of scriptUrl ) {
100+ const addedScripts : string [ ] = [ ] ;
101+ const removedScripts : SubscribeScript [ ] = [ ] ;
102+ const metaScriptUrlSet = new Set ( subscribe . metadata . scripturl || [ ] ) ; // 订阅列表
103+ const subscribeScripts = new Set ( Object . keys ( subscribe . scripts ) ) ; // 已关联 uuid 的列表
104+ // 注:首次安装时, subscribeScripts 是空的。
105+ for ( const url of metaScriptUrlSet ) {
100106 // 不存在于已安装的脚本中, 则添加
101- if ( ! scripts . includes ( url ) ) {
102- addScript . push ( url ) ;
107+ if ( ! subscribeScripts . has ( url ) ) {
108+ addedScripts . push ( url ) ;
103109 }
104110 }
105- for ( const url of scripts ) {
111+ for ( const url of subscribeScripts ) {
106112 // 不存在于订阅的脚本中, 则删除
107- if ( ! scriptUrl . includes ( url ) ) {
108- removeScript . push ( subscribe . scripts [ url ] ) ;
113+ if ( ! metaScriptUrlSet . has ( url ) ) {
114+ removedScripts . push ( subscribe . scripts [ url ] ) ;
109115 }
110116 }
111117
112- const notification : string [ ] [ ] = [ [ ] , [ ] ] ;
113- const result : Promise < boolean > [ ] = [ ] ;
114- // 添加脚本
115- addScript . forEach ( ( url ) => {
116- result . push (
118+ // 一次性取出所有已安装脚本,建立 URL → Script 的索引,避免每个 addedScript 都全表扫描
119+ const allScripts = await this . scriptDAO . find ( ) ;
120+ const scriptByUrl = new Map < string , Script > ( ) ;
121+ for ( const script of allScripts ) {
122+ if ( script . downloadUrl ) scriptByUrl . set ( script . downloadUrl , script ) ;
123+ if ( script . origin ) scriptByUrl . set ( script . origin , script ) ;
124+ }
125+
126+ const addedScriptNames : string [ ] = [ ] ;
127+ const removedScriptNames : string [ ] = [ ] ;
128+ const promises : Promise < void > [ ] = [ ] ;
129+ // 添加脚本: 根据 订阅列表 的 Script脚本URLs 进行安装
130+ addedScripts . forEach ( ( url ) => {
131+ promises . push (
117132 ( async ( ) => {
118- const script = await this . scriptService . installByUrl ( url , "subscribe" , subscribe . url ) ;
119- subscribe . scripts [ url ] = {
120- url,
121- uuid : script . uuid ,
122- } ;
123- notification [ 0 ] . push ( i18nName ( script ) ) ;
124- return true ;
133+ const existingScript = scriptByUrl . get ( url ) ;
134+ if ( existingScript ) {
135+ // 仅关联至 已安装脚本的 uuid
136+ // 注:1)已安装的脚本可能是用户用直接下载方式安装
137+ // 2)已安装的脚本可能是用户用其他 Subscribe 安装
138+ // 这里的 existingScript 的 subscribeUrl 值不一定是这个 Subscribe 的 url
139+ subscribe . scripts [ url ] = {
140+ url,
141+ uuid : existingScript . uuid ,
142+ } ;
143+ } else {
144+ // 安装Script脚本 ( script.subscribeUrl 会指定为这个 Subscribe. 当移除 Subscribe 时会一并移除 )
145+ const script = await this . scriptService . installByUrl ( url , "subscribe" , subscribe . url ) ;
146+ const name = i18nName ( script ) ;
147+ // 把Script脚本关联至Subscribe
148+ subscribe . scripts [ url ] = {
149+ url,
150+ uuid : script . uuid ,
151+ } ;
152+ addedScriptNames . push ( name ) ;
153+ }
125154 } ) ( ) . catch ( ( e ) => {
126155 logger . error ( "install script failed" , Logger . E ( e ) ) ;
127- return false ;
128156 } )
129157 ) ;
130158 } ) ;
131- // 删除脚本
132- removeScript . forEach ( ( item ) => {
159+ // 删除脚本: 根据 subscribeScripts 的 Script脚本UUIDs 进行反安装
160+ removedScripts . forEach ( ( item ) => {
133161 // 通过uuid查询脚本id
134- result . push (
162+ promises . push (
135163 ( async ( ) => {
136- const script = await this . scriptDAO . findByUUID ( item . uuid ) ;
164+ // 以 uuid 找出已安装的Script脚本资讯
165+ const script = await this . scriptDAO . get ( item . uuid ) ;
166+ const url = item . url ;
167+ // 无论是否删除脚本,都需要清理 subscribe.scripts 中的关联
168+ delete subscribe . scripts [ url ] ;
137169 if ( script ) {
138- notification [ 1 ] . push ( i18nName ( script ) ) ;
139- // 删除脚本
140- this . scriptService . deleteScript ( script . uuid ) ;
170+ const name = i18nName ( script ) ;
171+ // 如果不是以此 Subscribe 安装的话则略过删除(例如其他 Subscribe、直接安装、本地安装等)
172+ if ( script . subscribeUrl === subscribe . url ) {
173+ await this . scriptService . deleteScript ( script . uuid ) ;
174+ removedScriptNames . push ( name ) ;
175+ } else {
176+ logger . warn ( "Subscribe Update: skip deletion" , {
177+ scriptUUID : script . uuid ,
178+ scriptUrl : url ,
179+ scriptName : name ,
180+ } ) ;
181+ }
141182 }
142- return true ;
143183 } ) ( ) . catch ( ( e ) => {
144184 logger . error ( "delete script failed" , Logger . E ( e ) ) ;
145- return false ;
146185 } )
147186 ) ;
148187 } ) ;
149188
150- await Promise . allSettled ( result ) ;
189+ await Promise . allSettled ( promises ) ;
151190
191+ // 把 subscribe.scripts 的新资讯储存到 subscribeDAO
152192 await this . subscribeDAO . update ( subscribe . url , subscribe ) ;
153193
154194 InfoNotification (
155195 i18n . t ( "notification.subscribe_update" , { subscribeName : subscribe . name } ) ,
156196 i18n . t ( "notification.subscribe_update_desc" , {
157- newScripts : notification [ 0 ] . join ( "," ) ,
158- deletedScripts : notification [ 1 ] . join ( "," ) ,
197+ newScripts : addedScriptNames . join ( "," ) ,
198+ deletedScripts : removedScriptNames . join ( "," ) ,
159199 } )
160200 ) ;
161201
162- logger . info ( "subscribe update" , {
163- install : notification [ 0 ] ,
164- update : notification [ 1 ] ,
202+ logger . info ( "subscribe list update" , {
203+ installed : addedScriptNames ,
204+ deleted : removedScriptNames ,
165205 } ) ;
166206
167207 return true ;
@@ -183,9 +223,9 @@ export class SubscribeService {
183223 } ) ;
184224 try {
185225 if ( delayFn ) await delayFn ( ) ;
186- const code = await fetchScriptBody ( url ) ;
187- const metadata = parseMetadata ( code ) ;
188- if ( ! metadata ) {
226+ const code = await fetchScriptBody ( url ) ; // user.sub.js 的 代码
227+ const metadata = parseMetadata ( code ) ; // user.sub.js 的 metadata = 代码内容分析; metadata.usersubscribe 是 空阵列
228+ if ( ! metadata || ! metadata . usersubscribe ) {
189229 logger . error ( "parse metadata failed" ) ;
190230 return false ;
191231 }
@@ -203,11 +243,17 @@ export class SubscribeService {
203243 }
204244
205245 // 检查更新
246+ /**
247+ * @param url Subscribe脚本 的 url
248+ * @param source 系统自动检查: "system"; subscribeClient.checkUpdate(subscribe.url) 的时候: "user"
249+ * @returns
250+ */
206251 async checkUpdate ( url : string , source : InstallSource ) {
207252 const subscribe = await this . subscribeDAO . get ( url ) ;
208253 if ( ! subscribe ) {
209254 return false ;
210255 }
256+ // 先写入更新触发时间
211257 await this . subscribeDAO . update ( url , { checktime : Date . now ( ) } ) ;
212258 const logger = this . logger . with ( {
213259 url : subscribe . url ,
@@ -263,7 +309,8 @@ export class SubscribeService {
263309 } ) ;
264310
265311 for ( const subscribe of list ) {
266- if ( ! checkDisable && subscribe . status === SUBSCRIBE_STATUS_ENABLE ) {
312+ if ( ! checkDisable && subscribe . status === SubscribeStatusType . disable ) {
313+ // 旧代码一直写反了这个 enable disable
267314 continue ;
268315 }
269316 this . checkUpdate ( subscribe . url , "system" ) ;
@@ -280,7 +327,7 @@ export class SubscribeService {
280327 } ) ;
281328 try {
282329 await this . subscribeDAO . update ( param . url , {
283- status : param . enable ? SUBSCRIBE_STATUS_ENABLE : SUBSCRIBE_STATUS_DISABLE ,
330+ status : param . enable ? SubscribeStatusType . enable : SubscribeStatusType . disable ,
284331 } ) ;
285332 logger . info ( "enable subscribe success" ) ;
286333 return true ;
0 commit comments