export.ts 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. import { invoke } from "@tauri-apps/api/core"
  2. import { save } from "@tauri-apps/plugin-dialog"
  3. import { listen, type UnlistenFn } from "@tauri-apps/api/event"
  4. import type {
  5. BackupExportOptions,
  6. ExportParams,
  7. ExportResult,
  8. BackupProgressCallback,
  9. } from "./types"
  10. const LS_PREFIXES = ["qmai", "lk-"]
  11. const SENSITIVE_STORAGE_KEY = /(api[_-]?key|token|secret|password|fingerprint)/i
  12. function collectLocalStorage(includeCredentials: boolean): Record<string, string> {
  13. const data: Record<string, string> = {}
  14. for (let i = 0; i < localStorage.length; i++) {
  15. const key = localStorage.key(i)
  16. if (!key) continue
  17. if (LS_PREFIXES.some((prefix) => key.startsWith(prefix))) {
  18. if (!includeCredentials && SENSITIVE_STORAGE_KEY.test(key)) continue
  19. const value = localStorage.getItem(key)
  20. if (value !== null) {
  21. data[key] = value
  22. }
  23. }
  24. }
  25. return data
  26. }
  27. export async function exportBackup(
  28. options: BackupExportOptions,
  29. onProgress?: BackupProgressCallback,
  30. ): Promise<ExportResult> {
  31. const now = new Date()
  32. const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}`
  33. const defaultName = `qmai-backup-${dateStr}.zip`
  34. const savePath = await save({
  35. defaultPath: defaultName,
  36. filters: [{ name: "ZIP 备份文件", extensions: ["zip"] }],
  37. })
  38. if (!savePath) {
  39. return {
  40. success: false,
  41. warnings: [],
  42. fileCount: 0,
  43. totalSize: 0,
  44. error: "用户取消了导出",
  45. }
  46. }
  47. const localStorageData = options.includeUiPreferences
  48. ? collectLocalStorage(options.includeCredentials)
  49. : {}
  50. const params: ExportParams = {
  51. savePath,
  52. includeGlobalConfig: options.includeGlobalConfig,
  53. includeUiPreferences: options.includeUiPreferences,
  54. includeCredentials: options.includeCredentials,
  55. localStorageData,
  56. projects: options.projects,
  57. }
  58. let unlisten: UnlistenFn | undefined
  59. try {
  60. if (onProgress) {
  61. unlisten = await listen("backup-progress", (event) => {
  62. onProgress(event.payload as never)
  63. })
  64. }
  65. const result = await invoke<ExportResult>("export_backup", { params })
  66. return result
  67. } finally {
  68. unlisten?.()
  69. }
  70. }