mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
🚧 WIP(custom): optimize gallery db sync logic and force refresh gallery
This commit is contained in:
@@ -107,8 +107,8 @@ class GalleryDB {
|
|||||||
console.log('init gallery db')
|
console.log('init gallery db')
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(): DBStore {
|
static getInstance(forceRefresh: boolean = false): DBStore {
|
||||||
if (!GalleryDB.#instance) {
|
if (!GalleryDB.#instance || forceRefresh) {
|
||||||
GalleryDB.#instance = new DBStore(DB_PATH, 'gallery')
|
GalleryDB.#instance = new DBStore(DB_PATH, 'gallery')
|
||||||
}
|
}
|
||||||
return GalleryDB.#instance
|
return GalleryDB.#instance
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ export interface IConfigStruct {
|
|||||||
autoImport: boolean
|
autoImport: boolean
|
||||||
autoImportPicBed: string[]
|
autoImportPicBed: string[]
|
||||||
galleryPicBedFilter: string[]
|
galleryPicBedFilter: string[]
|
||||||
|
enableSecondUploader?: boolean
|
||||||
|
lastSyncTime?: number
|
||||||
}
|
}
|
||||||
needReload: boolean
|
needReload: boolean
|
||||||
picgoPlugins: IPicGoPlugins
|
picgoPlugins: IPicGoPlugins
|
||||||
@@ -189,6 +191,7 @@ export const configPaths = {
|
|||||||
autoImportPicBed: 'settings.autoImportPicBed',
|
autoImportPicBed: 'settings.autoImportPicBed',
|
||||||
galleryPicBedFilter: 'settings.galleryPicBedFilter',
|
galleryPicBedFilter: 'settings.galleryPicBedFilter',
|
||||||
enableSecondUploader: 'settings.enableSecondUploader',
|
enableSecondUploader: 'settings.enableSecondUploader',
|
||||||
|
lastSyncTime: 'settings.lastSyncTime',
|
||||||
},
|
},
|
||||||
needReload: 'needReload',
|
needReload: 'needReload',
|
||||||
picgoPlugins: 'picgoPlugins',
|
picgoPlugins: 'picgoPlugins',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import os from 'node:os'
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
import db from '@core/datastore'
|
import db from '@core/datastore'
|
||||||
|
import { GalleryDB } from '@core/datastore'
|
||||||
import logger from '@core/picgo/logger'
|
import logger from '@core/picgo/logger'
|
||||||
import { Octokit } from '@octokit/rest'
|
import { Octokit } from '@octokit/rest'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
@@ -10,15 +11,15 @@ import fs from 'fs-extra'
|
|||||||
import { HttpsProxyAgent } from 'hpagent'
|
import { HttpsProxyAgent } from 'hpagent'
|
||||||
import { AuthType, createClient, WebDAVClientOptions } from 'webdav'
|
import { AuthType, createClient, WebDAVClientOptions } from 'webdav'
|
||||||
|
|
||||||
import type { ISyncConfig } from '#/types/types'
|
import type { IGalleryDBFile, IGalleryDBGalleryItem, ISyncConfig } from '#/types/types'
|
||||||
import { extractData, zipData } from '~/utils/common'
|
import { extractData, zipData } from '~/utils/common'
|
||||||
import { formatEndpoint } from '~/utils/common'
|
import { formatEndpoint } from '~/utils/common'
|
||||||
import { configPaths } from '~/utils/configPaths'
|
import { configPaths } from '~/utils/configPaths'
|
||||||
|
|
||||||
const STORE_PATH = app.getPath('userData')
|
const STORE_PATH = app.getPath('userData')
|
||||||
const tempDir = path.join(os.tmpdir(), `piclist-sync-tmp`)
|
const tempDir = path.join(os.tmpdir(), `piclist-sync-tmp`)
|
||||||
const db1 = path.join(tempDir, 'db1')
|
const localDBPath = path.join(tempDir, 'db1')
|
||||||
const db2 = path.join(tempDir, 'db2')
|
const remoteDBPath = path.join(tempDir, 'db2')
|
||||||
const dbMerged = path.join(tempDir, 'db-merged')
|
const dbMerged = path.join(tempDir, 'db-merged')
|
||||||
const galleryDBList = ['piclist.db', 'piclist.bak.db']
|
const galleryDBList = ['piclist.db', 'piclist.bak.db']
|
||||||
|
|
||||||
@@ -30,37 +31,50 @@ const uploadOrUpdateMsg = (fileName: string, isUpdate: boolean = true) =>
|
|||||||
isUpdate ? `update ${fileName} from PicList` : `upload ${fileName} from PicList`
|
isUpdate ? `update ${fileName} from PicList` : `upload ${fileName} from PicList`
|
||||||
|
|
||||||
const emptyDir = async (): Promise<void> => {
|
const emptyDir = async (): Promise<void> => {
|
||||||
await fs.emptyDir(tempDir)
|
for (const dir of [tempDir, localDBPath, remoteDBPath, dbMerged]) {
|
||||||
await fs.emptyDir(db1)
|
await fs.emptyDir(dir)
|
||||||
await fs.emptyDir(db2)
|
}
|
||||||
await fs.emptyDir(dbMerged)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergeGalleryDB = async (targetFile: string) => {
|
const mergeGalleryDB = async (targetFile: string) => {
|
||||||
|
const lastSyncTime = db.get(configPaths.settings.lastSyncTime) || 0
|
||||||
try {
|
try {
|
||||||
const db1Data = await extractData(path.join(db1, targetFile))
|
const localDBData = (await extractData(path.join(localDBPath, targetFile))) as IGalleryDBFile
|
||||||
const db2Data = await extractData(path.join(db2, targetFile))
|
const remoteDBData = (await extractData(path.join(remoteDBPath, targetFile))) as IGalleryDBFile
|
||||||
const mergedData: any = {
|
const localMap = new Map(localDBData.gallery.map((item: IGalleryDBGalleryItem) => [item.id, item]))
|
||||||
gallery: [],
|
const remoteMap = new Map(remoteDBData.gallery.map((item: IGalleryDBGalleryItem) => [item.id, item]))
|
||||||
__gallery_KEY__: {},
|
const mergedGalleryMap = new Map<string, any>()
|
||||||
}
|
|
||||||
const db1Ids = new Set<string>(Object.keys(db1Data.__gallery_KEY__ || {}))
|
for (const [id, localItem] of localMap) {
|
||||||
const db2Ids = new Set<string>(Object.keys(db2Data.__gallery_KEY__ || {}))
|
const remoteItem = remoteMap.get(id)
|
||||||
const idSet = new Set<string>([...db1Ids, ...db2Ids])
|
if (!remoteItem) {
|
||||||
for (const id of idSet) {
|
mergedGalleryMap.set(id, localItem)
|
||||||
if (db2Ids.has(id)) {
|
} else {
|
||||||
mergedData.gallery.push(db2Data.gallery.find((item: any) => item.id === id))
|
const newest = (localItem.updatedAt || 0) >= (remoteItem.updatedAt || 0) ? localItem : remoteItem
|
||||||
} else if (db1Ids.has(id)) {
|
mergedGalleryMap.set(id, newest)
|
||||||
mergedData.gallery.push(db1Data.gallery.find((item: any) => item.id === id))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const item of mergedData.gallery) {
|
for (const [id, remoteItem] of remoteMap) {
|
||||||
mergedData.__gallery_KEY__[item.id] = 1
|
if (!localMap.has(id) && (remoteItem.updatedAt || 0) >= lastSyncTime) {
|
||||||
|
console.log('newer in remote:', JSON.stringify(remoteItem))
|
||||||
|
mergedGalleryMap.set(id, remoteItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await zipData(mergedData, path.join(dbMerged, targetFile))
|
|
||||||
await fs.copyFile(path.join(dbMerged, targetFile), path.join(STORE_PATH, targetFile))
|
const galleryKeyObj: Record<string, number> = {}
|
||||||
|
mergedGalleryMap.forEach((_, id) => {
|
||||||
|
galleryKeyObj[id] = 1
|
||||||
|
})
|
||||||
|
const mergedData = {
|
||||||
|
gallery: Array.from(mergedGalleryMap.values()),
|
||||||
|
__gallery_KEY__: galleryKeyObj,
|
||||||
|
}
|
||||||
|
const targetFilePath = path.join(dbMerged, targetFile)
|
||||||
|
await zipData(mergedData, targetFilePath)
|
||||||
|
await fs.copyFile(targetFilePath, path.join(STORE_PATH, targetFile))
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
logger.error('merge gallery db failed:', String(err))
|
logger.error('merge gallery db failed:', String(err))
|
||||||
|
throw new Error('merge gallery db failed')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +367,7 @@ async function downloadAndWriteFile(url: string, localFilePath: string, config:
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function downloadRemoteToLocal(syncConfig: ISyncConfig, fileName: string, galleryMode = false) {
|
async function downloadRemoteToLocal(syncConfig: ISyncConfig, fileName: string, galleryMode = false) {
|
||||||
const storePath = galleryMode ? db2 : STORE_PATH
|
const storePath = galleryMode ? remoteDBPath : STORE_PATH
|
||||||
const localFilePath = path.join(storePath, fileName)
|
const localFilePath = path.join(storePath, fileName)
|
||||||
const { username, repo, branch, token, proxy, type } = syncConfig
|
const { username, repo, branch, token, proxy, type } = syncConfig
|
||||||
try {
|
try {
|
||||||
@@ -582,6 +596,8 @@ async function syncGallery(): Promise<number> {
|
|||||||
await uploadLocalToRemote(syncConfig, file)
|
await uploadLocalToRemote(syncConfig, file)
|
||||||
logger.info(`gallery db ${file} not exist in cloud, upload local file instead`)
|
logger.info(`gallery db ${file} not exist in cloud, upload local file instead`)
|
||||||
successCount++
|
successCount++
|
||||||
|
db.set(configPaths.settings.lastSyncTime, Date.now())
|
||||||
|
GalleryDB.getInstance(true)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -589,9 +605,11 @@ async function syncGallery(): Promise<number> {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
await downloadRemoteToLocal(syncConfig, file, true)
|
await downloadRemoteToLocal(syncConfig, file, true)
|
||||||
await fs.copyFile(path.join(STORE_PATH, file), path.join(db1, file))
|
await fs.copyFile(path.join(STORE_PATH, file), path.join(localDBPath, file))
|
||||||
await mergeGalleryDB(file)
|
await mergeGalleryDB(file)
|
||||||
await updateLocalToRemote(syncConfig, file)
|
await updateLocalToRemote(syncConfig, file)
|
||||||
|
db.set(configPaths.settings.lastSyncTime, Date.now())
|
||||||
|
GalleryDB.getInstance(true) // refresh gallery db instance
|
||||||
logger.info(`sync gallery db ${file} success`)
|
logger.info(`sync gallery db ${file} success`)
|
||||||
successCount++
|
successCount++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -717,10 +717,10 @@
|
|||||||
"syncConfigNote": "The files to be synced are configuration files",
|
"syncConfigNote": "The files to be synced are configuration files",
|
||||||
"syncConfigProxy": "Proxy",
|
"syncConfigProxy": "Proxy",
|
||||||
"syncConfiguration": "Sync Configuration",
|
"syncConfiguration": "Sync Configuration",
|
||||||
"syncEndpointConfig": "Sync Endpoint Configuration",
|
"syncEndpointConfig": "Endpoint Configuration",
|
||||||
"syncResult": { "failed": "Sync Failed", "success": "Sync Successful" },
|
"syncResult": { "failed": "Sync Failed", "success": "Sync Successful" },
|
||||||
"title": "Sync",
|
"title": "Sync",
|
||||||
"upDownloadSettings": "Upload and Download Settings",
|
"upDownloadSettings": "Sync Config & Gallery",
|
||||||
"uploadSettings": "Upload Settings",
|
"uploadSettings": "Upload Settings",
|
||||||
"webdav": {
|
"webdav": {
|
||||||
"authType": "WebDAV Auth Type",
|
"authType": "WebDAV Auth Type",
|
||||||
|
|||||||
@@ -712,10 +712,10 @@
|
|||||||
"syncConfigNote": "同步的文件为配置文件",
|
"syncConfigNote": "同步的文件为配置文件",
|
||||||
"syncConfigProxy": "代理",
|
"syncConfigProxy": "代理",
|
||||||
"syncConfiguration": "同步配置",
|
"syncConfiguration": "同步配置",
|
||||||
"syncEndpointConfig": "同步方案配置",
|
"syncEndpointConfig": "平台设置",
|
||||||
"syncResult": { "failed": "同步失败", "success": "同步成功" },
|
"syncResult": { "failed": "同步失败", "success": "同步成功" },
|
||||||
"title": "配置/同步",
|
"title": "配置/相册同步",
|
||||||
"upDownloadSettings": "上传下载配置文件",
|
"upDownloadSettings": "同步配置和相册",
|
||||||
"uploadSettings": "上传配置",
|
"uploadSettings": "上传配置",
|
||||||
"webdav": {
|
"webdav": {
|
||||||
"authType": "WebDAV 认证类型",
|
"authType": "WebDAV 认证类型",
|
||||||
|
|||||||
@@ -712,10 +712,10 @@
|
|||||||
"syncConfigNote": "同步的文件為配置文件",
|
"syncConfigNote": "同步的文件為配置文件",
|
||||||
"syncConfigProxy": "代理",
|
"syncConfigProxy": "代理",
|
||||||
"syncConfiguration": "同步配置",
|
"syncConfiguration": "同步配置",
|
||||||
"syncEndpointConfig": "同步方案配置",
|
"syncEndpointConfig": "平台配置",
|
||||||
"syncResult": { "failed": "同步失敗", "success": "同步成功" },
|
"syncResult": { "failed": "同步失敗", "success": "同步成功" },
|
||||||
"title": "配置/同步",
|
"title": "配置/相冊同步",
|
||||||
"upDownloadSettings": "上傳下載配置文件",
|
"upDownloadSettings": "同步配置和相冊",
|
||||||
"uploadSettings": "上傳配置",
|
"uploadSettings": "上傳配置",
|
||||||
"webdav": {
|
"webdav": {
|
||||||
"authType": "WebDAV 認證類型",
|
"authType": "WebDAV 認證類型",
|
||||||
|
|||||||
@@ -523,3 +523,13 @@ export interface IHTTPProxy {
|
|||||||
port: number
|
port: number
|
||||||
protocol: string
|
protocol: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IGalleryDBGalleryItem {
|
||||||
|
id: string
|
||||||
|
updatedAt?: number
|
||||||
|
[propName: string]: any
|
||||||
|
}
|
||||||
|
export interface IGalleryDBFile {
|
||||||
|
gallery: IGalleryDBGalleryItem[]
|
||||||
|
__gallery_KEY__: Record<string, number>
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user