🎨 Style(custom): format with prettier

This commit is contained in:
Kuingsmile
2024-06-15 19:37:50 +08:00
parent 096f564c31
commit 5af8a6b529
157 changed files with 21365 additions and 22952 deletions
+3
View File
@@ -3,3 +3,6 @@ test/unit/*.js
test/e2e/*.js test/e2e/*.js
dist/ dist/
src/**/*.js src/**/*.js
node_modules
dist
vue.config.js
+20 -3
View File
@@ -10,7 +10,8 @@ module.exports = {
extends: [ extends: [
'plugin:vue/vue3-recommended', 'plugin:vue/vue3-recommended',
'@vue/standard', '@vue/standard',
'@vue/typescript' '@vue/typescript/recommended',
'@vue/eslint-config-prettier'
], ],
plugins: ['@typescript-eslint'], plugins: ['@typescript-eslint'],
rules: { rules: {
@@ -20,8 +21,24 @@ module.exports = {
'no-async-promise-executor': 'off', 'no-async-promise-executor': 'off',
'no-unused-vars': 'off', 'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/indent': ['error', 2], '@typescript-eslint/indent': 'off',
'vue/no-v-html': 'off' '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-expect-error': 'allow-with-description',
'ts-nocheck': 'allow-with-description'
}
],
'vue/no-v-html': 'off',
'prettier/prettier': [
'error',
{},
{
usePrettierrc: true
}
]
}, },
parserOptions: { parserOptions: {
parser: '@typescript-eslint/parser' parser: '@typescript-eslint/parser'
+18
View File
@@ -0,0 +1,18 @@
{
"arrowParens": "avoid",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": true,
"printWidth": 120,
"proseWrap": "always",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false,
"endOfLine": "lf"
}
+3 -5
View File
@@ -1,6 +1,4 @@
module.exports = { module.exports = {
presets: [ presets: ["@vue/cli-plugin-babel/preset"],
'@vue/cli-plugin-babel/preset' plugins: ["@babel/plugin-proposal-optional-chaining"],
], };
plugins: ['@babel/plugin-proposal-optional-chaining']
}
+4 -8
View File
@@ -87,7 +87,6 @@
"write-file-atomic": "^4.0.1" "write-file-atomic": "^4.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/video.js": "^7.3.58",
"@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@electron/notarize": "^2.1.0", "@electron/notarize": "^2.1.0",
"@types/ali-oss": "^6.16.11", "@types/ali-oss": "^6.16.11",
@@ -103,14 +102,14 @@
"@types/tunnel": "^0.0.7", "@types/tunnel": "^0.0.7",
"@types/upyun": "^3.4.3", "@types/upyun": "^3.4.3",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@types/video.js": "^7.3.58",
"@types/write-file-atomic": "^4.0.3", "@types/write-file-atomic": "^4.0.3",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"@vue/cli-plugin-babel": "^5.0.8", "@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8", "@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-router": "^5.0.8", "@vue/cli-plugin-router": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8", "@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8", "@vue/cli-service": "^5.0.8",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-standard": "^8.0.1", "@vue/eslint-config-standard": "^8.0.1",
"@vue/eslint-config-typescript": "^12.0.0", "@vue/eslint-config-typescript": "^12.0.0",
"@vue/runtime-dom": "^3.4.27", "@vue/runtime-dom": "^3.4.27",
@@ -119,16 +118,13 @@
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"dpdm": "^3.14.0", "dpdm": "^3.14.0",
"electron": "^22.3.27", "electron": "^22.3.27",
"eslint": "^8.54.0", "eslint": "^8.57.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-promise": "^6.2.0",
"eslint-plugin-vue": "^9.26.0", "eslint-plugin-vue": "^9.26.0",
"husky": "^3.1.0", "husky": "^3.1.0",
"node-bump-version": "^1.0.2", "node-bump-version": "^1.0.2",
"node-loader": "^2.0.0", "node-loader": "^2.0.0",
"npm-check-updates": "^16.14.20", "npm-check-updates": "^16.14.20",
"prettier": "^3.3.2",
"stylus": "^0.59.0", "stylus": "^0.59.0",
"stylus-loader": "^7.1.3", "stylus-loader": "^7.1.3",
"typescript": "^4.9.5", "typescript": "^4.9.5",
+3 -3
View File
@@ -1,5 +1,5 @@
module.exports = { module.exports = {
plugins: { plugins: {
autoprefixer: {} autoprefixer: {},
} },
} };
+13 -19
View File
@@ -1,21 +1,12 @@
import axios from 'axios' import axios from 'axios'
import { import { app, clipboard, dialog, shell } from 'electron'
app,
clipboard,
dialog,
shell
} from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path' import path from 'path'
import { gte, lte } from 'semver' import { gte, lte } from 'semver'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import { showNotification } from '~/utils/common' import { showNotification } from '~/utils/common'
import { import { IRemoteNoticeActionType, IRemoteNoticeTriggerCount, IRemoteNoticeTriggerHook } from '#/types/enum'
IRemoteNoticeActionType,
IRemoteNoticeTriggerCount,
IRemoteNoticeTriggerHook
} from '#/types/enum'
// for test // for test
const REMOTE_NOTICE_URL = 'https://release.piclist.cn/remote-notice.json' const REMOTE_NOTICE_URL = 'https://release.piclist.cn/remote-notice.json'
@@ -41,7 +32,10 @@ class RemoteNoticeHandler {
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({})) fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({}))
} }
try { try {
const localCountStorage: IRemoteNoticeLocalCountStorage = fs.readJSONSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, 'utf8') const localCountStorage: IRemoteNoticeLocalCountStorage = fs.readJSONSync(
REMOTE_NOTICE_LOCAL_STORAGE_PATH,
'utf8'
)
this.remoteNoticeLocalCountStorage = localCountStorage this.remoteNoticeLocalCountStorage = localCountStorage
} catch (e) { } catch (e) {
this.remoteNoticeLocalCountStorage = localCountStorage this.remoteNoticeLocalCountStorage = localCountStorage
@@ -57,11 +51,11 @@ class RemoteNoticeHandler {
private async getRemoteNoticeInfo(): Promise<IRemoteNotice | null> { private async getRemoteNoticeInfo(): Promise<IRemoteNotice | null> {
try { try {
const noticeInfo = await axios({ const noticeInfo = (await axios({
method: 'get', method: 'get',
url: REMOTE_NOTICE_URL, url: REMOTE_NOTICE_URL,
responseType: 'json' responseType: 'json'
}).then(res => res.data) as IRemoteNotice }).then(res => res.data)) as IRemoteNotice
return noticeInfo return noticeInfo
} catch { } catch {
return null return null
@@ -143,12 +137,14 @@ class RemoteNoticeHandler {
break break
case IRemoteNoticeActionType.SHOW_MESSAGE_BOX: { case IRemoteNoticeActionType.SHOW_MESSAGE_BOX: {
const currentWindow = windowManager.getAvailableWindow() const currentWindow = windowManager.getAvailableWindow()
dialog.showMessageBox(currentWindow, { dialog
.showMessageBox(currentWindow, {
title: action.data?.title || '', title: action.data?.title || '',
message: action.data?.content || '', message: action.data?.content || '',
type: 'info', type: 'info',
buttons: action.data?.buttons?.map(item => item.label) || ['Yes'] buttons: action.data?.buttons?.map(item => item.label) || ['Yes']
}).then(res => { })
.then(res => {
const button = action.data?.buttons?.[res.response] const button = action.data?.buttons?.[res.response]
if (button?.type === 'cancel') { if (button?.type === 'cancel') {
// do nothing // do nothing
@@ -198,6 +194,4 @@ class RemoteNoticeHandler {
const remoteNoticeHandler = new RemoteNoticeHandler() const remoteNoticeHandler = new RemoteNoticeHandler()
export { export { remoteNoticeHandler }
remoteNoticeHandler
}
@@ -1,7 +1,4 @@
import { globalShortcut } from 'electron'
import {
globalShortcut
} from 'electron'
import shortKeyService from 'apis/app/shortKey/shortKeyService' import shortKeyService from 'apis/app/shortKey/shortKeyService'
import GuiApi from 'apis/gui' import GuiApi from 'apis/gui'
@@ -72,7 +69,12 @@ class ShortKeyHandler {
} }
} }
private registerShortKey (config: IShortKeyConfig | IPluginShortKeyConfig, command: string, handler: IShortKeyHandler, writeFlag: boolean) { private registerShortKey(
config: IShortKeyConfig | IPluginShortKeyConfig,
command: string,
handler: IShortKeyHandler,
writeFlag: boolean
) {
shortKeyService.registerCommand(command, handler) shortKeyService.registerCommand(command, handler)
if (config.key) { if (config.key) {
globalShortcut.register(config.key, () => { globalShortcut.register(config.key, () => {
+71 -21
View File
@@ -75,7 +75,13 @@ export function createMenu () {
label: 'PicList', label: 'PicList',
submenu: [ submenu: [
{ label: T('OPEN_MAIN_WINDOW'), click: openMainWindow }, { label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
{ label: T('RELOAD_APP'), click () { app.relaunch(); app.exit(0) } } {
label: T('RELOAD_APP'),
click() {
app.relaunch()
app.exit(0)
}
}
] ]
}, },
{ label: T('CHOOSE_DEFAULT_PICBED'), type: 'submenu', submenu }, { label: T('CHOOSE_DEFAULT_PICBED'), type: 'submenu', submenu },
@@ -93,9 +99,7 @@ export function createMenu () {
}, },
{ {
label: T('QUIT'), label: T('QUIT'),
submenu: [ submenu: [{ label: T('QUIT'), role: 'quit' }]
{ label: T('QUIT'), role: 'quit' }
]
} }
]) ])
Menu.setApplicationMenu(appMenu) Menu.setApplicationMenu(appMenu)
@@ -104,7 +108,8 @@ export function createMenu () {
export function createContextMenu() { export function createContextMenu() {
const ClipboardWatcher = clipboardPoll const ClipboardWatcher = clipboardPoll
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const isMiniWindowVisible = windowManager.has(IWindowList.MINI_WINDOW) && windowManager.get(IWindowList.MINI_WINDOW)!.isVisible() const isMiniWindowVisible =
windowManager.has(IWindowList.MINI_WINDOW) && windowManager.get(IWindowList.MINI_WINDOW)!.isVisible()
const startWatchClipboard = () => { const startWatchClipboard = () => {
db.set(configPaths.settings.isListeningClipboard, true) db.set(configPaths.settings.isListeningClipboard, true)
@@ -125,18 +130,44 @@ export function createContextMenu () {
if (process.platform === 'darwin' || process.platform === 'win32') { if (process.platform === 'darwin' || process.platform === 'win32') {
const submenu = buildPicBedListMenu() const submenu = buildPicBedListMenu()
const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [ const template: Array<MenuItemConstructorOptions | MenuItem> = [
{ label: T('OPEN_MAIN_WINDOW'), click: openMainWindow }, { label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
{ label: T('CHOOSE_DEFAULT_PICBED'), type: 'submenu', submenu }, { label: T('CHOOSE_DEFAULT_PICBED'), type: 'submenu', submenu },
{ label: T('START_WATCH_CLIPBOARD'), click: startWatchClipboard, visible: !isListeningClipboard }, {
{ label: T('STOP_WATCH_CLIPBOARD'), click: stopWatchClipboard, visible: isListeningClipboard }, label: T('START_WATCH_CLIPBOARD'),
{ label: T('RELOAD_APP'), click () { app.relaunch(); app.exit(0) } }, click: startWatchClipboard,
visible: !isListeningClipboard
},
{
label: T('STOP_WATCH_CLIPBOARD'),
click: stopWatchClipboard,
visible: isListeningClipboard
},
{
label: T('RELOAD_APP'),
click() {
app.relaunch()
app.exit(0)
}
},
{ label: T('QUIT'), role: 'quit' } { label: T('QUIT'), role: 'quit' }
] ]
if (process.platform === 'win32') { if (process.platform === 'win32') {
template.splice(2, 0, template.splice(
{ label: T('OPEN_MINI_WINDOW'), click () { openMiniWindow(false) }, visible: !isMiniWindowVisible }, 2,
{ label: T('HIDE_MINI_WINDOW'), click: hideMiniWindow, visible: isMiniWindowVisible } 0,
{
label: T('OPEN_MINI_WINDOW'),
click() {
openMiniWindow(false)
},
visible: !isMiniWindowVisible
},
{
label: T('HIDE_MINI_WINDOW'),
click: hideMiniWindow,
visible: isMiniWindowVisible
}
) )
} }
contextMenu = Menu.buildFromTemplate(template) contextMenu = Menu.buildFromTemplate(template)
@@ -150,10 +181,28 @@ export function createContextMenu () {
contextMenu = Menu.buildFromTemplate([ contextMenu = Menu.buildFromTemplate([
{ label: T('OPEN_MAIN_WINDOW'), click: openMainWindow }, { label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
{ label: T('OPEN_MINI_WINDOW'), click () { openMiniWindow(false) }, visible: !isMiniWindowVisible }, {
{ label: T('HIDE_MINI_WINDOW'), click: hideMiniWindow, visible: isMiniWindowVisible }, label: T('OPEN_MINI_WINDOW'),
{ label: T('START_WATCH_CLIPBOARD'), click: startWatchClipboard, visible: !isListeningClipboard }, click() {
{ label: T('STOP_WATCH_CLIPBOARD'), click: stopWatchClipboard, visible: isListeningClipboard }, openMiniWindow(false)
},
visible: !isMiniWindowVisible
},
{
label: T('HIDE_MINI_WINDOW'),
click: hideMiniWindow,
visible: isMiniWindowVisible
},
{
label: T('START_WATCH_CLIPBOARD'),
click: startWatchClipboard,
visible: !isListeningClipboard
},
{
label: T('STOP_WATCH_CLIPBOARD'),
click: stopWatchClipboard,
visible: isListeningClipboard
},
{ {
label: T('ABOUT'), label: T('ABOUT'),
click() { click() {
@@ -260,9 +309,7 @@ export function createTray (tooltip: string) {
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const rawInput = cloneDeep(files) const rawInput = cloneDeep(files)
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)! const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
const imgs = await uploader const imgs = await uploader.setWebContents(trayWindow.webContents).upload(files)
.setWebContents(trayWindow.webContents)
.upload(files)
const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false
if (imgs !== false) { if (imgs !== false) {
const pasteText: string[] = [] const pasteText: string[] = []
@@ -270,8 +317,11 @@ export function createTray (tooltip: string) {
if (deleteLocalFile) { if (deleteLocalFile) {
await fs.remove(rawInput[i]) await fs.remove(rawInput[i])
} }
pasteText.push(await (pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink)))) pasteText.push(await pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink)))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification) const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined
? true
: !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) { if (isShowResultNotification) {
const notification = new Notification({ const notification = new Notification({
title: T('UPLOAD_SUCCEED'), title: T('UPLOAD_SUCCEED'),
+23 -12
View File
@@ -1,7 +1,4 @@
import { import { Notification, WebContents } from 'electron'
Notification,
WebContents
} from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
@@ -19,7 +16,10 @@ import { IPasteStyle, IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths' import { configPaths } from '#/utils/configPaths'
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => { const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
const useBuiltinClipboard = db.get(configPaths.settings.useBuiltinClipboard) === undefined ? true : !!db.get(configPaths.settings.useBuiltinClipboard) const useBuiltinClipboard =
db.get(configPaths.settings.useBuiltinClipboard) === undefined
? true
: !!db.get(configPaths.settings.useBuiltinClipboard)
const win = windowManager.getAvailableWindow() const win = windowManager.getAvailableWindow()
if (useBuiltinClipboard) { if (useBuiltinClipboard) {
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard() return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard()
@@ -33,8 +33,11 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
if (img.length > 0) { if (img.length > 0) {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW) const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
handleCopyUrl(await (pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink)))) handleCopyUrl(await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink)))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification) const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined
? true
: !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) { if (isShowResultNotification) {
const notification = new Notification({ const notification = new Notification({
title: T('UPLOAD_SUCCEED'), title: T('UPLOAD_SUCCEED'),
@@ -75,7 +78,10 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
} }
} }
export const uploadChoosedFiles = async (webContents: WebContents, files: IFileWithPath[]): Promise<IStringKeyMap[]> => { export const uploadChoosedFiles = async (
webContents: WebContents,
files: IFileWithPath[]
): Promise<IStringKeyMap[]> => {
const input = files.map(item => item.path) const input = files.map(item => item.path)
const rawInput = cloneDeep(input) const rawInput = cloneDeep(input)
const imgs = await uploader.setWebContents(webContents).upload(input) const imgs = await uploader.setWebContents(webContents).upload(input)
@@ -86,14 +92,19 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
const pasteText: string[] = [] const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) { for (let i = 0; i < imgs.length; i++) {
if (deleteLocalFile) { if (deleteLocalFile) {
fs.remove(rawInput[i]).then(() => { fs.remove(rawInput[i])
.then(() => {
picgo.log.info(`delete local file: ${rawInput[i]}`) picgo.log.info(`delete local file: ${rawInput[i]}`)
}).catch((err: Error) => { })
.catch((err: Error) => {
picgo.log.error(err) picgo.log.error(err)
}) })
} }
pasteText.push(await (pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink)))) pasteText.push(await pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink)))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification) const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined
? true
: !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) { if (isShowResultNotification) {
const notification = new Notification({ const notification = new Notification({
title: T('UPLOAD_SUCCEED'), title: T('UPLOAD_SUCCEED'),
+8 -16
View File
@@ -1,11 +1,5 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { import { BrowserWindow, clipboard, ipcMain, Notification, WebContents } from 'electron'
BrowserWindow,
clipboard,
ipcMain,
Notification,
WebContents
} from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import util from 'util' import util from 'util'
import path from 'path' import path from 'path'
@@ -21,17 +15,13 @@ import logger from '@core/picgo/logger'
import { T } from '~/i18n' import { T } from '~/i18n'
import { showNotification, getClipboardFilePath, calcDurationRange } from '~/utils/common' import { showNotification, getClipboardFilePath, calcDurationRange } from '~/utils/common'
import { import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME, TALKING_DATA_EVENT } from '#/events/constants'
GET_RENAME_FILE_NAME,
RENAME_FILE_NAME,
TALKING_DATA_EVENT
} from '#/events/constants'
import { ICOREBuildInEvent, IWindowList } from '#/types/enum' import { ICOREBuildInEvent, IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths' import { configPaths } from '#/utils/configPaths'
import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static' import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static'
const waitForRename = (window: BrowserWindow, id: number): Promise<string | null> => { const waitForRename = (window: BrowserWindow, id: number): Promise<string | null> => {
return new Promise((resolve) => { return new Promise(resolve => {
const windowId = window.id const windowId = window.id
ipcMain.once(`${RENAME_FILE_NAME}${id}`, (_: Event, newName: string) => { ipcMain.once(`${RENAME_FILE_NAME}${id}`, (_: Event, newName: string) => {
resolve(newName) resolve(newName)
@@ -91,7 +81,8 @@ class Uploader {
const rename = db.get(configPaths.settings.rename) const rename = db.get(configPaths.settings.rename)
const autoRename = db.get(configPaths.settings.autoRename) const autoRename = db.get(configPaths.settings.autoRename)
if (autoRename || rename) { if (autoRename || rename) {
await Promise.all(ctx.output.map(async (item, index) => { await Promise.all(
ctx.output.map(async (item, index) => {
let name: undefined | string | null let name: undefined | string | null
let fileName: string | undefined let fileName: string | undefined
if (autoRename) { if (autoRename) {
@@ -102,7 +93,7 @@ class Uploader {
if (rename) { if (rename) {
const window = windowManager.create(IWindowList.RENAME_WINDOW)! const window = windowManager.create(IWindowList.RENAME_WINDOW)!
logger.info('create rename window') logger.info('create rename window')
ipcMain.on(GET_RENAME_FILE_NAME, (evt) => { ipcMain.on(GET_RENAME_FILE_NAME, evt => {
try { try {
if (evt.sender.id === window.webContents.id) { if (evt.sender.id === window.webContents.id) {
logger.info('rename window ready, wait for rename...') logger.info('rename window ready, wait for rename...')
@@ -115,7 +106,8 @@ class Uploader {
name = await waitForRename(window, window.webContents.id) name = await waitForRename(window, window.webContents.id)
} }
item.fileName = name || fileName item.fileName = name || fileName
})) })
)
} }
} }
}) })
+12 -11
View File
@@ -1,25 +1,26 @@
const isDevelopment = process.env.NODE_ENV !== 'production' const isDevelopment = process.env.NODE_ENV !== 'production'
export const MANUAL_WINDOW_URL = process.env.NODE_ENV === 'development' export const MANUAL_WINDOW_URL =
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#documents` process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#documents`
: 'picgo://./index.html#documents' : 'picgo://./index.html#documents'
export const MINI_WINDOW_URL = isDevelopment export const MINI_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page` ? `${process.env.WEBPACK_DEV_SERVER_URL as string}#mini-page`
: 'picgo://./index.html#mini-page' : 'picgo://./index.html#mini-page'
export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development' export const RENAME_WINDOW_URL =
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page` process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#rename-page`
: 'picgo://./index.html#rename-page' : 'picgo://./index.html#rename-page'
export const SETTING_WINDOW_URL = isDevelopment export const SETTING_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#main-page/upload` ? `${process.env.WEBPACK_DEV_SERVER_URL as string}#main-page/upload`
: 'picgo://./index.html#main-page/upload' : 'picgo://./index.html#main-page/upload'
export const TRAY_WINDOW_URL = isDevelopment export const TRAY_WINDOW_URL = isDevelopment ? (process.env.WEBPACK_DEV_SERVER_URL as string) : 'picgo://./index.html'
? (process.env.WEBPACK_DEV_SERVER_URL as string)
: 'picgo://./index.html'
export const TOOLBOX_WINDOW_URL = process.env.NODE_ENV === 'development' export const TOOLBOX_WINDOW_URL =
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#toolbox-page` process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#toolbox-page`
: 'picgo://./index.html#toolbox-page' : 'picgo://./index.html#toolbox-page'
+7 -3
View File
@@ -23,8 +23,11 @@ const windowList = new Map<IWindowList, IWindowListItem>()
const handleWindowParams = (windowURL: string) => windowURL const handleWindowParams = (windowURL: string) => windowURL
const getDefaultWindowSizes = (): { width: number, height: number } => { const getDefaultWindowSizes = (): { width: number; height: number } => {
const [mainWindowWidth, mainWindowHeight] = db.get([configPaths.settings.mainWindowWidth, configPaths.settings.mainWindowHeight]) const [mainWindowWidth, mainWindowHeight] = db.get([
configPaths.settings.mainWindowWidth,
configPaths.settings.mainWindowHeight
])
return { return {
width: mainWindowWidth || 1200, width: mainWindowWidth || 1200,
height: mainWindowHeight || 800 height: mainWindowHeight || 800
@@ -237,7 +240,8 @@ windowList.set(IWindowList.RENAME_WINDOW, {
// if is the settingWindow // if is the settingWindow
if (bounds.height > 400) { if (bounds.height > 400) {
positionY = bounds.y + bounds.height / 2 - 88 positionY = bounds.y + bounds.height / 2 - 88
} else { // if is the miniWindow } else {
// if is the miniWindow
positionY = bounds.y + bounds.height / 2 positionY = bounds.y + bounds.height / 2
} }
window.setPosition(positionX, positionY, false) window.setPosition(positionX, positionY, false)
+9 -7
View File
@@ -11,10 +11,10 @@ import {
} from '@core/bus/constants' } from '@core/bus/constants'
export const uploadWithClipboardFiles = (): Promise<{ export const uploadWithClipboardFiles = (): Promise<{
success: boolean, success: boolean
result?: string[] result?: string[]
}> => { }> => {
return new Promise((resolve) => { return new Promise(resolve => {
bus.once(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, (result: string) => { bus.once(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, (result: string) => {
if (result) { if (result) {
return resolve({ return resolve({
@@ -31,11 +31,13 @@ export const uploadWithClipboardFiles = (): Promise<{
}) })
} }
export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{ export const uploadWithFiles = (
success: boolean, pathList: IFileWithPath[]
): Promise<{
success: boolean
result?: string[] result?: string[]
}> => { }> => {
return new Promise((resolve) => { return new Promise(resolve => {
bus.once(UPLOAD_WITH_FILES_RESPONSE, (result: string[]) => { bus.once(UPLOAD_WITH_FILES_RESPONSE, (result: string[]) => {
if (result.length) { if (result.length) {
return resolve({ return resolve({
@@ -55,7 +57,7 @@ export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
// get available window id: // get available window id:
// miniWindow or settingWindow or trayWindow // miniWindow or settingWindow or trayWindow
export const getWindowId = (): Promise<number> => { export const getWindowId = (): Promise<number> => {
return new Promise((resolve) => { return new Promise(resolve => {
bus.once(GET_WINDOW_ID_REPONSE, (id: number) => { bus.once(GET_WINDOW_ID_REPONSE, (id: number) => {
resolve(id) resolve(id)
}) })
@@ -65,7 +67,7 @@ export const getWindowId = (): Promise<number> => {
// get settingWindow id: // get settingWindow id:
export const getSettingWindowId = (): Promise<number> => { export const getSettingWindowId = (): Promise<number> => {
return new Promise((resolve) => { return new Promise(resolve => {
bus.once(GET_SETTING_WINDOW_ID_RESPONSE, (id: number) => { bus.once(GET_SETTING_WINDOW_ID_RESPONSE, (id: number) => {
resolve(id) resolve(id)
}) })
+7 -8
View File
@@ -54,7 +54,9 @@ function dbChecker () {
fs.unlinkSync(configFilePath) fs.unlinkSync(configFilePath)
if (fs.existsSync(configFileBackupPath)) { if (fs.existsSync(configFileBackupPath)) {
try { try {
configFile = fs.readFileSync(configFileBackupPath, { encoding: 'utf-8' }) configFile = fs.readFileSync(configFileBackupPath, {
encoding: 'utf-8'
})
JSON.parse(configFile) JSON.parse(configFile)
writeFile.sync(configFilePath, configFile, { encoding: 'utf-8' }) writeFile.sync(configFilePath, configFile, { encoding: 'utf-8' })
const stats = fs.statSync(configFileBackupPath) const stats = fs.statSync(configFileBackupPath)
@@ -91,7 +93,9 @@ function dbPathChecker (): string {
return _configFilePath return _configFilePath
} }
try { try {
const configString = fs.readFileSync(defaultConfigPath, { encoding: 'utf-8' }) const configString = fs.readFileSync(defaultConfigPath, {
encoding: 'utf-8'
})
const config = JSON.parse(configString) const config = JSON.parse(configString)
const userConfigPath: string = config.configPath || '' const userConfigPath: string = config.configPath || ''
if (userConfigPath) { if (userConfigPath) {
@@ -135,9 +139,4 @@ function getGalleryDBPath (): {
} }
} }
export { export { dbChecker, dbPathChecker, dbPathDir, getGalleryDBPath }
dbChecker,
dbPathChecker,
dbPathDir,
getGalleryDBPath
}
+1 -3
View File
@@ -115,6 +115,4 @@ class GalleryDB {
} }
} }
export { export { GalleryDB }
GalleryDB
}
+1 -3
View File
@@ -94,6 +94,4 @@ const getLogger = (logPath: string, logType: string) => {
} }
} }
export { export { getLogger }
getLogger
}
+32 -27
View File
@@ -1,9 +1,4 @@
import { import { BrowserWindow, dialog, ipcMain, Notification } from 'electron'
BrowserWindow,
dialog,
ipcMain,
Notification
} from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { DBStore } from '@picgo/store' import { DBStore } from '@picgo/store'
@@ -47,7 +42,7 @@ class GuiApi implements IGuiApi {
return true return true
} }
settingWindow?.show() settingWindow?.show()
return new Promise<void>((resolve) => { return new Promise<void>(resolve => {
setTimeout(() => { setTimeout(() => {
resolve() resolve()
}, 1000) // TODO: a better way to wait page loaded. }, 1000) // TODO: a better way to wait page loaded.
@@ -58,13 +53,15 @@ class GuiApi implements IGuiApi {
return BrowserWindow.fromId(id)?.webContents return BrowserWindow.fromId(id)?.webContents
} }
async showInputBox (options: IShowInputBoxOption = { async showInputBox(
options: IShowInputBoxOption = {
title: '', title: '',
placeholder: '' placeholder: ''
}) { }
) {
await this.showSettingWindow() await this.showSettingWindow()
this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options) this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
return new Promise<string>((resolve) => { return new Promise<string>(resolve => {
ipcMain.once(SHOW_INPUT_BOX, (_: Event, value: string) => { ipcMain.once(SHOW_INPUT_BOX, (_: Event, value: string) => {
resolve(value) resolve(value)
}) })
@@ -90,8 +87,11 @@ class GuiApi implements IGuiApi {
if (deleteLocalFile) { if (deleteLocalFile) {
await fs.remove(rawInput[i]) await fs.remove(rawInput[i])
} }
pasteText.push(await (pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink)))) pasteText.push(await pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink)))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification) const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined
? true
: !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) { if (isShowResultNotification) {
const notification = new Notification({ const notification = new Notification({
title: T('UPLOAD_SUCCEED'), title: T('UPLOAD_SUCCEED'),
@@ -112,10 +112,12 @@ class GuiApi implements IGuiApi {
return [] return []
} }
showNotification (options: IShowNotificationOption = { showNotification(
options: IShowNotificationOption = {
title: '', title: '',
body: '' body: ''
}) { }
) {
const notification = new Notification({ const notification = new Notification({
title: options.title, title: options.title,
body: options.body body: options.body
@@ -123,18 +125,17 @@ class GuiApi implements IGuiApi {
notification.show() notification.show()
} }
showMessageBox (options: IShowMessageBoxOption = { showMessageBox(
options: IShowMessageBoxOption = {
title: '', title: '',
message: '', message: '',
type: 'info', type: 'info',
buttons: ['Yes', 'No'] buttons: ['Yes', 'No']
}) { }
return new Promise<IShowMessageBoxResult>(async (resolve) => { ) {
return new Promise<IShowMessageBoxResult>(async resolve => {
this.windowId = await getWindowId() this.windowId = await getWindowId()
dialog.showMessageBox( dialog.showMessageBox(BrowserWindow.fromId(this.windowId)!, options).then(res => {
BrowserWindow.fromId(this.windowId)!,
options
).then((res) => {
resolve({ resolve({
result: res.response, result: res.response,
checkboxChecked: res.checkboxChecked checkboxChecked: res.checkboxChecked
@@ -162,14 +163,16 @@ class GuiApi implements IGuiApi {
if (prop === 'overwrite') { if (prop === 'overwrite') {
return new Proxy(GalleryDB.getInstance().overwrite, { return new Proxy(GalleryDB.getInstance().overwrite, {
apply(target, ctx, args) { apply(target, ctx, args) {
return new Promise((resolve) => { return new Promise(resolve => {
const guiApi = GuiApi.getInstance() const guiApi = GuiApi.getInstance()
guiApi.showMessageBox({ guiApi
.showMessageBox({
title: T('TIPS_WARNING'), title: T('TIPS_WARNING'),
message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'), message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'),
type: 'info', type: 'info',
buttons: ['Yes', 'No'] buttons: ['Yes', 'No']
}).then(res => { })
.then(res => {
if (res.result === 0) { if (res.result === 0) {
resolve(Reflect.apply(target, ctx, args)) resolve(Reflect.apply(target, ctx, args))
} else { } else {
@@ -183,14 +186,16 @@ class GuiApi implements IGuiApi {
if (prop === 'removeById') { if (prop === 'removeById') {
return new Proxy(GalleryDB.getInstance().removeById, { return new Proxy(GalleryDB.getInstance().removeById, {
apply(target, ctx, args) { apply(target, ctx, args) {
return new Promise((resolve) => { return new Promise(resolve => {
const guiApi = GuiApi.getInstance() const guiApi = GuiApi.getInstance()
guiApi.showMessageBox({ guiApi
.showMessageBox({
title: T('TIPS_WARNING'), title: T('TIPS_WARNING'),
message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'), message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'),
type: 'info', type: 'info',
buttons: ['Yes', 'No'] buttons: ['Yes', 'No']
}).then(res => { })
.then(res => {
if (res.result === 0) { if (res.result === 0) {
resolve(Reflect.apply(target, ctx, args)) resolve(Reflect.apply(target, ctx, args))
} else { } else {
+2 -7
View File
@@ -11,13 +11,8 @@ import {
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE
} from '@core/bus/constants' } from '@core/bus/constants'
import { import { createMenu } from 'apis/app/system'
createMenu import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
} from 'apis/app/system'
import {
uploadChoosedFiles,
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum' import { IWindowList } from '#/types/enum'
+29 -37
View File
@@ -1,20 +1,10 @@
import { import { app, dialog, BrowserWindow, Menu, shell, MenuItemConstructorOptions, MenuItem } from 'electron'
app,
dialog,
BrowserWindow,
Menu,
shell,
MenuItemConstructorOptions,
MenuItem
} from 'electron'
import { PicGo as PicGoCore } from 'piclist' import { PicGo as PicGoCore } from 'piclist'
import db from '@core/datastore' import db from '@core/datastore'
import picgo from '@core/picgo' import picgo from '@core/picgo'
import { import { uploadClipboardFiles } from 'apis/app/uploader/apis'
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import GuiApi from 'apis/gui' import GuiApi from 'apis/gui'
@@ -47,7 +37,7 @@ const buildMiniPageMenu = () => {
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const ClipboardWatcher = clipboardPoll const ClipboardWatcher = clipboardPoll
const submenu = buildPicBedListMenu() const submenu = buildPicBedListMenu()
const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [ const template: Array<MenuItemConstructorOptions | MenuItem> = [
{ {
label: T('OPEN_MAIN_WINDOW'), label: T('OPEN_MAIN_WINDOW'),
click: openMainWindow click: openMainWindow
@@ -145,8 +135,7 @@ const buildMainPageMenu = (win: BrowserWindow) => {
shell.openExternal(url) shell.openExternal(url)
} }
} }
] ] as Array<MenuItemConstructorOptions | MenuItem>
// @ts-ignore
return Menu.buildFromTemplate(template) return Menu.buildFromTemplate(template)
} }
@@ -155,28 +144,33 @@ const buildPicBedListMenu = () => {
const currentPicBed = picgo.getConfig(configPaths.picBed.uploader) const currentPicBed = picgo.getConfig(configPaths.picBed.uploader)
const currentPicBedName = picBeds.find(item => item.type === currentPicBed)?.name const currentPicBedName = picBeds.find(item => item.type === currentPicBed)?.name
const picBedConfigList = picgo.getConfig<IUploaderConfig>('uploader') const picBedConfigList = picgo.getConfig<IUploaderConfig>('uploader')
const currentPicBedMenuItem = [{ const currentPicBedMenuItem = [
{
label: `${T('CURRENT_PICBED')} - ${currentPicBedName}`, label: `${T('CURRENT_PICBED')} - ${currentPicBedName}`,
enabled: false enabled: false
}, { },
{
type: 'separator' type: 'separator'
}] }
let submenu = picBeds.filter(item => item.visible).map(item => { ]
let submenu = picBeds
.filter(item => item.visible)
.map(item => {
const configList = picBedConfigList?.[item.type]?.configList const configList = picBedConfigList?.[item.type]?.configList
const defaultId = picBedConfigList?.[item.type]?.defaultId const defaultId = picBedConfigList?.[item.type]?.defaultId
const hasSubmenu = !!configList const hasSubmenu = !!configList
return { return {
label: item.name, label: item.name,
type: !hasSubmenu ? 'checkbox' : undefined, type: !hasSubmenu ? 'checkbox' : undefined,
checked: !hasSubmenu ? (currentPicBed === item.type) : undefined, checked: !hasSubmenu ? currentPicBed === item.type : undefined,
submenu: hasSubmenu submenu: hasSubmenu
? configList.map((config) => { ? configList.map(config => {
return { return {
label: config._configName || 'Default', label: config._configName || 'Default',
// if only one config, use checkbox, or radio will checked as default // if only one config, use checkbox, or radio will checked as default
// see: https://github.com/electron/electron/issues/21292 // see: https://github.com/electron/electron/issues/21292
type: 'checkbox', type: 'checkbox',
checked: config._id === defaultId && (item.type === currentPicBed), checked: config._id === defaultId && item.type === currentPicBed,
click: function () { click: function () {
changeCurrentUploader(item.type, config, config._id) changeCurrentUploader(item.type, config, config._id)
if (windowManager.has(IWindowList.SETTING_WINDOW)) { if (windowManager.has(IWindowList.SETTING_WINDOW)) {
@@ -201,9 +195,9 @@ const buildPicBedListMenu = () => {
: undefined : undefined
} }
}) })
// @ts-ignore // @ts-expect-error submenu type
submenu = currentPicBedMenuItem.concat(submenu) submenu = currentPicBedMenuItem.concat(submenu)
// @ts-ignore // @ts-expect-error submenu type
return Menu.buildFromTemplate(submenu) return Menu.buildFromTemplate(submenu)
} }
@@ -230,7 +224,8 @@ const handleRestoreState = (item: string, name: string): void => {
} }
const buildPluginPageMenu = (plugin: IPicGoPlugin) => { const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const menu = [{ const menu = [
{
label: T('ENABLE_PLUGIN'), label: T('ENABLE_PLUGIN'),
enabled: !plugin.enabled, enabled: !plugin.enabled,
click() { click() {
@@ -240,7 +235,8 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, true) window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, true)
} }
}, { },
{
label: T('DISABLE_PLUGIN'), label: T('DISABLE_PLUGIN'),
enabled: plugin.enabled, enabled: plugin.enabled,
click() { click() {
@@ -258,21 +254,24 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
handleRestoreState('uploader', plugin.config.uploader.name) handleRestoreState('uploader', plugin.config.uploader.name)
} }
} }
}, { },
{
label: T('UNINSTALL_PLUGIN'), label: T('UNINSTALL_PLUGIN'),
click() { click() {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName) window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
handlePluginUninstall(plugin.fullName) handlePluginUninstall(plugin.fullName)
} }
}, { },
{
label: T('UPDATE_PLUGIN'), label: T('UPDATE_PLUGIN'),
click() { click() {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName) window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
handlePluginUpdate(plugin.fullName) handlePluginUpdate(plugin.fullName)
} }
}] }
] as Array<MenuItemConstructorOptions | MenuItem>
for (const i in plugin.config) { for (const i in plugin.config) {
if (plugin.config[i].config.length > 0) { if (plugin.config[i].config.length > 0) {
const obj = { const obj = {
@@ -317,7 +316,6 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
// plugin custom menus // plugin custom menus
if (plugin.guiMenu) { if (plugin.guiMenu) {
menu.push({ menu.push({
// @ts-ignore
type: 'separator' type: 'separator'
}) })
for (const i of plugin.guiMenu) { for (const i of plugin.guiMenu) {
@@ -338,13 +336,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
} }
} }
// @ts-ignore
return Menu.buildFromTemplate(menu) return Menu.buildFromTemplate(menu)
} }
export { export { buildMiniPageMenu, buildMainPageMenu, buildPicBedListMenu, buildPluginPageMenu }
buildMiniPageMenu,
buildMainPageMenu,
buildPicBedListMenu,
buildPluginPageMenu
}
+1 -3
View File
@@ -82,6 +82,4 @@ for (const route of routes) {
rpcServer.use(route) rpcServer.use(route)
} }
export { export { rpcServer }
rpcServer
}
+7 -5
View File
@@ -1,4 +1,3 @@
import { clipboard } from 'electron' import { clipboard } from 'electron'
import { GalleryDB } from '@core/datastore' import { GalleryDB } from '@core/datastore'
@@ -8,7 +7,12 @@ import { IFilter, IObject } from '@picgo/store/dist/types'
import GuiApi from 'apis/gui' import GuiApi from 'apis/gui'
import { RPCRouter } from '~/events/rpc/router' import { RPCRouter } from '~/events/rpc/router'
import { removeFileFromDogeInMain, removeFileFromHuaweiInMain, removeFileFromS3InMain, removeFileFromSFTPInMain } from '~/utils/deleteFunc' import {
removeFileFromDogeInMain,
removeFileFromHuaweiInMain,
removeFileFromS3InMain,
removeFileFromSFTPInMain
} from '~/utils/deleteFunc'
import pasteTemplate from '~/utils/pasteTemplate' import pasteTemplate from '~/utils/pasteTemplate'
import { ICOREBuildInEvent, IPasteStyle, IRPCActionType, IRPCType } from '#/types/enum' import { ICOREBuildInEvent, IPasteStyle, IRPCActionType, IRPCType } from '#/types/enum'
@@ -128,6 +132,4 @@ const galleryRoutes = [
galleryRouter.addBatch(galleryRoutes) galleryRouter.addBatch(galleryRoutes)
export { export { galleryRouter }
galleryRouter
}
+1 -3
View File
@@ -94,6 +94,4 @@ const picbedRoutes = [
picbedRouter.addBatch(picbedRoutes) picbedRouter.addBatch(picbedRoutes)
export { export { picbedRouter }
picbedRouter
}
+1 -4
View File
@@ -1,4 +1,3 @@
import { RPCRouter } from '~/events/rpc/router' import { RPCRouter } from '~/events/rpc/router'
import { import {
pluginImportLocalFunc, pluginImportLocalFunc,
@@ -32,6 +31,4 @@ const pluginRoutes = [
pluginRouter.addBatch(pluginRoutes) pluginRouter.addBatch(pluginRoutes)
export { export { pluginRouter }
pluginRouter
}
+4 -2
View File
@@ -104,11 +104,13 @@ const getPluginList = (): IPicGoPlugin[] => {
const handleNPMError = (): IDispose => { const handleNPMError = (): IDispose => {
const handler = (msg: string) => { const handler = (msg: string) => {
if (msg === 'NPM is not installed') { if (msg === 'NPM is not installed') {
dialog.showMessageBox({ dialog
.showMessageBox({
title: T('TIPS_ERROR'), title: T('TIPS_ERROR'),
message: T('TIPS_INSTALL_NODE_AND_RELOAD_PICGO'), message: T('TIPS_INSTALL_NODE_AND_RELOAD_PICGO'),
buttons: ['Yes'] buttons: ['Yes']
}).then((res) => { })
.then(res => {
if (res.response === 0) { if (res.response === 0) {
shell.openExternal('https://nodejs.org/') shell.openExternal('https://nodejs.org/')
} }
@@ -1,4 +1,3 @@
import { IRPCActionType } from '#/types/enum' import { IRPCActionType } from '#/types/enum'
import server from '~/server' import server from '~/server'
import webServer from '~/server/webServer' import webServer from '~/server/webServer'
@@ -16,19 +16,15 @@ export default [
action: IRPCActionType.CONFIGURE_MIGRATE_FROM_PICGO, action: IRPCActionType.CONFIGURE_MIGRATE_FROM_PICGO,
handler: async () => { handler: async () => {
const picGoConfigPath = STORE_PATH.replace('piclist', 'picgo') const picGoConfigPath = STORE_PATH.replace('piclist', 'picgo')
const files = [ const files = ['data.json', 'data.bak.json', 'picgo.db', 'picgo.bak.db']
'data.json',
'data.bak.json',
'picgo.db',
'picgo.bak.db'
]
try { try {
await Promise.all(files.map(async file => { await Promise.all(
files.map(async file => {
const sourcePath = path.join(picGoConfigPath, file) const sourcePath = path.join(picGoConfigPath, file)
const targetPath = path.join(STORE_PATH, file.replace('picgo', 'piclist')) const targetPath = path.join(STORE_PATH, file.replace('picgo', 'piclist'))
await fs.copy(sourcePath, targetPath, { overwrite: true }) await fs.copy(sourcePath, targetPath, { overwrite: true })
} })
)) )
return true return true
} catch (err: any) { } catch (err: any) {
logger.error(err) logger.error(err)
+2 -9
View File
@@ -7,15 +7,8 @@ import shortKeyRoutes from '~/events/rpc/routes/setting/shortKey'
const settingRouter = new RPCRouter() const settingRouter = new RPCRouter()
const settingRoutes = [ const settingRoutes = [...advancedRoutes, ...configureRoutes, ...mainAppRoutes, ...shortKeyRoutes]
...advancedRoutes,
...configureRoutes,
...mainAppRoutes,
...shortKeyRoutes
]
settingRouter.addBatch(settingRoutes) settingRouter.addBatch(settingRoutes)
export { export { settingRouter }
settingRouter
}
@@ -1,6 +1,4 @@
import { import { Notification } from 'electron'
Notification
} from 'electron'
import bus from '@core/bus' import bus from '@core/bus'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
+2 -7
View File
@@ -4,13 +4,8 @@ import windowRoutes from '~/events/rpc/routes/system/window'
const systemRouter = new RPCRouter() const systemRouter = new RPCRouter()
const systemRoutes = [ const systemRoutes = [...appRoutes, ...windowRoutes]
...appRoutes,
...windowRoutes
]
systemRouter.addBatch(systemRoutes) systemRouter.addBatch(systemRoutes)
export { export { systemRouter }
systemRouter
}
+1 -6
View File
@@ -2,12 +2,7 @@ import { app, BrowserWindow } from 'electron'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import { import { buildMainPageMenu, buildMiniPageMenu, buildPicBedListMenu, buildPluginPageMenu } from '~/events/remotes/menu'
buildMainPageMenu,
buildMiniPageMenu,
buildPicBedListMenu,
buildPluginPageMenu
} from '~/events/remotes/menu'
import { openMiniWindow } from '~/utils/windowHelper' import { openMiniWindow } from '~/utils/windowHelper'
import { IRPCActionType, IWindowList } from '#/types/enum' import { IRPCActionType, IWindowList } from '#/types/enum'
@@ -14,10 +14,8 @@ const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_
const defaultClipboardImagePath = path.join(defaultConfigPath, CLIPBOARD_IMAGE_FOLDER) const defaultClipboardImagePath = path.join(defaultConfigPath, CLIPBOARD_IMAGE_FOLDER)
export const checkClipboardUploadMap: IToolboxCheckerMap< export const checkClipboardUploadMap: IToolboxCheckerMap<IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD> = {
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD [IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async event => {
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async (event) => {
sendToolboxRes(event, { sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING status: IToolboxItemCheckStatus.LOADING
}) })
@@ -54,9 +52,7 @@ IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
} }
} }
export const fixClipboardUploadMap: IToolboxFixMap< export const fixClipboardUploadMap: IToolboxFixMap<IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD> = {
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async () => { [IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async () => {
const configFilePath = dbPathChecker() const configFilePath = dbPathChecker()
const dirPath = path.dirname(configFilePath) const dirPath = path.dirname(configFilePath)
@@ -38,7 +38,7 @@ IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
}) })
} }
}, },
[IToolboxItemType.IS_GALLERY_FILE_BROKEN]: async (event) => { [IToolboxItemType.IS_GALLERY_FILE_BROKEN]: async event => {
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_GALLERY_FILE_BROKEN) const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_GALLERY_FILE_BROKEN)
sendToolboxRes(event, { sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING status: IToolboxItemCheckStatus.LOADING
@@ -1,4 +1,3 @@
import axios, { AxiosRequestConfig } from 'axios' import axios, { AxiosRequestConfig } from 'axios'
import fs from 'fs-extra' import fs from 'fs-extra'
import { IConfig } from 'piclist' import { IConfig } from 'piclist'
@@ -27,10 +26,8 @@ function getProxy (proxyStr: string): AxiosRequestConfig['proxy'] | null {
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY) const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
export const checkProxyMap: IToolboxCheckerMap< export const checkProxyMap: IToolboxCheckerMap<IToolboxItemType.HAS_PROBLEM_WITH_PROXY> = {
IToolboxItemType.HAS_PROBLEM_WITH_PROXY [IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async event => {
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async (event) => {
sendToolboxRes(event, { sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING status: IToolboxItemCheckStatus.LOADING
}) })
@@ -38,9 +35,8 @@ IToolboxItemType.HAS_PROBLEM_WITH_PROXY
if (fs.existsSync(configFilePath)) { if (fs.existsSync(configFilePath)) {
let config: IConfig | undefined let config: IConfig | undefined
try { try {
config = await fs.readJSON(configFilePath) as IConfig config = (await fs.readJSON(configFilePath)) as IConfig
} catch (e) { } catch (e) {}
}
if (!config) { if (!config) {
return sendToolboxRes(event, { return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS, status: IToolboxItemCheckStatus.SUCCESS,
+3 -4
View File
@@ -42,7 +42,8 @@ toolboxRouter
IRPCType.SEND IRPCType.SEND
) )
.add( .add(
IRPCActionType.TOOLBOX_CHECK_FIX, async (event, args) => { IRPCActionType.TOOLBOX_CHECK_FIX,
async (event, args) => {
const [type] = args as IToolboxCheckArgs const [type] = args as IToolboxCheckArgs
const handler = toolboxFixMap[type] const handler = toolboxFixMap[type]
if (handler) { if (handler) {
@@ -52,6 +53,4 @@ toolboxRouter
IRPCType.INVOKE IRPCType.INVOKE
) )
export { export { toolboxRouter }
toolboxRouter
}
+7 -8
View File
@@ -1,6 +1,4 @@
import { import { Notification } from 'electron'
Notification
} from 'electron'
import { RPCRouter } from '~/events/rpc/router' import { RPCRouter } from '~/events/rpc/router'
import { generateShortUrl, setTrayToolTip, handleCopyUrl } from '~/utils/common' import { generateShortUrl, setTrayToolTip, handleCopyUrl } from '~/utils/common'
@@ -42,8 +40,11 @@ const trayRoutes = [
const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard() const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard()
if (img !== false) { if (img !== false) {
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
handleCopyUrl(await (pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink)))) handleCopyUrl(await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink)))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification) const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined
? true
: !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) { if (isShowResultNotification) {
const notification = new Notification({ const notification = new Notification({
title: T('UPLOAD_SUCCEED'), title: T('UPLOAD_SUCCEED'),
@@ -66,6 +67,4 @@ const trayRoutes = [
trayRouter.addBatch(trayRoutes) trayRouter.addBatch(trayRoutes)
export { export { trayRouter }
trayRouter
}
+1 -3
View File
@@ -30,6 +30,4 @@ const uploadRoutes = [
uploadRouter.addBatch(uploadRoutes) uploadRouter.addBatch(uploadRoutes)
export { export { uploadRouter }
uploadRouter
}
+4 -2
View File
@@ -27,9 +27,11 @@ export function startFileServer () {
}) })
}) })
server.listen(serverPort, () => { server
.listen(serverPort, () => {
logger.info(`File server is running, http://localhost:${serverPort}`) logger.info(`File server is running, http://localhost:${serverPort}`)
}).on('error', (err) => { })
.on('error', err => {
logger.error(err) logger.error(err)
}) })
} }
+3 -7
View File
@@ -1,7 +1,7 @@
// TODO: so how to import pure esm module in electron main process????? help wanted // TODO: so how to import pure esm module in electron main process????? help wanted
// just copy the fix-path because I can't import pure ESM module in electron main process // just copy the fix-path because I can't import pure ESM module in electron main process
// @ts-nocheck // @ts-nocheck since the module is not pure ESM
// shell-path 3.0.0 not work // shell-path 3.0.0 not work
const shellPath = require('shell-path') const shellPath = require('shell-path')
@@ -11,10 +11,6 @@ export default function fixPath () {
return return
} }
process.env.PATH = shellPath.sync() || [ process.env.PATH =
'./node_modules/.bin', shellPath.sync() || ['./node_modules/.bin', '/.nodebrew/current/bin', '/usr/local/bin', process.env.PATH].join(':')
'/.nodebrew/current/bin',
'/usr/local/bin',
process.env.PATH
].join(':')
} }
+30 -35
View File
@@ -1,19 +1,9 @@
import axios from 'axios' import axios from 'axios'
import fs from 'fs-extra' import fs from 'fs-extra'
import { import { app, globalShortcut, protocol, Notification, dialog, screen, shell } from 'electron'
app,
globalShortcut,
protocol,
Notification,
dialog,
screen,
shell
} from 'electron'
import { UpdateInfo, autoUpdater } from 'electron-updater' import { UpdateInfo, autoUpdater } from 'electron-updater'
import path from 'path' import path from 'path'
import { import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
createProtocol
} from 'vue-cli-plugin-electron-builder/lib'
import bus from '@core/bus' import bus from '@core/bus'
import db from '@core/datastore' import db from '@core/datastore'
@@ -22,13 +12,8 @@ import logger from '@core/picgo/logger'
import { remoteNoticeHandler } from 'apis/app/remoteNotice' import { remoteNoticeHandler } from 'apis/app/remoteNotice'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import { import { createTray, setDockMenu } from 'apis/app/system'
createTray, setDockMenu import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
} from 'apis/app/system'
import {
uploadChoosedFiles,
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import busEventList from '~/events/busEventList' import busEventList from '~/events/busEventList'
@@ -57,7 +42,8 @@ const isDevelopment = process.env.NODE_ENV !== 'production'
const handleStartUpFiles = (argv: string[], cwd: string) => { const handleStartUpFiles = (argv: string[], cwd: string) => {
const files = getUploadFiles(argv, cwd, logger) const files = getUploadFiles(argv, cwd, logger)
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动 if (files === null || files.length > 0) {
// 如果有文件列表作为参数,说明是命令行启动
if (files === null) { if (files === null) {
logger.info('cli -> uploading file from clipboard') logger.info('cli -> uploading file from clipboard')
uploadClipboardFiles() uploadClipboardFiles()
@@ -83,34 +69,43 @@ autoUpdater.on('update-available', async (info: UpdateInfo) => {
const lang = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN const lang = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN
let updateLog = '' let updateLog = ''
try { try {
const url = lang === II18nLanguage.ZH_CN ? 'https://release.piclist.cn/currentVersion.md' : 'https://release.piclist.cn/currentVersion_en.md' const url =
lang === II18nLanguage.ZH_CN
? 'https://release.piclist.cn/currentVersion.md'
: 'https://release.piclist.cn/currentVersion_en.md'
const res = await axios.get(url) const res = await axios.get(url)
updateLog = res.data updateLog = res.data
} catch (e: any) { } catch (e: any) {
logger.error(e) logger.error(e)
} }
dialog.showMessageBox({ dialog
.showMessageBox({
type: 'info', type: 'info',
title: T('FIND_NEW_VERSION'), title: T('FIND_NEW_VERSION'),
buttons: ['Yes', 'Go to download page'], buttons: ['Yes', 'Go to download page'],
message: T('TIPS_FIND_NEW_VERSION', { message:
T('TIPS_FIND_NEW_VERSION', {
v: info.version v: info.version
}) + '\n\n' + updateLog, }) +
'\n\n' +
updateLog,
checkboxLabel: T('NO_MORE_NOTICE'), checkboxLabel: T('NO_MORE_NOTICE'),
checkboxChecked: false checkboxChecked: false
}).then((result) => { })
.then(result => {
if (result.response === 0) { if (result.response === 0) {
autoUpdater.downloadUpdate() autoUpdater.downloadUpdate()
} else { } else {
shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest') shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest')
} }
db.set(configPaths.settings.showUpdateTip, !result.checkboxChecked) db.set(configPaths.settings.showUpdateTip, !result.checkboxChecked)
}).catch((err) => { })
.catch(err => {
logger.error(err) logger.error(err)
}) })
}) })
autoUpdater.on('download-progress', (progressObj) => { autoUpdater.on('download-progress', progressObj => {
const percent = { const percent = {
progress: progressObj.percent progress: progressObj.percent
} }
@@ -119,23 +114,26 @@ autoUpdater.on('download-progress', (progressObj) => {
}) })
autoUpdater.on('update-downloaded', () => { autoUpdater.on('update-downloaded', () => {
dialog.showMessageBox({ dialog
.showMessageBox({
type: 'info', type: 'info',
title: T('UPDATE_DOWNLOADED'), title: T('UPDATE_DOWNLOADED'),
buttons: ['Yes', 'No'], buttons: ['Yes', 'No'],
message: T('TIPS_UPDATE_DOWNLOADED') message: T('TIPS_UPDATE_DOWNLOADED')
}).then((result) => { })
.then(result => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send('updateProgress', { progress: 100 }) window.webContents.send('updateProgress', { progress: 100 })
if (result.response === 0) { if (result.response === 0) {
autoUpdater.quitAndInstall() autoUpdater.quitAndInstall()
} }
}).catch((err) => { })
.catch(err => {
logger.error(err) logger.error(err)
}) })
}) })
autoUpdater.on('error', (err) => { autoUpdater.on('error', err => {
console.log(err) console.log(err)
}) })
@@ -173,7 +171,6 @@ class LifeCycle {
const isHideDock = db.get(configPaths.settings.isHideDock) || false const isHideDock = db.get(configPaths.settings.isHideDock) || false
const startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET const startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET
const currentPicBed = db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms' const currentPicBed = db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms'
// @ts-ignore
const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default' const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default'
const tooltip = `${currentPicBed} ${currentPicBedConfig}` const tooltip = `${currentPicBed} ${currentPicBedConfig}`
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
@@ -319,6 +316,4 @@ class LifeCycle {
const bootstrap = new LifeCycle() const bootstrap = new LifeCycle()
export { export { bootstrap }
bootstrap
}
+99 -44
View File
@@ -8,7 +8,13 @@ import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue' import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { ManageLogger } from '~/manage/utils/logger' import { ManageLogger } from '~/manage/utils/logger'
import { hmacSha1Base64, getFileMimeType, formatError, NewDownloader, ConcurrencyPromisePool } from '~/manage/utils/common' import {
hmacSha1Base64,
getFileMimeType,
formatError,
NewDownloader,
ConcurrencyPromisePool
} from '~/manage/utils/common'
import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum' import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { isImage } from '#/utils/common' import { isImage } from '#/utils/common'
@@ -72,13 +78,19 @@ class AliyunApi {
}, {} as IStringKeyMap) }, {} as IStringKeyMap)
let canonicalizedOSSHeaders = '' let canonicalizedOSSHeaders = ''
const headerKeys = Object.keys(lowerCaseHeaders).sort() const headerKeys = Object.keys(lowerCaseHeaders).sort()
headerKeys.forEach((key) => { headerKeys.forEach(key => {
key.startsWith('x-oss-') && (canonicalizedOSSHeaders += `${key}:${lowerCaseHeaders[key]}\n`) key.startsWith('x-oss-') && (canonicalizedOSSHeaders += `${key}:${lowerCaseHeaders[key]}\n`)
}) })
return canonicalizedOSSHeaders return canonicalizedOSSHeaders
} }
authorization (method: string, canonicalizedResource: string, headers: IStringKeyMap, contentMd5: string, contentType: string) { authorization(
method: string,
canonicalizedResource: string,
headers: IStringKeyMap,
contentMd5: string,
contentType: string
) {
const date = new Date().toUTCString() const date = new Date().toUTCString()
const stringToSign = `${method.toUpperCase()}\n${contentMd5}\n${contentType}\n${date}\n${this.getCanonicalizedOSSHeaders(headers)}${canonicalizedResource}` const stringToSign = `${method.toUpperCase()}\n${contentMd5}\n${contentType}\n${date}\n${this.getCanonicalizedOSSHeaders(headers)}${canonicalizedResource}`
return `OSS ${this.accessKeyId}:${hmacSha1Base64(this.accessKeySecret, stringToSign)}` return `OSS ${this.accessKeyId}:${hmacSha1Base64(this.accessKeySecret, stringToSign)}`
@@ -99,17 +111,21 @@ class AliyunApi {
*/ */
async getBucketList(): Promise<any> { async getBucketList(): Promise<any> {
const getBuckets = async (marker?: string) => { const getBuckets = async (marker?: string) => {
const res = await this.ctx.listBuckets({ const res = (await this.ctx.listBuckets({
marker, marker,
'max-keys': 1000 'max-keys': 1000
}) as IStringKeyMap })) as IStringKeyMap
if (res?.res?.statusCode !== 200 || !res?.buckets) return { result: [], isTruncated: false } if (res?.res?.statusCode !== 200 || !res?.buckets) return { result: [], isTruncated: false }
const formattedBuckets = res.buckets.map((item: OSS.Bucket) => ({ const formattedBuckets = res.buckets.map((item: OSS.Bucket) => ({
Name: item.name, Name: item.name,
Location: item.region, Location: item.region,
CreationDate: item.creationDate CreationDate: item.creationDate
})) }))
return { result: formattedBuckets, isTruncated: res.isTruncated, nextMarker: res.nextMarker } return {
result: formattedBuckets,
isTruncated: res.isTruncated,
nextMarker: res.nextMarker
}
} }
const result: IStringKeyMap[] = [] const result: IStringKeyMap[] = []
let NextMarker: string | undefined let NextMarker: string | undefined
@@ -193,7 +209,12 @@ class AliyunApi {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> { async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com` const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
let marker let marker
@@ -212,13 +233,16 @@ class AliyunApi {
} }
const client = this.getNewCtx(region, bucket) const client = this.getNewCtx(region, bucket)
do { do {
res = await client.listV2({ res = await client.listV2(
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix, prefix: slicedPrefix === '' ? undefined : slicedPrefix,
'max-keys': '1000', 'max-keys': '1000',
'continuation-token': marker 'continuation-token': marker
}, { },
{
timeout: this.timeOut timeout: this.timeOut
}) }
)
if (res?.res?.statusCode === 200) { if (res?.res?.statusCode === 200) {
res?.objects?.forEach((item: OSS.ObjectMeta) => { res?.objects?.forEach((item: OSS.ObjectMeta) => {
item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
@@ -240,7 +264,12 @@ class AliyunApi {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com` const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
let marker let marker
@@ -259,14 +288,17 @@ class AliyunApi {
} }
const client = this.getNewCtx(region, bucket) const client = this.getNewCtx(region, bucket)
do { do {
res = await client.listV2({ res = await client.listV2(
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix, prefix: slicedPrefix === '' ? undefined : slicedPrefix,
delimiter: '/', delimiter: '/',
'max-keys': '1000', 'max-keys': '1000',
'continuation-token': marker 'continuation-token': marker
}, { },
{
timeout: this.timeOut timeout: this.timeOut
}) }
)
if (res?.res?.statusCode === 200) { if (res?.res?.statusCode === 200) {
res?.prefixes?.forEach((item: string) => { res?.prefixes?.forEach((item: string) => {
result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix)) result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
@@ -305,19 +337,28 @@ class AliyunApi {
* } * }
*/ */
async getBucketFileList(configMap: IStringKeyMap): Promise<any> { async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, marker, itemsPerPage } = configMap const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
marker,
itemsPerPage
} = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com` const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
const client = this.getNewCtx(region, bucket) const client = this.getNewCtx(region, bucket)
const res = await client.listV2({ const res = (await client.listV2(
{
prefix: slicedPrefix || undefined, prefix: slicedPrefix || undefined,
delimiter: '/', delimiter: '/',
'max-keys': itemsPerPage.toString(), 'max-keys': itemsPerPage.toString(),
'continuation-token': marker 'continuation-token': marker
}, { },
{
timeout: this.timeOut timeout: this.timeOut
}) as any }
)) as any
// prefixes can be null // prefixes can be null
// objects will be [] when no file // objects will be [] when no file
if (res?.res.statusCode !== 200) { if (res?.res.statusCode !== 200) {
@@ -330,7 +371,9 @@ class AliyunApi {
} }
const fullList = [ const fullList = [
...(res.prefixes?.map((item: string) => this.formatFolder(item, slicedPrefix, urlPrefix)) || []), ...(res.prefixes?.map((item: string) => this.formatFolder(item, slicedPrefix, urlPrefix)) || []),
...(res.objects?.filter((item: OSS.ObjectMeta) => item.size !== 0).map((item: OSS.ObjectMeta) => this.formatFile(item, slicedPrefix, urlPrefix)) || []) ...(res.objects
?.filter((item: OSS.ObjectMeta) => item.size !== 0)
.map((item: OSS.ObjectMeta) => this.formatFile(item, slicedPrefix, urlPrefix)) || [])
] ]
return { return {
fullList, fullList,
@@ -353,12 +396,9 @@ class AliyunApi {
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> { async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, oldKey, newKey } = configMap const { bucketName, region, oldKey, newKey } = configMap
const client = this.getNewCtx(region, bucketName) const client = this.getNewCtx(region, bucketName)
const copyRes = await client.copy( const copyRes = (await client.copy(newKey, oldKey)) as any
newKey,
oldKey
) as any
if (copyRes?.res.statusCode === 200) { if (copyRes?.res.statusCode === 200) {
const deleteRes = await client.delete(oldKey) as any const deleteRes = (await client.delete(oldKey)) as any
return deleteRes?.res.statusCode === 204 return deleteRes?.res.statusCode === 204
} }
return false return false
@@ -376,7 +416,7 @@ class AliyunApi {
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
const client = this.getNewCtx(region, bucketName) const client = this.getNewCtx(region, bucketName)
const res = await client.delete(key) as any const res = (await client.delete(key)) as any
return res?.res.statusCode === 204 return res?.res.statusCode === 204
} }
@@ -394,14 +434,17 @@ class AliyunApi {
Contents: [] as any[] Contents: [] as any[]
} }
do { do {
const res = await client.listV2({ const res = (await client.listV2(
{
prefix: key, prefix: key,
delimiter: '/', delimiter: '/',
'max-keys': '1000', 'max-keys': '1000',
'continuation-token': marker 'continuation-token': marker
}, { },
{
timeout: this.timeOut timeout: this.timeOut
}) as any }
)) as any
if (res?.res.statusCode !== 200) return false if (res?.res.statusCode !== 200) return false
res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes) res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes)
@@ -423,8 +466,9 @@ class AliyunApi {
if (allFileList.Contents.length > 0) { if (allFileList.Contents.length > 0) {
const cycle = Math.ceil(allFileList.Contents.length / 1000) const cycle = Math.ceil(allFileList.Contents.length / 1000)
for (let i = 0; i < cycle; i++) { for (let i = 0; i < cycle; i++) {
const deleteRes = await client.deleteMulti( const deleteRes = (await client.deleteMulti(
allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => item.name)) as any allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => item.name)
)) as any
if (deleteRes?.res.statusCode !== 200) return false if (deleteRes?.res.statusCode !== 200) return false
} }
} }
@@ -485,10 +529,8 @@ class AliyunApi {
targetFileBucket: bucketName, targetFileBucket: bucketName,
targetFileRegion: region targetFileRegion: region
}) })
client.multipartUpload( client
key, .multipartUpload(key, filePath, {
filePath,
{
partSize: 1 * 1024 * 1024, partSize: 1 * 1024 * 1024,
mime: getFileMimeType(fileName), mime: getFileMimeType(fileName),
progress: (p: number) => { progress: (p: number) => {
@@ -499,8 +541,8 @@ class AliyunApi {
status: uploadTaskSpecialStatus.uploading status: uploadTaskSpecialStatus.uploading
}) })
} }
} })
).then((res: any) => { .then((res: any) => {
const id = `${bucketName}-${region}-${key}-${filePath}` const id = `${bucketName}-${region}-${key}-${filePath}`
if (res?.res?.statusCode === 200) { if (res?.res?.statusCode === 200) {
instance.updateUploadTask({ instance.updateUploadTask({
@@ -519,8 +561,14 @@ class AliyunApi {
finishTime: new Date().toLocaleString() finishTime: new Date().toLocaleString()
}) })
} }
}).catch((err: any) => { })
this.logger.error(formatError(err, { class: 'AliyunApi', method: 'uploadBucketFile' })) .catch((err: any) => {
this.logger.error(
formatError(err, {
class: 'AliyunApi',
method: 'uploadBucketFile'
})
)
const id = `${bucketName}-${region}-${key}-${filePath}` const id = `${bucketName}-${region}-${key}-${filePath}`
instance.updateUploadTask({ instance.updateUploadTask({
id, id,
@@ -541,7 +589,7 @@ class AliyunApi {
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> { async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
const client = this.getNewCtx(region, bucketName) const client = this.getNewCtx(region, bucketName)
const res = await client.put(key, Buffer.from('')) as any const res = (await client.put(key, Buffer.from(''))) as any
return res?.res?.statusCode === 200 return res?.res?.statusCode === 200
} }
@@ -571,20 +619,27 @@ class AliyunApi {
const preSignedUrl = client.signatureUrl(key, { const preSignedUrl = client.signatureUrl(key, {
expires: 60 * 60 * 48 expires: 60 * 60 * 48
}) })
promises.push(() => new Promise((resolve, reject) => { promises.push(
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger) () =>
.then((res: boolean) => { new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) { if (res) {
resolve(res) resolve(res)
} else { } else {
reject(res) reject(res)
} }
}) })
})) })
)
} }
const pool = new ConcurrencyPromisePool(maxDownloadFileCount) const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error: any) => { pool.all(promises).catch((error: any) => {
this.logger.error(formatError(error, { class: 'AliyunApi', method: 'downloadBucketFile' })) this.logger.error(
formatError(error, {
class: 'AliyunApi',
method: 'downloadBucketFile'
})
)
}) })
return true return true
} }
+131 -49
View File
@@ -6,7 +6,14 @@ import path from 'path'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue' import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { gotUpload, NewDownloader, getAgent, getOptions, ConcurrencyPromisePool, formatError } from '~/manage/utils/common' import {
gotUpload,
NewDownloader,
getAgent,
getOptions,
ConcurrencyPromisePool,
formatError
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger' import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, IWindowList } from '#/types/enum' import { commonTaskStatus, IWindowList } from '#/types/enum'
@@ -41,7 +48,16 @@ class GithubApi {
rawUrl = cdnUrl rawUrl = cdnUrl
? placeholders.some(item => cdnUrl.includes(item)) ? placeholders.some(item => cdnUrl.includes(item))
? placeholders.reduce((url, ph) => { ? placeholders.reduce((url, ph) => {
const value = ph === '{username}' ? this.username : ph === '{repo}' ? repo : ph === '{branch}' ? branch : ph === '{path}' ? key : '' const value =
ph === '{username}'
? this.username
: ph === '{repo}'
? repo
: ph === '{branch}'
? branch
: ph === '{path}'
? key
: ''
return url.replaceAll(ph, value) return url.replaceAll(ph, value)
}, cdnUrl) }, cdnUrl)
: `${cdnUrl}/${key}` : `${cdnUrl}/${key}`
@@ -69,7 +85,16 @@ class GithubApi {
rawUrl = cdnUrl rawUrl = cdnUrl
? placeholders.some(item => cdnUrl.includes(item)) ? placeholders.some(item => cdnUrl.includes(item))
? placeholders.reduce((url, ph) => { ? placeholders.reduce((url, ph) => {
const value = ph === '{username}' ? this.username : ph === '{repo}' ? repo : ph === '{branch}' ? branch : ph === '{path}' ? `${slicedPrefix}/${item.path}` : '' const value =
ph === '{username}'
? this.username
: ph === '{repo}'
? repo
: ph === '{branch}'
? branch
: ph === '{path}'
? `${slicedPrefix}/${item.path}`
: ''
return url.replaceAll(ph, value) return url.replaceAll(ph, value)
}, cdnUrl) }, cdnUrl)
: `${cdnUrl}/${key}` : `${cdnUrl}/${key}`
@@ -99,10 +124,18 @@ class GithubApi {
let res let res
const result = [] as any[] const result = [] as any[]
do { do {
res = await got( res = (await got(
`${this.baseUrl}/user/repos`, `${this.baseUrl}/user/repos`,
getOptions('GET', this.commonHeaders, { page: initPage, per_page: 100 }, 'json', undefined, undefined, this.proxy) getOptions(
) as any 'GET',
this.commonHeaders,
{ page: initPage, per_page: 100 },
'json',
undefined,
undefined,
this.proxy
)
)) as any
if (res.statusCode === 200) { if (res.statusCode === 200) {
res.body.forEach((item: any) => { res.body.forEach((item: any) => {
result.push({ result.push({
@@ -129,10 +162,18 @@ class GithubApi {
let res let res
const result = [] as string[] const result = [] as string[]
do { do {
res = await got( res = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/branches`, `${this.baseUrl}/repos/${this.username}/${repo}/branches`,
getOptions('GET', this.commonHeaders, { page: initPage, per_page: 100 }, 'json', undefined, undefined, this.proxy) getOptions(
) as any 'GET',
this.commonHeaders,
{ page: initPage, per_page: 100 },
'json',
undefined,
undefined,
this.proxy
)
)) as any
if (res.statusCode === 200) { if (res.statusCode === 200) {
res.body.forEach((item: any) => result.push(item.name)) res.body.forEach((item: any) => result.push(item.name))
} else { } else {
@@ -167,10 +208,10 @@ class GithubApi {
return result return result
} }
const currentPrefix = treeQueue[0] const currentPrefix = treeQueue[0]
res = await got( res = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${treeQueue.shift()}`, `${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${treeQueue.shift()}`,
getOptions('GET', this.commonHeaders, {}, 'json', undefined, undefined, this.proxy) getOptions('GET', this.commonHeaders, {}, 'json', undefined, undefined, this.proxy)
) as any )) as any
if (res && res.statusCode === 200) { if (res && res.statusCode === 200) {
const { tree } = res.body const { tree } = res.body
tree.forEach((item: any) => { tree.forEach((item: any) => {
@@ -265,65 +306,98 @@ class GithubApi {
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName: repo, githubBranch: branch, key } = configMap const { bucketName: repo, githubBranch: branch, key } = configMap
// get sha of the branch // get sha of the branch
const refRes = await got( const refRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`, `${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`,
getOptions('GET', this.commonHeaders, undefined, 'json', undefined, undefined, this.proxy) getOptions('GET', this.commonHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any )) as any
if (refRes.statusCode !== 200) return false if (refRes.statusCode !== 200) return false
const refSha = refRes.body.object.sha const refSha = refRes.body.object.sha
// get sha of the root tree // get sha of the root tree
const rootRes = await got( const rootRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/branches/${branch}`, `${this.baseUrl}/repos/${this.username}/${repo}/branches/${branch}`,
getOptions('GET', undefined, undefined, 'json', undefined, undefined, this.proxy) getOptions('GET', undefined, undefined, 'json', undefined, undefined, this.proxy)
) as any )) as any
if (rootRes.statusCode !== 200) return false if (rootRes.statusCode !== 200) return false
const rootSha = rootRes.body.commit.commit.tree.sha const rootSha = rootRes.body.commit.commit.tree.sha
// TODO: if there are more than 10000 files in the folder, it will be truncated // TODO: if there are more than 10000 files in the folder, it will be truncated
// Rare cases, not considered for now // Rare cases, not considered for now
// get sha of the folder tree // get sha of the folder tree
const treeRes = await got( const treeRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${key.replace(/(^\/+|\/+$)/g, '')}`, `${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${key.replace(/(^\/+|\/+$)/g, '')}`,
getOptions('GET', this.commonHeaders, { getOptions(
'GET',
this.commonHeaders,
{
recursive: true recursive: true
}, 'json', undefined, undefined, this.proxy) },
) as any 'json',
undefined,
undefined,
this.proxy
)
)) as any
if (treeRes.statusCode !== 200) return false if (treeRes.statusCode !== 200) return false
const oldTree = treeRes.body.tree const oldTree = treeRes.body.tree
// create a new tree // create a new tree
const newTree = oldTree.filter((item: any) => item.type === 'blob') const newTree = oldTree
.filter((item: any) => item.type === 'blob')
.map((item: any) => ({ .map((item: any) => ({
path: `${key.replace(/(^\/+|\/+$)/g, '')}/${item.path}`, path: `${key.replace(/(^\/+|\/+$)/g, '')}/${item.path}`,
mode: item.mode, mode: item.mode,
type: item.type, type: item.type,
sha: null sha: null
})) }))
const newTreeShaRes = await got( const newTreeShaRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/trees`, `${this.baseUrl}/repos/${this.username}/${repo}/git/trees`,
getOptions('POST', this.commonHeaders, undefined, 'json', JSON.stringify({ getOptions(
'POST',
this.commonHeaders,
undefined,
'json',
JSON.stringify({
base_tree: rootSha, base_tree: rootSha,
tree: newTree tree: newTree
}), undefined, this.proxy) }),
) as any undefined,
this.proxy
)
)) as any
if (newTreeShaRes.statusCode !== 201) return false if (newTreeShaRes.statusCode !== 201) return false
const newTreeSha = newTreeShaRes.body.sha const newTreeSha = newTreeShaRes.body.sha
// create a new commit // create a new commit
const commitRes = await got( const commitRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/commits`, `${this.baseUrl}/repos/${this.username}/${repo}/git/commits`,
getOptions('POST', this.commonHeaders, undefined, 'json', JSON.stringify({ getOptions(
'POST',
this.commonHeaders,
undefined,
'json',
JSON.stringify({
message: 'deleted by PicList', message: 'deleted by PicList',
tree: newTreeSha, tree: newTreeSha,
parents: [refSha] parents: [refSha]
}), undefined, this.proxy) }),
) as any undefined,
this.proxy
)
)) as any
if (commitRes.statusCode !== 201) return false if (commitRes.statusCode !== 201) return false
const commitSha = commitRes.body.sha const commitSha = commitRes.body.sha
// update the branch // update the branch
const updateRefRes = await got( const updateRefRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`, `${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`,
getOptions('PATCH', this.commonHeaders, undefined, 'json', JSON.stringify({ getOptions(
'PATCH',
this.commonHeaders,
undefined,
'json',
JSON.stringify({
sha: commitSha sha: commitSha
}), undefined, this.proxy) }),
) as any undefined,
this.proxy
)
)) as any
return updateRefRes.statusCode === 200 return updateRefRes.statusCode === 200
} }
@@ -341,12 +415,20 @@ class GithubApi {
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> { async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName: repo, customUrl: branch, key, rawUrl, githubPrivate: isPrivate } = configMap const { bucketName: repo, customUrl: branch, key, rawUrl, githubPrivate: isPrivate } = configMap
if (!isPrivate) return rawUrl if (!isPrivate) return rawUrl
const res = await got( const res = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/contents/${key}`, `${this.baseUrl}/repos/${this.username}/${repo}/contents/${key}`,
getOptions('GET', this.commonHeaders, { getOptions(
'GET',
this.commonHeaders,
{
ref: branch ref: branch
}, 'json', undefined, undefined, this.proxy) },
) as any 'json',
undefined,
undefined,
this.proxy
)
)) as any
return res.statusCode === 200 ? res.body.download_url : '' return res.statusCode === 200 ? res.body.download_url : ''
} }
@@ -454,27 +536,27 @@ class GithubApi {
} else { } else {
downloadUrl = githubUrl downloadUrl = githubUrl
} }
promises.push(() => new Promise((resolve, reject) => { promises.push(
NewDownloader( () =>
instance, new Promise((resolve, reject) => {
downloadUrl, NewDownloader(instance, downloadUrl, id, savedFilePath, this.logger, this.proxyStr).then((res: boolean) => {
id,
savedFilePath,
this.logger,
this.proxyStr
)
.then((res: boolean) => {
if (res) { if (res) {
resolve(res) resolve(res)
} else { } else {
reject(res) reject(res)
} }
}) })
})) })
)
} }
const pool = new ConcurrencyPromisePool(maxDownloadFileCount) const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => { pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'GithubApi', method: 'downloadBucketFile' })) this.logger.error(
formatError(error, {
class: 'GithubApi',
method: 'downloadBucketFile'
})
)
}) })
return true return true
} }
+19 -21
View File
@@ -69,10 +69,10 @@ class ImgurApi {
let res let res
const result = [] as any[] const result = [] as any[]
do { do {
res = await got( res = (await got(
`${this.baseUrl}/account/${this.userName}/albums/${initPage}`, `${this.baseUrl}/account/${this.userName}/albums/${initPage}`,
getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy) getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any )) as any
if (!(res.statusCode === 200 && res.body.success)) { if (!(res.statusCode === 200 && res.body.success)) {
return [] return []
} }
@@ -95,7 +95,10 @@ class ImgurApi {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketConfig: { Location: albumHash }, cancelToken } = configMap const {
bucketConfig: { Location: albumHash },
cancelToken
} = configMap
const cancelTask = [false] const cancelTask = [false]
ipcMain.on('cancelLoadingFileList', (_: IpcMainEvent, token: string) => { ipcMain.on('cancelLoadingFileList', (_: IpcMainEvent, token: string) => {
if (token === cancelToken) { if (token === cancelToken) {
@@ -110,10 +113,10 @@ class ImgurApi {
finished: false finished: false
} }
if (albumHash !== 'unclassified') { if (albumHash !== 'unclassified') {
res = await got( res = (await got(
`${this.baseUrl}/account/${this.userName}/album/${albumHash}`, `${this.baseUrl}/account/${this.userName}/album/${albumHash}`,
getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy) getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any )) as any
if (res.statusCode === 200 && res.body.success) { if (res.statusCode === 200 && res.body.success) {
res.body.data.images.forEach((item: any) => { res.body.data.images.forEach((item: any) => {
result.fullList.push(this.formatFile(item)) result.fullList.push(this.formatFile(item))
@@ -127,10 +130,10 @@ class ImgurApi {
} else { } else {
let initPage = 0 let initPage = 0
do { do {
res = await got( res = (await got(
`${this.baseUrl}/account/${this.userName}/images/${initPage}`, `${this.baseUrl}/account/${this.userName}/images/${initPage}`,
getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy) getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any )) as any
if (res.statusCode === 200 && res.body.success) { if (res.statusCode === 200 && res.body.success) {
res.body.data.forEach((item: any) => { res.body.data.forEach((item: any) => {
result.fullList.push(this.formatFile(item)) result.fullList.push(this.formatFile(item))
@@ -152,10 +155,10 @@ class ImgurApi {
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { DeleteHash: deleteHash } = configMap const { DeleteHash: deleteHash } = configMap
const res = await got( const res = (await got(
`${this.baseUrl}/account/${this.userName}/image/${deleteHash}`, `${this.baseUrl}/account/${this.userName}/image/${deleteHash}`,
getOptions('DELETE', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy) getOptions('DELETE', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any )) as any
return res.statusCode === 200 && res.body.success return res.statusCode === 200 && res.body.success
} }
@@ -241,26 +244,21 @@ class ImgurApi {
sourceFileName: fileName, sourceFileName: fileName,
targetFilePath: savedFilePath targetFilePath: savedFilePath
}) })
promises.push(() => new Promise((resolve, reject) => { promises.push(
NewDownloader( () =>
instance, new Promise((resolve, reject) => {
url, NewDownloader(instance, url, id, savedFilePath, this.logger, this.proxyStr).then((res: boolean) => {
id,
savedFilePath,
this.logger,
this.proxyStr
)
.then((res: boolean) => {
if (res) { if (res) {
resolve(res) resolve(res)
} else { } else {
reject(res) reject(res)
} }
}) })
})) })
)
} }
const pool = new ConcurrencyPromisePool(maxDownloadFileCount) const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => { pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'ImgurApi', method: 'downloadBucketFile' })) this.logger.error(formatError(error, { class: 'ImgurApi', method: 'downloadBucketFile' }))
}) })
return true return true
+5 -3
View File
@@ -21,8 +21,7 @@ class LocalApi {
this.isWindows = process.platform === 'win32' this.isWindows = process.platform === 'win32'
} }
logParam = (error:any, method: string) => logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'LocalApi', method }))
this.logger.error(formatError(error, { class: 'LocalApi', method }))
// windows 系统下将路径转换为 unix 风格 // windows 系统下将路径转换为 unix 风格
transPathToUnix(filePath: string | undefined) { transPathToUnix(filePath: string | undefined) {
@@ -33,7 +32,10 @@ class LocalApi {
transBack(filePath: string | undefined) { transBack(filePath: string | undefined) {
if (!filePath) return '' if (!filePath) return ''
return this.isWindows return this.isWindows
? filePath.split(path.posix.sep).join(path.sep).replace(/^\\+|\\+$/g, '') ? filePath
.split(path.posix.sep)
.join(path.sep)
.replace(/^\\+|\\+$/g, '')
: `/${filePath.replace(/^\/+|\/+$/g, '')}` : `/${filePath.replace(/^\/+|\/+$/g, '')}`
} }
+86 -41
View File
@@ -1,4 +1,3 @@
import axios from 'axios' import axios from 'axios'
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
import path from 'path' import path from 'path'
@@ -7,7 +6,13 @@ import qiniu from 'qiniu/index'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue' import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { hmacSha1Base64, getFileMimeType, NewDownloader, formatError, ConcurrencyPromisePool } from '~/manage/utils/common' import {
hmacSha1Base64,
getFileMimeType,
NewDownloader,
formatError,
ConcurrencyPromisePool
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger' import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum' import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
@@ -79,7 +84,7 @@ class QiniuApi {
if (xQiniuHeaders) { if (xQiniuHeaders) {
const xQiniuHeaderStr = Object.keys(xQiniuHeaders) const xQiniuHeaderStr = Object.keys(xQiniuHeaders)
.sort() .sort()
.map((key) => `\n${key}:${xQiniuHeaders[key]}`) .map(key => `\n${key}:${xQiniuHeaders[key]}`)
.join('') .join('')
signStr += xQiniuHeaderStr signStr += xQiniuHeaderStr
} }
@@ -252,11 +257,14 @@ class QiniuApi {
const bucketManager = new qiniu.rs.BucketManager(this.mac, config) const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
do { do {
res = await new Promise((resolve, reject) => { res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucket, { bucketManager.listPrefix(
bucket,
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix, prefix: slicedPrefix === '' ? undefined : slicedPrefix,
marker, marker,
limit: 1000 limit: 1000
}, (err: any, respBody: any, respInfo: any) => { },
(err: any, respBody: any, respInfo: any) => {
if (err) { if (err) {
reject(err) reject(err)
} else { } else {
@@ -265,10 +273,13 @@ class QiniuApi {
respInfo respInfo
}) })
} }
}) }
)
}) })
if (res && res.respInfo.statusCode === 200) { if (res && res.respInfo.statusCode === 200) {
res.respBody && res.respBody.items && res.respBody.items.forEach((item: any) => { res.respBody &&
res.respBody.items &&
res.respBody.items.forEach((item: any) => {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
}) })
window.webContents.send(refreshDownloadFileTransferList, result) window.webContents.send(refreshDownloadFileTransferList, result)
@@ -308,12 +319,15 @@ class QiniuApi {
const bucketManager = new qiniu.rs.BucketManager(this.mac, config) const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
do { do {
res = await new Promise((resolve, reject) => { res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucket, { bucketManager.listPrefix(
bucket,
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix, prefix: slicedPrefix === '' ? undefined : slicedPrefix,
delimiter: '/', delimiter: '/',
marker, marker,
limit: 1000 limit: 1000
}, (err: any, respBody: any, respInfo: any) => { },
(err: any, respBody: any, respInfo: any) => {
if (err) { if (err) {
reject(err) reject(err)
} else { } else {
@@ -322,13 +336,18 @@ class QiniuApi {
respInfo respInfo
}) })
} }
}) }
)
}) })
if (res && res.respInfo.statusCode === 200) { if (res && res.respInfo.statusCode === 200) {
res.respBody && res.respBody.commonPrefixes && res.respBody.commonPrefixes.forEach((item: any) => { res.respBody &&
res.respBody.commonPrefixes &&
res.respBody.commonPrefixes.forEach((item: any) => {
result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix)) result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
}) })
res.respBody && res.respBody.items && res.respBody.items.forEach((item: any) => { res.respBody &&
res.respBody.items &&
res.respBody.items.forEach((item: any) => {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
}) })
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
@@ -374,12 +393,15 @@ class QiniuApi {
success: false success: false
} }
res = await new Promise((resolve, reject) => { res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucket, { bucketManager.listPrefix(
bucket,
{
limit: itemsPerPage, limit: itemsPerPage,
prefix: slicedPrefix === '' ? undefined : slicedPrefix, prefix: slicedPrefix === '' ? undefined : slicedPrefix,
marker, marker,
delimiter: '/' delimiter: '/'
}, (err, respBody, respInfo) => { },
(err, respBody, respInfo) => {
if (err) { if (err) {
reject(err) reject(err)
} else { } else {
@@ -388,7 +410,8 @@ class QiniuApi {
respInfo respInfo
}) })
} }
}) }
)
}) })
if (res?.respInfo?.statusCode === 200) { if (res?.respInfo?.statusCode === 200) {
if (res.respBody?.commonPrefixes) { if (res.respBody?.commonPrefixes) {
@@ -401,7 +424,7 @@ class QiniuApi {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
}) })
} }
result.isTruncated = !!(res.respBody?.marker) result.isTruncated = !!res.respBody?.marker
result.nextMarker = res.respBody?.marker ? res.respBody.marker : '' result.nextMarker = res.respBody?.marker ? res.respBody.marker : ''
result.success = true result.success = true
} }
@@ -421,7 +444,7 @@ class QiniuApi {
const { bucketName, key } = configMap const { bucketName, key } = configMap
const config = new qiniu.conf.Config() const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config) const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
const res = await new Promise((resolve, reject) => { const res = (await new Promise((resolve, reject) => {
bucketManager.delete(bucketName, key, (err, respBody, respInfo) => { bucketManager.delete(bucketName, key, (err, respBody, respInfo) => {
if (err) { if (err) {
reject(err) reject(err)
@@ -432,7 +455,7 @@ class QiniuApi {
}) })
} }
}) })
}) as any })) as any
return res?.respInfo?.statusCode === 200 return res?.respInfo?.statusCode === 200
} }
@@ -450,12 +473,15 @@ class QiniuApi {
Contents: [] as any[] Contents: [] as any[]
} }
do { do {
const res = await new Promise((resolve, reject) => { const res = (await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucketName, { bucketManager.listPrefix(
bucketName,
{
prefix: key, prefix: key,
marker, marker,
limit: 1000 limit: 1000
}, (err, respBody, respInfo) => { },
(err, respBody, respInfo) => {
if (err) { if (err) {
reject(err) reject(err)
} else { } else {
@@ -464,13 +490,14 @@ class QiniuApi {
respInfo respInfo
}) })
} }
}) }
}) as any )
})) as any
if (res?.respInfo?.statusCode === 200) { if (res?.respInfo?.statusCode === 200) {
if (res.respBody?.items) { if (res.respBody?.items) {
allFileList.Contents = allFileList.Contents.concat(res.respBody.items) allFileList.Contents = allFileList.Contents.concat(res.respBody.items)
} }
isTruncated = !!(res.respBody?.marker) isTruncated = !!res.respBody?.marker
marker = res.respBody?.marker ? res.respBody.marker : '' marker = res.respBody?.marker ? res.respBody.marker : ''
} else { } else {
return false return false
@@ -481,7 +508,7 @@ class QiniuApi {
const deleteOps = allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => { const deleteOps = allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => {
return qiniu.rs.deleteOp(bucketName, item.key) return qiniu.rs.deleteOp(bucketName, item.key)
}) })
const res = await new Promise((resolve, reject) => { const res = (await new Promise((resolve, reject) => {
bucketManager.batch(deleteOps, (err, respBody, respInfo) => { bucketManager.batch(deleteOps, (err, respBody, respInfo) => {
if (err) { if (err) {
reject(err) reject(err)
@@ -492,7 +519,7 @@ class QiniuApi {
}) })
} }
}) })
}) as any })) as any
if (res?.respInfo?.statusCode !== 200) return false if (res?.respInfo?.statusCode !== 200) return false
} }
return true return true
@@ -512,10 +539,16 @@ class QiniuApi {
const { bucketName, oldKey, newKey } = configMap const { bucketName, oldKey, newKey } = configMap
const config = new qiniu.conf.Config() const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config) const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
const res = await new Promise((resolve, reject) => { const res = (await new Promise((resolve, reject) => {
bucketManager.move(bucketName, oldKey, bucketName, newKey, { bucketManager.move(
bucketName,
oldKey,
bucketName,
newKey,
{
force: true force: true
}, (err, respBody, respInfo) => { },
(err, respBody, respInfo) => {
if (err) { if (err) {
reject(err) reject(err)
} else { } else {
@@ -524,8 +557,9 @@ class QiniuApi {
respInfo respInfo
}) })
} }
}) }
}) as any )
})) as any
return res?.respInfo?.statusCode === 200 return res?.respInfo?.statusCode === 200
} }
@@ -585,7 +619,7 @@ class QiniuApi {
putExtra.version = 'v2' putExtra.version = 'v2'
putExtra.partSize = 4 * 1024 * 1024 putExtra.partSize = 4 * 1024 * 1024
putExtra.progressCallback = (uploadBytes, totalBytes) => { putExtra.progressCallback = (uploadBytes, totalBytes) => {
const progress = Math.floor(uploadBytes / totalBytes * 100) const progress = Math.floor((uploadBytes / totalBytes) * 100)
instance.updateUploadTask({ instance.updateUploadTask({
id: `${bucketName}-${region}-${key}-${filePath}`, id: `${bucketName}-${region}-${key}-${filePath}`,
progress, progress,
@@ -594,7 +628,12 @@ class QiniuApi {
} }
resumeUploader.putFile(uploadToken, key, filePath, putExtra, (respErr, respBody, respInfo) => { resumeUploader.putFile(uploadToken, key, filePath, putExtra, (respErr, respBody, respInfo) => {
if (respErr) { if (respErr) {
this.logger.error(formatError(respErr, { class: 'Qiniu', method: 'uploadBucketFile' })) this.logger.error(
formatError(respErr, {
class: 'Qiniu',
method: 'uploadBucketFile'
})
)
instance.updateUploadTask({ instance.updateUploadTask({
id: `${bucketName}-${region}-${key}-${filePath}`, id: `${bucketName}-${region}-${key}-${filePath}`,
progress: 0, progress: 0,
@@ -636,7 +675,7 @@ class QiniuApi {
const uploadToken = putPolicy.uploadToken(this.mac) const uploadToken = putPolicy.uploadToken(this.mac)
const FormUploader = new qiniu.form_up.FormUploader() const FormUploader = new qiniu.form_up.FormUploader()
const putExtra = new qiniu.form_up.PutExtra() const putExtra = new qiniu.form_up.PutExtra()
const res = await new Promise((resolve, reject) => { const res = (await new Promise((resolve, reject) => {
FormUploader.put(uploadToken, key, '', putExtra, (err, respBody, respInfo) => { FormUploader.put(uploadToken, key, '', putExtra, (err, respBody, respInfo) => {
if (err) { if (err) {
reject(err) reject(err)
@@ -647,7 +686,7 @@ class QiniuApi {
}) })
} }
}) })
}) as any })) as any
return res?.respInfo?.statusCode === 200 return res?.respInfo?.statusCode === 200
} }
@@ -673,20 +712,26 @@ class QiniuApi {
sourceFileName: fileName, sourceFileName: fileName,
targetFilePath: savedFilePath targetFilePath: savedFilePath
}) })
const preSignedUrl = await this.getPreSignedUrl({ key, expires: 36000, customUrl }) const preSignedUrl = await this.getPreSignedUrl({
promises.push(() => new Promise((resolve, reject) => { key,
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger) expires: 36000,
.then((res: boolean) => { customUrl
})
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) { if (res) {
resolve(res) resolve(res)
} else { } else {
reject(res) reject(res)
} }
}) })
})) })
)
} }
const pool = new ConcurrencyPromisePool(maxDownloadFileCount) const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => { pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'QiniuApi', method: 'downloadBucketFile' })) this.logger.error(formatError(error, { class: 'QiniuApi', method: 'downloadBucketFile' }))
}) })
return true return true
+78 -32
View File
@@ -77,7 +77,7 @@ class S3plistApi {
async getDogeCloudToken() { async getDogeCloudToken() {
if (!this.dogeCloudSupport) return if (!this.dogeCloudSupport) return
const token = await getTempToken(this.accessKeyId, this.secretAccessKey) as DogecloudToken const token = (await getTempToken(this.accessKeyId, this.secretAccessKey)) as DogecloudToken
if (Object.keys(token).length === 0) { if (Object.keys(token).length === 0) {
throw new Error('manage.setting.dogeCloudTokenError') throw new Error('manage.setting.dogeCloudTokenError')
} }
@@ -115,8 +115,7 @@ class S3plistApi {
}) })
} }
logParam = (error:any, method: string) => logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'S3plistApi', method }))
this.logger.error(formatError(error, { class: 'S3plistApi', method }))
formatFolder(item: CommonPrefix, slicedPrefix: string, urlPrefix: string): any { formatFolder(item: CommonPrefix, slicedPrefix: string, urlPrefix: string): any {
return { return {
@@ -186,7 +185,7 @@ class S3plistApi {
const command = new ListBucketsCommand({}) const command = new ListBucketsCommand({})
const data = await client.send(command) const data = await client.send(command)
if (data.$metadata.httpStatusCode === 200) { if (data.$metadata.httpStatusCode === 200) {
const bucketList = data.Buckets?.map((item) => item.Name) const bucketList = data.Buckets?.map(item => item.Name)
if (bucketList?.includes(BucketName)) { if (bucketList?.includes(BucketName)) {
return true return true
} }
@@ -258,7 +257,7 @@ class S3plistApi {
} }
const options = Object.assign({}, this.baseOptions) as S3ClientConfig const options = Object.assign({}, this.baseOptions) as S3ClientConfig
const result: IStringKeyMap[] = [] const result: IStringKeyMap[] = []
const endpoint = options.endpoint as string || '' const endpoint = (options.endpoint as string) || ''
options.region = endpoint.includes('cloudflarestorage') ? 'auto' : 'us-east-1' options.region = endpoint.includes('cloudflarestorage') ? 'auto' : 'us-east-1'
try { try {
const client = new S3Client(options) const client = new S3Client(options)
@@ -271,21 +270,26 @@ class S3plistApi {
if (data.Buckets) { if (data.Buckets) {
if (endpoint.includes('cloudflarestorage')) { if (endpoint.includes('cloudflarestorage')) {
result.push(...data.Buckets.map(bucket => ({ result.push(
...data.Buckets.map(bucket => ({
Name: bucket.Name, Name: bucket.Name,
CreationDate: bucket.CreationDate, CreationDate: bucket.CreationDate,
Location: 'auto' Location: 'auto'
}))) }))
)
} else { } else {
for (const bucket of data.Buckets) { for (const bucket of data.Buckets) {
const bucketName = bucket.Name const bucketName = bucket.Name
const bucketConfig = await client.send(new GetBucketLocationCommand({ const bucketConfig = await client.send(
new GetBucketLocationCommand({
Bucket: bucketName Bucket: bucketName
})) })
)
result.push({ result.push({
Name: bucketName, Name: bucketName,
CreationDate: bucket.CreationDate, CreationDate: bucket.CreationDate,
Location: bucketConfig.$metadata.httpStatusCode === 200 Location:
bucketConfig.$metadata.httpStatusCode === 200
? bucketConfig.LocationConstraint?.toLowerCase() || 'us-east-1' ? bucketConfig.LocationConstraint?.toLowerCase() || 'us-east-1'
: 'us-east-1' : 'us-east-1'
}) })
@@ -303,7 +307,12 @@ class S3plistApi {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> { async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com` const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
let marker let marker
@@ -333,7 +342,8 @@ class S3plistApi {
}) })
res = await client.send(command) res = await client.send(command)
if (res.$metadata.httpStatusCode === 200) { if (res.$metadata.httpStatusCode === 200) {
res.Contents && res.Contents.forEach((item: _Object) => { res.Contents &&
res.Contents.forEach((item: _Object) => {
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
}) })
window.webContents.send(refreshDownloadFileTransferList, result) window.webContents.send(refreshDownloadFileTransferList, result)
@@ -361,7 +371,12 @@ class S3plistApi {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com` const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
let marker let marker
@@ -393,10 +408,12 @@ class S3plistApi {
}) })
res = await client.send(command) res = await client.send(command)
if (res.$metadata.httpStatusCode === 200) { if (res.$metadata.httpStatusCode === 200) {
res.CommonPrefixes && res.CommonPrefixes.forEach((item: CommonPrefix) => { res.CommonPrefixes &&
res.CommonPrefixes.forEach((item: CommonPrefix) => {
result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix)) result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
}) })
res.Contents && res.Contents.forEach((item: _Object) => { res.Contents &&
res.Contents.forEach((item: _Object) => {
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
}) })
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
@@ -423,7 +440,13 @@ class S3plistApi {
} }
async getBucketFileList(configMap: IStringKeyMap): Promise<any> { async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, marker, itemsPerPage } = configMap const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
marker,
itemsPerPage
} = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com` const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
const result = { const result = {
@@ -434,7 +457,10 @@ class S3plistApi {
} }
try { try {
await this.getDogeCloudToken() await this.getDogeCloudToken()
const options = Object.assign({}, { ...this.baseOptions, region: String(region) || 'us-east-1' }) as S3ClientConfig const options = Object.assign(
{},
{ ...this.baseOptions, region: String(region) || 'us-east-1' }
) as S3ClientConfig
const client = new S3Client(options) const client = new S3Client(options)
const command = new ListObjectsV2Command({ const command = new ListObjectsV2Command({
Bucket: bucket, Bucket: bucket,
@@ -474,7 +500,10 @@ class S3plistApi {
let result = false let result = false
try { try {
await this.getDogeCloudToken() await this.getDogeCloudToken()
const options = Object.assign({}, { ...this.baseOptions, region: String(region) || 'us-east-1' }) as S3ClientConfig const options = Object.assign(
{},
{ ...this.baseOptions, region: String(region) || 'us-east-1' }
) as S3ClientConfig
const client = new S3Client(options) const client = new S3Client(options)
const command = new CopyObjectCommand({ const command = new CopyObjectCommand({
Bucket: bucketName, Bucket: bucketName,
@@ -562,7 +591,7 @@ class S3plistApi {
Delimiter: '/', Delimiter: '/',
MaxKeys: 1000 MaxKeys: 1000
}) })
res = await client.send(command) as ListObjectsV2CommandOutput res = (await client.send(command)) as ListObjectsV2CommandOutput
if (res.$metadata.httpStatusCode === 200) { if (res.$metadata.httpStatusCode === 200) {
res.CommonPrefixes && allFileList.CommonPrefixes.push(...res.CommonPrefixes) res.CommonPrefixes && allFileList.CommonPrefixes.push(...res.CommonPrefixes)
res.Contents && allFileList.Contents.push(...res.Contents) res.Contents && allFileList.Contents.push(...res.Contents)
@@ -595,7 +624,7 @@ class S3plistApi {
const deleteCommand = new DeleteObjectsCommand({ const deleteCommand = new DeleteObjectsCommand({
Bucket: bucketName, Bucket: bucketName,
Delete: { Delete: {
Objects: deleteList.map((item) => { Objects: deleteList.map(item => {
return { return {
Key: item.Key Key: item.Key
} }
@@ -635,12 +664,16 @@ class S3plistApi {
const options = Object.assign({}, this.baseOptions) as S3ClientConfig const options = Object.assign({}, this.baseOptions) as S3ClientConfig
options.region = String(region) || 'us-east-1' options.region = String(region) || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
const signedUrl = await getSignedUrl(client, new GetObjectCommand({ const signedUrl = await getSignedUrl(
client,
new GetObjectCommand({
Bucket: bucketName, Bucket: bucketName,
Key: key Key: key
}), { }),
{
expiresIn: expires || 3600 expiresIn: expires || 3600
}) }
)
return signedUrl return signedUrl
} catch (error) { } catch (error) {
this.logParam(error, 'getPreSignedUrl') this.logParam(error, 'getPreSignedUrl')
@@ -693,7 +726,15 @@ class S3plistApi {
fileArray.forEach((item: any) => { fileArray.forEach((item: any) => {
item.key.startsWith('/') && (item.key = item.key.slice(1)) item.key.startsWith('/') && (item.key = item.key.slice(1))
}) })
const allowedAcl = ['private', 'public-read', 'public-read-write', 'aws-exec-read', 'authenticated-read', 'bucket-owner-read', 'bucket-owner-full-control'] const allowedAcl = [
'private',
'public-read',
'public-read-write',
'aws-exec-read',
'authenticated-read',
'bucket-owner-read',
'bucket-owner-full-control'
]
for (const item of fileArray) { for (const item of fileArray) {
const { bucketName, region, key, filePath, fileName, aclForUpload } = item const { bucketName, region, key, filePath, fileName, aclForUpload } = item
const id = `${bucketName}-${String(region)}-${key}-${filePath}` const id = `${bucketName}-${String(region)}-${key}-${filePath}`
@@ -743,11 +784,13 @@ class S3plistApi {
parallelUploads3.on('httpUploadProgress', (progress: Progress) => { parallelUploads3.on('httpUploadProgress', (progress: Progress) => {
instance.updateUploadTask({ instance.updateUploadTask({
id, id,
progress: progress.loaded && progress.total ? Math.floor(progress.loaded / progress.total * 100) : 0, progress: progress.loaded && progress.total ? Math.floor((progress.loaded / progress.total) * 100) : 0,
status: uploadTaskSpecialStatus.uploading status: uploadTaskSpecialStatus.uploading
}) })
}) })
parallelUploads3.done().then((data) => { parallelUploads3
.done()
.then(data => {
if (data.$metadata.httpStatusCode === 200) { if (data.$metadata.httpStatusCode === 200) {
instance.updateUploadTask({ instance.updateUploadTask({
id, id,
@@ -763,7 +806,8 @@ class S3plistApi {
finishTime: new Date().toLocaleString() finishTime: new Date().toLocaleString()
}) })
} }
}).catch((error) => { })
.catch(error => {
this.logParam(error, 'uploadBucketFile') this.logParam(error, 'uploadBucketFile')
instance.updateUploadTask({ instance.updateUploadTask({
id, id,
@@ -806,19 +850,21 @@ class S3plistApi {
expires: 36000, expires: 36000,
customUrl customUrl
}) })
promises.push(() => new Promise((resolve, reject) => { promises.push(
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxy) () =>
.then((res: boolean) => { new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxy).then((res: boolean) => {
if (res) { if (res) {
resolve(res) resolve(res)
} else { } else {
reject(res) reject(res)
} }
}) })
})) })
)
} }
const pool = new ConcurrencyPromisePool(maxDownloadFileCount) const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => { pool.all(promises).catch(error => {
this.logParam(error, 'downloadBucketFile') this.logParam(error, 'downloadBucketFile')
}) })
return true return true
+3 -3
View File
@@ -76,8 +76,7 @@ class SftpApi {
} }
} }
logParam = (error:any, method: string) => logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'SftpApi', method }))
this.logger.error(formatError(error, { class: 'SftpApi', method }))
transFormPermission = (permissionsStr: string) => { transFormPermission = (permissionsStr: string) => {
const permissions = permissionsStr.length === 10 ? permissionsStr.slice(1) : permissionsStr const permissions = permissionsStr.length === 10 ? permissionsStr.slice(1) : permissionsStr
@@ -250,7 +249,8 @@ class SftpApi {
if (formatedLSRes.length) { if (formatedLSRes.length) {
formatedLSRes.forEach((item: listDirResult) => { formatedLSRes.forEach((item: listDirResult) => {
const relativePath = path.relative(baseDir, item.key.startsWith('/') ? item.key : `/${item.key}`) const relativePath = path.relative(baseDir, item.key.startsWith('/') ? item.key : `/${item.key}`)
const relative = webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/') const relative =
webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/')
if (item.isDir) { if (item.isDir) {
result.fullList.push(this.formatFolder(item, webPath ? relative : urlPrefix, !!webPath)) result.fullList.push(this.formatFolder(item, webPath ? relative : urlPrefix, !!webPath))
} else { } else {
+12 -18
View File
@@ -71,9 +71,7 @@ class SmmsApi {
finished: false finished: false
} }
do { do {
res = await this.axiosInstance( res = await this.axiosInstance('/upload_history', {
'/upload_history',
{
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
@@ -131,9 +129,7 @@ class SmmsApi {
nextMarker: '', nextMarker: '',
success: false success: false
} }
const res = await this.axiosInstance( const res = await this.axiosInstance('/upload_history', {
'/upload_history',
{
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
@@ -141,8 +137,7 @@ class SmmsApi {
params: { params: {
page: currentPage page: currentPage
} }
} })
)
if (res?.status !== 200 || !res?.data?.success) return result if (res?.status !== 200 || !res?.data?.success) return result
if (res.data.Count === 0) return { ...result, success: true } if (res.data.Count === 0) return { ...result, success: true }
@@ -167,16 +162,13 @@ class SmmsApi {
* } * }
*/ */
async deleteBucketFile({ DeleteHash }: IStringKeyMap): Promise<boolean> { async deleteBucketFile({ DeleteHash }: IStringKeyMap): Promise<boolean> {
const res = await this.axiosInstance( const res = await this.axiosInstance(`/delete/${DeleteHash}`, {
`/delete/${DeleteHash}`,
{
method: 'GET', method: 'GET',
params: { params: {
hash: DeleteHash, hash: DeleteHash,
format: 'json' format: 'json'
} }
} })
)
return res?.status === 200 && res?.data?.success return res?.status === 200 && res?.data?.success
} }
@@ -239,19 +231,21 @@ class SmmsApi {
sourceFileName: fileName, sourceFileName: fileName,
targetFilePath: savedFilePath targetFilePath: savedFilePath
}) })
promises.push(() => new Promise((resolve, reject) => { promises.push(
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger) () =>
.then((res: boolean) => { new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) { if (res) {
resolve(res) resolve(res)
} else { } else {
reject(res) reject(res)
} }
}) })
})) })
)
} }
const pool = new ConcurrencyPromisePool(maxDownloadFileCount) const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => { pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'SmmsApi', method: 'downloadBucketFile' })) this.logger.error(formatError(error, { class: 'SmmsApi', method: 'downloadBucketFile' }))
}) })
return true return true
+66 -21
View File
@@ -1,4 +1,3 @@
import COS from 'cos-nodejs-sdk-v5' import COS from 'cos-nodejs-sdk-v5'
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
@@ -99,7 +98,13 @@ class TcyunApi {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> { async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, customUrl, cancelToken } = configMap const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
customUrl,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1, prefix.length) const slicedPrefix = prefix.slice(1, prefix.length)
const urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com` const urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
const cancelTask = [false] const cancelTask = [false]
@@ -125,8 +130,11 @@ class TcyunApi {
Marker: marker Marker: marker
}) })
if (res?.statusCode === 200) { if (res?.statusCode === 200) {
result.fullList.push(...res.Contents.filter(item => parseInt(item.Size) !== 0) result.fullList.push(
.map(item => this.formatFile(item, slicedPrefix, urlPrefix))) ...res.Contents.filter(item => parseInt(item.Size) !== 0).map(item =>
this.formatFile(item, slicedPrefix, urlPrefix)
)
)
window.webContents.send(refreshDownloadFileTransferList, result) window.webContents.send(refreshDownloadFileTransferList, result)
} else { } else {
result.finished = true result.finished = true
@@ -144,7 +152,13 @@ class TcyunApi {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, customUrl, cancelToken } = configMap const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
customUrl,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1, prefix.length) const slicedPrefix = prefix.slice(1, prefix.length)
const urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com` const urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
const cancelTask = [false] const cancelTask = [false]
@@ -173,8 +187,9 @@ class TcyunApi {
if (res?.statusCode === 200) { if (res?.statusCode === 200) {
result.fullList.push( result.fullList.push(
...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix, urlPrefix)), ...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix, urlPrefix)),
...res.Contents.filter(item => parseInt(item.Size) !== 0) ...res.Contents.filter(item => parseInt(item.Size) !== 0).map(item =>
.map(item => this.formatFile(item, slicedPrefix, urlPrefix)) this.formatFile(item, slicedPrefix, urlPrefix)
)
) )
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
} else { } else {
@@ -207,17 +222,24 @@ class TcyunApi {
* } * }
*/ */
async getBucketFileList(configMap: IStringKeyMap): Promise<any> { async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, customUrl, marker, itemsPerPage } = configMap const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
customUrl,
marker,
itemsPerPage
} = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
const urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com` const urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
const res = await this.ctx.getBucket({ const res = (await this.ctx.getBucket({
Bucket: bucket, Bucket: bucket,
Region: region, Region: region,
Prefix: slicedPrefix === '' ? undefined : slicedPrefix, Prefix: slicedPrefix === '' ? undefined : slicedPrefix,
Delimiter: '/', Delimiter: '/',
Marker: marker, Marker: marker,
MaxKeys: itemsPerPage MaxKeys: itemsPerPage
}) as COS.GetBucketResult })) as COS.GetBucketResult
if (res?.statusCode !== 200) { if (res?.statusCode !== 200) {
return { return {
fullList: [], fullList: [],
@@ -229,8 +251,9 @@ class TcyunApi {
const result = { const result = {
fullList: [ fullList: [
...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix, urlPrefix)), ...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix, urlPrefix)),
...res.Contents.filter(item => parseInt(item.Size) !== 0) ...res.Contents.filter(item => parseInt(item.Size) !== 0).map(item =>
.map(item => this.formatFile(item, slicedPrefix, urlPrefix)) this.formatFile(item, slicedPrefix, urlPrefix)
)
], ],
isTruncated: res.IsTruncated === 'true', isTruncated: res.IsTruncated === 'true',
nextMarker: res.NextMarker || '', nextMarker: res.NextMarker || '',
@@ -317,7 +340,14 @@ class TcyunApi {
marker = res.NextMarker marker = res.NextMarker
} while (res.IsTruncated === 'true') } while (res.IsTruncated === 'true')
for (const item of allFileList.CommonPrefixes) { for (const item of allFileList.CommonPrefixes) {
if (!(await this.deleteBucketFolder({ bucketName, region, key: item.Prefix }))) return false if (
!(await this.deleteBucketFolder({
bucketName,
region,
key: item.Prefix
}))
)
return false
} }
const cycles = Math.ceil(allFileList.Contents.length / 1000) const cycles = Math.ceil(allFileList.Contents.length / 1000)
for (let i = 0; i < cycles; i++) { for (let i = 0; i < cycles; i++) {
@@ -344,14 +374,16 @@ class TcyunApi {
*/ */
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> { async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName, region, key, expires, customUrl } = configMap const { bucketName, region, key, expires, customUrl } = configMap
const res = this.ctx.getObjectUrl({ const res = this.ctx.getObjectUrl(
{
Bucket: bucketName, Bucket: bucketName,
Region: region, Region: region,
Key: key, Key: key,
Expires: expires, Expires: expires,
Sign: true Sign: true
}, () => { },
}) () => {}
)
return customUrl ? `${customUrl.replace(/\/+$/, '')}/${key}${res.slice(res.indexOf('?'))}` : res return customUrl ? `${customUrl.replace(/\/+$/, '')}/${key}${res.slice(res.indexOf('?'))}` : res
} }
@@ -412,7 +444,12 @@ class TcyunApi {
finishTime: new Date().toLocaleString() finishTime: new Date().toLocaleString()
}) })
} else { } else {
this.logger.error(formatError(err, { method: 'uploadBucketFile', class: 'TcyunApi' })) this.logger.error(
formatError(err, {
method: 'uploadBucketFile',
class: 'TcyunApi'
})
)
instance.updateUploadTask({ instance.updateUploadTask({
id, id,
progress: 0, progress: 0,
@@ -472,7 +509,8 @@ class TcyunApi {
targetFilePath: path.join(downloadPath, fileName) targetFilePath: path.join(downloadPath, fileName)
}) })
fs.ensureDirSync(path.dirname(path.join(downloadPath, fileName))) fs.ensureDirSync(path.dirname(path.join(downloadPath, fileName)))
this.ctx.downloadFile({ this.ctx
.downloadFile({
Bucket: bucketName, Bucket: bucketName,
Region: region, Region: region,
Key: key, Key: key,
@@ -486,7 +524,8 @@ class TcyunApi {
status: downloadTaskSpecialStatus.downloading status: downloadTaskSpecialStatus.downloading
}) })
} }
}).then((res: any) => { })
.then((res: any) => {
instance.updateDownloadTask({ instance.updateDownloadTask({
id, id,
progress: res && res.statusCode === 200 ? 100 : 0, progress: res && res.statusCode === 200 ? 100 : 0,
@@ -494,8 +533,14 @@ class TcyunApi {
response: typeof res === 'object' ? JSON.stringify(res) : String(res), response: typeof res === 'object' ? JSON.stringify(res) : String(res),
finishTime: new Date().toLocaleString() finishTime: new Date().toLocaleString()
}) })
}).catch((err: any) => { })
this.logger.error(formatError(err, { method: 'downloadBucketFile', class: 'TcyunApi' })) .catch((err: any) => {
this.logger.error(
formatError(err, {
method: 'downloadBucketFile',
class: 'TcyunApi'
})
)
instance.updateDownloadTask({ instance.updateDownloadTask({
id, id,
progress: 0, progress: 0,
+29 -16
View File
@@ -7,7 +7,15 @@ import Upyun from 'upyun'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import { md5, hmacSha1Base64, getFileMimeType, NewDownloader, gotUpload, ConcurrencyPromisePool, formatError } from '~/manage/utils/common' import {
md5,
hmacSha1Base64,
getFileMimeType,
NewDownloader,
gotUpload,
ConcurrencyPromisePool,
formatError
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger' import { ManageLogger } from '~/manage/utils/logger'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue' import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
@@ -26,7 +34,14 @@ class UpyunApi {
stopMarker = 'g2gCZAAEbmV4dGQAA2VvZg' stopMarker = 'g2gCZAAEbmV4dGQAA2VvZg'
logger: ManageLogger logger: ManageLogger
constructor (bucket: string, operator: string, password: string, logger: ManageLogger, antiLeechToken?: string, expireTime?: number) { constructor(
bucket: string,
operator: string,
password: string,
logger: ManageLogger,
antiLeechToken?: string,
expireTime?: number
) {
this.ser = new Upyun.Service(bucket, operator, password) this.ser = new Upyun.Service(bucket, operator, password)
this.cli = new Upyun.Client(this.ser) this.cli = new Upyun.Client(this.ser)
this.bucket = bucket this.bucket = bucket
@@ -87,13 +102,7 @@ class UpyunApi {
} }
} }
authorization ( authorization(method: string, uri: string, contentMd5: string, operator: string, password: string) {
method: string,
uri: string,
contentMd5: string,
operator: string,
password: string
) {
return `UPYUN ${operator}:${hmacSha1Base64( return `UPYUN ${operator}:${hmacSha1Base64(
md5(password, 'hex'), md5(password, 'hex'),
`${method.toUpperCase()}&${encodeURI(uri)}&${new Date().toUTCString()}${contentMd5 ? `&${contentMd5}` : ''}` `${method.toUpperCase()}&${encodeURI(uri)}&${new Date().toUTCString()}${contentMd5 ? `&${contentMd5}` : ''}`
@@ -313,11 +322,13 @@ class UpyunApi {
}) })
if (res) { if (res) {
res.files.forEach((item: any) => { res.files.forEach((item: any) => {
item.type === 'N' && allFileList.Contents.push({ item.type === 'N' &&
allFileList.Contents.push({
...item, ...item,
key: `${key}${item.name}` key: `${key}${item.name}`
}) })
item.type === 'F' && allFileList.CommonPrefixes.push({ item.type === 'F' &&
allFileList.CommonPrefixes.push({
...item, ...item,
key: `${key}${item.name}/` key: `${key}${item.name}/`
}) })
@@ -445,19 +456,21 @@ class UpyunApi {
targetFilePath: savedFilePath targetFilePath: savedFilePath
}) })
const preSignedUrl = `${customUrl}/${key}` const preSignedUrl = `${customUrl}/${key}`
promises.push(() => new Promise((resolve, reject) => { promises.push(
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger) () =>
.then((res: boolean) => { new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) { if (res) {
resolve(res) resolve(res)
} else { } else {
reject(res) reject(res)
} }
}) })
})) })
)
} }
const pool = new ConcurrencyPromisePool(maxDownloadFileCount) const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => { pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'UpyunApi', method: 'downloadBucketFile' })) this.logger.error(formatError(error, { class: 'UpyunApi', method: 'downloadBucketFile' }))
}) })
return true return true
+35 -21
View File
@@ -29,7 +29,15 @@ class WebdavplistApi {
agent: https.Agent | http.Agent agent: https.Agent | http.Agent
ctx: WebDAVClient ctx: WebDAVClient
constructor (endpoint: string, username: string, password: string, sslEnabled: boolean, proxy: string | undefined, authType: 'basic' | 'digest' | undefined, logger: ManageLogger) { constructor(
endpoint: string,
username: string,
password: string,
sslEnabled: boolean,
proxy: string | undefined,
authType: 'basic' | 'digest' | undefined,
logger: ManageLogger
) {
this.endpoint = formatEndpoint(endpoint, sslEnabled) this.endpoint = formatEndpoint(endpoint, sslEnabled)
this.username = username this.username = username
this.password = password this.password = password
@@ -50,14 +58,10 @@ class WebdavplistApi {
if (this.authType === 'digest') { if (this.authType === 'digest') {
options.authType = AuthType.Digest options.authType = AuthType.Digest
} }
this.ctx = createClient( this.ctx = createClient(this.endpoint, options)
this.endpoint,
options
)
} }
logParam = (error:any, method: string) => logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'WebdavplistApi', method }))
this.logger.error(formatError(error, { class: 'WebdavplistApi', method }))
formatFolder(item: FileStat, urlPrefix: string, isWebPath = false) { formatFolder(item: FileStat, urlPrefix: string, isWebPath = false) {
const key = item.filename.replace(/^\/+/, '') const key = item.filename.replace(/^\/+/, '')
@@ -166,7 +170,8 @@ class WebdavplistApi {
if (res.data?.length) { if (res.data?.length) {
res.data.forEach((item: FileStat) => { res.data.forEach((item: FileStat) => {
const relativePath = path.relative(baseDir, item.filename) const relativePath = path.relative(baseDir, item.filename)
const relative = webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/') const relative =
webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/')
if (item.type === 'directory') { if (item.type === 'directory') {
result.fullList.push(this.formatFolder(item, webPath ? relative : urlPrefix, !!webPath)) result.fullList.push(this.formatFolder(item, webPath ? relative : urlPrefix, !!webPath))
} else { } else {
@@ -261,10 +266,8 @@ class WebdavplistApi {
targetFileRegion: region, targetFileRegion: region,
noProgress: true noProgress: true
}) })
this.ctx.putFileContents( this.ctx
key, .putFileContents(key, this.authType === 'digest' ? fs.readFileSync(filePath) : fs.createReadStream(filePath), {
this.authType === 'digest' ? fs.readFileSync(filePath) : fs.createReadStream(filePath),
{
overwrite: true, overwrite: true,
onUploadProgress: (progressEvent: ProgressEvent) => { onUploadProgress: (progressEvent: ProgressEvent) => {
instance.updateUploadTask({ instance.updateUploadTask({
@@ -273,8 +276,8 @@ class WebdavplistApi {
status: uploadTaskSpecialStatus.uploading status: uploadTaskSpecialStatus.uploading
}) })
} }
} })
).then((res: boolean) => { .then((res: boolean) => {
if (res) { if (res) {
instance.updateUploadTask({ instance.updateUploadTask({
id, id,
@@ -290,7 +293,8 @@ class WebdavplistApi {
finishTime: new Date().toLocaleString() finishTime: new Date().toLocaleString()
}) })
} }
}).catch((error: any) => { })
.catch((error: any) => {
this.logParam(error, 'uploadBucketFile') this.logParam(error, 'uploadBucketFile')
instance.updateUploadTask({ instance.updateUploadTask({
id, id,
@@ -345,25 +349,35 @@ class WebdavplistApi {
Authorization: `Basic ${base64Str}` Authorization: `Basic ${base64Str}`
} }
} else if (this.authType === 'digest') { } else if (this.authType === 'digest') {
const authHeader = await getAuthHeader('GET', this.endpoint, `/${key.replace(/^\/+/, '')}`, this.username, this.password) const authHeader = await getAuthHeader(
'GET',
this.endpoint,
`/${key.replace(/^\/+/, '')}`,
this.username,
this.password
)
headers = { headers = {
Authorization: authHeader Authorization: authHeader
} }
preSignedUrl = `${this.endpoint}/${key.replace(/^\/+/, '')}` preSignedUrl = `${this.endpoint}/${key.replace(/^\/+/, '')}`
} }
promises.push(() => new Promise((resolve, reject) => { promises.push(
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxyStr, headers) () =>
.then((res: boolean) => { new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxyStr, headers).then(
(res: boolean) => {
if (res) { if (res) {
resolve(res) resolve(res)
} else { } else {
reject(res) reject(res)
} }
}
)
}) })
})) )
} }
const pool = new ConcurrencyPromisePool(maxDownloadFileCount) const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => { pool.all(promises).catch(error => {
this.logParam(error, 'downloadBucketFile') this.logParam(error, 'downloadBucketFile')
}) })
return true return true
+13 -9
View File
@@ -42,9 +42,13 @@ function manageDbChecker () {
fs.unlinkSync(manageConfigFilePath) fs.unlinkSync(manageConfigFilePath)
if (fs.existsSync(manageConfigFileBackupPath)) { if (fs.existsSync(manageConfigFileBackupPath)) {
try { try {
configFile = fs.readFileSync(manageConfigFileBackupPath, { encoding: 'utf-8' }) configFile = fs.readFileSync(manageConfigFileBackupPath, {
encoding: 'utf-8'
})
JSON.parse(configFile) JSON.parse(configFile)
writeFile.sync(manageConfigFilePath, configFile, { encoding: 'utf-8' }) writeFile.sync(manageConfigFilePath, configFile, {
encoding: 'utf-8'
})
const stats = fs.statSync(manageConfigFileBackupPath) const stats = fs.statSync(manageConfigFileBackupPath)
optionsTpl.body = `${errorMsg.brokenButBackup}\n${T('TIPS_PICGO_BACKUP_FILE_VERSION', { optionsTpl.body = `${errorMsg.brokenButBackup}\n${T('TIPS_PICGO_BACKUP_FILE_VERSION', {
v: dayjs(stats.mtime).format('YYYY-MM-DD HH:mm:ss') v: dayjs(stats.mtime).format('YYYY-MM-DD HH:mm:ss')
@@ -61,7 +65,9 @@ function manageDbChecker () {
global.notificationList.push(optionsTpl) global.notificationList.push(optionsTpl)
return return
} }
writeFile.sync(manageConfigFileBackupPath, configFile, { encoding: 'utf-8' }) writeFile.sync(manageConfigFileBackupPath, configFile, {
encoding: 'utf-8'
})
} }
} }
@@ -80,7 +86,9 @@ function managePathChecker (): string {
return _configFilePath return _configFilePath
} }
try { try {
const configString = fs.readFileSync(defaultManageConfigPath, { encoding: 'utf-8' }) const configString = fs.readFileSync(defaultManageConfigPath, {
encoding: 'utf-8'
})
const config = JSON.parse(configString) const config = JSON.parse(configString)
const userConfigPath: string = config.configPath || '' const userConfigPath: string = config.configPath || ''
if (userConfigPath) { if (userConfigPath) {
@@ -111,8 +119,4 @@ function managePathDir () {
return path.dirname(managePathChecker()) return path.dirname(managePathChecker())
} }
export { export { managePathChecker, managePathDir, manageDbChecker }
managePathChecker,
managePathDir,
manageDbChecker
}
+27 -8
View File
@@ -104,11 +104,21 @@ class UpDownTaskQueue {
} }
removeUploadedTask() { removeUploadedTask() {
UpDownTaskQueue.getInstance().uploadTaskQueue = UpDownTaskQueue.getInstance().uploadTaskQueue.filter(item => item.status !== uploadTaskSpecialStatus.uploaded && item.status !== commonTaskStatus.canceled && item.status !== commonTaskStatus.failed) UpDownTaskQueue.getInstance().uploadTaskQueue = UpDownTaskQueue.getInstance().uploadTaskQueue.filter(
item =>
item.status !== uploadTaskSpecialStatus.uploaded &&
item.status !== commonTaskStatus.canceled &&
item.status !== commonTaskStatus.failed
)
} }
removeDownloadedTask() { removeDownloadedTask() {
UpDownTaskQueue.getInstance().downloadTaskQueue = UpDownTaskQueue.getInstance().downloadTaskQueue.filter(item => item.status !== downloadTaskSpecialStatus.downloaded && item.status !== commonTaskStatus.canceled && item.status !== commonTaskStatus.failed) UpDownTaskQueue.getInstance().downloadTaskQueue = UpDownTaskQueue.getInstance().downloadTaskQueue.filter(
item =>
item.status !== downloadTaskSpecialStatus.downloaded &&
item.status !== commonTaskStatus.canceled &&
item.status !== commonTaskStatus.failed
)
} }
clearDownloadTaskQueue() { clearDownloadTaskQueue() {
@@ -123,10 +133,13 @@ class UpDownTaskQueue {
persist() { persist() {
try { try {
this.checkPersistPath() this.checkPersistPath()
fs.writeFileSync(this.persistPath, JSON.stringify({ fs.writeFileSync(
this.persistPath,
JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue, uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue downloadTaskQueue: this.downloadTaskQueue
})) })
)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
@@ -146,18 +159,24 @@ class UpDownTaskQueue {
private checkPersistPath() { private checkPersistPath() {
if (!fs.existsSync(this.persistPath)) { if (!fs.existsSync(this.persistPath)) {
fs.writeFileSync(this.persistPath, JSON.stringify({ fs.writeFileSync(
this.persistPath,
JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue, uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue downloadTaskQueue: this.downloadTaskQueue
})) })
)
} }
try { try {
JSON.parse(fs.readFileSync(this.persistPath, { encoding: 'utf-8' })) JSON.parse(fs.readFileSync(this.persistPath, { encoding: 'utf-8' }))
} catch (e) { } catch (e) {
fs.writeFileSync(this.persistPath, JSON.stringify({ fs.writeFileSync(
this.persistPath,
JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue, uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue downloadTaskQueue: this.downloadTaskQueue
})) })
)
} }
} }
} }
+5 -2
View File
@@ -59,10 +59,13 @@ export const manageIpcList = {
return manage.getBucketListBackstage(param) return manage.getBucketListBackstage(param)
}) })
ipcMain.on('getBucketListRecursively', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => { ipcMain.on(
'getBucketListRecursively',
async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed) const manage = new ManageApi(currentPicBed)
return manage.getBucketListRecursively(param) return manage.getBucketListRecursively(param)
}) }
)
ipcMain.handle('convertPathToBase64', async (_: IpcMainInvokeEvent, filePath: string) => { ipcMain.handle('convertPathToBase64', async (_: IpcMainInvokeEvent, filePath: string) => {
const res = fs.readFileSync(filePath, 'base64') const res = fs.readFileSync(filePath, 'base64')
+6 -6
View File
@@ -1,11 +1,11 @@
import { import { IpcMainEvent, IpcMainInvokeEvent, ipcMain } from 'electron'
IpcMainEvent,
IpcMainInvokeEvent,
ipcMain
} from 'electron'
import getManageApi from '~/manage/Main' import getManageApi from '~/manage/Main'
import { PICLIST_MANAGE_GET_CONFIG, PICLIST_MANAGE_SAVE_CONFIG, PICLIST_MANAGE_REMOVE_CONFIG } from '~/manage/events/constants' import {
PICLIST_MANAGE_GET_CONFIG,
PICLIST_MANAGE_SAVE_CONFIG,
PICLIST_MANAGE_REMOVE_CONFIG
} from '~/manage/events/constants'
const manageApi = getManageApi() const manageApi = getManageApi()
+88 -87
View File
@@ -1,4 +1,3 @@
import { ipcMain } from 'electron' import { ipcMain } from 'electron'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import fs from 'fs-extra' import fs from 'fs-extra'
@@ -15,12 +14,7 @@ import { isInputConfigValid, formatError } from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger' import { ManageLogger } from '~/manage/utils/logger'
import { IWindowList } from '#/types/enum' import { IWindowList } from '#/types/enum'
import { import { IManageApiType, IManageConfigType, IManageError, IPicBedMangeConfig } from '#/types/manage'
IManageApiType,
IManageConfigType,
IManageError,
IPicBedMangeConfig
} from '#/types/manage'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static' import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
export class ManageApi extends EventEmitter implements IManageApiType { export class ManageApi extends EventEmitter implements IManageApiType {
@@ -58,11 +52,25 @@ export class ManageApi extends EventEmitter implements IManageApiType {
const name = this.currentPicBedConfig.picBedName const name = this.currentPicBedConfig.picBedName
switch (name) { switch (name) {
case 'aliyun': case 'aliyun':
return new API.AliyunApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.accessKeySecret, this.logger) return new API.AliyunApi(
this.currentPicBedConfig.accessKeyId,
this.currentPicBedConfig.accessKeySecret,
this.logger
)
case 'github': case 'github':
return new API.GithubApi(this.currentPicBedConfig.token, this.currentPicBedConfig.githubUsername, this.currentPicBedConfig.proxy, this.logger) return new API.GithubApi(
this.currentPicBedConfig.token,
this.currentPicBedConfig.githubUsername,
this.currentPicBedConfig.proxy,
this.logger
)
case 'imgur': case 'imgur':
return new API.ImgurApi(this.currentPicBedConfig.imgurUserName, this.currentPicBedConfig.accessToken, this.currentPicBedConfig.proxy, this.logger) return new API.ImgurApi(
this.currentPicBedConfig.imgurUserName,
this.currentPicBedConfig.accessToken,
this.currentPicBedConfig.proxy,
this.logger
)
case 'local': case 'local':
return new API.LocalApi(this.logger) return new API.LocalApi(this.logger)
case 'qiniu': case 'qiniu':
@@ -70,15 +78,50 @@ export class ManageApi extends EventEmitter implements IManageApiType {
case 'smms': case 'smms':
return new API.SmmsApi(this.currentPicBedConfig.token, this.logger) return new API.SmmsApi(this.currentPicBedConfig.token, this.logger)
case 's3plist': 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, this.currentPicBedConfig.dogeCloudSupport || false, this.currentPicBedConfig.bucketName || '') return new API.S3plistApi(
this.currentPicBedConfig.accessKeyId,
this.currentPicBedConfig.secretAccessKey,
this.currentPicBedConfig.endpoint,
this.currentPicBedConfig.sslEnabled,
this.currentPicBedConfig.s3ForcePathStyle,
this.currentPicBedConfig.proxy,
this.logger,
this.currentPicBedConfig.dogeCloudSupport || false,
this.currentPicBedConfig.bucketName || ''
)
case 'sftp': case 'sftp':
return new API.SftpApi(this.currentPicBedConfig.host, this.currentPicBedConfig.port, this.currentPicBedConfig.username, this.currentPicBedConfig.password, this.currentPicBedConfig.privateKey, this.currentPicBedConfig.passphrase, this.currentPicBedConfig.fileMode, this.currentPicBedConfig.dirMode, this.logger) return new API.SftpApi(
this.currentPicBedConfig.host,
this.currentPicBedConfig.port,
this.currentPicBedConfig.username,
this.currentPicBedConfig.password,
this.currentPicBedConfig.privateKey,
this.currentPicBedConfig.passphrase,
this.currentPicBedConfig.fileMode,
this.currentPicBedConfig.dirMode,
this.logger
)
case 'tcyun': case 'tcyun':
return new API.TcyunApi(this.currentPicBedConfig.secretId, this.currentPicBedConfig.secretKey, this.logger) return new API.TcyunApi(this.currentPicBedConfig.secretId, this.currentPicBedConfig.secretKey, this.logger)
case 'upyun': case 'upyun':
return new API.UpyunApi(this.currentPicBedConfig.bucketName, this.currentPicBedConfig.operator, this.currentPicBedConfig.password, this.logger, this.currentPicBedConfig.antiLeechToken, this.currentPicBedConfig.expireTime) return new API.UpyunApi(
this.currentPicBedConfig.bucketName,
this.currentPicBedConfig.operator,
this.currentPicBedConfig.password,
this.logger,
this.currentPicBedConfig.antiLeechToken,
this.currentPicBedConfig.expireTime
)
case 'webdavplist': case 'webdavplist':
return new API.WebdavplistApi(this.currentPicBedConfig.endpoint, this.currentPicBedConfig.username, this.currentPicBedConfig.password, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.proxy, this.currentPicBedConfig.authType, this.logger) return new API.WebdavplistApi(
this.currentPicBedConfig.endpoint,
this.currentPicBedConfig.username,
this.currentPicBedConfig.password,
this.currentPicBedConfig.sslEnabled,
this.currentPicBedConfig.proxy,
this.currentPicBedConfig.authType,
this.logger
)
default: default:
return {} as any return {} as any
} }
@@ -117,9 +160,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
saveConfig(config: IStringKeyMap): void { saveConfig(config: IStringKeyMap): void {
if (!isInputConfigValid(config)) { if (!isInputConfigValid(config)) {
this.logger.warn( this.logger.warn('the format of config is invalid, please provide object')
'the format of config is invalid, please provide object'
)
return return
} }
this.setConfig(config) this.setConfig(config)
@@ -136,9 +177,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
setConfig(config: IStringKeyMap): void { setConfig(config: IStringKeyMap): void {
if (!isInputConfigValid(config)) { if (!isInputConfigValid(config)) {
this.logger.warn( this.logger.warn('the format of config is invalid, please provide object')
'the format of config is invalid, please provide object'
)
return return
} }
Object.keys(config).forEach((name: string) => { Object.keys(config).forEach((name: string) => {
@@ -151,9 +190,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
unset(this.getConfig(key), propName) unset(this.getConfig(key), propName)
} }
async getBucketList ( async getBucketList(param?: IStringKeyMap | undefined): Promise<any> {
param?: IStringKeyMap | undefined
): Promise<any> {
let client let client
const name = this.currentPicBedConfig.picBedName.replace('plist', '') const name = this.currentPicBedConfig.picBedName.replace('plist', '')
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
@@ -171,36 +208,36 @@ export class ManageApi extends EventEmitter implements IManageApiType {
return [] return []
} }
case 'upyun': case 'upyun':
return [{ return [
{
Name: this.currentPicBedConfig.bucketName, Name: this.currentPicBedConfig.bucketName,
Location: 'upyun', Location: 'upyun',
CreationDate: new Date().toISOString() CreationDate: new Date().toISOString()
}] }
]
case 'smms': case 'smms':
case 'webdavplist': case 'webdavplist':
case 'local': case 'local':
case 'sftp': case 'sftp':
return [{ return [
{
Name: name, Name: name,
Location: name, Location: name,
CreationDate: new Date().toISOString() CreationDate: new Date().toISOString()
}] }
]
default: default:
console.log(param) console.log(param)
return [] return []
} }
} }
async getBucketInfo ( async getBucketInfo(param?: IStringKeyMap | undefined): Promise<IStringKeyMap | IManageError> {
param?: IStringKeyMap | undefined
): Promise<IStringKeyMap | IManageError> {
console.log(param) console.log(param)
return {} return {}
} }
async getBucketDomain ( async getBucketDomain(param: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
param: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -225,9 +262,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async createBucket ( async createBucket(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -246,44 +281,32 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async deleteBucket ( async deleteBucket(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
console.log(param) console.log(param)
return false return false
} }
async getOperatorList ( async getOperatorList(param?: IStringKeyMap): Promise<string[] | IManageError> {
param?: IStringKeyMap
): Promise<string[] | IManageError> {
console.log(param) console.log(param)
return [] return []
} }
async addOperator ( async addOperator(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
console.log(param) console.log(param)
return false return false
} }
async deleteOperator ( async deleteOperator(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
console.log(param) console.log(param)
return false return false
} }
async getBucketAclPolicy ( async getBucketAclPolicy(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
console.log(param) console.log(param)
return {} return {}
} }
async setBucketAclPolicy ( async setBucketAclPolicy(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'qiniu': case 'qiniu':
@@ -299,9 +322,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async getBucketListRecursively ( async getBucketListRecursively(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
let client let client
let window let window
const defaultResult = { const defaultResult = {
@@ -344,9 +365,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
* @param param * @param param
* @returns * @returns
*/ */
async getBucketListBackstage ( async getBucketListBackstage(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
let client let client
let window let window
const defaultResult = { const defaultResult = {
@@ -393,9 +412,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
* isDir: 是否是文件夹 * isDir: 是否是文件夹
* fileSize: 文件大小 * fileSize: 文件大小
**/ **/
async getBucketFileList ( async getBucketFileList(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
const defaultResponse = { const defaultResponse = {
fullList: <any>[], fullList: <any>[],
isTruncated: false, isTruncated: false,
@@ -422,9 +439,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async deleteBucketFile ( async deleteBucketFile(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -451,9 +466,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async deleteBucketFolder ( async deleteBucketFolder(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -477,9 +490,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async renameBucketFile ( async renameBucketFile(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -502,9 +513,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async downloadBucketFile ( async downloadBucketFile(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -531,16 +540,12 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async copyMoveBucketFile ( async copyMoveBucketFile(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
console.log(param) console.log(param)
return false return false
} }
async createBucketFolder ( async createBucketFolder(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -564,9 +569,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async uploadBucketFile ( async uploadBucketFile(param?: IStringKeyMap): Promise<boolean> {
param?: IStringKeyMap
): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -592,9 +595,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async getPreSignedUrl ( async getPreSignedUrl(param?: IStringKeyMap): Promise<string> {
param?: IStringKeyMap
): Promise<string> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
+18 -19
View File
@@ -18,17 +18,12 @@ import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, downloadTaskSpecialStatus, uploadTaskSpecialStatus } from '#/types/enum' import { commonTaskStatus, downloadTaskSpecialStatus, uploadTaskSpecialStatus } from '#/types/enum'
import { formatHttpProxy } from '#/utils/common' import { formatHttpProxy } from '#/utils/common'
export const getFSFile = async ( export const getFSFile = async (filePath: string, stream: boolean = false): Promise<IStringKeyMap> => {
filePath: string,
stream: boolean = false
): Promise<IStringKeyMap> => {
try { try {
return { return {
extension: path.extname(filePath), extension: path.extname(filePath),
fileName: path.basename(filePath), fileName: path.basename(filePath),
buffer: stream buffer: stream ? fs.createReadStream(filePath) : await fs.readFile(filePath),
? fs.createReadStream(filePath)
: await fs.readFile(filePath),
success: true success: true
} }
} catch (e) { } catch (e) {
@@ -39,9 +34,7 @@ export const getFSFile = async (
} }
export function isInputConfigValid(config: any): boolean { export function isInputConfigValid(config: any): boolean {
return typeof config === 'object' && return typeof config === 'object' && !Array.isArray(config) && Object.keys(config).length > 0
!Array.isArray(config) &&
Object.keys(config).length > 0
} }
export const getFileMimeType = (filePath: string): string => mime.lookup(filePath) || 'application/octet-stream' export const getFileMimeType = (filePath: string): string => mime.lookup(filePath) || 'application/octet-stream'
@@ -83,7 +76,8 @@ export const clearTempFolder = () => fs.emptyDirSync(getTempDirPath())
export const md5 = (str: string, code: 'hex' | 'base64'): string => crypto.createHash('md5').update(str).digest(code) export const md5 = (str: string, code: 'hex' | 'base64'): string => crypto.createHash('md5').update(str).digest(code)
export const hmacSha1Base64 = (secretKey: string, stringToSign: string) : string => crypto.createHmac('sha1', secretKey).update(Buffer.from(stringToSign, 'utf8')).digest('base64') export const hmacSha1Base64 = (secretKey: string, stringToSign: string): string =>
crypto.createHmac('sha1', secretKey).update(Buffer.from(stringToSign, 'utf8')).digest('base64')
export const NewDownloader = async ( export const NewDownloader = async (
instance: UpDownTaskQueue, instance: UpDownTaskQueue,
@@ -150,9 +144,7 @@ export const gotUpload = async (
throwHttpErrors: boolean = false, throwHttpErrors: boolean = false,
agent: any = {} agent: any = {}
) => { ) => {
got( got(url, {
url,
{
headers, headers,
method, method,
body, body,
@@ -161,8 +153,7 @@ export const gotUpload = async (
}, },
throwHttpErrors, throwHttpErrors,
agent agent
} })
)
.on('uploadProgress', (progress: any) => { .on('uploadProgress', (progress: any) => {
instance.updateUploadTask({ instance.updateUploadTask({
id, id,
@@ -174,7 +165,10 @@ export const gotUpload = async (
instance.updateUploadTask({ instance.updateUploadTask({
id, id,
progress: res?.statusCode === 200 || res?.statusCode === 201 ? 100 : 0, progress: res?.statusCode === 200 || res?.statusCode === 201 ? 100 : 0,
status: res?.statusCode === 200 || res?.statusCode === 201 ? uploadTaskSpecialStatus.uploaded : commonTaskStatus.failed, status:
res?.statusCode === 200 || res?.statusCode === 201
? uploadTaskSpecialStatus.uploaded
: commonTaskStatus.failed,
finishTime: new Date().toLocaleString() finishTime: new Date().toLocaleString()
}) })
}) })
@@ -220,7 +214,10 @@ const commonOptions = {
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined scheduling: 'lifo' as 'lifo' | 'fifo' | undefined
} as any } as any
export const getAgent = (proxy:any, https: boolean = true): { export const getAgent = (
proxy: any,
https: boolean = true
): {
https?: HttpsProxyAgent https?: HttpsProxyAgent
http?: HttpProxyAgent http?: HttpProxyAgent
} => { } => {
@@ -298,7 +295,9 @@ export function getOptions (
...(body && { body }), ...(body && { body }),
...(responseType && { responseType }), ...(responseType && { responseType }),
...(timeout !== undefined ? { timeout: { request: timeout } } : { timeout: { request: 30000 } }), ...(timeout !== undefined ? { timeout: { request: timeout } } : { timeout: { request: 30000 } }),
...(proxy && { agent: Object.fromEntries(Object.entries(getAgent(proxy)).filter(([, v]) => v !== undefined)) }), ...(proxy && {
agent: Object.fromEntries(Object.entries(getAgent(proxy)).filter(([, v]) => v !== undefined))
}),
throwHttpErrors: false throwHttpErrors: false
} }
} }
+14 -5
View File
@@ -18,7 +18,10 @@ export async function dogecloudApi (
secretKey: string secretKey: string
) { ) {
const body = jsonMode ? JSON.stringify(data) : querystring.encode(data) const body = jsonMode ? JSON.stringify(data) : querystring.encode(data)
const sign = crypto.createHmac('sha1', secretKey).update(Buffer.from(apiPath + '\n' + body, 'utf8')).digest('hex') const sign = crypto
.createHmac('sha1', secretKey)
.update(Buffer.from(apiPath + '\n' + body, 'utf8'))
.digest('hex')
const authorization = `TOKEN ${accessKey}:${sign}` const authorization = `TOKEN ${accessKey}:${sign}`
try { try {
const res = await axios.request({ const res = await axios.request({
@@ -40,16 +43,22 @@ export async function dogecloudApi (
} }
} }
export async function getTempToken (accessKey: string, secretKey: string): Promise<{} | DogecloudToken> { export async function getTempToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudToken> {
const dogeTempToken = await picgo.getConfig('Credentials.doge-token') || {} as any const dogeTempToken = (await picgo.getConfig('Credentials.doge-token')) || ({} as any)
if (dogeTempToken.token && dogeTempToken.expires > Date.now() + 7200000) { if (dogeTempToken.token && dogeTempToken.expires > Date.now() + 7200000) {
return dogeTempToken.token return dogeTempToken.token
} }
try { try {
const data = await dogecloudApi('/auth/tmp_token.json', { const data = await dogecloudApi(
'/auth/tmp_token.json',
{
channel: 'OSS_FULL', channel: 'OSS_FULL',
scopes: ['*'] scopes: ['*']
}, true, accessKey, secretKey) },
true,
accessKey,
secretKey
)
const token = data.Credentials const token = data.Credentials
picgo.saveConfig({ picgo.saveConfig({
Credentials: { Credentials: {
+4 -17
View File
@@ -27,9 +27,7 @@ export class ManageLogger implements ILogger {
} }
#handleLog(type: ILogType, ...msg: ILogArgvTypeWithError[]): void { #handleLog(type: ILogType, ...msg: ILogArgvTypeWithError[]): void {
const logHeader = chalk[this.#level[type] as ILogColor]( const logHeader = chalk[this.#level[type] as ILogColor](`[PicList ${type.toUpperCase()}]`)
`[PicList ${type.toUpperCase()}]`
)
console.log(logHeader, ...msg) console.log(logHeader, ...msg)
this.#logLevel = this.#ctx.getConfig(configPaths.settings.logLevel) this.#logLevel = this.#ctx.getConfig(configPaths.settings.logLevel)
this.#logPath = this.#logPath =
@@ -61,11 +59,7 @@ export class ManageLogger implements ILogger {
if (fs.existsSync(logPath)) { if (fs.existsSync(logPath)) {
const logFileSize = fs.statSync(logPath).size const logFileSize = fs.statSync(logPath).size
const logFileSizeLimit = const logFileSizeLimit =
enforceNumber( enforceNumber(this.#ctx.getConfig<Undefinable<number>>(configPaths.settings.logFileSizeLimit) || 10) *
this.#ctx.getConfig<Undefinable<number>>(
configPaths.settings.logFileSizeLimit
) || 10
) *
1024 * 1024 *
1024 1024
return { return {
@@ -87,11 +81,7 @@ export class ManageLogger implements ILogger {
} }
} }
#handleWriteLog ( #handleWriteLog(logPath: string, type: string, ...msg: ILogArgvTypeWithError[]): void {
logPath: string,
type: string,
...msg: ILogArgvTypeWithError[]
): void {
try { try {
if (this.#checkLogLevel(type, this.#logLevel)) { if (this.#checkLogLevel(type, this.#logLevel)) {
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicList ${type.toUpperCase()}] ` let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicList ${type.toUpperCase()}] `
@@ -122,10 +112,7 @@ export class ManageLogger implements ILogger {
return result return result
} }
#checkLogLevel ( #checkLogLevel(type: string, level: undefined | string | string[]): boolean {
type: string,
level: undefined | string | string[]
): boolean {
if (level === undefined || level === 'all') { if (level === undefined || level === 'all') {
return true return true
} }
+6 -11
View File
@@ -9,10 +9,7 @@ import picgo from '@core/picgo'
import logger from '@core/picgo/logger' import logger from '@core/picgo/logger'
import routers from '~/server/routerManager' import routers from '~/server/routerManager'
import { import { handleResponse, ensureHTTPLink } from '~/server/utils'
handleResponse,
ensureHTTPLink
} from '~/server/utils'
import { configPaths } from '#/utils/configPaths' import { configPaths } from '#/utils/configPaths'
@@ -31,9 +28,7 @@ const multerStorage = multer.diskStorage({
filename: function (_req: any, file: { originalname: any }, cb: (arg0: null, arg1: any) => void) { filename: function (_req: any, file: { originalname: any }, cb: (arg0: null, arg1: any) => void) {
// eslint-disable-next-line no-control-regex // eslint-disable-next-line no-control-regex
if (!/[^\u0000-\u00ff]/.test(file.originalname)) { if (!/[^\u0000-\u00ff]/.test(file.originalname)) {
file.originalname = Buffer.from(file.originalname, 'latin1').toString( file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
'utf8'
)
} }
cb(null, file.originalname) cb(null, file.originalname)
} }
@@ -63,7 +58,7 @@ class Server {
} }
#isValidConfig(config: IObj | undefined) { #isValidConfig(config: IObj | undefined) {
return config && config.port && config.host && (config.enable !== undefined) return config && config.port && config.host && config.enable !== undefined
} }
#handleRequest = (request: http.IncomingMessage, response: http.ServerResponse) => { #handleRequest = (request: http.IncomingMessage, response: http.ServerResponse) => {
@@ -108,7 +103,7 @@ class Server {
} }
} }
if (request.headers['content-type'] && request.headers['content-type'].startsWith('multipart/form-data')) { if (request.headers['content-type'] && request.headers['content-type'].startsWith('multipart/form-data')) {
// @ts-ignore // @ts-expect-error since the multer type is not correct
uploadMulter.any()(request, response, (err: any) => { uploadMulter.any()(request, response, (err: any) => {
if (err) { if (err) {
logger.info('[PicList Server]', err) logger.info('[PicList Server]', err)
@@ -120,7 +115,7 @@ class Server {
} }
}) })
} }
// @ts-ignore // @ts-expect-error since the multer type is not correct
const list = request.files.map(file => file.path) const list = request.files.map(file => file.path)
logger.info('[PicList Server] get a formData request') logger.info('[PicList Server] get a formData request')
const handler = routers.getHandler(url!, 'POST')?.handler const handler = routers.getHandler(url!, 'POST')?.handler
@@ -140,7 +135,7 @@ class Server {
}) })
request.on('end', () => { request.on('end', () => {
try { try {
postObj = (body === '') ? {} : JSON.parse(body) postObj = body === '' ? {} : JSON.parse(body)
} catch (err: any) { } catch (err: any) {
logger.error('[PicList Server]', err) logger.error('[PicList Server]', err)
return handleResponse({ return handleResponse({
+1 -1
View File
@@ -1,7 +1,7 @@
type HttpMethod = 'GET' | 'POST' type HttpMethod = 'GET' | 'POST'
class Router { class Router {
#router = new Map<string, Map<HttpMethod, {handler: routeHandler, urlparams?: URLSearchParams}>>() #router = new Map<string, Map<HttpMethod, { handler: routeHandler; urlparams?: URLSearchParams }>>()
#addRoute(method: HttpMethod, url: string, callback: routeHandler, urlparams?: URLSearchParams): void { #addRoute(method: HttpMethod, url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
if (!this.#router.has(url)) { if (!this.#router.has(url)) {
+16 -23
View File
@@ -16,10 +16,7 @@ import windowManager from 'apis/app/window/windowManager'
import { markdownContent } from '~/server/apiDoc' import { markdownContent } from '~/server/apiDoc'
import router from '~/server/router' import router from '~/server/router'
import { import { deleteChoosedFiles, handleResponse } from '~/server/utils'
deleteChoosedFiles,
handleResponse
} from '~/server/utils'
import { configPaths } from '#/utils/configPaths' import { configPaths } from '#/utils/configPaths'
@@ -42,13 +39,15 @@ async function responseForGet ({ response } : {response: http.ServerResponse}) {
router.get('/', responseForGet) router.get('/', responseForGet)
router.get('/upload', responseForGet) router.get('/upload', responseForGet)
router.post('/upload', async ({ router.post(
'/upload',
async ({
response, response,
list = [], list = [],
urlparams urlparams
}: { }: {
response: IHttpResponse, response: IHttpResponse
list?: string[], list?: string[]
urlparams?: URLSearchParams urlparams?: URLSearchParams
}): Promise<void> => { }): Promise<void> => {
try { try {
@@ -70,9 +69,9 @@ router.post('/upload', async ({
let currentPicBedConfigId = '' let currentPicBedConfigId = ''
let needRestore = false let needRestore = false
if (picbed) { if (picbed) {
const currentPicBed = picgo.getConfig<IStringKeyMap>('picBed') || {} as IStringKeyMap const currentPicBed = picgo.getConfig<IStringKeyMap>('picBed') || ({} as IStringKeyMap)
currentPicBedType = currentPicBed.current || '' currentPicBedType = currentPicBed.current || ''
currentPicBedConfig = currentPicBed[currentPicBedType] || {} as IStringKeyMap currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
currentPicBedConfigId = currentPicBedConfig._id currentPicBedConfigId = currentPicBedConfig._id
const configName = urlparams?.get('configName') || currentPicBed[picbed]?._configName const configName = urlparams?.get('configName') || currentPicBed[picbed]?._configName
if (picbed === currentPicBedType && configName === currentPicBedConfig._configName) { if (picbed === currentPicBedType && configName === currentPicBedConfig._configName) {
@@ -176,15 +175,12 @@ router.post('/upload', async ({
} }
}) })
} }
}) }
)
router.post('/delete', async ({ router.post(
response, '/delete',
list = [] async ({ response, list = [] }: { response: IHttpResponse; list?: IStringKeyMap[] }): Promise<void> => {
} : {
response: IHttpResponse,
list?: IStringKeyMap[]
}): Promise<void> => {
if (list.length === 0) { if (list.length === 0) {
handleResponse({ handleResponse({
response, response,
@@ -221,13 +217,10 @@ router.post('/delete', async ({
} }
}) })
} }
}) }
)
router.any('/heartbeat', async ({ router.any('/heartbeat', async ({ response }: { response: IHttpResponse }) => {
response
} : {
response: IHttpResponse,
}) => {
handleResponse({ handleResponse({
response, response,
body: { body: {
+6 -13
View File
@@ -1,6 +1,4 @@
import { import { Notification } from 'electron'
Notification
} from 'electron'
import picgo from '@core/picgo' import picgo from '@core/picgo'
import logger from '@core/picgo/logger' import logger from '@core/picgo/logger'
@@ -29,9 +27,9 @@ export const handleResponse = ({
success: false success: false
} }
}: { }: {
response: IHttpResponse, response: IHttpResponse
statusCode?: number, statusCode?: number
header?: IObj, header?: IObj
body?: any body?: any
}) => { }) => {
if (body?.success === false) { if (body?.success === false) {
@@ -43,9 +41,7 @@ export const handleResponse = ({
} }
export const ensureHTTPLink = (url: string): string => { export const ensureHTTPLink = (url: string): string => {
return url.startsWith('http') return url.startsWith('http') ? url : `http://${url}`
? url
: `http://${url}`
} }
export const deleteChoosedFiles = async (list: ImgInfo[]): Promise<boolean[]> => { export const deleteChoosedFiles = async (list: ImgInfo[]): Promise<boolean[]> => {
@@ -61,10 +57,7 @@ export const deleteChoosedFiles = async (list: ImgInfo[]): Promise<boolean[]> =>
const noteFunc = (value: boolean) => { const noteFunc = (value: boolean) => {
const notification = new Notification({ const notification = new Notification({
title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'), title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'),
body: T(value body: T(value ? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED' : 'GALLERY_SYNC_DELETE_NOTICE_FAILED')
? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED'
: 'GALLERY_SYNC_DELETE_NOTICE_FAILED'
)
}) })
notification.show() notification.show()
} }
+8 -4
View File
@@ -83,10 +83,14 @@ class WebServer {
this.#server this.#server
.listen( .listen(
this.#config.webServerPort === 36699 ? 37777 : this.#config.webServerPort, this.#config.webServerPort === 36699 ? 37777 : this.#config.webServerPort,
this.#config.webServerHost, () => { this.#config.webServerHost,
logger.info(`Web server is running at http://${this.#config.webServerHost}:${this.#config.webServerPort}, root path is ${this.#config.webServerPath}`) () => {
}) logger.info(
.on('error', (err) => { `Web server is running at http://${this.#config.webServerHost}:${this.#config.webServerPort}, root path is ${this.#config.webServerPath}`
)
}
)
.on('error', err => {
logger.error(err) logger.error(err)
}) })
} else { } else {
+2 -11
View File
@@ -18,10 +18,7 @@ function beforeOpen () {
resolveOtherI18nFiles() resolveOtherI18nFiles()
} }
function copyFileOutsideOfElectronAsar ( function copyFileOutsideOfElectronAsar(sourceInAsarArchive: string, destOutsideAsarArchive: string) {
sourceInAsarArchive: string,
destOutsideAsarArchive: string
) {
if (fs.existsSync(sourceInAsarArchive)) { if (fs.existsSync(sourceInAsarArchive)) {
// file will be copied // file will be copied
if (fs.statSync(sourceInAsarArchive).isFile()) { if (fs.statSync(sourceInAsarArchive).isFile()) {
@@ -85,13 +82,7 @@ function resolveClipboardImageGenerator () {
} }
function getClipboardFiles() { function getClipboardFiles() {
const files = [ const files = ['/linux.sh', '/mac.applescript', '/windows.ps1', '/windows10.ps1', '/wsl.sh']
'/linux.sh',
'/mac.applescript',
'/windows.ps1',
'/windows10.ps1',
'/wsl.sh'
]
return files.map(item => { return files.map(item => {
return { return {
+21 -10
View File
@@ -12,7 +12,9 @@ import { configPaths } from '#/utils/configPaths'
export let tray: Tray export let tray: Tray
export const setTray = (t: Tray) => { tray = t } export const setTray = (t: Tray) => {
tray = t
}
export const getTray = () => tray export const getTray = () => tray
@@ -32,13 +34,15 @@ export const handleCopyUrl = (str: string): void => {
* show notification * show notification
* @param options * @param options
*/ */
export const showNotification = (options: IPrivateShowNotificationOption = { export const showNotification = (
options: IPrivateShowNotificationOption = {
title: '', title: '',
body: '', body: '',
clickToCopy: false, clickToCopy: false,
copyContent: '', copyContent: '',
clickFn: () => {} clickFn: () => {}
}) => { }
) => {
const notification = new Notification({ const notification = new Notification({
title: options.title, title: options.title,
body: options.body body: options.body
@@ -60,10 +64,8 @@ export const showNotification = (options: IPrivateShowNotificationOption = {
} }
export const showMessageBox = (options: any) => { export const showMessageBox = (options: any) => {
return new Promise<IShowMessageBoxResult>(async (resolve) => { return new Promise<IShowMessageBoxResult>(async resolve => {
dialog.showMessageBox( dialog.showMessageBox(options).then(res => {
options
).then((res) => {
resolve({ resolve({
result: res.response, result: res.response,
checkboxChecked: res.checkboxChecked checkboxChecked: res.checkboxChecked
@@ -118,14 +120,18 @@ export const getClipboardFilePath = (): string => {
} }
if (img.isEmpty() && platform === 'win32') { if (img.isEmpty() && platform === 'win32') {
const imgPath = clipboard.readBuffer('FileNameW')?.toString('ucs2')?.replace(RegExp(String.fromCharCode(0), 'g'), '') const imgPath = clipboard
.readBuffer('FileNameW')
?.toString('ucs2')
?.replace(RegExp(String.fromCharCode(0), 'g'), '')
return imgPath || '' return imgPath || ''
} }
return '' return ''
} }
export const handleUrlEncodeWithSetting = (url: string) => db.get(configPaths.settings.encodeOutputURL) ? handleUrlEncode(url) : url export const handleUrlEncodeWithSetting = (url: string) =>
db.get(configPaths.settings.encodeOutputURL) ? handleUrlEncode(url) : url
const c1nApi = 'https://c1n.cn/link/short' const c1nApi = 'https://c1n.cn/link/short'
@@ -163,7 +169,12 @@ const generateYOURLSShortUrl = async (url: string) => {
if (!/^https?:\/\//.test(domain)) { if (!/^https?:\/\//.test(domain)) {
domain = `http://${domain}` domain = `http://${domain}`
} }
const params = new URLSearchParams({ signature, action: 'shorturl', format: 'json', url }) const params = new URLSearchParams({
signature,
action: 'shorturl',
format: 'json',
url
})
try { try {
const res = await axios.get(`${domain}/yourls-api.php?${params.toString()}`) const res = await axios.get(`${domain}/yourls-api.php?${params.toString()}`)
if (res.data?.shorturl) { if (res.data?.shorturl) {
+27 -9
View File
@@ -40,7 +40,10 @@ async function dogecloudApi (
secretKey: string secretKey: string
) { ) {
const body = jsonMode ? JSON.stringify(data) : querystring.encode(data) const body = jsonMode ? JSON.stringify(data) : querystring.encode(data)
const sign = crypto.createHmac('sha1', secretKey).update(Buffer.from(apiPath + '\n' + body, 'utf8')).digest('hex') const sign = crypto
.createHmac('sha1', secretKey)
.update(Buffer.from(apiPath + '\n' + body, 'utf8'))
.digest('hex')
const authorization = `TOKEN ${accessKey}:${sign}` const authorization = `TOKEN ${accessKey}:${sign}`
try { try {
const res = await axios.request({ const res = await axios.request({
@@ -62,12 +65,18 @@ async function dogecloudApi (
} }
} }
async function getDogeToken (accessKey: string, secretKey: string): Promise<{} | DogecloudTokenFull> { async function getDogeToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudTokenFull> {
try { try {
const data = await dogecloudApi('/auth/tmp_token.json', { const data = await dogecloudApi(
'/auth/tmp_token.json',
{
channel: 'OSS_FULL', channel: 'OSS_FULL',
scopes: ['*'] scopes: ['*']
}, true, accessKey, secretKey) },
true,
accessKey,
secretKey
)
return data return data
} catch (err: any) { } catch (err: any) {
console.log(err) console.log(err)
@@ -77,8 +86,15 @@ async function getDogeToken (accessKey: string, secretKey: string): Promise<{} |
export async function removeFileFromS3InMain(configMap: IStringKeyMap, dogeMode: boolean = false) { export async function removeFileFromS3InMain(configMap: IStringKeyMap, dogeMode: boolean = false) {
try { try {
const { url: rawUrl, type, config: { accessKeyID, secretAccessKey, bucketName, endpoint, pathStyleAccess, rejectUnauthorized, proxy } } = configMap const {
let { imgUrl, config: { region } } = configMap url: rawUrl,
type,
config: { accessKeyID, secretAccessKey, bucketName, endpoint, pathStyleAccess, rejectUnauthorized, proxy }
} = configMap
let {
imgUrl,
config: { region }
} = configMap
if (type === 'aws-s3' || type === 'aws-s3-plist') { if (type === 'aws-s3' || type === 'aws-s3-plist') {
imgUrl = rawUrl || imgUrl || '' imgUrl = rawUrl || imgUrl || ''
} }
@@ -164,8 +180,10 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
export async function removeFileFromDogeInMain(configMap: IStringKeyMap) { export async function removeFileFromDogeInMain(configMap: IStringKeyMap) {
try { try {
const { config: { bucketName, AccessKey, SecretKey } } = configMap const {
const token = await getDogeToken(AccessKey, SecretKey) as DogecloudTokenFull config: { bucketName, AccessKey, SecretKey }
} = configMap
const token = (await getDogeToken(AccessKey, SecretKey)) as DogecloudTokenFull
const bucket = token.Buckets?.find(item => item.name === bucketName || item.s3Bucket === bucketName) const bucket = token.Buckets?.find(item => item.name === bucketName || item.s3Bucket === bucketName)
const newConfigMap = Object.assign({}, configMap) const newConfigMap = Object.assign({}, configMap)
newConfigMap.config = { newConfigMap.config = {
@@ -227,7 +245,7 @@ export async function removeFileFromSFTPInMain (config: ISftpPlistConfig, fileNa
try { try {
const client = SSHClient.instance const client = SSHClient.instance
await client.connect(config) await client.connect(config)
const uploadPath = `/${(config.uploadPath || '')}/`.replace(/\/+/g, '/') const uploadPath = `/${config.uploadPath || ''}/`.replace(/\/+/g, '/')
const remote = path.join(uploadPath, fileName) const remote = path.join(uploadPath, fileName)
const deleteResult = await client.deleteFileSFTP(config, remote) const deleteResult = await client.deleteFileSFTP(config, remote)
client.close() client.close()
+2 -5
View File
@@ -9,11 +9,8 @@ export const isMacOS = process.platform === 'darwin'
let version: string | undefined let version: string | undefined
const clean = (version: string) => version.split('.').length === 1 const clean = (version: string) =>
? `${version}.0.0` version.split('.').length === 1 ? `${version}.0.0` : version.split('.').length === 2 ? `${version}.0` : version
: version.split('.').length === 2
? `${version}.0`
: version
const parseVersion = (plist: string) => { const parseVersion = (plist: string) => {
const matches = /<key>ProductVersion<\/key>\s*<string>([\d.]+)<\/string>/.exec(plist) const matches = /<key>ProductVersion<\/key>\s*<string>([\d.]+)<\/string>/.exec(plist)
+4 -2
View File
@@ -4,14 +4,16 @@ import { configPaths } from '#/utils/configPaths'
const getPicBeds = () => { const getPicBeds = () => {
const picBedTypes = picgo.helper.uploader.getIdList() const picBedTypes = picgo.helper.uploader.getIdList()
const picBedFromDB = picgo.getConfig<IPicBedType[]>(configPaths.picBed.list) || [] const picBedFromDB = picgo.getConfig<IPicBedType[]>(configPaths.picBed.list) || []
const picBeds = picBedTypes.map((item: string) => { const picBeds = picBedTypes
.map((item: string) => {
const visible = picBedFromDB.find((i: IPicBedType) => i.type === item) // object or undefined const visible = picBedFromDB.find((i: IPicBedType) => i.type === item) // object or undefined
return { return {
type: item, type: item,
name: picgo.helper.uploader.get(item)!.name || item, name: picgo.helper.uploader.get(item)!.name || item,
visible: visible ? visible.visible : true visible: visible ? visible.visible : true
} }
}).sort((a) => { })
.sort(a => {
if (a.type === 'tcyun') { if (a.type === 'tcyun') {
return -1 return -1
} }
+5 -5
View File
@@ -36,7 +36,8 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd(), logger: Logger
if (fileList?.length === 0) { if (fileList?.length === 0) {
return null // for uploading images in clipboard return null // for uploading images in clipboard
} else if ((fileList?.length || 0) > 0) { } else if ((fileList?.length || 0) > 0) {
const result = fileList!.map(item => { const result = fileList!
.map(item => {
if (isUrl(item)) { if (isUrl(item)) {
return { return {
path: item path: item
@@ -57,13 +58,12 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd(), logger: Logger
return null return null
} }
} }
}).filter(item => item !== null) as Result })
.filter(item => item !== null) as Result
return result return result
} }
} }
return [] return []
} }
export { export { getUploadFiles }
getUploadFiles
}
+11 -4
View File
@@ -13,6 +13,7 @@ export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IP
config[i].default = config[i].default() config[i].default = config[i].default()
} }
if (typeof config[i].choices === 'function') { if (typeof config[i].choices === 'function') {
// eslint-disable-next-line @typescript-eslint/ban-types
config[i].choices = (config[i].choices as Function)() config[i].choices = (config[i].choices as Function)()
} }
} }
@@ -20,13 +21,17 @@ export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IP
} }
export const completeUploaderMetaConfig = (originData: IStringKeyMap): IUploaderConfigListItem => { export const completeUploaderMetaConfig = (originData: IStringKeyMap): IUploaderConfigListItem => {
return Object.assign({ return Object.assign(
{
_configName: 'Default' _configName: 'Default'
}, trimValues(originData), { },
trimValues(originData),
{
_id: uuid(), _id: uuid(),
_createdAt: Date.now(), _createdAt: Date.now(),
_updatedAt: Date.now() _updatedAt: Date.now()
}) }
)
} }
/** /**
@@ -130,7 +135,9 @@ export const deleteUploaderConfig = (type: string, id: string): IUploaderConfigI
/** /**
* upgrade old uploader config to new format * upgrade old uploader config to new format
*/ */
export const upgradeUploaderConfig = (type: string): { export const upgradeUploaderConfig = (
type: string
): {
configList: IStringKeyMap[] configList: IStringKeyMap[]
defaultId: string defaultId: string
} => { } => {
+36 -14
View File
@@ -1,4 +1,3 @@
// @ts-nocheck
import fs from 'fs-extra' import fs from 'fs-extra'
import { NodeSSH, Config, SSHExecCommandResponse } from 'node-ssh-no-cpu-features' import { NodeSSH, Config, SSHExecCommandResponse } from 'node-ssh-no-cpu-features'
import path from 'path' import path from 'path'
@@ -26,7 +25,11 @@ class SSHClient {
async connect(config: ISftpPlistConfig): Promise<boolean> { async connect(config: ISftpPlistConfig): Promise<boolean> {
const { username, password, privateKey, passphrase } = config const { username, password, privateKey, passphrase } = config
const loginInfo: Config = privateKey const loginInfo: Config = privateKey
? { username, privateKeyPath: privateKey, passphrase: passphrase || undefined } ? {
username,
privateKeyPath: privateKey,
passphrase: passphrase || undefined
}
: { username, password } : { username, password }
try { try {
await SSHClient.client.connect({ await SSHClient.client.connect({
@@ -46,30 +49,42 @@ class SSHClient {
const client = new Client() const client = new Client()
const { username, password, privateKey, passphrase } = config const { username, password, privateKey, passphrase } = config
const loginInfo: Config = privateKey const loginInfo: Config = privateKey
? { username, privateKey: fs.readFileSync(privateKey), passphrase: passphrase || undefined } ? {
username,
privateKey: fs.readFileSync(privateKey),
passphrase: passphrase || undefined
}
: { username, password } : { username, password }
remote = this.changeWinStylePathToUnix(remote) remote = this.changeWinStylePathToUnix(remote)
if (remote === '/' || remote.includes('*')) return false if (remote === '/' || remote.includes('*')) return false
const promise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
client.on('ready', () => { client
client.sftp((err, sftp) => { .on('ready', () => {
client.sftp(
(
err: any,
sftp: {
unlink: (arg0: string, arg1: (err: any) => void) => void
}
) => {
// eslint-disable-next-line prefer-promise-reject-errors // eslint-disable-next-line prefer-promise-reject-errors
if (err) reject(false) if (err) reject(false)
sftp.unlink(remote, (err) => { sftp.unlink(remote, (err: any) => {
// eslint-disable-next-line prefer-promise-reject-errors // eslint-disable-next-line prefer-promise-reject-errors
if (err) reject(false) if (err) reject(false)
client.end() client.end()
resolve(true) resolve(true)
}) })
}
)
}) })
}).connect({ .connect({
host: config.host, host: config.host,
port: Number(config.port) || 22, port: Number(config.port) || 22,
...loginInfo ...loginInfo
}) })
} })
) return (await promise) as boolean
return await promise
} catch (err: any) { } catch (err: any) {
console.log(err) console.log(err)
return false return false
@@ -103,10 +118,14 @@ class SSHClient {
} }
} }
async putFile (local: string, remote: string, config: { async putFile(
local: string,
remote: string,
config: {
fileMode?: string fileMode?: string
dirMode?: string dirMode?: string
} = {}): Promise<boolean> { } = {}
): Promise<boolean> {
if (!this._isConnected) { if (!this._isConnected) {
throw new Error('SSH 未连接') throw new Error('SSH 未连接')
} }
@@ -126,9 +145,12 @@ class SSHClient {
} }
} }
async mkdir (dirPath: string, config: { async mkdir(
dirPath: string,
config: {
dirMode?: string dirMode?: string
} = {}): Promise<boolean> { } = {}
): Promise<boolean> {
if (!this._isConnected) { if (!this._isConnected) {
throw new Error('SSH 未连接') throw new Error('SSH 未连接')
} }
+25 -19
View File
@@ -15,10 +15,12 @@ const STORE_PATH = app.getPath('userData')
const readFileAsBase64 = (filePath: string) => fs.readFileSync(filePath, { encoding: 'base64' }) const readFileAsBase64 = (filePath: string) => fs.readFileSync(filePath, { encoding: 'base64' })
const isHttpResSuccess = (res: any) => res.status >= 200 && res.status < 300 const isHttpResSuccess = (res: any) => res.status >= 200 && res.status < 300
const uploadOrUpdateMsg = (fileName: string, isUpdate: boolean = true) => isUpdate ? `update ${fileName} from PicList` : `upload ${fileName} from PicList` const uploadOrUpdateMsg = (fileName: string, isUpdate: boolean = true) =>
isUpdate ? `update ${fileName} from PicList` : `upload ${fileName} from PicList`
const getSyncConfig = () => { const getSyncConfig = () => {
return db.get(configPaths.settings.sync) || { return (
db.get(configPaths.settings.sync) || {
type: 'github', type: 'github',
username: '', username: '',
repo: '', repo: '',
@@ -26,6 +28,7 @@ const getSyncConfig = () => {
token: '', token: '',
proxy: '' proxy: ''
} }
)
} }
const getProxyagent = (proxy: string | undefined) => { const getProxyagent = (proxy: string | undefined) => {
@@ -50,13 +53,7 @@ function getOctokit (syncConfig: ISyncConfig) {
}) })
} }
const isSyncConfigValidate = ({ const isSyncConfigValidate = ({ type, username, repo, branch, token }: ISyncConfig) => {
type,
username,
repo,
branch,
token
}: ISyncConfig) => {
return type && username && repo && branch && token return type && username && repo && branch && token
} }
@@ -179,12 +176,16 @@ async function updateLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
} }
const data = shaRes.data as any const data = shaRes.data as any
const sha = data.sha const sha = data.sha
const res = await axios.put(apiUrl, { const res = await axios.put(
apiUrl,
{
...defaultConfig, ...defaultConfig,
sha sha
}, { },
{
headers headers
}) }
)
return isHttpResSuccess(res) return isHttpResSuccess(res)
} }
default: default:
@@ -220,7 +221,10 @@ async function uploadFile (fileName: string[]): Promise<number> {
async function downloadAndWriteFile(url: string, localFilePath: string, config: any, isWriteJson = false) { async function downloadAndWriteFile(url: string, localFilePath: string, config: any, isWriteJson = false) {
const res = await axios.get(url, config) const res = await axios.get(url, config)
if (isHttpResSuccess(res)) { if (isHttpResSuccess(res)) {
await fs.writeFile(localFilePath, isWriteJson ? JSON.stringify(res.data, null, 2) : Buffer.from(res.data.content, 'base64')) await fs.writeFile(
localFilePath,
isWriteJson ? JSON.stringify(res.data, null, 2) : Buffer.from(res.data.content, 'base64')
)
return true return true
} }
return false return false
@@ -251,9 +255,14 @@ async function downloadRemoteToLocal (syncConfig: ISyncConfig, fileName: string)
if (res.status === 200) { if (res.status === 200) {
const data = res.data as any const data = res.data as any
const downloadUrl = data.download_url const downloadUrl = data.download_url
return downloadAndWriteFile(downloadUrl, localFilePath, { return downloadAndWriteFile(
downloadUrl,
localFilePath,
{
httpsAgent: getProxyagent(proxy) httpsAgent: getProxyagent(proxy)
}, true) },
true
)
} }
return false return false
} }
@@ -294,7 +303,4 @@ async function downloadFile (fileName: string[]): Promise<number> {
return (await Promise.all(fileName.map(downloadFunc))).reduce((a, b) => a + b, 0) return (await Promise.all(fileName.map(downloadFunc))).reduce((a, b) => a + b, 0)
} }
export { export { uploadFile, downloadFile }
uploadFile,
downloadFile
}
+1 -5
View File
@@ -1,8 +1,5 @@
<template> <template>
<div <div id="app" :key="pageReloadCount">
id="app"
:key="pageReloadCount"
>
<router-view /> <router-view />
</div> </div>
</template> </template>
@@ -25,7 +22,6 @@ onBeforeMount(async () => {
store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms') store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
} }
}) })
</script> </script>
<script lang="ts"> <script lang="ts">
+1 -3
View File
@@ -9,9 +9,7 @@ interface IConfigMap {
export default class AliyunApi { export default class AliyunApi {
static #getKey(fileName: string, path?: string): string { static #getKey(fileName: string, path?: string): string {
return path && path !== '/' return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${fileName}` : fileName
? `${path.replace(/^\/+|\/+$/, '')}/${fileName}`
: fileName
} }
static async delete(configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
+1 -1
View File
@@ -9,7 +9,7 @@ export default class AwsS3Api {
static async delete(configMap: IStringKeyMap): Promise<boolean> { static async delete(configMap: IStringKeyMap): Promise<boolean> {
try { try {
return ipcRenderer return ipcRenderer
? await triggerRPC(IRPCActionType.GALLERY_DELETE_AWS_S3_FILE, getRawData(configMap)) || false ? (await triggerRPC(IRPCActionType.GALLERY_DELETE_AWS_S3_FILE, getRawData(configMap))) || false
: await removeFileFromS3InMain(getRawData(configMap)) : await removeFileFromS3InMain(getRawData(configMap))
} catch (error: any) { } catch (error: any) {
deleteFailedLog(configMap.fileName, 'AWS S3', error) deleteFailedLog(configMap.fileName, 'AWS S3', error)
+1 -1
View File
@@ -10,7 +10,7 @@ export default class AwsS3Api {
static async delete(configMap: IStringKeyMap): Promise<boolean> { static async delete(configMap: IStringKeyMap): Promise<boolean> {
try { try {
return ipcRenderer return ipcRenderer
? await triggerRPC(IRPCActionType.GALLERY_DELETE_DOGE_FILE, getRawData(configMap)) || false ? (await triggerRPC(IRPCActionType.GALLERY_DELETE_DOGE_FILE, getRawData(configMap))) || false
: await removeFileFromDogeInMain(getRawData(configMap)) : await removeFileFromDogeInMain(getRawData(configMap))
} catch (error: any) { } catch (error: any) {
deleteFailedLog(configMap.fileName, 'DogeCloud', error) deleteFailedLog(configMap.fileName, 'DogeCloud', error)
+6 -4
View File
@@ -17,13 +17,15 @@ export default class GithubApi {
static #createKey(path: string | undefined, fileName: string): string { static #createKey(path: string | undefined, fileName: string): string {
const formatedFileName = fileName.replace(/%2F/g, '/') const formatedFileName = fileName.replace(/%2F/g, '/')
return path && path !== '/' return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${formatedFileName}` : formatedFileName
? `${path.replace(/^\/+|\/+$/, '')}/${formatedFileName}`
: formatedFileName
} }
static async delete(configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { fileName, hash, config: { repo, token, branch, path } } = configMap const {
fileName,
hash,
config: { repo, token, branch, path }
} = configMap
const [owner, repoName] = repo.split('/') const [owner, repoName] = repo.split('/')
const octokit = GithubApi.#createOctokit(token) const octokit = GithubApi.#createOctokit(token)
const key = GithubApi.#createKey(path, fileName) const key = GithubApi.#createKey(path, fileName)
+1 -1
View File
@@ -10,7 +10,7 @@ export default class HuaweicloudApi {
static async delete(configMap: IStringKeyMap): Promise<boolean> { static async delete(configMap: IStringKeyMap): Promise<boolean> {
try { try {
return ipcRenderer return ipcRenderer
? await triggerRPC(IRPCActionType.GALLERY_DELETE_HUAWEI_OSS_FILE, getRawData(configMap)) || false ? (await triggerRPC(IRPCActionType.GALLERY_DELETE_HUAWEI_OSS_FILE, getRawData(configMap))) || false
: await removeFileFromHuaweiInMain(getRawData(configMap)) : await removeFileFromHuaweiInMain(getRawData(configMap))
} catch (error: any) { } catch (error: any) {
deleteFailedLog(configMap.fileName, 'HuaweiCloud', error) deleteFailedLog(configMap.fileName, 'HuaweiCloud', error)
+1 -4
View File
@@ -11,10 +11,7 @@ export default class ImgurApi {
static #baseUrl = 'https://api.imgur.com/3' static #baseUrl = 'https://api.imgur.com/3'
static async delete(configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { const { config: { clientId = '', username = '', accessToken = '' } = {}, hash = '' } = configMap
config: { clientId = '', username = '', accessToken = '' } = {},
hash = ''
} = configMap
let Authorization: string, apiUrl: string let Authorization: string, apiUrl: string
if (username && accessToken) { if (username && accessToken) {
+1 -2
View File
@@ -26,8 +26,7 @@ export default class LskyplistApi {
rejectUnauthorized: false rejectUnauthorized: false
}) })
try { try {
const response: AxiosResponse = await axios.delete( const response: AxiosResponse = await axios.delete(`${host}/api/v1/images/${hash}`, {
`${host}/api/v1/images/${hash}`, {
headers: v2Headers, headers: v2Headers,
timeout: 30000, timeout: 30000,
httpsAgent: requestAgent httpsAgent: requestAgent
+2 -5
View File
@@ -14,12 +14,9 @@ export default class PiclistApi {
const url = `http://${host || '127.0.0.1'}:${port || 36677}/delete` const url = `http://${host || '127.0.0.1'}:${port || 36677}/delete`
try { try {
const response: AxiosResponse = await axios.post( const response: AxiosResponse = await axios.post(url, {
url,
{
list: [fullResult] list: [fullResult]
} })
)
if (response.status === 200 && response.data?.success) { if (response.status === 200 && response.data?.success) {
deleteLog(fullResult, 'Piclist') deleteLog(fullResult, 'Piclist')
return true return true
+6 -3
View File
@@ -9,14 +9,17 @@ interface IConfigMap {
export default class QiniuApi { export default class QiniuApi {
static async delete(configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { fileName, config: { accessKey, secretKey, bucket, path } } = configMap const {
fileName,
config: { accessKey, secretKey, bucket, path }
} = configMap
const mac = new Qiniu.auth.digest.Mac(accessKey, secretKey) const mac = new Qiniu.auth.digest.Mac(accessKey, secretKey)
const qiniuConfig = new Qiniu.conf.Config() const qiniuConfig = new Qiniu.conf.Config()
try { try {
const bucketManager = new Qiniu.rs.BucketManager(mac, qiniuConfig) const bucketManager = new Qiniu.rs.BucketManager(mac, qiniuConfig)
const formattedPath = path?.replace(/^\/+|\/+$/, '') || '' const formattedPath = path?.replace(/^\/+|\/+$/, '') || ''
const key = path === '/' || !path ? fileName : `${formattedPath}/${fileName}` const key = path === '/' || !path ? fileName : `${formattedPath}/${fileName}`
const res = await new Promise((resolve, reject) => { const res = (await new Promise((resolve, reject) => {
bucketManager.delete(bucket, key, (err, respBody, respInfo) => { bucketManager.delete(bucket, key, (err, respBody, respInfo) => {
if (err) { if (err) {
reject(err) reject(err)
@@ -27,7 +30,7 @@ export default class QiniuApi {
}) })
} }
}) })
}) as any })) as any
if (res?.respInfo?.statusCode === 200) { if (res?.respInfo?.statusCode === 200) {
deleteLog(fileName, 'Qiniu') deleteLog(fileName, 'Qiniu')
return true return true
+1 -2
View File
@@ -11,8 +11,7 @@ export default class SftpPlistApi {
const { fileName, config } = configMap const { fileName, config } = configMap
try { try {
return ipcRenderer return ipcRenderer
? await triggerRPC(IRPCActionType.GALLERY_DELETE_SFTP_FILE, getRawData(config), ? (await triggerRPC(IRPCActionType.GALLERY_DELETE_SFTP_FILE, getRawData(config), fileName)) || false
fileName) || false
: await removeFileFromSFTPInMain(getRawData(config), fileName) : await removeFileFromSFTPInMain(getRawData(config), fileName)
} catch (error: any) { } catch (error: any) {
deleteFailedLog(fileName, 'SFTP', error) deleteFailedLog(fileName, 'SFTP', error)
+1 -2
View File
@@ -20,8 +20,7 @@ export default class SmmsApi {
const { token } = config const { token } = config
try { try {
const response: AxiosResponse = await axios.get( const response: AxiosResponse = await axios.get(`${SmmsApi.#baseUrl}/delete/${hash}`, {
`${SmmsApi.#baseUrl}/delete/${hash}`, {
headers: { headers: {
Authorization: token Authorization: token
}, },
+4 -1
View File
@@ -16,7 +16,10 @@ export default class TcyunApi {
} }
static async delete(configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { fileName, config: { secretId, secretKey, bucket, area, path } } = configMap const {
fileName,
config: { secretId, secretKey, bucket, area, path }
} = configMap
try { try {
const cos = TcyunApi.#createCOS(secretId, secretKey) const cos = TcyunApi.#createCOS(secretId, secretKey)
let key let key
+4 -1
View File
@@ -9,7 +9,10 @@ interface IConfigMap {
export default class UpyunApi { export default class UpyunApi {
static async delete(configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { fileName, config: { bucket, operator, password, path } } = configMap const {
fileName,
config: { bucket, operator, password, path }
} = configMap
try { try {
const service = new Upyun.Service(bucket, operator, password) const service = new Upyun.Service(bucket, operator, password)
const client = new Upyun.Client(service) const client = new Upyun.Client(service)
+5 -5
View File
@@ -10,7 +10,10 @@ interface IConfigMap {
export default class WebdavApi { export default class WebdavApi {
static async delete(configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { fileName, config: { host, username, password, path, sslEnabled, authType } } = configMap const {
fileName,
config: { host, username, password, path, sslEnabled, authType }
} = configMap
const endpoint = formatEndpoint(host, sslEnabled) const endpoint = formatEndpoint(host, sslEnabled)
const options: WebDAVClientOptions = { const options: WebDAVClientOptions = {
username, username,
@@ -19,10 +22,7 @@ export default class WebdavApi {
if (authType === 'digest') { if (authType === 'digest') {
options.authType = AuthType.Digest options.authType = AuthType.Digest
} }
const ctx = createClient( const ctx = createClient(endpoint, options)
endpoint,
options
)
let key let key
if (path === '/' || !path) { if (path === '/' || !path) {
key = fileName key = fileName
+23 -46
View File
@@ -1,25 +1,8 @@
<template> <template>
<div <div id="config-form" :class="props.colorMode === 'white' ? 'white' : ''">
id="config-form" <el-form ref="$form" label-position="left" label-width="50%" :model="ruleForm" size="small">
:class="props.colorMode === 'white' ? 'white' : ''" <el-form-item :label="$T('UPLOADER_CONFIG_NAME')" required prop="_configName">
> <el-input v-model="ruleForm._configName" type="input" :placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')" />
<el-form
ref="$form"
label-position="left"
label-width="50%"
:model="ruleForm"
size="small"
>
<el-form-item
:label="$T('UPLOADER_CONFIG_NAME')"
required
prop="_configName"
>
<el-input
v-model="ruleForm._configName"
type="input"
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
/>
</el-form-item> </el-form-item>
<!-- dynamic config --> <!-- dynamic config -->
<el-form-item <el-form-item
@@ -32,18 +15,9 @@
<el-row align="middle"> <el-row align="middle">
{{ item.alias || item.name }} {{ item.alias || item.name }}
<template v-if="item.tips"> <template v-if="item.tips">
<el-tooltip <el-tooltip class="item" effect="dark" placement="right" :persistent="false" teleported>
class="item"
effect="dark"
placement="right"
:persistent="false"
teleported
>
<template #content> <template #content>
<span <span class="config-form-common-tips" v-html="transformMarkdownToHTML(item.tips)" />
class="config-form-common-tips"
v-html="transformMarkdownToHTML(item.tips)"
/>
</template> </template>
<el-icon class="ml-[4px] cursor-pointer hover:text-blue"> <el-icon class="ml-[4px] cursor-pointer hover:text-blue">
<InfoFilled /> <InfoFilled />
@@ -124,19 +98,23 @@ const $form = ref<FormInstance>()
const configList = ref<IPicGoPluginConfig[]>([]) const configList = ref<IPicGoPluginConfig[]>([])
const ruleForm = reactive<IStringKeyMap>({}) const ruleForm = reactive<IStringKeyMap>({})
watch(toRefs(props.config), (val: IPicGoPluginConfig[]) => { watch(
toRefs(props.config),
(val: IPicGoPluginConfig[]) => {
handleConfigChange(val) handleConfigChange(val)
}, { },
{
deep: true, deep: true,
immediate: true immediate: true
}) }
)
function handleConfigChange(val: any) { function handleConfigChange(val: any) {
handleConfig(val) handleConfig(val)
} }
async function validate(): Promise<IStringKeyMap | false> { async function validate(): Promise<IStringKeyMap | false> {
return new Promise((resolve) => { return new Promise(resolve => {
$form.value?.validate((valid: boolean) => { $form.value?.validate((valid: boolean) => {
if (valid) { if (valid) {
resolve(ruleForm) resolve(ruleForm)
@@ -176,17 +154,16 @@ async function handleConfig (val: IPicGoPluginConfig[]) {
const configId = $route.params.configId const configId = $route.params.configId
Object.assign(ruleForm, config) Object.assign(ruleForm, config)
if (val.length > 0) { if (val.length > 0) {
configList.value = cloneDeep(val).map((item) => { configList.value = cloneDeep(val).map(item => {
if (!configId) return item if (!configId) return item
let defaultValue = item.default !== undefined let defaultValue = item.default !== undefined ? item.default : item.type === 'checkbox' ? [] : null
? item.default
: item.type === 'checkbox'
? []
: null
if (item.type === 'checkbox') { if (item.type === 'checkbox') {
const defaults = item.choices?.filter((i: any) => { const defaults =
item.choices
?.filter((i: any) => {
return i.checked return i.checked
}).map((i: any) => i.value) || [] })
.map((i: any) => i.value) || []
defaultValue = union(defaultValue, defaults) defaultValue = union(defaultValue, defaults)
} }
if (config && config[item.name] !== undefined) { if (config && config[item.name] !== undefined) {
@@ -200,7 +177,7 @@ async function handleConfig (val: IPicGoPluginConfig[]) {
async function getCurConfigFormData() { async function getCurConfigFormData() {
const configId = $route.params.configId const configId = $route.params.configId
const curTypeConfigList = await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`) || [] const curTypeConfigList = (await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`)) || []
return curTypeConfigList.find(i => i._id === configId) || {} return curTypeConfigList.find(i => i._id === configId) || {}
} }
@@ -218,7 +195,7 @@ defineExpose({
getConfigType getConfigType
}) })
</script> </script>
<style lang='stylus'> <style lang="stylus">
.config-form-common-tips .config-form-common-tips
a a
color #409EFF color #409EFF
+21 -35
View File
@@ -1,25 +1,8 @@
<template> <template>
<div <div id="config-form" :class="props.colorMode === 'white' ? 'white' : ''">
id="config-form" <el-form ref="$form" label-position="left" label-width="50%" :model="ruleForm" size="small">
:class="props.colorMode === 'white' ? 'white' : ''" <el-form-item :label="$T('UPLOADER_CONFIG_NAME')" required prop="_configName">
> <el-input v-model="ruleForm._configName" type="input" :placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')" />
<el-form
ref="$form"
label-position="left"
label-width="50%"
:model="ruleForm"
size="small"
>
<el-form-item
:label="$T('UPLOADER_CONFIG_NAME')"
required
prop="_configName"
>
<el-input
v-model="ruleForm._configName"
type="input"
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
/>
</el-form-item> </el-form-item>
<!-- dynamic config --> <!-- dynamic config -->
<el-form-item <el-form-item
@@ -98,19 +81,23 @@ const $form = ref<FormInstance>()
const configList = ref<IPicGoPluginConfig[]>([]) const configList = ref<IPicGoPluginConfig[]>([])
const ruleForm = reactive<IStringKeyMap>({}) const ruleForm = reactive<IStringKeyMap>({})
watch(() => props.config, (val: IPicGoPluginConfig[]) => { watch(
() => props.config,
(val: IPicGoPluginConfig[]) => {
handleConfigChange(val) handleConfigChange(val)
}, { },
{
deep: true, deep: true,
immediate: true immediate: true
}) }
)
function handleConfigChange(val: any) { function handleConfigChange(val: any) {
handleConfig(val) handleConfig(val)
} }
async function validate(): Promise<IStringKeyMap | false> { async function validate(): Promise<IStringKeyMap | false> {
return new Promise((resolve) => { return new Promise(resolve => {
$form.value?.validate((valid: boolean) => { $form.value?.validate((valid: boolean) => {
if (valid) { if (valid) {
resolve(ruleForm) resolve(ruleForm)
@@ -141,16 +128,15 @@ async function handleConfig (val: IPicGoPluginConfig[]) {
const config = await getCurConfigFormData() const config = await getCurConfigFormData()
Object.assign(ruleForm, config) Object.assign(ruleForm, config)
if (val.length > 0) { if (val.length > 0) {
configList.value = cloneDeep(val).map((item) => { configList.value = cloneDeep(val).map(item => {
let defaultValue = item.default !== undefined let defaultValue = item.default !== undefined ? item.default : item.type === 'checkbox' ? [] : null
? item.default
: item.type === 'checkbox'
? []
: null
if (item.type === 'checkbox') { if (item.type === 'checkbox') {
const defaults = item.choices?.filter((i: any) => { const defaults =
item.choices
?.filter((i: any) => {
return i.checked return i.checked
}).map((i: any) => i.value) || [] })
.map((i: any) => i.value) || []
defaultValue = union(defaultValue, defaults) defaultValue = union(defaultValue, defaults)
} }
if (config && config[item.name] !== undefined) { if (config && config[item.name] !== undefined) {
@@ -163,7 +149,7 @@ async function handleConfig (val: IPicGoPluginConfig[]) {
} }
async function getCurConfigFormData() { async function getCurConfigFormData() {
return await getConfig<IStringKeyMap>(`${props.id}`) || {} return (await getConfig<IStringKeyMap>(`${props.id}`)) || {}
} }
function updateRuleForm(key: string, value: any) { function updateRuleForm(key: string, value: any) {
@@ -180,7 +166,7 @@ defineExpose({
getConfigType getConfigType
}) })
</script> </script>
<style lang='stylus'> <style lang="stylus">
#config-form #config-form
.el-form .el-form
label label
+7 -5
View File
@@ -1,10 +1,12 @@
<template> <template>
<el-image <el-image
:src="isShowThumbnail && item.isImage ? :src="
base64Image isShowThumbnail && item.isImage
: require(`../manage/pages/assets/icons/${getFileIconPath(item.fileName ?? '')}`)" ? base64Image
: require(`../manage/pages/assets/icons/${getFileIconPath(item.fileName ?? '')}`)
"
fit="contain" fit="contain"
style="height: 100px;width: 100%;margin: 0 auto;" style="height: 100px; width: 100%; margin: 0 auto"
> >
<template #placeholder> <template #placeholder>
<el-icon> <el-icon>
@@ -15,7 +17,7 @@
<el-image <el-image
:src="require(`../manage/pages/assets/icons/${getFileIconPath(item.fileName ?? '')}`)" :src="require(`../manage/pages/assets/icons/${getFileIconPath(item.fileName ?? '')}`)"
fit="contain" fit="contain"
style="height: 100px;width: 100%;margin: 0 auto;" style="height: 100px; width: 100%; margin: 0 auto"
/> />
</template> </template>
</el-image> </el-image>
+71 -180
View File
@@ -6,18 +6,10 @@
size="default" size="default"
:model="waterMarkForm" :model="waterMarkForm"
> >
<el-form-item <el-form-item :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISADDWM')">
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISADDWM')" <el-switch v-model="waterMarkForm.isAddWatermark" :style="switchStyle" />
>
<el-switch
v-model="waterMarkForm.isAddWatermark"
:style="switchStyle"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-show="waterMarkForm.isAddWatermark" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_WMTYPE')">
v-show="waterMarkForm.isAddWatermark"
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_WMTYPE')"
>
<el-radio-group v-model="waterMarkForm.watermarkType"> <el-radio-group v-model="waterMarkForm.watermarkType">
<el-radio value="text"> <el-radio value="text">
{{ $T('UPLOAD_PAGE_IMAGE_PROCESS_WMTYPE_TEXT') }} {{ $T('UPLOAD_PAGE_IMAGE_PROCESS_WMTYPE_TEXT') }}
@@ -27,23 +19,11 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-show="waterMarkForm.isAddWatermark" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISFULLSCREEN_WM')">
v-show="waterMarkForm.isAddWatermark" <el-switch v-model="waterMarkForm.isFullScreenWatermark" :style="switchStyle" />
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISFULLSCREEN_WM')"
>
<el-switch
v-model="waterMarkForm.isFullScreenWatermark"
:style="switchStyle"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-show="waterMarkForm.isAddWatermark" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_WMDEGREE')">
v-show="waterMarkForm.isAddWatermark" <el-input-number v-model="waterMarkForm.watermarkDegree" :step="1" />
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_WMDEGREE')"
>
<el-input-number
v-model="waterMarkForm.watermarkDegree"
:step="1"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-show="waterMarkForm.isAddWatermark && waterMarkForm.watermarkType === 'text'" v-show="waterMarkForm.isAddWatermark && waterMarkForm.watermarkType === 'text'"
@@ -57,25 +37,14 @@
> >
<el-input v-model="waterMarkForm.watermarkFontPath" /> <el-input v-model="waterMarkForm.watermarkFontPath" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-show="waterMarkForm.isAddWatermark" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_WMRATIO')">
v-show="waterMarkForm.isAddWatermark" <el-input-number v-model="waterMarkForm.watermarkScaleRatio" :min="0" :max="1" :step="0.01" />
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_WMRATIO')"
>
<el-input-number
v-model="waterMarkForm.watermarkScaleRatio"
:min="0"
:max="1"
:step="0.01"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-show="waterMarkForm.isAddWatermark && waterMarkForm.watermarkType === 'text'" v-show="waterMarkForm.isAddWatermark && waterMarkForm.watermarkType === 'text'"
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_WMCOLOR')" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_WMCOLOR')"
> >
<el-color-picker <el-color-picker v-model="waterMarkForm.watermarkColor" show-alpha />
v-model="waterMarkForm.watermarkColor"
show-alpha
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-show="waterMarkForm.isAddWatermark && waterMarkForm.watermarkType === 'image'" v-show="waterMarkForm.isAddWatermark && waterMarkForm.watermarkType === 'image'"
@@ -83,175 +52,76 @@
> >
<el-input v-model="waterMarkForm.watermarkImagePath" /> <el-input v-model="waterMarkForm.watermarkImagePath" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-show="waterMarkForm.isAddWatermark" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_WMPOSITION')">
v-show="waterMarkForm.isAddWatermark" <el-radio-group v-model="waterMarkForm.watermarkPosition">
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_WMPOSITION')" <el-radio v-for="item in waterMarkPositionMap" :key="item[0]" :value="item[0]">
>
<el-radio-group
v-model="waterMarkForm.watermarkPosition"
>
<el-radio
v-for="item in waterMarkPositionMap"
:key="item[0]"
:value="item[0]"
>
{{ item[1] }} {{ item[1] }}
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item <el-form-item :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISREMOVEEXIF')">
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISREMOVEEXIF')" <el-switch v-model="compressForm.isRemoveExif" :style="switchStyle" />
>
<el-switch
v-model="compressForm.isRemoveExif"
:style="switchStyle"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_QUALITY')">
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_QUALITY')" <el-input-number v-model="compressForm.quality" :min="0" :max="100" :step="1" />
>
<el-input-number
v-model="compressForm.quality"
:min="0"
:max="100"
:step="1"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISCONVERT')">
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISCONVERT')" <el-switch v-model="compressForm.isConvert" :style="switchStyle" />
>
<el-switch
v-model="compressForm.isConvert"
:style="switchStyle"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-show="compressForm.isConvert" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_CONVERTFORMAT')">
v-show="compressForm.isConvert" <el-select v-model="compressForm.convertFormat" :persistent="false" teleported>
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_CONVERTFORMAT')" <el-option v-for="item in availableFormat" :key="item" :label="item" :value="item" />
>
<el-select
v-model="compressForm.convertFormat"
:persistent="false"
teleported
>
<el-option
v-for="item in availableFormat"
:key="item"
:label="item"
:value="item"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-show="compressForm.isConvert" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_CONVERTFORMAT_SPECIFIC')">
v-show="compressForm.isConvert"
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_CONVERTFORMAT_SPECIFIC')"
>
<el-input <el-input
v-model="formatConvertObj" v-model="formatConvertObj"
placeholder="{&quot;jpg&quot;: &quot;png&quot;, &quot;png&quot;: &quot;jpg&quot;}" placeholder='{"jpg": "png", "png": "jpg"}'
type="textarea" type="textarea"
:autosize="{ minRows: 2, maxRows: 4 }" :autosize="{ minRows: 2, maxRows: 4 }"
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISFLIP')">
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISFLIP')" <el-switch v-model="compressForm.isFlip" :style="switchStyle" />
>
<el-switch
v-model="compressForm.isFlip"
:style="switchStyle"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISFLOP')">
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISFLOP')" <el-switch v-model="compressForm.isFlop" :style="switchStyle" />
>
<el-switch
v-model="compressForm.isFlop"
:style="switchStyle"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISRESIZE')">
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISRESIZE')" <el-switch v-model="compressForm.isReSize" :style="switchStyle" />
>
<el-switch
v-model="compressForm.isReSize"
:style="switchStyle"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-show="compressForm.isReSize" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_RESIZEWIDTH')">
v-show="compressForm.isReSize" <el-input-number v-model="compressForm.reSizeWidth" :min="0" />
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_RESIZEWIDTH')"
>
<el-input-number
v-model="compressForm.reSizeWidth"
:min="0"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-show="compressForm.isReSize" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_RESIZEHEIGHT')">
v-show="compressForm.isReSize" <el-input-number v-model="compressForm.reSizeHeight" :min="0" />
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_RESIZEHEIGHT')"
>
<el-input-number
v-model="compressForm.reSizeHeight"
:min="0"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-show="compressForm.isReSize && compressForm.reSizeHeight! > 0 && compressForm.reSizeWidth === 0" v-show="compressForm.isReSize && compressForm.reSizeHeight! > 0 && compressForm.reSizeWidth === 0"
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_SKIPRESIZEOfSMALLIMG_HEIGHT')" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_SKIPRESIZEOfSMALLIMG_HEIGHT')"
> >
<el-switch <el-switch v-model="compressForm.skipReSizeOfSmallImg" :style="switchStyle" />
v-model="compressForm.skipReSizeOfSmallImg"
:style="switchStyle"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-show="compressForm.isReSize && compressForm.reSizeWidth! > 0 && compressForm.reSizeHeight === 0" v-show="compressForm.isReSize && compressForm.reSizeWidth! > 0 && compressForm.reSizeHeight === 0"
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_SKIPRESIZEOfSMALLIMG_WIDTH')" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_SKIPRESIZEOfSMALLIMG_WIDTH')"
> >
<el-switch <el-switch v-model="compressForm.skipReSizeOfSmallImg" :style="switchStyle" />
v-model="compressForm.skipReSizeOfSmallImg"
:style="switchStyle"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISRESIZEBYPERCENT')">
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISRESIZEBYPERCENT')" <el-switch v-model="compressForm.isReSizeByPercent" :style="switchStyle" />
>
<el-switch
v-model="compressForm.isReSizeByPercent"
:style="switchStyle"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-show="compressForm.isReSizeByPercent" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_RESIZEPERCENT')">
v-show="compressForm.isReSizeByPercent" <el-input-number v-model="compressForm.reSizePercent" :min="0" />
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_RESIZEPERCENT')"
>
<el-input-number
v-model="compressForm.reSizePercent"
:min="0"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISROTATE')">
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ISROTATE')" <el-switch v-model="compressForm.isRotate" :style="switchStyle" />
>
<el-switch
v-model="compressForm.isRotate"
:style="switchStyle"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-show="compressForm.isRotate" :label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ROTATEDEGREE')">
v-show="compressForm.isRotate" <el-input-number v-model="compressForm.rotateDegree" :step="1" />
:label="$T('UPLOAD_PAGE_IMAGE_PROCESS_ROTATEDEGREE')"
>
<el-input-number
v-model="compressForm.rotateDegree"
:step="1"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button <el-button type="primary" @click="handleSaveConfig">
type="primary"
@click="handleSaveConfig"
>
{{ $T('UPLOAD_PAGE_IMAGE_PROCESS_CONFIRM') }} {{ $T('UPLOAD_PAGE_IMAGE_PROCESS_CONFIRM') }}
</el-button> </el-button>
<el-button @click="closeDialog"> <el-button @click="closeDialog">
@@ -283,7 +153,29 @@ const waterMarkPositionMap = new Map([
['centre', $T('UPLOAD_PAGE_IMAGE_PROCESS_POSITION_CENTER')] ['centre', $T('UPLOAD_PAGE_IMAGE_PROCESS_POSITION_CENTER')]
]) ])
const imageExtList = ['jpg', 'jpeg', 'png', 'webp', 'bmp', 'tiff', 'tif', 'svg', 'ico', 'avif', 'heif', 'heic'] const imageExtList = ['jpg', 'jpeg', 'png', 'webp', 'bmp', 'tiff', 'tif', 'svg', 'ico', 'avif', 'heif', 'heic']
const availableFormat = ['avif', 'dz', 'fits', 'gif', 'heif', 'input', 'jpeg', 'jpg', 'jp2', 'jxl', 'magick', 'openslide', 'pdf', 'png', 'ppm', 'raw', 'svg', 'tiff', 'tif', 'v', 'webp'] const availableFormat = [
'avif',
'dz',
'fits',
'gif',
'heif',
'input',
'jpeg',
'jpg',
'jp2',
'jxl',
'magick',
'openslide',
'pdf',
'png',
'ppm',
'raw',
'svg',
'tiff',
'tif',
'v',
'webp'
]
const waterMarkForm = reactive<IBuildInWaterMarkOptions>({ const waterMarkForm = reactive<IBuildInWaterMarkOptions>({
isAddWatermark: false, isAddWatermark: false,
@@ -324,8 +216,7 @@ function handleSaveConfig () {
let iformatConvertObj = {} let iformatConvertObj = {}
try { try {
iformatConvertObj = JSON.parse(formatConvertObj.value) iformatConvertObj = JSON.parse(formatConvertObj.value)
} catch (error) { } catch (error) {}
}
const formatConvertObjEntries = Object.entries(iformatConvertObj) const formatConvertObjEntries = Object.entries(iformatConvertObj)
const formatConvertObjEntriesFilter = formatConvertObjEntries.filter((item: any) => { const formatConvertObjEntriesFilter = formatConvertObjEntries.filter((item: any) => {
return imageExtList.includes(item[0]) && availableFormat.includes(item[1]) return imageExtList.includes(item[0]) && availableFormat.includes(item[1])
@@ -342,7 +233,7 @@ async function initData () {
const compress = await getConfig<any>(configPaths.buildIn.compress) const compress = await getConfig<any>(configPaths.buildIn.compress)
const watermark = await getConfig<any>(configPaths.buildIn.watermark) const watermark = await getConfig<any>(configPaths.buildIn.watermark)
if (compress) { if (compress) {
compressFormKeys.forEach((key) => { compressFormKeys.forEach(key => {
compressForm[key] = compress[key] ?? compressForm[key] compressForm[key] = compress[key] ?? compressForm[key]
}) })
try { try {
@@ -356,7 +247,7 @@ async function initData () {
} }
} }
if (watermark) { if (watermark) {
waterMarkFormKeys.forEach((key) => { waterMarkFormKeys.forEach(key => {
waterMarkForm[key] = watermark[key] ?? waterMarkForm[key] waterMarkForm[key] = watermark[key] ?? waterMarkForm[key]
}) })
waterMarkForm.watermarkColor = watermark.watermarkColor === '' ? '#CCCCCC73' : watermark.watermarkColor waterMarkForm.watermarkColor = watermark.watermarkColor === '' ? '#CCCCCC73' : watermark.watermarkColor
+3 -12
View File
@@ -1,20 +1,12 @@
<template> <template>
<el-image <el-image :src="imageSource" fit="contain" style="height: 100px; width: 100%; margin: 0 auto">
:src="imageSource"
fit="contain"
style="height: 100px;width: 100%;margin: 0 auto;"
>
<template #placeholder> <template #placeholder>
<el-icon> <el-icon>
<Loading /> <Loading />
</el-icon> </el-icon>
</template> </template>
<template #error> <template #error>
<el-image <el-image :src="iconPath" fit="contain" style="height: 100px; width: 100%; margin: 0 auto" />
:src="iconPath"
fit="contain"
style="height: 100px;width: 100%;margin: 0 auto;"
/>
</template> </template>
</el-image> </el-image>
</template> </template>
@@ -43,7 +35,7 @@ const props = defineProps<{
}>() }>()
const imageSource = computed(() => { const imageSource = computed(() => {
return (props.isShowThumbnail && props.item.isImage && success.value) return props.isShowThumbnail && props.item.isImage && success.value
? base64Url.value ? base64Url.value
: require(`../manage/pages/assets/icons/${getFileIconPath(props.item.fileName ?? '')}`) : require(`../manage/pages/assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)
}) })
@@ -91,5 +83,4 @@ const fetchImage = async () => {
watch(() => [props.url, props.item], fetchImage, { deep: true }) watch(() => [props.url, props.item], fetchImage, { deep: true })
onMounted(fetchImage) onMounted(fetchImage)
</script> </script>
+6 -14
View File
@@ -32,11 +32,13 @@ export default defineComponent({
const success = ref(false) const success = ref(false)
const imageSource = computed(() => { const imageSource = computed(() => {
return (props.isShowThumbnail && props.item.isImage && success.value) return props.isShowThumbnail && props.item.isImage && success.value
? base64Url.value ? base64Url.value
: require(`../manage/pages/assets/icons/${getFileIconPath(props.item.fileName ?? '')}`) : require(`../manage/pages/assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)
}) })
const iconPath = computed(() => require(`../manage/pages/assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)) const iconPath = computed(() =>
require(`../manage/pages/assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)
)
async function getWebdavHeader(key: string) { async function getWebdavHeader(key: string) {
let headers = {} as any let headers = {} as any
@@ -79,24 +81,14 @@ export default defineComponent({
onMounted(fetchImage) onMounted(fetchImage)
return () => ( return () => (
<ElImage <ElImage src={imageSource.value} fit='contain' style='height: 100px;width: 100%;margin: 0 auto;'>
src={imageSource.value}
fit="contain"
style="height: 100px;width: 100%;margin: 0 auto;"
>
{{ {{
placeholder: () => ( placeholder: () => (
<ElIcon> <ElIcon>
<Loading /> <Loading />
</ElIcon> </ElIcon>
), ),
error: () => ( error: () => <ElImage src={iconPath.value} fit='contain' style='height: 100px;width: 100%;margin: 0 auto;' />
<ElImage
src={iconPath.value}
fit="contain"
style="height: 100px;width: 100%;margin: 0 auto;"
/>
)
}} }}
</ElImage> </ElImage>
) )
+5 -20
View File
@@ -5,22 +5,12 @@
:modal-append-to-body="false" :modal-append-to-body="false"
append-to-body append-to-body
> >
<el-input <el-input v-model="inputBoxValue" :placeholder="inputBoxOptions.placeholder" />
v-model="inputBoxValue"
:placeholder="inputBoxOptions.placeholder"
/>
<template #footer> <template #footer>
<el-button <el-button round @click="handleInputBoxCancel">
round
@click="handleInputBoxCancel"
>
{{ $T('CANCEL') }} {{ $T('CANCEL') }}
</el-button> </el-button>
<el-button <el-button type="primary" round @click="handleInputBoxConfirm">
type="primary"
round
@click="handleInputBoxConfirm"
>
{{ $T('CONFIRM') }} {{ $T('CONFIRM') }}
</el-button> </el-button>
</template> </template>
@@ -35,10 +25,7 @@ import { T as $T } from '@/i18n/index'
import $bus from '@/utils/bus' import $bus from '@/utils/bus'
import { sendToMain } from '@/utils/common' import { sendToMain } from '@/utils/common'
import { import { SHOW_INPUT_BOX, SHOW_INPUT_BOX_RESPONSE } from '#/events/constants'
SHOW_INPUT_BOX,
SHOW_INPUT_BOX_RESPONSE
} from '#/events/constants'
const inputBoxValue = ref('') const inputBoxValue = ref('')
const showInputBoxVisible = ref(false) const showInputBoxVisible = ref(false)
@@ -80,12 +67,10 @@ onBeforeUnmount(() => {
ipcRenderer.removeListener(SHOW_INPUT_BOX, ipcEventHandler) ipcRenderer.removeListener(SHOW_INPUT_BOX, ipcEventHandler)
$bus.off(SHOW_INPUT_BOX) $bus.off(SHOW_INPUT_BOX)
}) })
</script> </script>
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'InputBoxDialog' name: 'InputBoxDialog'
} }
</script> </script>
<style lang='stylus'> <style lang="stylus"></style>
</style>
+2 -8
View File
@@ -1,10 +1,6 @@
<template> <template>
<div class="toolbox-handler"> <div class="toolbox-handler">
<ElButton <ElButton type="primary" :link="true" @click="() => props.handler(value)">
type="primary"
:link="true"
@click="() => props.handler(value)"
>
{{ props.handlerText }} {{ props.handlerText }}
</ElButton> </ElButton>
</div> </div>
@@ -21,12 +17,10 @@ interface IProps {
} }
const props = defineProps<IProps>() const props = defineProps<IProps>()
</script> </script>
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'ToolboxHandler' name: 'ToolboxHandler'
} }
</script> </script>
<style lang='stylus'> <style lang="stylus"></style>
</style>
@@ -1,8 +1,5 @@
<template> <template>
<el-icon <el-icon :color="color" class="toolbox-status-icon">
:color="color"
class="toolbox-status-icon"
>
<template v-if="props.status === IToolboxItemCheckStatus.SUCCESS"> <template v-if="props.status === IToolboxItemCheckStatus.SUCCESS">
<SuccessFilled /> <SuccessFilled />
</template> </template>
@@ -37,14 +34,13 @@ const color = computed(() => {
return '#909399' return '#909399'
} }
}) })
</script> </script>
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'ToolboxStatusIcon' name: 'ToolboxStatusIcon'
} }
</script> </script>
<style lang='stylus'> <style lang="stylus">
.toolbox-status-icon { .toolbox-status-icon {
margin-left: 8px; margin-left: 8px;
} }
+1 -4
View File
@@ -59,7 +59,4 @@ const T = (key: ILocalesKey, args: IStringKeyMap = {}): string => {
return i18nManager.T(key, args) return i18nManager.T(key, args)
} }
export { export { i18nManager, T }
i18nManager,
T
}
+47 -120
View File
@@ -1,48 +1,24 @@
<template> <template>
<div id="main-page"> <div id="main-page">
<div <div class="fake-title-bar">
class="fake-title-bar" <div class="fake-title-bar__title">PicList - {{ version }}</div>
> <div v-if="osGlobal !== 'darwin'" class="handle-bar">
<div class="fake-title-bar__title">
PicList - {{ version }}
</div>
<div
v-if="osGlobal !== 'darwin'"
class="handle-bar"
>
<el-icon <el-icon
class="minus" class="minus"
:color="isAlwaysOnTop ? '#409EFF' : '#fff'" :color="isAlwaysOnTop ? '#409EFF' : '#fff'"
size="20" size="20"
style="margin-right: 10px;" style="margin-right: 10px"
@click="setAlwaysOnTop" @click="setAlwaysOnTop"
> >
<ArrowUpBold /> <ArrowUpBold />
</el-icon> </el-icon>
<el-icon <el-icon class="minus" color="#fff" size="20" style="margin-right: 10px" @click="minimizeWindow">
class="minus"
color="#fff"
size="20"
style="margin-right: 10px;"
@click="minimizeWindow"
>
<SemiSelect /> <SemiSelect />
</el-icon> </el-icon>
<el-icon <el-icon class="plus" color="orange" size="20" style="margin-right: 10px" @click="openMiniWindow">
class="plus"
color="orange"
size="20"
style="margin-right: 10px;"
@click="openMiniWindow"
>
<ArrowDownBold /> <ArrowDownBold />
</el-icon> </el-icon>
<el-icon <el-icon class="close" color="#fff" size="20" @click="closeWindow">
class="close"
color="#fff"
size="20"
@click="closeWindow"
>
<CloseBold /> <CloseBold />
</el-icon> </el-icon>
</div> </div>
@@ -56,19 +32,9 @@
status="success" status="success"
class="progress-bar" class="progress-bar"
/> />
<el-row <el-row style="padding-top: 22px" class="main-content">
style="padding-top: 22px;" <el-col class="side-bar-menu">
class="main-content" <el-menu class="picgo-sidebar" :default-active="defaultActive" :unique-opened="true" @select="handleSelect">
>
<el-col
class="side-bar-menu"
>
<el-menu
class="picgo-sidebar"
:default-active="defaultActive"
:unique-opened="true"
@select="handleSelect"
>
<el-menu-item :index="routerConfig.UPLOAD_PAGE"> <el-menu-item :index="routerConfig.UPLOAD_PAGE">
<el-icon> <el-icon>
<UploadFilled /> <UploadFilled />
@@ -87,21 +53,14 @@
</el-icon> </el-icon>
<span>{{ $T('GALLERY') }}</span> <span>{{ $T('GALLERY') }}</span>
</el-menu-item> </el-menu-item>
<el-sub-menu <el-sub-menu index="sub-menu" :show-timeout="0" :hide-timeout="0" :popper-offset="0">
index="sub-menu"
:show-timeout="0"
:hide-timeout="0"
:popper-offset="0"
>
<template #title> <template #title>
<el-icon> <el-icon>
<Menu /> <Menu />
</el-icon> </el-icon>
<span>{{ $T('PICBEDS_SETTINGS') }}</span> <span>{{ $T('PICBEDS_SETTINGS') }}</span>
</template> </template>
<template <template v-for="item in picBedGlobal">
v-for="item in picBedGlobal"
>
<el-menu-item <el-menu-item
v-if="item.visible" v-if="item.visible"
:key="item.type" :key="item.type"
@@ -123,39 +82,22 @@
</el-icon> </el-icon>
<span>{{ $T('PLUGIN_SETTINGS') }}</span> <span>{{ $T('PLUGIN_SETTINGS') }}</span>
</el-menu-item> </el-menu-item>
<el-menu-item <el-menu-item :index="routerConfig.DocumentPage">
:index="routerConfig.DocumentPage"
>
<el-icon> <el-icon>
<Link /> <Link />
</el-icon> </el-icon>
<span>{{ $T('MANUAL') }}</span> <span>{{ $T('MANUAL') }}</span>
</el-menu-item> </el-menu-item>
</el-menu> </el-menu>
<el-icon <el-icon class="info-window" @click="openMenu">
class="info-window"
@click="openMenu"
>
<InfoFilled /> <InfoFilled />
</el-icon> </el-icon>
</el-col> </el-col>
<el-col <el-col :span="21" :offset="3" style="height: 100%" class="main-wrapper">
:span="21" <router-view v-slot="{ Component }">
:offset="3" <transition name="picgo-fade" mode="out-in">
style="height: 100%"
class="main-wrapper"
>
<router-view
v-slot="{ Component }"
>
<transition
name="picgo-fade"
mode="out-in"
>
<keep-alive :include="keepAlivePages"> <keep-alive :include="keepAlivePages">
<component <component :is="Component" />
:is="Component"
/>
</keep-alive> </keep-alive>
</transition> </transition>
</router-view> </router-view>
@@ -171,27 +113,10 @@
lock-scroll lock-scroll
append-to-body append-to-body
> >
<el-form <el-form label-position="left" label-width="70px" size="small">
label-position="left" <el-form-item :label="$T('CHOOSE_PICBED')">
label-width="70px" <el-select v-model="choosedPicBedForQRCode" multiple collapse-tags :persistent="false" teleported>
size="small" <el-option v-for="item in picBedGlobal" :key="item.type" :label="item.name" :value="item.type" />
>
<el-form-item
:label="$T('CHOOSE_PICBED')"
>
<el-select
v-model="choosedPicBedForQRCode"
multiple
collapse-tags
:persistent="false"
teleported
>
<el-option
v-for="item in picBedGlobal"
:key="item.type"
:label="item.name"
:value="item.type"
/>
</el-select> </el-select>
<el-button <el-button
v-show="choosedPicBedForQRCode.length > 0" v-show="choosedPicBedForQRCode.length > 0"
@@ -205,11 +130,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="qrcode-container"> <div class="qrcode-container">
<qrcode-vue <qrcode-vue v-show="choosedPicBedForQRCode.length > 0" :size="280" :value="picBedConfigString" />
v-show="choosedPicBedForQRCode.length > 0"
:size="280"
:value="picBedConfigString"
/>
</div> </div>
</el-dialog> </el-dialog>
<input-box-dialog /> <input-box-dialog />
@@ -231,11 +152,7 @@ import {
Link, Link,
ArrowUpBold ArrowUpBold
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { import { ipcRenderer, IpcRendererEvent, clipboard } from 'electron'
ipcRenderer,
IpcRendererEvent,
clipboard
} from 'electron'
import { ElMessage as $message, ElMessageBox } from 'element-plus' import { ElMessage as $message, ElMessageBox } from 'element-plus'
import pick from 'lodash/pick' import pick from 'lodash/pick'
import QrcodeVue from 'qrcode.vue' import QrcodeVue from 'qrcode.vue'
@@ -249,9 +166,7 @@ import { getConfig, saveConfig } from '@/utils/dataSender'
import { sendRPC } from '@/utils/common' import { sendRPC } from '@/utils/common'
import { osGlobal, picBedGlobal, updatePicBedGlobal } from '@/utils/global' import { osGlobal, picBedGlobal, updatePicBedGlobal } from '@/utils/global'
import { import { SHOW_MAIN_PAGE_QRCODE } from '#/events/constants'
SHOW_MAIN_PAGE_QRCODE
} from '#/events/constants'
import { configPaths, manualPageOpenType } from '#/utils/configPaths' import { configPaths, manualPageOpenType } from '#/utils/configPaths'
import { II18nLanguage, IRPCActionType } from '#/types/enum' import { II18nLanguage, IRPCActionType } from '#/types/enum'
@@ -265,7 +180,10 @@ const qrcodeVisible = ref(false)
const picBedConfigString = ref('') const picBedConfigString = ref('')
const choosedPicBedForQRCode: Ref<string[]> = ref([]) const choosedPicBedForQRCode: Ref<string[]> = ref([])
const isAlwaysOnTop = ref(false) const isAlwaysOnTop = ref(false)
const keepAlivePages = $router.getRoutes().filter(item => item.meta.keepAlive).map(item => item.name as string) const keepAlivePages = $router
.getRoutes()
.filter(item => item.meta.keepAlive)
.map(item => item.name as string)
const isShowprogress = ref(false) const isShowprogress = ref(false)
const progress = ref(0) const progress = ref(0)
@@ -281,7 +199,9 @@ onBeforeMount(() => {
}) })
}) })
watch(() => choosedPicBedForQRCode, (val) => { watch(
() => choosedPicBedForQRCode,
val => {
if (val.value.length > 0) { if (val.value.length > 0) {
nextTick(async () => { nextTick(async () => {
const picBedConfig = await getConfig('picBed') const picBedConfig = await getConfig('picBed')
@@ -289,15 +209,21 @@ watch(() => choosedPicBedForQRCode, (val) => {
picBedConfigString.value = JSON.stringify(config) picBedConfigString.value = JSON.stringify(config)
}) })
} }
}, { deep: true }) },
{ deep: true }
)
const handleSelect = async (index: string) => { const handleSelect = async (index: string) => {
defaultActive.value = index defaultActive.value = index
if (index === routerConfig.DocumentPage) { if (index === routerConfig.DocumentPage) {
const manualPageOpenSetting = await getConfig<manualPageOpenType>(configPaths.settings.manualPageOpen) const manualPageOpenSetting = await getConfig<manualPageOpenType>(configPaths.settings.manualPageOpen)
const lang = await getConfig(configPaths.settings.language) || II18nLanguage.ZH_CN const lang = (await getConfig(configPaths.settings.language)) || II18nLanguage.ZH_CN
const openManual = () => sendRPC(IRPCActionType.OPEN_MANUAL_WINDOW) const openManual = () => sendRPC(IRPCActionType.OPEN_MANUAL_WINDOW)
const openExternal = () => sendRPC(IRPCActionType.OPEN_URL, lang === II18nLanguage.ZH_CN ? 'https://piclist.cn/app.html' : 'https://piclist.cn/en/app.html') const openExternal = () =>
sendRPC(
IRPCActionType.OPEN_URL,
lang === II18nLanguage.ZH_CN ? 'https://piclist.cn/app.html' : 'https://piclist.cn/en/app.html'
)
if (!manualPageOpenSetting) { if (!manualPageOpenSetting) {
ElMessageBox.confirm($T('MANUAL_PAGE_OPEN_TIP'), $T('MANUAL_PAGE_OPEN_TIP_TITLE'), { ElMessageBox.confirm($T('MANUAL_PAGE_OPEN_TIP'), $T('MANUAL_PAGE_OPEN_TIP_TITLE'), {
@@ -305,10 +231,12 @@ const handleSelect = async (index: string) => {
cancelButtonText: $T('MANUAL_PAGE_OPEN_BY_BUILD_IN'), cancelButtonText: $T('MANUAL_PAGE_OPEN_BY_BUILD_IN'),
type: 'info', type: 'info',
center: true center: true
}).then(() => { })
.then(() => {
saveConfig(configPaths.settings.manualPageOpen, 'browser') saveConfig(configPaths.settings.manualPageOpen, 'browser')
openExternal() openExternal()
}).catch(() => { })
.catch(() => {
saveConfig(configPaths.settings.manualPageOpen, 'window') saveConfig(configPaths.settings.manualPageOpen, 'window')
openManual() openManual()
}) })
@@ -359,7 +287,7 @@ function setAlwaysOnTop () {
sendRPC(IRPCActionType.MAIN_WINDOW_ON_TOP) sendRPC(IRPCActionType.MAIN_WINDOW_ON_TOP)
} }
onBeforeRouteUpdate(async (to) => { onBeforeRouteUpdate(async to => {
if (to.params.type) { if (to.params.type) {
defaultActive.value = `${routerConfig.UPLOADER_CONFIG_PAGE}-${to.params.type}` defaultActive.value = `${routerConfig.UPLOADER_CONFIG_PAGE}-${to.params.type}`
} else { } else {
@@ -371,14 +299,13 @@ onBeforeUnmount(() => {
ipcRenderer.removeAllListeners(SHOW_MAIN_PAGE_QRCODE) ipcRenderer.removeAllListeners(SHOW_MAIN_PAGE_QRCODE)
ipcRenderer.removeAllListeners('updateProgress') ipcRenderer.removeAllListeners('updateProgress')
}) })
</script> </script>
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'MainPage' name: 'MainPage'
} }
</script> </script>
<style lang='stylus'> <style lang="stylus">
$darwinBg = transparentify(#172426, #000, 0.7) $darwinBg = transparentify(#172426, #000, 0.7)
.setting-list-scroll .setting-list-scroll
height 800px height 800px
@@ -1,21 +1,11 @@
<template> <template>
<el-form-item> <el-form-item>
<template #label> <template #label>
<span style="position:absolute;left: 0;"> <span style="position: absolute; left: 0">
<span <span v-for="(segment, index) in segments" :key="index" :style="segment.style">
v-for="(segment, index) in segments"
:key="index"
:style="segment.style"
>
{{ segment.text }} {{ segment.text }}
</span> </span>
<el-tooltip <el-tooltip :content="tooltip" effect="dark" placement="right" :persistent="false" teleported>
:content="tooltip"
effect="dark"
placement="right"
:persistent="false"
teleported
>
<el-icon> <el-icon>
<InfoFilled /> <InfoFilled />
</el-icon> </el-icon>
@@ -26,7 +16,7 @@
v-model="value" v-model="value"
:active-text="activeText" :active-text="activeText"
:inactive-text="inactiveText" :inactive-text="inactiveText"
style="--el-switch-on-color: #13ce66;--el-switch-off-color: #ff4949; position:absolute; right: 0;" style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949; position: absolute; right: 0"
/> />
</el-form-item> </el-form-item>
</template> </template>
@@ -35,12 +25,11 @@
import { InfoFilled } from '@element-plus/icons-vue' import { InfoFilled } from '@element-plus/icons-vue'
defineProps<{ defineProps<{
tooltip?: string, tooltip?: string
activeText?: string, activeText?: string
inactiveText?: string, inactiveText?: string
segments?: { text: string, style: string }[], segments?: { text: string; style: string }[]
}>() }>()
const value = defineModel<boolean>() const value = defineModel<boolean>()
</script> </script>

Some files were not shown because too many files have changed in this diff Show More