diff --git a/backend/services/browser_service.go b/backend/services/browser_service.go index a73e0b8..dfb4e86 100644 --- a/backend/services/browser_service.go +++ b/backend/services/browser_service.go @@ -2073,7 +2073,7 @@ func (b *browserService) DeleteOneKey(server string, db int, k any) (resp types. } // DeleteKeys delete keys sync with notification -func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo string) (resp types.JSResp) { +func (b *browserService) DeleteKeys(server string, db int, ks []any, notice bool, serialNo string) (resp types.JSResp) { // connect a new connection to export keys conf := Connection().getConnection(server) if conf == nil { @@ -2093,7 +2093,7 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st defer cancelFunc() cancelEvent := "delete:stop:" + serialNo - runtime.EventsOnce(ctx, cancelEvent, func(data ...any) { + cancelStopEvent := runtime.EventsOnce(ctx, cancelEvent, func(data ...any) { cancelFunc() }) processEvent := "deleting:" + serialNo @@ -2104,29 +2104,41 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st var mutex sync.Mutex del := func(ctx context.Context, cli redis.UniversalClient) error { startTime := time.Now().Add(-10 * time.Second) + supportUnlink := true for i, k := range ks { // emit progress per second - if i >= total-1 || time.Now().Sub(startTime).Milliseconds() > 100 { + if notice && (i >= total-1 || time.Now().Sub(startTime).Milliseconds() > 100) { startTime = time.Now() param := map[string]any{ "total": total, "progress": i + 1, "processing": k, } - runtime.EventsEmit(b.ctx, processEvent, param) + runtime.EventsEmit(ctx, processEvent, param) // do some sleep to prevent blocking the Redis server time.Sleep(10 * time.Millisecond) } key := strutil.DecodeRedisKey(k) - delErr := cli.Del(ctx, key).Err() - if err != nil { - failed.Add(1) - } else { - // save deleted key - mutex.Lock() - deletedKeys = append(deletedKeys, k) - mutex.Unlock() + var delErr error + if supportUnlink { + if delErr = cli.Unlink(ctx, key).Err(); delErr != nil { + supportUnlink = false + delErr = nil + } + } + if !supportUnlink { + delErr = cli.Del(ctx, key).Err() + } + if notice { + if delErr != nil { + failed.Add(1) + } else { + // save deleted key + mutex.Lock() + deletedKeys = append(deletedKeys, k) + mutex.Unlock() + } } if errors.Is(delErr, context.Canceled) || canceled { canceled = true @@ -2145,7 +2157,7 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st err = del(ctx, client) } - runtime.EventsOff(ctx, cancelEvent) + cancelStopEvent() resp.Success = true resp.Data = struct { Canceled bool `json:"canceled"` @@ -2189,8 +2201,7 @@ func (b *browserService) ExportKey(server string, db int, ks []any, path string, writer := csv.NewWriter(file) defer writer.Flush() - cancelEvent := "export:stop:" + path - runtime.EventsOnce(ctx, cancelEvent, func(data ...any) { + cancelStopEvent := runtime.EventsOnce(ctx, "export:stop:"+path, func(data ...any) { cancelFunc() }) processEvent := "exporting:" + path @@ -2206,7 +2217,7 @@ func (b *browserService) ExportKey(server string, db int, ks []any, path string, "progress": i + 1, "processing": k, } - runtime.EventsEmit(b.ctx, processEvent, param) + runtime.EventsEmit(ctx, processEvent, param) } key := strutil.DecodeRedisKey(k) @@ -2230,7 +2241,7 @@ func (b *browserService) ExportKey(server string, db int, ks []any, path string, } } - runtime.EventsOff(ctx, cancelEvent) + cancelStopEvent() resp.Success = true resp.Data = struct { Canceled bool `json:"canceled"` @@ -2274,7 +2285,7 @@ func (b *browserService) ImportCSV(server string, db int, path string, conflict reader := csv.NewReader(file) cancelEvent := "import:stop:" + path - runtime.EventsOnce(ctx, cancelEvent, func(data ...any) { + cancelStopEvent := runtime.EventsOnce(ctx, cancelEvent, func(data ...any) { cancelFunc() }) processEvent := "importing:" + path @@ -2349,7 +2360,7 @@ func (b *browserService) ImportCSV(server string, db int, path string, conflict } } - runtime.EventsOff(ctx, cancelEvent) + cancelStopEvent() resp.Success = true resp.Data = struct { Canceled bool `json:"canceled"` diff --git a/frontend/src/components/dialogs/DeleteKeyDialog.vue b/frontend/src/components/dialogs/DeleteKeyDialog.vue index 40d5755..0bb83ec 100644 --- a/frontend/src/components/dialogs/DeleteKeyDialog.vue +++ b/frontend/src/components/dialogs/DeleteKeyDialog.vue @@ -74,7 +74,7 @@ const onConfirmDelete = async () => { deleting.value = true const { server, db, key, affectedKeys } = deleteForm await nextTick() - browserStore.deleteKeys(server, db, affectedKeys).catch((e) => {}) + browserStore.deleteKeys(server, db, affectedKeys, !deleteForm.async).catch((e) => {}) } catch (e) { $message.error(e.message) return @@ -115,11 +115,9 @@ const onClose = () => { required> - - - - - + + {{ $t('dialogue.key.silent') }} + } */ - async deleteKeys(server, db, keys) { - const msgRef = $message.loading('', { duration: 0, closable: true }) + async deleteKeys(server, db, keys, notice) { + const msgRef = $message.loading(i18nGlobal.t('dialogue.delete.deleting'), { duration: 0, closable: true }) let deleted = [] let failCount = 0 let canceled = false const serialNo = Date.now().valueOf().toString() - const eventName = 'deleting:' + serialNo - const cancelEvent = 'delete:stop:' + serialNo - try { - let maxProgress = 0 - EventsOn(eventName, ({ total, progress, processing }) => { - // update delete progress - if (progress > maxProgress) { - maxProgress = progress - } - const k = decodeRedisKey(processing) - msgRef.content = i18nGlobal.t('dialogue.delete.doing', { - key: k, - index: maxProgress, - count: total, - }) - }) - msgRef.onClose = () => { - EventsEmit(cancelEvent) + let maxProgress = 0 + const cancelEventFn = EventsOn('deleting:' + serialNo, ({ total, progress, processing }) => { + // update delete progress + if (progress > maxProgress) { + maxProgress = progress } - const { data, success, msg } = await DeleteKeys(server, db, keys, serialNo) + const k = decodeRedisKey(processing) + msgRef.content = i18nGlobal.t('dialogue.delete.doing', { + key: k, + index: maxProgress, + count: total, + }) + }) + msgRef.onClose = () => { + EventsEmit('delete:stop:' + serialNo) + } + try { + const { data, success, msg } = await DeleteKeys(server, db, keys, notice, serialNo) if (success) { canceled = get(data, 'canceled', false) deleted = get(data, 'deleted', []) @@ -1650,8 +1649,8 @@ const useBrowserStore = defineStore('browser', { $message.error(msg) } } finally { + cancelEventFn() msgRef.destroy() - EventsOff(eventName) // clear checked keys const tab = useTabStore() tab.setCheckedKeys(server) @@ -1662,13 +1661,40 @@ const useBrowserStore = defineStore('browser', { $message.info(i18nGlobal.t('dialogue.handle_cancel')) } else if (failCount <= 0) { // no fail - $message.success(i18nGlobal.t('dialogue.delete.completed', { success: deletedCount, fail: failCount })) + let msg = i18nGlobal.t('dialogue.delete.completed') + if (notice) { + msg += + '\n' + + i18nGlobal.t('dialogue.delete.completed_status', { + success: deletedCount, + fail: failCount, + }) + } + $message.success(msg) } else if (failCount >= deletedCount) { // all fail - $message.error(i18nGlobal.t('dialogue.delete.completed', { success: deletedCount, fail: failCount })) + let msg = i18nGlobal.t('dialogue.delete.completed') + if (notice) { + msg += + '\n' + + i18nGlobal.t('dialogue.delete.completed_status', { + success: deletedCount, + fail: failCount, + }) + } + $message.error(msg) } else { // some fail - $message.warn(i18nGlobal.t('dialogue.delete.completed', { success: deletedCount, fail: failCount })) + let msg = i18nGlobal.t('dialogue.delete.completed') + if (notice) { + msg += + '\n' + + i18nGlobal.t('dialogue.delete.completed_status', { + success: deletedCount, + fail: failCount, + }) + } + $message.warn(msg) } // update ui timeout(100).then(async () => { @@ -1703,19 +1729,18 @@ const useBrowserStore = defineStore('browser', { let exported = 0 let failCount = 0 let canceled = false - const eventName = 'exporting:' + path - try { - EventsOn(eventName, ({ total, progress, processing }) => { - // update export progress - msgRef.content = i18nGlobal.t('dialogue.export.exporting', { - // key: decodeRedisKey(processing), - index: progress, - count: total, - }) + const cancelEventFn = EventsOn('exporting:' + path, ({ total, progress, processing }) => { + // update export progress + msgRef.content = i18nGlobal.t('dialogue.export.exporting', { + // key: decodeRedisKey(processing), + index: progress, + count: total, }) - msgRef.onClose = () => { - EventsEmit('export:stop:' + path) - } + }) + msgRef.onClose = () => { + EventsEmit('export:stop:' + path) + } + try { const { data, success, msg } = await ExportKey(server, db, keys, path, expire) if (success) { canceled = get(data, 'canceled', false) @@ -1726,7 +1751,7 @@ const useBrowserStore = defineStore('browser', { } } finally { msgRef.destroy() - EventsOff(eventName) + cancelEventFn() } if (canceled) { $message.info(i18nGlobal.t('dialogue.handle_cancel')) @@ -1759,19 +1784,18 @@ const useBrowserStore = defineStore('browser', { let imported = 0 let ignored = 0 let canceled = false - const eventName = 'importing:' + path - try { - EventsOn(eventName, ({ imported = 0, ignored = 0 }) => { - // update export progress - msgRef.content = i18nGlobal.t('dialogue.import.importing', { - // key: decodeRedisKey(processing), - imported, - conflict: ignored, - }) + const cancelEventFn = EventsOn('importing:' + path, ({ imported = 0, ignored = 0 }) => { + // update export progress + msgRef.content = i18nGlobal.t('dialogue.import.importing', { + // key: decodeRedisKey(processing), + imported, + conflict: ignored, }) - msgRef.onClose = () => { - EventsEmit('import:stop:' + path) - } + }) + msgRef.onClose = () => { + EventsEmit('import:stop:' + path) + } + try { const { data, success, msg } = await ImportCSV(server, db, path, conflict, ttl) if (success) { canceled = get(data, 'canceled', false) @@ -1781,8 +1805,8 @@ const useBrowserStore = defineStore('browser', { $message.error(msg) } } finally { + cancelEventFn() msgRef.destroy() - EventsOff(eventName) } if (canceled) { $message.info(i18nGlobal.t('dialogue.handle_cancel'))