🚧 WIP: add local path picbed for manage page

This commit is contained in:
萌萌哒赫萝
2023-08-12 01:00:40 -07:00
parent f585bb4d7d
commit e56bd78096
14 changed files with 457 additions and 102 deletions
+9 -30
View File
@@ -154,42 +154,21 @@ export const deleteChoosedFiles = async (list: ImgInfo[]): Promise<boolean[]> =>
const file = await dbStore.removeById(item.id)
if (await picgo.getConfig('settings.deleteCloudFile')) {
if (item.type !== undefined && picBedsCanbeDeleted.includes(item.type)) {
const noteFunc = (value: boolean) => {
const notification = new Notification({
title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'),
body: T(value ? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED' : 'GALLERY_SYNC_DELETE_NOTICE_FAILED')
})
notification.show()
}
if (item.type === 'webdavplist') {
const { fileName, config } = item
setTimeout(() => {
deleteWebdavFile(getRawData(config), fileName || '').then((value: boolean) => {
if (value) {
const notification = new Notification({
title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'),
body: T('GALLERY_SYNC_DELETE_NOTICE_SUCCEED')
})
notification.show()
} else {
const notification = new Notification({
title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'),
body: T('GALLERY_SYNC_DELETE_NOTICE_FAILED')
})
notification.show()
}
})
deleteWebdavFile(getRawData(config), fileName || '').then(noteFunc)
}, 0)
} else {
setTimeout(() => {
ALLApi.delete(item).then((value: boolean) => {
if (value) {
const notification = new Notification({
title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'),
body: T('GALLERY_SYNC_DELETE_NOTICE_SUCCEED')
})
notification.show()
} else {
const notification = new Notification({
title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'),
body: T('GALLERY_SYNC_DELETE_NOTICE_FAILED')
})
notification.show()
}
})
ALLApi.delete(item).then(noteFunc)
}, 0)
}
}
+2
View File
@@ -1,6 +1,7 @@
import AliyunApi from './aliyun'
import GithubApi from './github'
import ImgurApi from './imgur'
import LocalApi from './local'
import QiniuApi from './qiniu'
import S3plistApi from './s3plist'
import SmmsApi from './smms'
@@ -12,6 +13,7 @@ export default {
AliyunApi,
GithubApi,
ImgurApi,
LocalApi,
QiniuApi,
S3plistApi,
SmmsApi,
+319
View File
@@ -0,0 +1,319 @@
// 日志记录器
import ManageLogger from '../utils/logger'
// 错误格式化函数、端点地址格式化函数、获取内部代理、新的下载器、并发异步任务池
import { formatError } from '../utils/common'
// HTTP 代理格式化函数、是否为图片的判断函数
import { isImage } from '@/manage/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { IWindowList } from '#/types/enum'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron'
// 上传下载任务队列
import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus, downloadTaskSpecialStatus } from '../datastore/upDownTaskQueue'
// 文件系统库
import fs from 'fs-extra'
// 路径处理库
import path from 'path'
import * as fsWalk from '@nodelib/fs.walk'
// 取消下载任务的加载文件列表、刷新下载文件传输列表
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
class LocalApi {
logger: ManageLogger
isWindows: boolean
constructor (logger: ManageLogger) {
this.logger = logger
this.isWindows = process.platform === 'win32'
}
logParam = (error:any, method: string) =>
this.logger.error(formatError(error, { class: 'LocalApi', method }))
// windows 系统下将路径转换为 unix 风格
transPathToUnix (filePath: string | undefined) {
if (!filePath) return ''
return this.isWindows ? filePath.split(path.sep).join(path.posix.sep) : filePath.replace(/^\/+/, '')
}
transBack (filePath: string | undefined) {
if (!filePath) return ''
return this.isWindows
? filePath.split(path.posix.sep).join(path.sep).replace(/^\\+/, '')
: `/${filePath.replace(/^\/+/, '')}`
}
formatFolder (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) {
const key = this.transPathToUnix(filePath)
return {
...item,
key,
fileName,
fileSize: 0,
Key: key,
formatedTime: '',
isDir: true,
checked: false,
isImage: false,
match: false,
url: urlPrefix
}
}
formatFile (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) {
const key = this.transPathToUnix(filePath)
return {
...item,
key,
fileName,
fileSize: item.size,
Key: key,
formatedTime: new Date(item.mtime).toLocaleString(),
isDir: false,
checked: false,
match: false,
isImage: isImage(fileName),
url: urlPrefix
}
}
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl = '', cancelToken } = configMap
const urlPrefix = customUrl.replace(/\/+$/, '')
const cancelTask = [false]
ipcMain.on(cancelDownloadLoadingFileList, (_evt: IpcMainEvent, token: string) => {
if (token === cancelToken) {
cancelTask[0] = true
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
})
let res = {} as any
const result = {
fullList: <any>[],
success: false,
finished: false
}
try {
res = fsWalk.walkSync(prefix, {
followSymbolicLinks: true,
fs,
stats: true,
throwErrorOnBrokenSymbolicLink: false
})
if (res.length) {
result.fullList.push(
...res.data
.filter((item: fsWalk.Entry) => item.stats?.isFile())
.map((item: any) => this.formatFile(item, urlPrefix, item.name, item.path))
)
result.success = true
}
} catch (error) {
this.logParam(error, 'getBucketListRecursively')
}
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { customUrl = '', cancelToken, baseDir } = configMap
let prefix = configMap.prefix
prefix = this.transBack(prefix)
let urlPrefix = customUrl.replace(/\/+$/, '')
let webPath = configMap.webPath || ''
if (webPath && customUrl && webPath !== '/') {
webPath = webPath.replace(/^\/+|\/+$/, '')
}
const cancelTask = [false]
ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => {
if (token === cancelToken) {
cancelTask[0] = true
ipcMain.removeAllListeners('cancelLoadingFileList')
}
})
const result = {
fullList: <any>[],
success: false,
finished: false
}
try {
const res = await fs.readdir(prefix, {
withFileTypes: true
})
if (res.length) {
res.forEach((item: fs.Dirent) => {
const pathOfFile = path.join(prefix, item.name)
const relativePath = path.relative(baseDir, pathOfFile)
const relative = webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/')
if (webPath && customUrl) {
urlPrefix = relative
} else {
urlPrefix = pathOfFile
}
const stats = fs.statSync(pathOfFile)
if (item.isDirectory()) {
result.fullList.push(this.formatFolder(stats, urlPrefix, item.name, relativePath))
} else {
result.fullList.push(this.formatFile(stats, urlPrefix, item.name, relativePath))
}
})
result.success = true
}
} catch (error) {
this.logParam(error, 'getBucketListBackstage')
}
result.finished = true
window.webContents.send('refreshFileTransferList', result)
ipcMain.removeAllListeners('cancelLoadingFileList')
}
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
const { oldKey, newKey } = configMap
let result = false
try {
await fs.rename(this.transBack(oldKey), this.transBack(newKey))
result = true
} catch (error) {
this.logParam(error, 'renameBucketFile')
}
return result
}
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
await fs.remove(this.transBack(key))
result = true
} catch (error) {
this.logParam(error, 'deleteBucketFile')
}
return result
}
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
await fs.rmdir(this.transBack(key), {
recursive: true
})
result = true
} catch (error) {
this.logParam(error, 'deleteBucketFolder')
}
return result
}
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) {
const { alias, bucketName, key, filePath, fileName } = item
const id = `${alias}-${bucketName}-${key}-${filePath}`
if (instance.getUploadTask(id)) {
continue
}
instance.addUploadTask({
id,
progress: 0,
status: commonTaskStatus.queuing,
sourceFileName: fileName,
sourceFilePath: filePath,
targetFilePath: key,
targetFileBucket: bucketName,
targetFileRegion: '',
noProgress: true
})
try {
fs.ensureFileSync(filePath)
await fs.copyFile(filePath, this.transBack(key))
instance.updateUploadTask({
id,
progress: 100,
status: uploadTaskSpecialStatus.uploaded,
finishTime: new Date().toLocaleString()
})
} catch (error) {
this.logParam(error, 'uploadBucketFile')
instance.updateUploadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
finishTime: new Date().toLocaleString()
})
}
}
return true
}
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
await fs.mkdir(this.transBack(key), {
recursive: true
})
result = true
} catch (error) {
this.logParam(error, 'createBucketFolder')
}
return result
}
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) {
const { alias, bucketName, key, fileName } = item
const savedFilePath = path.join(downloadPath, fileName)
const id = `${alias}-${bucketName}-local-${key}`
if (instance.getDownloadTask(id)) {
continue
}
instance.addDownloadTask({
id,
progress: 0,
status: commonTaskStatus.queuing,
sourceFileName: fileName,
targetFilePath: savedFilePath
})
try {
fs.ensureFileSync(savedFilePath)
await fs.copyFile(this.transBack(key), savedFilePath)
instance.updateDownloadTask({
id,
progress: 100,
status: downloadTaskSpecialStatus.downloaded,
finishTime: new Date().toLocaleString()
})
} catch (error) {
this.logParam(error, 'downloadBucketFile')
instance.updateDownloadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
finishTime: new Date().toLocaleString()
})
}
}
return true
}
}
export default LocalApi
+1 -10
View File
@@ -137,20 +137,11 @@ class WebdavplistApi {
}
})
}
} else {
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return
result.success = true
}
} catch (error) {
this.logParam(error, 'getBucketListRecursively')
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return
}
result.success = true
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
+22 -15
View File
@@ -54,22 +54,24 @@ export class ManageApi extends EventEmitter implements ManageApiType {
createClient () {
const name = this.currentPicBedConfig.picBedName
switch (name) {
case 'tcyun':
return new API.TcyunApi(this.currentPicBedConfig.secretId, this.currentPicBedConfig.secretKey, this.logger)
case 'aliyun':
return new API.AliyunApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.accessKeySecret, this.logger)
case 'qiniu':
return new API.QiniuApi(this.currentPicBedConfig.accessKey, this.currentPicBedConfig.secretKey, this.logger)
case 'upyun':
return new API.UpyunApi(this.currentPicBedConfig.bucketName, this.currentPicBedConfig.operator, this.currentPicBedConfig.password, this.logger)
case 'smms':
return new API.SmmsApi(this.currentPicBedConfig.token, this.logger)
case 'github':
return new API.GithubApi(this.currentPicBedConfig.token, this.currentPicBedConfig.githubUsername, this.currentPicBedConfig.proxy, this.logger)
case 'imgur':
return new API.ImgurApi(this.currentPicBedConfig.imgurUserName, this.currentPicBedConfig.accessToken, this.currentPicBedConfig.proxy, this.logger)
case 'local':
return new API.LocalApi(this.logger)
case 'qiniu':
return new API.QiniuApi(this.currentPicBedConfig.accessKey, this.currentPicBedConfig.secretKey, this.logger)
case 'smms':
return new API.SmmsApi(this.currentPicBedConfig.token, this.logger)
case 's3plist':
return new API.S3plistApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.secretAccessKey, this.currentPicBedConfig.endpoint, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.s3ForcePathStyle, this.currentPicBedConfig.proxy, this.logger)
case 'tcyun':
return new API.TcyunApi(this.currentPicBedConfig.secretId, this.currentPicBedConfig.secretKey, this.logger)
case 'upyun':
return new API.UpyunApi(this.currentPicBedConfig.bucketName, this.currentPicBedConfig.operator, this.currentPicBedConfig.password, this.logger)
case 'webdavplist':
return new API.WebdavplistApi(this.currentPicBedConfig.endpoint, this.currentPicBedConfig.username, this.currentPicBedConfig.password, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.proxy, this.logger)
default:
@@ -148,6 +150,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
param?: IStringKeyMap | undefined
): Promise<any> {
let client
const name = this.currentPicBedConfig.picBedName.replace('plist', '')
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
case 'aliyun':
@@ -169,15 +172,11 @@ export class ManageApi extends EventEmitter implements ManageApiType {
CreationDate: new Date().toISOString()
}]
case 'smms':
return [{
Name: 'smms',
Location: 'smms',
CreationDate: new Date().toISOString()
}]
case 'webdavplist':
case 'local':
return [{
Name: 'webdav',
Location: 'webdav',
Name: name,
Location: name,
CreationDate: new Date().toISOString()
}]
default:
@@ -313,6 +312,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
case 'imgur':
case 's3plist':
case 'webdavplist':
case 'local':
try {
client = this.createClient() as any
return await client.getBucketListRecursively(param!)
@@ -356,6 +356,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
case 'imgur':
case 's3plist':
case 'webdavplist':
case 'local':
try {
client = this.createClient() as any
return await client.getBucketListBackstage(param!)
@@ -426,6 +427,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
case 'imgur':
case 's3plist':
case 'webdavplist':
case 'local':
try {
client = this.createClient() as any
const res = await client.deleteBucketFile(param!)
@@ -451,6 +453,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
case 'github':
case 's3plist':
case 'webdavplist':
case 'local':
try {
client = this.createClient() as any
return await client.deleteBucketFolder(param!)
@@ -474,6 +477,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
case 'upyun':
case 's3plist':
case 'webdavplist':
case 'local':
try {
client = this.createClient() as any
return await client.renameBucketFile(param!)
@@ -500,6 +504,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
case 'imgur':
case 's3plist':
case 'webdavplist':
case 'local':
try {
client = this.createClient() as any
const res = await client.downloadBucketFile(param!)
@@ -532,6 +537,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
case 'github':
case 's3plist':
case 'webdavplist':
case 'local':
try {
client = this.createClient() as any
return await client.createBucketFolder(param!)
@@ -558,6 +564,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
case 'imgur':
case 's3plist':
case 'webdavplist':
case 'local':
try {
client = this.createClient() as any
return await client.uploadBucketFile(param!)
+5 -8
View File
@@ -21,14 +21,11 @@ function beforeOpen () {
*/
function resolveMacWorkFlow () {
const dest = `${os.homedir()}/Library/Services/Upload pictures with PicList.workflow`
if (fs.existsSync(dest)) {
return true
} else {
try {
fs.copySync(path.join(__static, 'Upload pictures with PicList.workflow'), dest)
} catch (e) {
console.log(e)
}
if (fs.existsSync(dest)) return true
try {
fs.copySync(path.join(__static, 'Upload pictures with PicList.workflow'), dest)
} catch (e) {
console.log(e)
}
}