@@ -34,6 +34,7 @@ import { dayFormat } from "@App/pkg/utils/day_format";
3434import i18n , { i18nName } from "@App/locales/locales" ;
3535import { InfoNotification } from "./utils" ;
3636import { stackAsyncTask } from "@App/pkg/utils/async_queue" ;
37+ import { md5OfText } from "@App/pkg/utils/crypto" ;
3738
3839// type SynchronizeTarget = "local";
3940
@@ -67,6 +68,10 @@ type ScriptcatSyncStatus = {
6768
6869type PushScriptParam = TInstallScriptParams ;
6970
71+ type FileDigestMap = {
72+ [ key : string ] : string ;
73+ } ;
74+
7075const SYNC_SERVICE_TASK_KEY = "cloud_sync_queue" ;
7176
7277export class SynchronizeService {
@@ -348,10 +353,7 @@ export class SynchronizeService {
348353 // 根据文件名生成一个map
349354 const uuidMap = new Map < string , Partial < SyncFiles > > ( ) ;
350355 // 储存文件摘要,用于检测文件是否有变化
351- const fileDigestMap =
352- ( ( await this . storage . get ( "file_digest" ) ) as {
353- [ key : string ] : string ;
354- } ) || { } ;
356+ const fileDigestMap = ( ( await this . storage . get ( "file_digest" ) ) as FileDigestMap ) || { } ;
355357
356358 for ( const file of list ) {
357359 if ( file . name . endsWith ( ".user.js" ) ) {
@@ -397,7 +399,7 @@ export class SynchronizeService {
397399 }
398400
399401 // 对比脚本列表和文件列表,进行同步
400- const result : Promise < void > [ ] = [ ] ;
402+ const result : Promise < FileDigestMap | void > [ ] = [ ] ;
401403 const updateScript : Map < string , boolean > = new Map ( ) ;
402404 // 记录被跳过的孤儿云端脚本(仅 .user.js 无 .meta.json)
403405 // 避免本机回写 scriptcat-sync.json 时丢失对应 uuid 的云端 status
@@ -426,7 +428,7 @@ export class SynchronizeService {
426428 } else {
427429 // 否则认为是一个无效的.meta文件,进行删除,并进行同步
428430 await fs . delete ( file . meta ! . name ) ;
429- await this . pushScript ( fs , script ) ;
431+ return await this . pushScript ( fs , script ) ;
430432 }
431433 } ) ( )
432434 ) ;
@@ -469,7 +471,13 @@ export class SynchronizeService {
469471 result . push ( this . pushScript ( fs , script ) ) ;
470472 } ) ;
471473 // 忽略错误
472- await Promise . allSettled ( result ) ;
474+ const syncResults = await Promise . allSettled ( result ) ;
475+ const pushedFileDigestMap : FileDigestMap = { } ;
476+ syncResults . forEach ( ( ret ) => {
477+ if ( ret . status === "fulfilled" && ret . value ) {
478+ Object . assign ( pushedFileDigestMap , ret . value ) ;
479+ }
480+ } ) ;
473481 // 同步状态
474482 if ( syncConfig . syncStatus ) {
475483 const scriptlist = await this . scriptDAO . all ( ) ;
@@ -533,17 +541,25 @@ export class SynchronizeService {
533541 }
534542 // 重新获取文件列表,保存文件摘要
535543 this . logger . info ( "update file digest" ) ;
536- await this . updateFileDigest ( fs ) ;
544+ await this . updateFileDigest ( fs , pushedFileDigestMap ) ;
537545 this . logger . info ( "sync complete" ) ;
538546 return ;
539547 }
540548
541- async updateFileDigest ( fs : FileSystem ) {
549+ async updateFileDigest ( fs : FileSystem , knownFileDigestMap : FileDigestMap = { } ) {
542550 const newList = await fs . list ( ) ;
543- const newFileDigestMap : { [ key : string ] : string } = { } ;
551+ const newFileDigestMap : FileDigestMap = { } ;
544552 for ( const file of newList ) {
545553 newFileDigestMap [ file . name ] = file . digest ;
546554 }
555+ // 各后端 digest 格式不一(WebDAV/OneDrive/S3 是 etag、Dropbox 是 content_hash、Zip 为空,
556+ // 仅 GoogleDrive/Baidu 是 md5),只在云端列表暂时漏掉刚上传的文件时用本地 md5 兜底,
557+ // 不能覆盖 fs.list 已返回的原生 digest,否则下次同步比对会因格式不一致而误判
558+ for ( const name in knownFileDigestMap ) {
559+ if ( ! ( name in newFileDigestMap ) ) {
560+ newFileDigestMap [ name ] = knownFileDigestMap [ name ] ;
561+ }
562+ }
547563 await this . storage . set ( "file_digest" , newFileDigestMap ) ;
548564 return ;
549565 }
@@ -581,8 +597,9 @@ export class SynchronizeService {
581597 }
582598
583599 // 上传脚本
584- async pushScript ( fs : FileSystem , script : PushScriptParam ) {
600+ async pushScript ( fs : FileSystem , script : PushScriptParam ) : Promise < FileDigestMap > {
585601 const filename = `${ script . uuid } .user.js` ;
602+ const metaFilename = `${ script . uuid } .meta.json` ;
586603 const logger = this . logger . with ( {
587604 uuid : script . uuid ,
588605 name : script . name ,
@@ -592,22 +609,25 @@ export class SynchronizeService {
592609 const w = await fs . create ( filename ) ;
593610 // 获取脚本代码
594611 const code = await this . scriptCodeDAO . get ( script . uuid ) ;
595- await w . write ( code ! . code ) ;
596- const meta = await fs . create ( ` ${ script . uuid } .meta.json` ) ;
597- await meta . write (
598- JSON . stringify ( < SyncMeta > {
599- uuid : script . uuid ,
600- origin : script . origin ,
601- downloadUrl : script . downloadUrl ,
602- checkUpdateUrl : script . checkUpdateUrl ,
603- } )
604- ) ;
612+ const scriptCode = code ! . code ;
613+ await w . write ( scriptCode ) ;
614+ const meta = await fs . create ( metaFilename ) ;
615+ const metaJson = JSON . stringify ( < SyncMeta > {
616+ uuid : script . uuid ,
617+ origin : script . origin ,
618+ downloadUrl : script . downloadUrl ,
619+ checkUpdateUrl : script . checkUpdateUrl ,
620+ } ) ;
621+ await meta . write ( metaJson ) ;
605622 logger . info ( "push script success" ) ;
623+ return {
624+ [ filename ] : md5OfText ( scriptCode ) ,
625+ [ metaFilename ] : md5OfText ( metaJson ) ,
626+ } ;
606627 } catch ( e ) {
607628 logger . error ( "push script error" , Logger . E ( e ) ) ;
608629 throw e ;
609630 }
610- return ;
611631 }
612632
613633 async pullScript ( fs : FileSystem , file : SyncFiles , status : ScriptcatSyncStatus | undefined , existingScript ?: Script ) {
@@ -703,8 +723,8 @@ export class SynchronizeService {
703723 if ( config . enable ) {
704724 stackAsyncTask ( SYNC_SERVICE_TASK_KEY , async ( ) => {
705725 const fs = await this . buildFileSystem ( config ) ;
706- await this . pushScript ( fs , params . script ) ;
707- await this . updateFileDigest ( fs ) ;
726+ const pushedFileDigestMap = await this . pushScript ( fs , params . script ) ;
727+ await this . updateFileDigest ( fs , pushedFileDigestMap ) ;
708728 } ) . catch ( ( e ) => {
709729 this . logger . error ( "push script on install error" , Logger . E ( e ) ) ;
710730 } ) ;
0 commit comments