🎨 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

View File

@@ -1,5 +1,8 @@
test/unit/coverage/**
test/unit/*.js
test/e2e/*.js
dist/
src/**/*.js
test/unit/coverage/**
test/unit/*.js
test/e2e/*.js
dist/
src/**/*.js
node_modules
dist
vue.config.js

View File

@@ -10,7 +10,8 @@ module.exports = {
extends: [
'plugin:vue/vue3-recommended',
'@vue/standard',
'@vue/typescript'
'@vue/typescript/recommended',
'@vue/eslint-config-prettier'
],
plugins: ['@typescript-eslint'],
rules: {
@@ -20,8 +21,24 @@ module.exports = {
'no-async-promise-executor': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/indent': ['error', 2],
'vue/no-v-html': 'off'
'@typescript-eslint/indent': '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: {
parser: '@typescript-eslint/parser'

18
.prettierrc Normal file
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"
}

View File

@@ -1,6 +1,4 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: ['@babel/plugin-proposal-optional-chaining']
}
presets: ["@vue/cli-plugin-babel/preset"],
plugins: ["@babel/plugin-proposal-optional-chaining"],
};

View File

@@ -1,159 +1,155 @@
{
"name": "piclist",
"version": "2.9.0",
"author": {
"name": "Kuingsmile",
"email": "pkukuing@gmail.com"
},
"description": "A powerful cloude storage manage tool.",
"homepage": "https://piclist.cn",
"bugs": {
"url": "https://github.com/Kuingsmile/PicList/issues",
"email": "pkukuing@gmail.com"
},
"license": "MIT",
"private": true,
"scripts": {
"build": "vue-cli-service electron:build",
"lint": "vue-cli-service lint",
"bump": "bump-version",
"cz": "git-cz",
"dev": "vue-cli-service electron:serve",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"i18n": "node ./scripts/gen-i18n-types.js",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"release": "vue-cli-service electron:build --publish always",
"upload-dist": "node ./scripts/upload-dist-to-r2.js",
"upload-beta": "node ./scripts/upload-beta.js",
"link": "node ./scripts/link.js",
"sha256": "node ./scripts/gen-sha256.js",
"ncu": "node ./scripts/check-dep.js",
"lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/background.ts"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.421.0",
"@aws-sdk/lib-storage": "^3.421.0",
"@aws-sdk/s3-request-presigner": "^3.421.0",
"@element-plus/icons-vue": "^2.3.1",
"@highlightjs/vue-plugin": "^2.1.2",
"@nodelib/fs.walk": "^2.0.0",
"@octokit/rest": "^19.0.7",
"@picgo/i18n": "^1.0.0",
"@picgo/store": "^2.1.0",
"@smithy/node-http-handler": "^2.1.6",
"@videojs-player/vue": "^1.0.0",
"ali-oss": "^6.18.1",
"axios": "^1.6.8",
"compare-versions": "^4.1.3",
"core-js": "^3.37.1",
"cos-nodejs-sdk-v5": "^2.12.5",
"dexie": "^3.2.4",
"electron-updater": "^6.1.4",
"element-plus": "2.7.4",
"epipebomb": "^1.0.0",
"fast-xml-parser": "^4.3.2",
"form-data": "^4.0.0",
"fs-extra": "^11.2.0",
"got": "^12.6.0",
"highlight.js": "^11.9.0",
"hpagent": "^1.2.0",
"lowdb": "^1.0.0",
"marked": "^9.1.5",
"mime-types": "^2.1.35",
"mitt": "^3.0.1",
"multer": "^1.4.5-lts.1",
"node-ssh-no-cpu-features": "^2.0.0",
"nodejs-file-downloader": "^4.12.1",
"piclist": "^1.8.10",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"proxy-agent": "^5.0.0",
"qiniu": "7.9.0",
"qrcode.vue": "^3.4.1",
"querystring": "^0.2.1",
"shell-path": "2.1.0",
"ssh2-no-cpu-features": "^2.0.0",
"upyun": "^3.4.6",
"uuid": "^9.0.1",
"video.js": "^8.6.1",
"vue": "^3.4.27",
"vue-router": "^4.3.2",
"vue3-lazyload": "^0.3.8",
"vue3-photo-preview": "^0.3.0",
"webdav": "^5.3.1",
"write-file-atomic": "^4.0.1"
},
"devDependencies": {
"@types/video.js": "^7.3.58",
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@electron/notarize": "^2.1.0",
"@types/ali-oss": "^6.16.11",
"@types/electron-devtools-installer": "^2.2.5",
"@types/fs-extra": "^11.0.4",
"@types/inquirer": "^6.5.0",
"@types/js-yaml": "^4.0.9",
"@types/lowdb": "^1.0.15",
"@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.11",
"@types/node": "^16.10.2",
"@types/semver": "^7.5.6",
"@types/tunnel": "^0.0.7",
"@types/upyun": "^3.4.3",
"@types/uuid": "^9.0.8",
"@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-eslint": "^5.0.8",
"@vue/cli-plugin-router": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/eslint-config-standard": "^8.0.1",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/runtime-dom": "^3.4.27",
"conventional-changelog": "^5.1.0",
"cz-customizable": "^7.0.0",
"dotenv": "^16.3.1",
"dpdm": "^3.14.0",
"electron": "^22.3.27",
"eslint": "^8.54.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",
"husky": "^3.1.0",
"node-bump-version": "^1.0.2",
"node-loader": "^2.0.0",
"npm-check-updates": "^16.14.20",
"stylus": "^0.59.0",
"stylus-loader": "^7.1.3",
"typescript": "^4.9.5",
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4"
},
"commitlint": {
"extends": [
"./node_modules/node-bump-version/commitlint-node"
]
},
"config": {
"commitizen": {
"path": "./node_modules/cz-customizable"
},
"cz-customizable": {
"config": "./node_modules/node-bump-version/.cz-config.js"
}
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"resolutions": {
"@types/node": "^16.10.2",
"vue-cli-plugin-electron-builder/**/electron-builder": "23.3.3"
}
}
{
"name": "piclist",
"version": "2.9.0",
"author": {
"name": "Kuingsmile",
"email": "pkukuing@gmail.com"
},
"description": "A powerful cloude storage manage tool.",
"homepage": "https://piclist.cn",
"bugs": {
"url": "https://github.com/Kuingsmile/PicList/issues",
"email": "pkukuing@gmail.com"
},
"license": "MIT",
"private": true,
"scripts": {
"build": "vue-cli-service electron:build",
"lint": "vue-cli-service lint",
"bump": "bump-version",
"cz": "git-cz",
"dev": "vue-cli-service electron:serve",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"i18n": "node ./scripts/gen-i18n-types.js",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"release": "vue-cli-service electron:build --publish always",
"upload-dist": "node ./scripts/upload-dist-to-r2.js",
"upload-beta": "node ./scripts/upload-beta.js",
"link": "node ./scripts/link.js",
"sha256": "node ./scripts/gen-sha256.js",
"ncu": "node ./scripts/check-dep.js",
"lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/background.ts"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.421.0",
"@aws-sdk/lib-storage": "^3.421.0",
"@aws-sdk/s3-request-presigner": "^3.421.0",
"@element-plus/icons-vue": "^2.3.1",
"@highlightjs/vue-plugin": "^2.1.2",
"@nodelib/fs.walk": "^2.0.0",
"@octokit/rest": "^19.0.7",
"@picgo/i18n": "^1.0.0",
"@picgo/store": "^2.1.0",
"@smithy/node-http-handler": "^2.1.6",
"@videojs-player/vue": "^1.0.0",
"ali-oss": "^6.18.1",
"axios": "^1.6.8",
"compare-versions": "^4.1.3",
"core-js": "^3.37.1",
"cos-nodejs-sdk-v5": "^2.12.5",
"dexie": "^3.2.4",
"electron-updater": "^6.1.4",
"element-plus": "2.7.4",
"epipebomb": "^1.0.0",
"fast-xml-parser": "^4.3.2",
"form-data": "^4.0.0",
"fs-extra": "^11.2.0",
"got": "^12.6.0",
"highlight.js": "^11.9.0",
"hpagent": "^1.2.0",
"lowdb": "^1.0.0",
"marked": "^9.1.5",
"mime-types": "^2.1.35",
"mitt": "^3.0.1",
"multer": "^1.4.5-lts.1",
"node-ssh-no-cpu-features": "^2.0.0",
"nodejs-file-downloader": "^4.12.1",
"piclist": "^1.8.10",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"proxy-agent": "^5.0.0",
"qiniu": "7.9.0",
"qrcode.vue": "^3.4.1",
"querystring": "^0.2.1",
"shell-path": "2.1.0",
"ssh2-no-cpu-features": "^2.0.0",
"upyun": "^3.4.6",
"uuid": "^9.0.1",
"video.js": "^8.6.1",
"vue": "^3.4.27",
"vue-router": "^4.3.2",
"vue3-lazyload": "^0.3.8",
"vue3-photo-preview": "^0.3.0",
"webdav": "^5.3.1",
"write-file-atomic": "^4.0.1"
},
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@electron/notarize": "^2.1.0",
"@types/ali-oss": "^6.16.11",
"@types/electron-devtools-installer": "^2.2.5",
"@types/fs-extra": "^11.0.4",
"@types/inquirer": "^6.5.0",
"@types/js-yaml": "^4.0.9",
"@types/lowdb": "^1.0.15",
"@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.11",
"@types/node": "^16.10.2",
"@types/semver": "^7.5.6",
"@types/tunnel": "^0.0.7",
"@types/upyun": "^3.4.3",
"@types/uuid": "^9.0.8",
"@types/video.js": "^7.3.58",
"@types/write-file-atomic": "^4.0.3",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-router": "^5.0.8",
"@vue/cli-plugin-typescript": "^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-typescript": "^12.0.0",
"@vue/runtime-dom": "^3.4.27",
"conventional-changelog": "^5.1.0",
"cz-customizable": "^7.0.0",
"dotenv": "^16.3.1",
"dpdm": "^3.14.0",
"electron": "^22.3.27",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.26.0",
"husky": "^3.1.0",
"node-bump-version": "^1.0.2",
"node-loader": "^2.0.0",
"npm-check-updates": "^16.14.20",
"prettier": "^3.3.2",
"stylus": "^0.59.0",
"stylus-loader": "^7.1.3",
"typescript": "^4.9.5",
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4"
},
"commitlint": {
"extends": [
"./node_modules/node-bump-version/commitlint-node"
]
},
"config": {
"commitizen": {
"path": "./node_modules/cz-customizable"
},
"cz-customizable": {
"config": "./node_modules/node-bump-version/.cz-config.js"
}
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"resolutions": {
"@types/node": "^16.10.2",
"vue-cli-plugin-electron-builder/**/electron-builder": "23.3.3"
}
}

View File

@@ -1,5 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}
autoprefixer: {},
},
};

View File

@@ -1,21 +1,12 @@
import axios from 'axios'
import {
app,
clipboard,
dialog,
shell
} from 'electron'
import { app, clipboard, dialog, shell } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import { gte, lte } from 'semver'
import windowManager from 'apis/app/window/windowManager'
import { showNotification } from '~/utils/common'
import {
IRemoteNoticeActionType,
IRemoteNoticeTriggerCount,
IRemoteNoticeTriggerHook
} from '#/types/enum'
import { IRemoteNoticeActionType, IRemoteNoticeTriggerCount, IRemoteNoticeTriggerHook } from '#/types/enum'
// for test
const REMOTE_NOTICE_URL = 'https://release.piclist.cn/remote-notice.json'
@@ -30,38 +21,41 @@ class RemoteNoticeHandler {
private remoteNotice: IRemoteNotice | null = null
private remoteNoticeLocalCountStorage: IRemoteNoticeLocalCountStorage | null = null
async init () {
async init() {
this.remoteNotice = await this.getRemoteNoticeInfo()
this.initLocalCountStorage()
}
private initLocalCountStorage () {
private initLocalCountStorage() {
const localCountStorage = {}
if (!fs.existsSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH)) {
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({}))
}
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
} catch (e) {
this.remoteNoticeLocalCountStorage = localCountStorage
}
}
private saveLocalCountStorage (newData?: IRemoteNoticeLocalCountStorage) {
private saveLocalCountStorage(newData?: IRemoteNoticeLocalCountStorage) {
if (newData) {
this.remoteNoticeLocalCountStorage = newData
}
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify(this.remoteNoticeLocalCountStorage))
}
private async getRemoteNoticeInfo (): Promise<IRemoteNotice | null> {
private async getRemoteNoticeInfo(): Promise<IRemoteNotice | null> {
try {
const noticeInfo = await axios({
const noticeInfo = (await axios({
method: 'get',
url: REMOTE_NOTICE_URL,
responseType: 'json'
}).then(res => res.data) as IRemoteNotice
}).then(res => res.data)) as IRemoteNotice
return noticeInfo
} catch {
return null
@@ -72,7 +66,7 @@ class RemoteNoticeHandler {
* if the notice is not shown or is always shown, then show the notice
* @param action
*/
private checkActionCount (action: IRemoteNoticeAction) {
private checkActionCount(action: IRemoteNoticeAction) {
try {
if (!this.remoteNoticeLocalCountStorage) {
return true
@@ -106,7 +100,7 @@ class RemoteNoticeHandler {
}
}
private async doActions (actions: IRemoteNoticeAction[]) {
private async doActions(actions: IRemoteNoticeAction[]) {
for (const action of actions) {
if (this.checkActionCount(action)) {
switch (action.type) {
@@ -121,7 +115,7 @@ class RemoteNoticeHandler {
body: action.data?.content || '',
clickToCopy: !!action.data?.copyToClipboard,
copyContent: action.data?.copyToClipboard || '',
clickFn () {
clickFn() {
if (action.data?.url) {
shell.openExternal(action.data.url)
}
@@ -129,11 +123,11 @@ class RemoteNoticeHandler {
})
break
case IRemoteNoticeActionType.OPEN_URL:
// OPEN URL
// OPEN URL
shell.openExternal(action.data?.url || '')
break
case IRemoteNoticeActionType.COMMON:
// DO COMMON CASE
// DO COMMON CASE
if (action.data?.copyToClipboard) {
clipboard.writeText(action.data.copyToClipboard)
}
@@ -143,21 +137,23 @@ class RemoteNoticeHandler {
break
case IRemoteNoticeActionType.SHOW_MESSAGE_BOX: {
const currentWindow = windowManager.getAvailableWindow()
dialog.showMessageBox(currentWindow, {
title: action.data?.title || '',
message: action.data?.content || '',
type: 'info',
buttons: action.data?.buttons?.map(item => item.label) || ['Yes']
}).then(res => {
const button = action.data?.buttons?.[res.response]
if (button?.type === 'cancel') {
// do nothing
} else {
if (button?.action) {
this.doActions([button?.action])
dialog
.showMessageBox(currentWindow, {
title: action.data?.title || '',
message: action.data?.content || '',
type: 'info',
buttons: action.data?.buttons?.map(item => item.label) || ['Yes']
})
.then(res => {
const button = action.data?.buttons?.[res.response]
if (button?.type === 'cancel') {
// do nothing
} else {
if (button?.action) {
this.doActions([button?.action])
}
}
}
})
})
break
}
}
@@ -165,7 +161,7 @@ class RemoteNoticeHandler {
}
}
triggerHook (hook: IRemoteNoticeTriggerHook) {
triggerHook(hook: IRemoteNoticeTriggerHook) {
if (!this.remoteNotice || !this.remoteNotice.list) {
return
}
@@ -198,6 +194,4 @@ class RemoteNoticeHandler {
const remoteNoticeHandler = new RemoteNoticeHandler()
export {
remoteNoticeHandler
}
export { remoteNoticeHandler }

View File

@@ -1,7 +1,4 @@
import {
globalShortcut
} from 'electron'
import { globalShortcut } from 'electron'
import shortKeyService from 'apis/app/shortKey/shortKeyService'
import GuiApi from 'apis/gui'
@@ -16,18 +13,18 @@ import { configPaths } from '#/utils/configPaths'
class ShortKeyHandler {
private isInModifiedMode: boolean = false
constructor () {
constructor() {
bus.on(TOGGLE_SHORTKEY_MODIFIED_MODE, flag => {
this.isInModifiedMode = flag
})
}
init () {
init() {
this.initBuiltInShortKey()
this.initPluginsShortKey()
}
private initBuiltInShortKey () {
private initBuiltInShortKey() {
const commands = db.get(configPaths.settings.shortKey._path) as IShortKeyConfigs
Object.keys(commands)
.filter(item => item.includes('picgo:'))
@@ -42,7 +39,7 @@ class ShortKeyHandler {
})
}
private initPluginsShortKey () {
private initPluginsShortKey() {
// get enabled plugin
const pluginList = picgo.pluginLoader.getList()
for (const item of pluginList) {
@@ -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)
if (config.key) {
globalShortcut.register(config.key, () => {
@@ -96,7 +98,7 @@ class ShortKeyHandler {
}
// enable or disable shortKey
bindOrUnbindShortKey (item: IShortKeyConfig, from: string): boolean {
bindOrUnbindShortKey(item: IShortKeyConfig, from: string): boolean {
const command = `${from}:${item.name}`
if (item.enable === false) {
globalShortcut.unregister(item.key)
@@ -120,7 +122,7 @@ class ShortKeyHandler {
}
// update shortKey bindings
updateShortKey (item: IShortKeyConfig, oldKey: string, from: string): boolean {
updateShortKey(item: IShortKeyConfig, oldKey: string, from: string): boolean {
const command = `${from}:${item.name}`
if (globalShortcut.isRegistered(item.key)) return false
globalShortcut.unregister(oldKey)
@@ -133,7 +135,7 @@ class ShortKeyHandler {
return true
}
private async handler (command: string) {
private async handler(command: string) {
if (this.isInModifiedMode) {
return
}
@@ -149,7 +151,7 @@ class ShortKeyHandler {
}
}
registerPluginShortKey (pluginName: string) {
registerPluginShortKey(pluginName: string) {
const plugin = picgo.pluginLoader.getPlugin(pluginName)
if (plugin && plugin.commands) {
if (typeof plugin.commands !== 'function') {
@@ -169,7 +171,7 @@ class ShortKeyHandler {
}
}
unregisterPluginShortKey (pluginName: string) {
unregisterPluginShortKey(pluginName: string) {
const commands = db.get(configPaths.settings.shortKey._path) as IShortKeyConfigs
const keyList = Object.keys(commands)
.filter(command => command.includes(pluginName))

View File

@@ -2,22 +2,22 @@ import logger from '@core/picgo/logger'
class ShortKeyService {
private commandList: Map<string, IShortKeyHandler> = new Map()
registerCommand (command: string, handler: IShortKeyHandler) {
registerCommand(command: string, handler: IShortKeyHandler) {
this.commandList.set(command, handler)
}
unregisterCommand (command: string) {
unregisterCommand(command: string) {
this.commandList.delete(command)
}
getShortKeyHandler (command: string): IShortKeyHandler | null {
getShortKeyHandler(command: string): IShortKeyHandler | null {
const handler = this.commandList.get(command)
if (handler) return handler
logger.warn(`cannot find command: ${command}`)
return null
}
getCommandList () {
getCommandList() {
return [...this.commandList.keys()]
}
}

View File

@@ -34,7 +34,7 @@ import { hideMiniWindow, openMainWindow, openMiniWindow } from '~/utils/windowHe
let contextMenu: Menu | null
export function setDockMenu () {
export function setDockMenu() {
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const dockMenu = Menu.buildFromTemplate([
{
@@ -43,7 +43,7 @@ export function setDockMenu () {
},
{
label: T('START_WATCH_CLIPBOARD'),
click () {
click() {
db.set(configPaths.settings.isListeningClipboard, true)
clipboardPoll.startListening()
clipboardPoll.on('change', () => {
@@ -56,7 +56,7 @@ export function setDockMenu () {
},
{
label: T('STOP_WATCH_CLIPBOARD'),
click () {
click() {
db.set(configPaths.settings.isListeningClipboard, false)
clipboardPoll.stopListening()
clipboardPoll.removeAllListeners()
@@ -68,14 +68,20 @@ export function setDockMenu () {
app.dock.setMenu(dockMenu)
}
export function createMenu () {
export function createMenu() {
const submenu = buildPicBedListMenu()
const appMenu = Menu.buildFromTemplate([
{
label: 'PicList',
submenu: [
{ 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 },
@@ -93,18 +99,17 @@ export function createMenu () {
},
{
label: T('QUIT'),
submenu: [
{ label: T('QUIT'), role: 'quit' }
]
submenu: [{ label: T('QUIT'), role: 'quit' }]
}
])
Menu.setApplicationMenu(appMenu)
}
export function createContextMenu () {
export function createContextMenu() {
const ClipboardWatcher = clipboardPoll
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 = () => {
db.set(configPaths.settings.isListeningClipboard, true)
@@ -125,18 +130,44 @@ export function createContextMenu () {
if (process.platform === 'darwin' || process.platform === 'win32') {
const submenu = buildPicBedListMenu()
const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
const template: Array<MenuItemConstructorOptions | MenuItem> = [
{ label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
{ 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('RELOAD_APP'), click () { app.relaunch(); app.exit(0) } },
{
label: T('START_WATCH_CLIPBOARD'),
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' }
]
if (process.platform === 'win32') {
template.splice(2, 0,
{ label: T('OPEN_MINI_WINDOW'), click () { openMiniWindow(false) }, visible: !isMiniWindowVisible },
{ label: T('HIDE_MINI_WINDOW'), click: hideMiniWindow, visible: isMiniWindowVisible }
template.splice(
2,
0,
{
label: T('OPEN_MINI_WINDOW'),
click() {
openMiniWindow(false)
},
visible: !isMiniWindowVisible
},
{
label: T('HIDE_MINI_WINDOW'),
click: hideMiniWindow,
visible: isMiniWindowVisible
}
)
}
contextMenu = Menu.buildFromTemplate(template)
@@ -150,13 +181,31 @@ export function createContextMenu () {
contextMenu = Menu.buildFromTemplate([
{ 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('START_WATCH_CLIPBOARD'), click: startWatchClipboard, visible: !isListeningClipboard },
{ label: T('STOP_WATCH_CLIPBOARD'), click: stopWatchClipboard, visible: isListeningClipboard },
{
label: T('OPEN_MINI_WINDOW'),
click() {
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'),
click () {
click() {
dialog.showMessageBox({
title: 'PicList',
message: 'PicList',
@@ -179,7 +228,7 @@ const getTrayIcon = () => {
}
}
export function createTray (tooltip: string) {
export function createTray(tooltip: string) {
const menubarPic = getTrayIcon()
setTray(new Tray(menubarPic))
tray.setToolTip(tooltip)
@@ -260,9 +309,7 @@ export function createTray (tooltip: string) {
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const rawInput = cloneDeep(files)
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
const imgs = await uploader
.setWebContents(trayWindow.webContents)
.upload(files)
const imgs = await uploader.setWebContents(trayWindow.webContents).upload(files)
const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false
if (imgs !== false) {
const pasteText: string[] = []
@@ -270,8 +317,11 @@ export function createTray (tooltip: string) {
if (deleteLocalFile) {
await fs.remove(rawInput[i])
}
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)
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)
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),
@@ -290,8 +340,8 @@ export function createTray (tooltip: string) {
})
// toggleWindow()
} else if (process.platform === 'linux') {
// click事件在Ubuntu上无法触发Unity不支持在Mac和Windows上可以触发
// 需要使用 setContextMenu 设置菜单
// click事件在Ubuntu上无法触发Unity不支持在Mac和Windows上可以触发
// 需要使用 setContextMenu 设置菜单
createContextMenu()
tray!.setContextMenu(contextMenu)
}

View File

@@ -1,7 +1,4 @@
import {
Notification,
WebContents
} from 'electron'
import { Notification, WebContents } from 'electron'
import fs from 'fs-extra'
import { cloneDeep } from 'lodash'
@@ -19,7 +16,10 @@ import { IPasteStyle, IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
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()
if (useBuiltinClipboard) {
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard()
@@ -33,8 +33,11 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
if (img.length > 0) {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
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)
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)
if (isShowResultNotification) {
const notification = new Notification({
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 rawInput = cloneDeep(input)
const imgs = await uploader.setWebContents(webContents).upload(input)
@@ -86,14 +92,19 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
if (deleteLocalFile) {
fs.remove(rawInput[i]).then(() => {
picgo.log.info(`delete local file: ${rawInput[i]}`)
}).catch((err: Error) => {
picgo.log.error(err)
})
fs.remove(rawInput[i])
.then(() => {
picgo.log.info(`delete local file: ${rawInput[i]}`)
})
.catch((err: Error) => {
picgo.log.error(err)
})
}
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)
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)
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),

View File

@@ -1,11 +1,5 @@
import dayjs from 'dayjs'
import {
BrowserWindow,
clipboard,
ipcMain,
Notification,
WebContents
} from 'electron'
import { BrowserWindow, clipboard, ipcMain, Notification, WebContents } from 'electron'
import fs from 'fs-extra'
import util from 'util'
import path from 'path'
@@ -21,17 +15,13 @@ import logger from '@core/picgo/logger'
import { T } from '~/i18n'
import { showNotification, getClipboardFilePath, calcDurationRange } from '~/utils/common'
import {
GET_RENAME_FILE_NAME,
RENAME_FILE_NAME,
TALKING_DATA_EVENT
} from '#/events/constants'
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME, TALKING_DATA_EVENT } from '#/events/constants'
import { ICOREBuildInEvent, IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static'
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
return new Promise((resolve) => {
const waitForRename = (window: BrowserWindow, id: number): Promise<string | null> => {
return new Promise(resolve => {
const windowId = window.id
ipcMain.once(`${RENAME_FILE_NAME}${id}`, (_: Event, newName: string) => {
resolve(newName)
@@ -62,11 +52,11 @@ const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) =>
class Uploader {
private webContents: WebContents | null = null
constructor () {
constructor() {
this.init()
}
init () {
init() {
picgo.on(ICOREBuildInEvent.NOTIFICATION, (message: Electron.NotificationConstructorOptions | undefined) => {
const notification = new Notification(message)
notification.show()
@@ -91,37 +81,39 @@ class Uploader {
const rename = db.get(configPaths.settings.rename)
const autoRename = db.get(configPaths.settings.autoRename)
if (autoRename || rename) {
await Promise.all(ctx.output.map(async (item, index) => {
let name: undefined | string | null
let fileName: string | undefined
if (autoRename) {
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS') + item.extname
} else {
fileName = item.fileName
}
if (rename) {
const window = windowManager.create(IWindowList.RENAME_WINDOW)!
logger.info('create rename window')
ipcMain.on(GET_RENAME_FILE_NAME, (evt) => {
try {
if (evt.sender.id === window.webContents.id) {
logger.info('rename window ready, wait for rename...')
window.webContents.send(RENAME_FILE_NAME, fileName, item.fileName, window.webContents.id)
await Promise.all(
ctx.output.map(async (item, index) => {
let name: undefined | string | null
let fileName: string | undefined
if (autoRename) {
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS') + item.extname
} else {
fileName = item.fileName
}
if (rename) {
const window = windowManager.create(IWindowList.RENAME_WINDOW)!
logger.info('create rename window')
ipcMain.on(GET_RENAME_FILE_NAME, evt => {
try {
if (evt.sender.id === window.webContents.id) {
logger.info('rename window ready, wait for rename...')
window.webContents.send(RENAME_FILE_NAME, fileName, item.fileName, window.webContents.id)
}
} catch (e: any) {
logger.error(e)
}
} catch (e: any) {
logger.error(e)
}
})
name = await waitForRename(window, window.webContents.id)
}
item.fileName = name || fileName
}))
})
name = await waitForRename(window, window.webContents.id)
}
item.fileName = name || fileName
})
)
}
}
})
}
setWebContents (webContents: WebContents) {
setWebContents(webContents: WebContents) {
this.webContents = webContents
return this
}
@@ -129,7 +121,7 @@ class Uploader {
/**
* use electron's clipboard image to upload
*/
async uploadWithBuildInClipboard (): Promise<ImgInfo[]|false> {
async uploadWithBuildInClipboard(): Promise<ImgInfo[] | false> {
let filePath = ''
try {
const imgPath = getClipboardFilePath()
@@ -157,7 +149,7 @@ class Uploader {
}
}
async upload (img?: IUploadOption): Promise<ImgInfo[]|false> {
async upload(img?: IUploadOption): Promise<ImgInfo[] | false> {
try {
const startTime = Date.now()
const output = await picgo.upload(img)

View File

@@ -1,25 +1,26 @@
const isDevelopment = process.env.NODE_ENV !== 'production'
export const MANUAL_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#documents`
: 'picgo://./index.html#documents'
export const MANUAL_WINDOW_URL =
process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#documents`
: 'picgo://./index.html#documents'
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'
export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page`
: 'picgo://./index.html#rename-page'
export const RENAME_WINDOW_URL =
process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#rename-page`
: 'picgo://./index.html#rename-page'
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'
export const TRAY_WINDOW_URL = isDevelopment
? (process.env.WEBPACK_DEV_SERVER_URL as string)
: 'picgo://./index.html'
export const TRAY_WINDOW_URL = isDevelopment ? (process.env.WEBPACK_DEV_SERVER_URL as string) : 'picgo://./index.html'
export const TOOLBOX_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#toolbox-page`
: 'picgo://./index.html#toolbox-page'
export const TOOLBOX_WINDOW_URL =
process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#toolbox-page`
: 'picgo://./index.html#toolbox-page'

View File

@@ -23,8 +23,11 @@ const windowList = new Map<IWindowList, IWindowListItem>()
const handleWindowParams = (windowURL: string) => windowURL
const getDefaultWindowSizes = (): { width: number, height: number } => {
const [mainWindowWidth, mainWindowHeight] = db.get([configPaths.settings.mainWindowWidth, configPaths.settings.mainWindowHeight])
const getDefaultWindowSizes = (): { width: number; height: number } => {
const [mainWindowWidth, mainWindowHeight] = db.get([
configPaths.settings.mainWindowWidth,
configPaths.settings.mainWindowHeight
])
return {
width: mainWindowWidth || 1200,
height: mainWindowHeight || 800
@@ -176,7 +179,7 @@ windowList.set(IWindowList.TRAY_WINDOW, {
isValid: process.platform !== 'linux',
multiple: false,
options: () => trayWindowOptions,
callback (window) {
callback(window) {
window.loadURL(handleWindowParams(TRAY_WINDOW_URL))
window.on('blur', () => {
window.hide()
@@ -188,7 +191,7 @@ windowList.set(IWindowList.MANUAL_WINDOW, {
isValid: true,
multiple: false,
options: () => manualWindowOptions,
callback (window) {
callback(window) {
window.loadURL(handleWindowParams(MANUAL_WINDOW_URL))
window.focus()
}
@@ -198,7 +201,7 @@ windowList.set(IWindowList.SETTING_WINDOW, {
isValid: true,
multiple: false,
options: () => settingWindowOptions,
callback (window, windowManager) {
callback(window, windowManager) {
window.loadURL(handleWindowParams(SETTING_WINDOW_URL))
window.on('closed', () => {
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
@@ -217,7 +220,7 @@ windowList.set(IWindowList.MINI_WINDOW, {
isValid: process.platform !== 'darwin',
multiple: false,
options: () => miniWindowOptions,
callback (window) {
callback(window) {
window.loadURL(handleWindowParams(MINI_WINDOW_URL))
}
})
@@ -226,18 +229,19 @@ windowList.set(IWindowList.RENAME_WINDOW, {
isValid: true,
multiple: true,
options: () => renameWindowOptions,
async callback (window, windowManager) {
async callback(window, windowManager) {
window.loadURL(handleWindowParams(RENAME_WINDOW_URL))
const currentWindow = windowManager.getAvailableWindow(true)
if (currentWindow && currentWindow.isVisible()) {
// bounds: { x: 821, y: 75, width: 800, height: 450 }
// bounds: { x: 821, y: 75, width: 800, height: 450 }
const bounds = currentWindow.getBounds()
const positionX = bounds.x + bounds.width / 2 - 150
let positionY
// if is the settingWindow
if (bounds.height > 400) {
positionY = bounds.y + bounds.height / 2 - 88
} else { // if is the miniWindow
} else {
// if is the miniWindow
positionY = bounds.y + bounds.height / 2
}
window.setPosition(positionX, positionY, false)
@@ -249,7 +253,7 @@ windowList.set(IWindowList.TOOLBOX_WINDOW, {
isValid: true,
multiple: false,
options: () => toolboxWindowOptions,
async callback (window, windowManager) {
async callback(window, windowManager) {
window.loadURL(TOOLBOX_WINDOW_URL)
const currentWindow = windowManager.getAvailableWindow(true)
if (currentWindow && currentWindow.isVisible()) {

View File

@@ -7,7 +7,7 @@ class WindowManager implements IWindowManager {
#windowMap: Map<IWindowList | string, BrowserWindow> = new Map()
#windowIdMap: Map<number, IWindowList | string> = new Map()
create (name: IWindowList) {
create(name: IWindowList) {
const windowConfig: IWindowListItem = windowList.get(name)!
if (windowConfig.isValid) {
if (!windowConfig.multiple) {
@@ -32,7 +32,7 @@ class WindowManager implements IWindowManager {
}
}
get (name: IWindowList) {
get(name: IWindowList) {
if (this.has(name)) {
return this.#windowMap.get(name)!
} else {
@@ -41,7 +41,7 @@ class WindowManager implements IWindowManager {
}
}
has (name: IWindowList) {
has(name: IWindowList) {
return this.#windowMap.has(name)
}
@@ -53,7 +53,7 @@ class WindowManager implements IWindowManager {
}
}
getAvailableWindow (isSkipMiniWindow = false) {
getAvailableWindow(isSkipMiniWindow = false) {
const miniWindow = this.#windowMap.get(IWindowList.MINI_WINDOW)
if (miniWindow && miniWindow.isVisible() && !isSkipMiniWindow) {
return miniWindow

View File

@@ -11,10 +11,10 @@ import {
} from '@core/bus/constants'
export const uploadWithClipboardFiles = (): Promise<{
success: boolean,
success: boolean
result?: string[]
}> => {
return new Promise((resolve) => {
return new Promise(resolve => {
bus.once(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, (result: string) => {
if (result) {
return resolve({
@@ -31,11 +31,13 @@ export const uploadWithClipboardFiles = (): Promise<{
})
}
export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
success: boolean,
export const uploadWithFiles = (
pathList: IFileWithPath[]
): Promise<{
success: boolean
result?: string[]
}> => {
return new Promise((resolve) => {
return new Promise(resolve => {
bus.once(UPLOAD_WITH_FILES_RESPONSE, (result: string[]) => {
if (result.length) {
return resolve({
@@ -55,7 +57,7 @@ export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
// get available window id:
// miniWindow or settingWindow or trayWindow
export const getWindowId = (): Promise<number> => {
return new Promise((resolve) => {
return new Promise(resolve => {
bus.once(GET_WINDOW_ID_REPONSE, (id: number) => {
resolve(id)
})
@@ -65,7 +67,7 @@ export const getWindowId = (): Promise<number> => {
// get settingWindow id:
export const getSettingWindowId = (): Promise<number> => {
return new Promise((resolve) => {
return new Promise(resolve => {
bus.once(GET_SETTING_WINDOW_ID_RESPONSE, (id: number) => {
resolve(id)
})

View File

@@ -25,7 +25,7 @@ const errorMsg = {
/** ensure notification list */
if (!global.notificationList) global.notificationList = []
function dbChecker () {
function dbChecker() {
if (process.type !== 'renderer') {
// db save bak
try {
@@ -54,7 +54,9 @@ function dbChecker () {
fs.unlinkSync(configFilePath)
if (fs.existsSync(configFileBackupPath)) {
try {
configFile = fs.readFileSync(configFileBackupPath, { encoding: 'utf-8' })
configFile = fs.readFileSync(configFileBackupPath, {
encoding: 'utf-8'
})
JSON.parse(configFile)
writeFile.sync(configFilePath, configFile, { encoding: 'utf-8' })
const stats = fs.statSync(configFileBackupPath)
@@ -80,7 +82,7 @@ function dbChecker () {
/**
* Get config path
*/
function dbPathChecker (): string {
function dbPathChecker(): string {
if (_configFilePath) {
return _configFilePath
}
@@ -91,7 +93,9 @@ function dbPathChecker (): string {
return _configFilePath
}
try {
const configString = fs.readFileSync(defaultConfigPath, { encoding: 'utf-8' })
const configString = fs.readFileSync(defaultConfigPath, {
encoding: 'utf-8'
})
const config = JSON.parse(configString)
const userConfigPath: string = config.configPath || ''
if (userConfigPath) {
@@ -118,11 +122,11 @@ function dbPathChecker (): string {
}
}
function dbPathDir () {
function dbPathDir() {
return path.dirname(dbPathChecker())
}
function getGalleryDBPath (): {
function getGalleryDBPath(): {
dbPath: string
dbBackupPath: string
} {
@@ -135,9 +139,4 @@ function getGalleryDBPath (): {
}
}
export {
dbChecker,
dbPathChecker,
dbPathDir,
getGalleryDBPath
}
export { dbChecker, dbPathChecker, dbPathDir, getGalleryDBPath }

View File

@@ -19,7 +19,7 @@ export const DB_PATH: string = getGalleryDBPath().dbPath
class ConfigStore {
#db: JSONStore
constructor () {
constructor() {
this.#db = new JSONStore(CONFIG_PATH)
if (!this.#db.has('picBed')) {
@@ -43,11 +43,11 @@ class ConfigStore {
this.read()
}
read (flush?: boolean): IJSON {
read(flush?: boolean): IJSON {
return this.#db.read(flush)
}
getSingle (key = ''): any {
getSingle(key = ''): any {
if (key === '') {
return this.#db.read(true)
}
@@ -55,43 +55,43 @@ class ConfigStore {
return this.#db.get(key)
}
get (key: string): any
get (key: string[]): any[]
get (key: string | string[] = ''): any {
get(key: string): any
get(key: string[]): any[]
get(key: string | string[] = ''): any {
if (Array.isArray(key)) {
return key.map(k => this.getSingle(k))
}
return this.getSingle(key)
}
set (key: string, value: any): void {
set(key: string, value: any): void {
this.read(true)
return this.#db.set(key, value)
}
has (key: string) {
has(key: string) {
this.read(true)
return this.#db.has(key)
}
unset (key: string, value: any): boolean {
unset(key: string, value: any): boolean {
this.read(true)
return this.#db.unset(key, value)
}
saveConfig (config: Partial<IConfig>): void {
saveConfig(config: Partial<IConfig>): void {
Object.keys(config).forEach((name: string) => {
this.set(name, config[name])
})
}
removeConfig (config: IConfig): void {
removeConfig(config: IConfig): void {
Object.keys(config).forEach((name: string) => {
this.unset(name, config[name])
})
}
getConfigPath () {
getConfigPath() {
return CONFIG_PATH
}
}
@@ -103,11 +103,11 @@ export default db
// v2.3.0 add gallery db
class GalleryDB {
static #instance: DBStore
private constructor () {
private constructor() {
console.log('init gallery db')
}
static getInstance (): DBStore {
static getInstance(): DBStore {
if (!GalleryDB.#instance) {
GalleryDB.#instance = new DBStore(DB_PATH, 'gallery')
}
@@ -115,6 +115,4 @@ class GalleryDB {
}
}
export {
GalleryDB
}
export { GalleryDB }

View File

@@ -21,7 +21,7 @@ picgo.GUI_VERSION = global.PICGO_GUI_VERSION
const originPicGoSaveConfig = picgo.saveConfig.bind(picgo)
function flushDB () {
function flushDB() {
db.read(true)
}

View File

@@ -94,6 +94,4 @@ const getLogger = (logPath: string, logType: string) => {
}
}
export {
getLogger
}
export { getLogger }

View File

@@ -1,9 +1,4 @@
import {
BrowserWindow,
dialog,
ipcMain,
Notification
} from 'electron'
import { BrowserWindow, dialog, ipcMain, Notification } from 'electron'
import fs from 'fs-extra'
import { cloneDeep } from 'lodash'
import { DBStore } from '@picgo/store'
@@ -29,55 +24,57 @@ class GuiApi implements IGuiApi {
private static instance: GuiApi
private windowId: number = -1
private settingWindowId: number = -1
private constructor () {
private constructor() {
console.log('init guiapi')
}
static getInstance (): GuiApi {
static getInstance(): GuiApi {
if (!GuiApi.instance) {
GuiApi.instance = new GuiApi()
}
return GuiApi.instance
}
private async showSettingWindow () {
private async showSettingWindow() {
this.settingWindowId = await getSettingWindowId()
const settingWindow = BrowserWindow.fromId(this.settingWindowId)
if (settingWindow?.isVisible()) {
return true
}
settingWindow?.show()
return new Promise<void>((resolve) => {
return new Promise<void>(resolve => {
setTimeout(() => {
resolve()
}, 1000) // TODO: a better way to wait page loaded.
})
}
private getWebcontentsByWindowId (id: number) {
private getWebcontentsByWindowId(id: number) {
return BrowserWindow.fromId(id)?.webContents
}
async showInputBox (options: IShowInputBoxOption = {
title: '',
placeholder: ''
}) {
async showInputBox(
options: IShowInputBoxOption = {
title: '',
placeholder: ''
}
) {
await this.showSettingWindow()
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) => {
resolve(value)
})
})
}
async showFileExplorer (options: IShowFileExplorerOption = {}) {
async showFileExplorer(options: IShowFileExplorerOption = {}) {
this.windowId = await getWindowId()
const res = await dialog.showOpenDialog(BrowserWindow.fromId(this.windowId)!, options)
return res.filePaths || []
}
async upload (input: IUploadOption) {
async upload(input: IUploadOption) {
this.windowId = await getWindowId()
const webContents = this.getWebcontentsByWindowId(this.windowId)
const rawInput = cloneDeep(input)
@@ -90,8 +87,11 @@ class GuiApi implements IGuiApi {
if (deleteLocalFile) {
await fs.remove(rawInput[i])
}
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)
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)
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),
@@ -112,10 +112,12 @@ class GuiApi implements IGuiApi {
return []
}
showNotification (options: IShowNotificationOption = {
title: '',
body: ''
}) {
showNotification(
options: IShowNotificationOption = {
title: '',
body: ''
}
) {
const notification = new Notification({
title: options.title,
body: options.body
@@ -123,18 +125,17 @@ class GuiApi implements IGuiApi {
notification.show()
}
showMessageBox (options: IShowMessageBoxOption = {
title: '',
message: '',
type: 'info',
buttons: ['Yes', 'No']
}) {
return new Promise<IShowMessageBoxResult>(async (resolve) => {
showMessageBox(
options: IShowMessageBoxOption = {
title: '',
message: '',
type: 'info',
buttons: ['Yes', 'No']
}
) {
return new Promise<IShowMessageBoxResult>(async resolve => {
this.windowId = await getWindowId()
dialog.showMessageBox(
BrowserWindow.fromId(this.windowId)!,
options
).then((res) => {
dialog.showMessageBox(BrowserWindow.fromId(this.windowId)!, options).then(res => {
resolve({
result: res.response,
checkboxChecked: res.checkboxChecked
@@ -146,7 +147,7 @@ class GuiApi implements IGuiApi {
/**
* get picgo config/data path
*/
async getConfigPath () {
async getConfigPath() {
const currentConfigPath = dbPathChecker()
const galleryDBPath = getGalleryDBPath().dbPath
return {
@@ -156,47 +157,51 @@ class GuiApi implements IGuiApi {
}
}
get galleryDB (): DBStore {
get galleryDB(): DBStore {
return new Proxy<DBStore>(GalleryDB.getInstance(), {
get (target, prop: keyof DBStore) {
get(target, prop: keyof DBStore) {
if (prop === 'overwrite') {
return new Proxy(GalleryDB.getInstance().overwrite, {
apply (target, ctx, args) {
return new Promise((resolve) => {
apply(target, ctx, args) {
return new Promise(resolve => {
const guiApi = GuiApi.getInstance()
guiApi.showMessageBox({
title: T('TIPS_WARNING'),
message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'),
type: 'info',
buttons: ['Yes', 'No']
}).then(res => {
if (res.result === 0) {
resolve(Reflect.apply(target, ctx, args))
} else {
resolve(undefined)
}
})
guiApi
.showMessageBox({
title: T('TIPS_WARNING'),
message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'),
type: 'info',
buttons: ['Yes', 'No']
})
.then(res => {
if (res.result === 0) {
resolve(Reflect.apply(target, ctx, args))
} else {
resolve(undefined)
}
})
})
}
})
}
if (prop === 'removeById') {
return new Proxy(GalleryDB.getInstance().removeById, {
apply (target, ctx, args) {
return new Promise((resolve) => {
apply(target, ctx, args) {
return new Promise(resolve => {
const guiApi = GuiApi.getInstance()
guiApi.showMessageBox({
title: T('TIPS_WARNING'),
message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'),
type: 'info',
buttons: ['Yes', 'No']
}).then(res => {
if (res.result === 0) {
resolve(Reflect.apply(target, ctx, args))
} else {
resolve(undefined)
}
})
guiApi
.showMessageBox({
title: T('TIPS_WARNING'),
message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'),
type: 'info',
buttons: ['Yes', 'No']
})
.then(res => {
if (res.result === 0) {
resolve(Reflect.apply(target, ctx, args))
} else {
resolve(undefined)
}
})
})
}
})

View File

@@ -11,18 +11,13 @@ import {
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE
} from '@core/bus/constants'
import {
createMenu
} from 'apis/app/system'
import {
uploadChoosedFiles,
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import { createMenu } from 'apis/app/system'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum'
function initEventCenter () {
function initEventCenter() {
const eventList: any = {
'picgo:upload': uploadClipboardFiles,
[UPLOAD_WITH_CLIPBOARD_FILES]: busCallUploadClipboardFiles,
@@ -36,31 +31,31 @@ function initEventCenter () {
}
}
async function busCallUploadClipboardFiles () {
async function busCallUploadClipboardFiles() {
const result = await uploadClipboardFiles()
const imgUrl = result.url
bus.emit(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, imgUrl)
}
async function busCallUploadFiles (pathList: IFileWithPath[]) {
async function busCallUploadFiles(pathList: IFileWithPath[]) {
const win = windowManager.getAvailableWindow()
const result = await uploadChoosedFiles(win.webContents, pathList)
const urls = result.map((item: any) => item.url)
bus.emit(UPLOAD_WITH_FILES_RESPONSE, urls)
}
function busCallGetWindowId () {
function busCallGetWindowId() {
const win = windowManager.getAvailableWindow()
bus.emit(GET_WINDOW_ID_REPONSE, win.id)
}
function busCallGetSettingWindowId () {
function busCallGetSettingWindowId() {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
bus.emit(GET_SETTING_WINDOW_ID_RESPONSE, settingWindow.id)
}
export default {
listen () {
listen() {
initEventCenter()
}
}

View File

@@ -1,20 +1,10 @@
import {
app,
dialog,
BrowserWindow,
Menu,
shell,
MenuItemConstructorOptions,
MenuItem
} from 'electron'
import { app, dialog, BrowserWindow, Menu, shell, MenuItemConstructorOptions, MenuItem } from 'electron'
import { PicGo as PicGoCore } from 'piclist'
import db from '@core/datastore'
import picgo from '@core/picgo'
import {
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import { uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import GuiApi from 'apis/gui'
@@ -47,7 +37,7 @@ const buildMiniPageMenu = () => {
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const ClipboardWatcher = clipboardPoll
const submenu = buildPicBedListMenu()
const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
const template: Array<MenuItemConstructorOptions | MenuItem> = [
{
label: T('OPEN_MAIN_WINDOW'),
click: openMainWindow
@@ -59,19 +49,19 @@ const buildMiniPageMenu = () => {
},
{
label: T('UPLOAD_BY_CLIPBOARD'),
click () {
click() {
uploadClipboardFiles()
}
},
{
label: T('HIDE_MINI_WINDOW'),
click () {
click() {
BrowserWindow.getFocusedWindow()!.hide()
}
},
{
label: T('START_WATCH_CLIPBOARD'),
click () {
click() {
db.set(configPaths.settings.isListeningClipboard, true)
ClipboardWatcher.startListening()
ClipboardWatcher.on('change', () => {
@@ -84,7 +74,7 @@ const buildMiniPageMenu = () => {
},
{
label: T('STOP_WATCH_CLIPBOARD'),
click () {
click() {
db.set(configPaths.settings.isListeningClipboard, false)
ClipboardWatcher.stopListening()
ClipboardWatcher.removeAllListeners()
@@ -94,7 +84,7 @@ const buildMiniPageMenu = () => {
},
{
label: T('RELOAD_APP'),
click () {
click() {
app.relaunch()
app.exit(0)
}
@@ -111,7 +101,7 @@ const buildMainPageMenu = (win: BrowserWindow) => {
const template = [
{
label: T('ABOUT'),
click () {
click() {
dialog.showMessageBox({
title: 'PicList',
message: 'PicList',
@@ -121,32 +111,31 @@ const buildMainPageMenu = (win: BrowserWindow) => {
},
{
label: T('SHOW_PICBED_QRCODE'),
click () {
click() {
win?.webContents?.send(SHOW_MAIN_PAGE_QRCODE)
}
},
{
label: T('OPEN_TOOLBOX'),
click () {
click() {
const window = windowManager.create(IWindowList.TOOLBOX_WINDOW)
window?.show()
}
},
{
label: T('SHOW_DEVTOOLS'),
click () {
click() {
win?.webContents?.openDevTools({ mode: 'detach' })
}
},
{
label: T('FEEDBACK'),
click () {
click() {
const url = 'https://github.com/Kuingsmile/PicList/issues'
shell.openExternal(url)
}
}
]
// @ts-ignore
] as Array<MenuItemConstructorOptions | MenuItem>
return Menu.buildFromTemplate(template)
}
@@ -155,55 +144,60 @@ const buildPicBedListMenu = () => {
const currentPicBed = picgo.getConfig(configPaths.picBed.uploader)
const currentPicBedName = picBeds.find(item => item.type === currentPicBed)?.name
const picBedConfigList = picgo.getConfig<IUploaderConfig>('uploader')
const currentPicBedMenuItem = [{
label: `${T('CURRENT_PICBED')} - ${currentPicBedName}`,
enabled: false
}, {
type: 'separator'
}]
let submenu = picBeds.filter(item => item.visible).map(item => {
const configList = picBedConfigList?.[item.type]?.configList
const defaultId = picBedConfigList?.[item.type]?.defaultId
const hasSubmenu = !!configList
return {
label: item.name,
type: !hasSubmenu ? 'checkbox' : undefined,
checked: !hasSubmenu ? (currentPicBed === item.type) : undefined,
submenu: hasSubmenu
? configList.map((config) => {
return {
label: config._configName || 'Default',
// if only one config, use checkbox, or radio will checked as default
// see: https://github.com/electron/electron/issues/21292
type: 'checkbox',
checked: config._id === defaultId && (item.type === currentPicBed),
click: function () {
changeCurrentUploader(item.type, config, config._id)
const currentPicBedMenuItem = [
{
label: `${T('CURRENT_PICBED')} - ${currentPicBedName}`,
enabled: false
},
{
type: 'separator'
}
]
let submenu = picBeds
.filter(item => item.visible)
.map(item => {
const configList = picBedConfigList?.[item.type]?.configList
const defaultId = picBedConfigList?.[item.type]?.defaultId
const hasSubmenu = !!configList
return {
label: item.name,
type: !hasSubmenu ? 'checkbox' : undefined,
checked: !hasSubmenu ? currentPicBed === item.type : undefined,
submenu: hasSubmenu
? configList.map(config => {
return {
label: config._configName || 'Default',
// if only one config, use checkbox, or radio will checked as default
// see: https://github.com/electron/electron/issues/21292
type: 'checkbox',
checked: config._id === defaultId && item.type === currentPicBed,
click: function () {
changeCurrentUploader(item.type, config, config._id)
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
setTrayToolTip(`${item.type} ${config._configName || 'Default'}`)
}
}
})
: undefined,
click: !hasSubmenu
? function () {
picgo.saveConfig({
[configPaths.picBed.current]: item.type,
[configPaths.picBed.uploader]: item.type
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
setTrayToolTip(`${item.type} ${config._configName || 'Default'}`)
setTrayToolTip(item.type)
}
}
})
: undefined,
click: !hasSubmenu
? function () {
picgo.saveConfig({
[configPaths.picBed.current]: item.type,
[configPaths.picBed.uploader]: item.type
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
setTrayToolTip(item.type)
}
: undefined
}
})
// @ts-ignore
: undefined
}
})
// @ts-expect-error submenu type
submenu = currentPicBedMenuItem.concat(submenu)
// @ts-ignore
// @ts-expect-error submenu type
return Menu.buildFromTemplate(submenu)
}
@@ -230,56 +224,61 @@ const handleRestoreState = (item: string, name: string): void => {
}
const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const menu = [{
label: T('ENABLE_PLUGIN'),
enabled: !plugin.enabled,
click () {
picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: true
})
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, true)
}
}, {
label: T('DISABLE_PLUGIN'),
enabled: plugin.enabled,
click () {
picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: false
})
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, false)
window.webContents.send(PICGO_HANDLE_PLUGIN_DONE, plugin.fullName)
if (plugin.config.transformer.name) {
handleRestoreState('transformer', plugin.config.transformer.name)
const menu = [
{
label: T('ENABLE_PLUGIN'),
enabled: !plugin.enabled,
click() {
picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: true
})
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, true)
}
if (plugin.config.uploader.name) {
handleRestoreState('uploader', plugin.config.uploader.name)
},
{
label: T('DISABLE_PLUGIN'),
enabled: plugin.enabled,
click() {
picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: false
})
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, false)
window.webContents.send(PICGO_HANDLE_PLUGIN_DONE, plugin.fullName)
if (plugin.config.transformer.name) {
handleRestoreState('transformer', plugin.config.transformer.name)
}
if (plugin.config.uploader.name) {
handleRestoreState('uploader', plugin.config.uploader.name)
}
}
},
{
label: T('UNINSTALL_PLUGIN'),
click() {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
handlePluginUninstall(plugin.fullName)
}
},
{
label: T('UPDATE_PLUGIN'),
click() {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
handlePluginUpdate(plugin.fullName)
}
}
}, {
label: T('UNINSTALL_PLUGIN'),
click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
handlePluginUninstall(plugin.fullName)
}
}, {
label: T('UPDATE_PLUGIN'),
click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
handlePluginUpdate(plugin.fullName)
}
}]
] as Array<MenuItemConstructorOptions | MenuItem>
for (const i in plugin.config) {
if (plugin.config[i].config.length > 0) {
const obj = {
label: T('CONFIG_THING', {
c: `${i} - ${plugin.config[i].fullName || plugin.config[i].name}`
}),
click () {
click() {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const currentType = i
const configName = plugin.config[i].fullName || plugin.config[i].name
@@ -297,7 +296,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const pluginTransformer = plugin.config.transformer.name
const obj = {
label: `${currentTransformer === pluginTransformer ? T('DISABLE') : T('ENABLE')}transformer - ${plugin.config.transformer.name}`,
click () {
click() {
const transformer = plugin.config.transformer.name
const currentTransformer = picgo.getConfig<string>(configPaths.picBed.transformer) || 'path'
if (currentTransformer === transformer) {
@@ -317,13 +316,12 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
// plugin custom menus
if (plugin.guiMenu) {
menu.push({
// @ts-ignore
type: 'separator'
})
for (const i of plugin.guiMenu) {
menu.push({
label: i.label,
click () {
click() {
const picgPlugin = picgo.pluginLoader.getPlugin(plugin.fullName)
if (picgPlugin?.guiMenu?.(picgo)?.length) {
const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo)
@@ -338,13 +336,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
}
}
// @ts-ignore
return Menu.buildFromTemplate(menu)
}
export {
buildMiniPageMenu,
buildMainPageMenu,
buildPicBedListMenu,
buildPluginPageMenu
}
export { buildMiniPageMenu, buildMainPageMenu, buildPicBedListMenu, buildPluginPageMenu }

View File

@@ -45,12 +45,12 @@ class RPCServer implements IRPCServer {
}
}
start () {
start() {
ipcMain.on(RPC_ACTIONS, this.rpcEventHandler)
ipcMain.handle(RPC_ACTIONS_INVOKE, this.rpcEventHandlerWithResponse)
}
use (routes: IRPCRoutes) {
use(routes: IRPCRoutes) {
for (const [action, route] of routes) {
if (route.type === IRPCType.SEND) {
this.routes.set(action, route)
@@ -60,7 +60,7 @@ class RPCServer implements IRPCServer {
}
}
stop () {
stop() {
ipcMain.off(RPC_ACTIONS, this.rpcEventHandler)
}
}
@@ -82,6 +82,4 @@ for (const route of routes) {
rpcServer.use(route)
}
export {
rpcServer
}
export { rpcServer }

View File

@@ -20,7 +20,7 @@ export class RPCRouter implements IRPCRouter {
return this
}
routes () {
routes() {
return this.routeMap
}
}

View File

@@ -1,4 +1,3 @@
import { clipboard } from 'electron'
import { GalleryDB } from '@core/datastore'
@@ -8,7 +7,12 @@ import { IFilter, IObject } from '@picgo/store/dist/types'
import GuiApi from 'apis/gui'
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 { ICOREBuildInEvent, IPasteStyle, IRPCActionType, IRPCType } from '#/types/enum'
@@ -19,7 +23,7 @@ const galleryRouter = new RPCRouter()
const galleryRoutes = [
{
action: IRPCActionType.GALLERY_PASTE_TEXT,
handler: async (_: IIPCEvent, args: [ item: ImgInfo, copy?: boolean]) => {
handler: async (_: IIPCEvent, args: [item: ImgInfo, copy?: boolean]) => {
const [item, copy = true] = args
const pasteStyle = picgo.getConfig<IPasteStyle>(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const customLink = picgo.getConfig<string>(configPaths.settings.customLink)
@@ -128,6 +132,4 @@ const galleryRoutes = [
galleryRouter.addBatch(galleryRoutes)
export {
galleryRouter
}
export { galleryRouter }

View File

@@ -94,6 +94,4 @@ const picbedRoutes = [
picbedRouter.addBatch(picbedRoutes)
export {
picbedRouter
}
export { picbedRouter }

View File

@@ -1,4 +1,3 @@
import { RPCRouter } from '~/events/rpc/router'
import {
pluginImportLocalFunc,
@@ -32,6 +31,4 @@ const pluginRoutes = [
pluginRouter.addBatch(pluginRoutes)
export {
pluginRouter
}
export { pluginRouter }

View File

@@ -17,7 +17,7 @@ import { ICOREBuildInEvent, IPicGoHelperType, IWindowList } from '#/types/enum'
const STORE_PATH = dbPathDir()
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
// get uploader or transformer config
const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => {
@@ -104,15 +104,17 @@ const getPluginList = (): IPicGoPlugin[] => {
const handleNPMError = (): IDispose => {
const handler = (msg: string) => {
if (msg === 'NPM is not installed') {
dialog.showMessageBox({
title: T('TIPS_ERROR'),
message: T('TIPS_INSTALL_NODE_AND_RELOAD_PICGO'),
buttons: ['Yes']
}).then((res) => {
if (res.response === 0) {
shell.openExternal('https://nodejs.org/')
}
})
dialog
.showMessageBox({
title: T('TIPS_ERROR'),
message: T('TIPS_INSTALL_NODE_AND_RELOAD_PICGO'),
buttons: ['Yes']
})
.then(res => {
if (res.response === 0) {
shell.openExternal('https://nodejs.org/')
}
})
}
}
picgo.once(ICOREBuildInEvent.FAILED, handler)

View File

@@ -1,4 +1,3 @@
import { IRPCActionType } from '#/types/enum'
import server from '~/server'
import webServer from '~/server/webServer'

View File

@@ -16,19 +16,15 @@ export default [
action: IRPCActionType.CONFIGURE_MIGRATE_FROM_PICGO,
handler: async () => {
const picGoConfigPath = STORE_PATH.replace('piclist', 'picgo')
const files = [
'data.json',
'data.bak.json',
'picgo.db',
'picgo.bak.db'
]
const files = ['data.json', 'data.bak.json', 'picgo.db', 'picgo.bak.db']
try {
await Promise.all(files.map(async file => {
const sourcePath = path.join(picGoConfigPath, file)
const targetPath = path.join(STORE_PATH, file.replace('picgo', 'piclist'))
await fs.copy(sourcePath, targetPath, { overwrite: true })
}
))
await Promise.all(
files.map(async file => {
const sourcePath = path.join(picGoConfigPath, file)
const targetPath = path.join(STORE_PATH, file.replace('picgo', 'piclist'))
await fs.copy(sourcePath, targetPath, { overwrite: true })
})
)
return true
} catch (err: any) {
logger.error(err)

View File

@@ -7,15 +7,8 @@ import shortKeyRoutes from '~/events/rpc/routes/setting/shortKey'
const settingRouter = new RPCRouter()
const settingRoutes = [
...advancedRoutes,
...configureRoutes,
...mainAppRoutes,
...shortKeyRoutes
]
const settingRoutes = [...advancedRoutes, ...configureRoutes, ...mainAppRoutes, ...shortKeyRoutes]
settingRouter.addBatch(settingRoutes)
export {
settingRouter
}
export { settingRouter }

View File

@@ -1,6 +1,4 @@
import {
Notification
} from 'electron'
import { Notification } from 'electron'
import bus from '@core/bus'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'

View File

@@ -4,13 +4,8 @@ import windowRoutes from '~/events/rpc/routes/system/window'
const systemRouter = new RPCRouter()
const systemRoutes = [
...appRoutes,
...windowRoutes
]
const systemRoutes = [...appRoutes, ...windowRoutes]
systemRouter.addBatch(systemRoutes)
export {
systemRouter
}
export { systemRouter }

View File

@@ -2,12 +2,7 @@ import { app, BrowserWindow } from 'electron'
import windowManager from 'apis/app/window/windowManager'
import {
buildMainPageMenu,
buildMiniPageMenu,
buildPicBedListMenu,
buildPluginPageMenu
} from '~/events/remotes/menu'
import { buildMainPageMenu, buildMiniPageMenu, buildPicBedListMenu, buildPluginPageMenu } from '~/events/remotes/menu'
import { openMiniWindow } from '~/utils/windowHelper'
import { IRPCActionType, IWindowList } from '#/types/enum'

View File

@@ -14,10 +14,8 @@ const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_
const defaultClipboardImagePath = path.join(defaultConfigPath, CLIPBOARD_IMAGE_FOLDER)
export const checkClipboardUploadMap: IToolboxCheckerMap<
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async (event) => {
export const checkClipboardUploadMap: IToolboxCheckerMap<IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async event => {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
@@ -54,9 +52,7 @@ IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
}
}
export const fixClipboardUploadMap: IToolboxFixMap<
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
> = {
export const fixClipboardUploadMap: IToolboxFixMap<IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async () => {
const configFilePath = dbPathChecker()
const dirPath = path.dirname(configFilePath)

View File

@@ -11,7 +11,7 @@ import { T } from '~/i18n'
import { IToolboxItemCheckStatus, IToolboxItemType } from '#/types/enum'
export const checkFileMap: IToolboxCheckerMap<
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
> = {
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async (event: IpcMainEvent) => {
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_CONFIG_FILE_BROKEN)
@@ -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)
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
@@ -63,7 +63,7 @@ IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
}
export const fixFileMap: IToolboxFixMap<
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
> = {
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async () => {
try {

View File

@@ -1,4 +1,3 @@
import axios, { AxiosRequestConfig } from 'axios'
import fs from 'fs-extra'
import { IConfig } from 'piclist'
@@ -11,7 +10,7 @@ import { T } from '~/i18n'
import { IToolboxItemCheckStatus, IToolboxItemType } from '#/types/enum'
function getProxy (proxyStr: string): AxiosRequestConfig['proxy'] | null {
function getProxy(proxyStr: string): AxiosRequestConfig['proxy'] | null {
if (proxyStr) {
try {
const proxyOptions = new URL(proxyStr)
@@ -27,10 +26,8 @@ function getProxy (proxyStr: string): AxiosRequestConfig['proxy'] | null {
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
export const checkProxyMap: IToolboxCheckerMap<
IToolboxItemType.HAS_PROBLEM_WITH_PROXY
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async (event) => {
export const checkProxyMap: IToolboxCheckerMap<IToolboxItemType.HAS_PROBLEM_WITH_PROXY> = {
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async event => {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
@@ -38,9 +35,8 @@ IToolboxItemType.HAS_PROBLEM_WITH_PROXY
if (fs.existsSync(configFilePath)) {
let config: IConfig | undefined
try {
config = await fs.readJSON(configFilePath) as IConfig
} catch (e) {
}
config = (await fs.readJSON(configFilePath)) as IConfig
} catch (e) {}
if (!config) {
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,

View File

@@ -30,7 +30,7 @@ toolboxRouter
handler(event as IpcMainEvent)
}
} else {
// do check all
// do check all
for (const key in toolboxCheckMap) {
const handler = toolboxCheckMap[key as IToolboxItemType]
if (handler) {
@@ -42,7 +42,8 @@ toolboxRouter
IRPCType.SEND
)
.add(
IRPCActionType.TOOLBOX_CHECK_FIX, async (event, args) => {
IRPCActionType.TOOLBOX_CHECK_FIX,
async (event, args) => {
const [type] = args as IToolboxCheckArgs
const handler = toolboxFixMap[type]
if (handler) {
@@ -52,6 +53,4 @@ toolboxRouter
IRPCType.INVOKE
)
export {
toolboxRouter
}
export { toolboxRouter }

View File

@@ -1,7 +1,7 @@
import { IpcMainEvent } from 'electron'
import { IRPCActionType, IToolboxItemType } from '#/types/enum'
export function sendToolboxResWithType (type: IToolboxItemType) {
export function sendToolboxResWithType(type: IToolboxItemType) {
return (event: IpcMainEvent, res?: Omit<IToolboxCheckRes, 'type'>) => {
return event.sender.send(IRPCActionType.TOOLBOX_CHECK_RES, {
...res,

View File

@@ -1,6 +1,4 @@
import {
Notification
} from 'electron'
import { Notification } from 'electron'
import { RPCRouter } from '~/events/rpc/router'
import { generateShortUrl, setTrayToolTip, handleCopyUrl } from '~/utils/common'
@@ -42,8 +40,11 @@ const trayRoutes = [
const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard()
if (img !== false) {
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
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)
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)
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),
@@ -66,6 +67,4 @@ const trayRoutes = [
trayRouter.addBatch(trayRoutes)
export {
trayRouter
}
export { trayRouter }

View File

@@ -30,6 +30,4 @@ const uploadRoutes = [
uploadRouter.addBatch(uploadRoutes)
export {
uploadRouter
}
export { uploadRouter }

View File

@@ -12,7 +12,7 @@ const serverPort = 36699
let server: http.Server
export function startFileServer () {
export function startFileServer() {
server = http.createServer((req, res) => {
const requestPath = req.url?.split('?')[0]
const filePath = path.join(imgFilePath, decodeURIComponent(requestPath as string))
@@ -27,14 +27,16 @@ export function startFileServer () {
})
})
server.listen(serverPort, () => {
logger.info(`File server is running, http://localhost:${serverPort}`)
}).on('error', (err) => {
logger.error(err)
})
server
.listen(serverPort, () => {
logger.info(`File server is running, http://localhost:${serverPort}`)
})
.on('error', err => {
logger.error(err)
})
}
export function stopFileServer () {
export function stopFileServer() {
server.close(() => {
logger.info('File server is stopped')
})

View File

@@ -15,18 +15,18 @@ class I18nManager {
readonly defaultLanguage: string = 'zh-CN'
private i18nFileList: II18nItem[] = builtinI18nList
setOutterI18nFolder (folder: string) {
setOutterI18nFolder(folder: string) {
this.outterI18nFolder = folder
}
addI18nFile (file: string, label: string) {
addI18nFile(file: string, label: string) {
this.i18nFileList.push({
label,
value: file
})
}
private getLocales (lang: string): ILocales {
private getLocales(lang: string): ILocales {
if (this.localesMap.has(lang)) {
return this.localesMap.get(lang)!
}
@@ -53,13 +53,13 @@ class I18nManager {
}
}
setCurrentLanguage (lang: string) {
setCurrentLanguage(lang: string) {
const locales = this.getLocales(lang)
this.currentLanguage = lang
this.initI18n(lang, locales)
}
private initI18n (lang: string = this.defaultLanguage, locales: ILocales) {
private initI18n(lang: string = this.defaultLanguage, locales: ILocales) {
const objectAdapter = new ObjectAdapter({
[lang]: locales
})
@@ -69,15 +69,15 @@ class I18nManager {
})
}
T (key: ILocalesKey, args: IStringKeyMap = {}): string {
T(key: ILocalesKey, args: IStringKeyMap = {}): string {
return this.i18n?.translate(key, args) || key
}
get languageList () {
get languageList() {
return this.i18nFileList
}
getCurrentLocales () {
getCurrentLocales() {
return {
lang: this.currentLanguage,
locales: this.getLocales(this.currentLanguage)

View File

@@ -24,9 +24,9 @@ process.on('unhandledRejection', (error: any) => {
})
// acconrding to https://github.com/Molunerfinn/PicGo/commit/7363be798cfef11e980934e542817ff1d6c04389#diff-896d0db4fbd446798fbffec14d456b4cd98d4c72c46856c770a585fa7ab0926f
function bootstrapEPIPESuppression () {
function bootstrapEPIPESuppression() {
let suppressing = false
function logEPIPEErrorOnce () {
function logEPIPEErrorOnce() {
if (suppressing) {
return
}

View File

@@ -1,20 +1,16 @@
// 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
// @ts-nocheck
// @ts-nocheck since the module is not pure ESM
// shell-path 3.0.0 not work
const shellPath = require('shell-path')
export default function fixPath () {
export default function fixPath() {
if (process.platform === 'win32') {
return
}
process.env.PATH = shellPath.sync() || [
'./node_modules/.bin',
'/.nodebrew/current/bin',
'/usr/local/bin',
process.env.PATH
].join(':')
process.env.PATH =
shellPath.sync() || ['./node_modules/.bin', '/.nodebrew/current/bin', '/usr/local/bin', process.env.PATH].join(':')
}

View File

@@ -1,19 +1,9 @@
import axios from 'axios'
import fs from 'fs-extra'
import {
app,
globalShortcut,
protocol,
Notification,
dialog,
screen,
shell
} from 'electron'
import { app, globalShortcut, protocol, Notification, dialog, screen, shell } from 'electron'
import { UpdateInfo, autoUpdater } from 'electron-updater'
import path from 'path'
import {
createProtocol
} from 'vue-cli-plugin-electron-builder/lib'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import bus from '@core/bus'
import db from '@core/datastore'
@@ -22,13 +12,8 @@ import logger from '@core/picgo/logger'
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import {
createTray, setDockMenu
} from 'apis/app/system'
import {
uploadChoosedFiles,
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import { createTray, setDockMenu } from 'apis/app/system'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import busEventList from '~/events/busEventList'
@@ -57,7 +42,8 @@ const isDevelopment = process.env.NODE_ENV !== 'production'
const handleStartUpFiles = (argv: string[], cwd: string) => {
const files = getUploadFiles(argv, cwd, logger)
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动
if (files === null || files.length > 0) {
// 如果有文件列表作为参数,说明是命令行启动
if (files === null) {
logger.info('cli -> uploading file from clipboard')
uploadClipboardFiles()
@@ -83,34 +69,43 @@ autoUpdater.on('update-available', async (info: UpdateInfo) => {
const lang = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN
let updateLog = ''
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)
updateLog = res.data
} catch (e: any) {
logger.error(e)
}
dialog.showMessageBox({
type: 'info',
title: T('FIND_NEW_VERSION'),
buttons: ['Yes', 'Go to download page'],
message: T('TIPS_FIND_NEW_VERSION', {
v: info.version
}) + '\n\n' + updateLog,
checkboxLabel: T('NO_MORE_NOTICE'),
checkboxChecked: false
}).then((result) => {
if (result.response === 0) {
autoUpdater.downloadUpdate()
} else {
shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest')
}
db.set(configPaths.settings.showUpdateTip, !result.checkboxChecked)
}).catch((err) => {
logger.error(err)
})
dialog
.showMessageBox({
type: 'info',
title: T('FIND_NEW_VERSION'),
buttons: ['Yes', 'Go to download page'],
message:
T('TIPS_FIND_NEW_VERSION', {
v: info.version
}) +
'\n\n' +
updateLog,
checkboxLabel: T('NO_MORE_NOTICE'),
checkboxChecked: false
})
.then(result => {
if (result.response === 0) {
autoUpdater.downloadUpdate()
} else {
shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest')
}
db.set(configPaths.settings.showUpdateTip, !result.checkboxChecked)
})
.catch(err => {
logger.error(err)
})
})
autoUpdater.on('download-progress', (progressObj) => {
autoUpdater.on('download-progress', progressObj => {
const percent = {
progress: progressObj.percent
}
@@ -119,28 +114,31 @@ autoUpdater.on('download-progress', (progressObj) => {
})
autoUpdater.on('update-downloaded', () => {
dialog.showMessageBox({
type: 'info',
title: T('UPDATE_DOWNLOADED'),
buttons: ['Yes', 'No'],
message: T('TIPS_UPDATE_DOWNLOADED')
}).then((result) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send('updateProgress', { progress: 100 })
if (result.response === 0) {
autoUpdater.quitAndInstall()
}
}).catch((err) => {
logger.error(err)
})
dialog
.showMessageBox({
type: 'info',
title: T('UPDATE_DOWNLOADED'),
buttons: ['Yes', 'No'],
message: T('TIPS_UPDATE_DOWNLOADED')
})
.then(result => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send('updateProgress', { progress: 100 })
if (result.response === 0) {
autoUpdater.quitAndInstall()
}
})
.catch(err => {
logger.error(err)
})
})
autoUpdater.on('error', (err) => {
autoUpdater.on('error', err => {
console.log(err)
})
class LifeCycle {
async #beforeReady () {
async #beforeReady() {
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
// fix the $PATH in macOS & linux
fixPath()
@@ -153,7 +151,7 @@ class LifeCycle {
busEventList.listen()
}
#onReady () {
#onReady() {
const readyFunction = async () => {
createProtocol('picgo')
windowManager.create(IWindowList.TRAY_WINDOW)
@@ -173,7 +171,6 @@ class LifeCycle {
const isHideDock = db.get(configPaths.settings.isHideDock) || false
const startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET
const currentPicBed = db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms'
// @ts-ignore
const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default'
const tooltip = `${currentPicBed} ${currentPicBedConfig}`
if (process.platform === 'darwin') {
@@ -237,7 +234,7 @@ class LifeCycle {
app.whenReady().then(readyFunction)
}
#onRunning () {
#onRunning() {
app.on('second-instance', (_, commandLine, workingDirectory) => {
logger.info('detect second instance')
const result = handleStartUpFiles(commandLine, workingDirectory)
@@ -272,7 +269,7 @@ class LifeCycle {
}
}
#onQuit () {
#onQuit() {
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
@@ -304,7 +301,7 @@ class LifeCycle {
}
}
async launchApp () {
async launchApp() {
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
@@ -319,6 +316,4 @@ class LifeCycle {
const bootstrap = new LifeCycle()
export {
bootstrap
}
export { bootstrap }

View File

@@ -8,7 +8,13 @@ import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
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 { isImage } from '#/utils/common'
@@ -22,7 +28,7 @@ class AliyunApi {
timeOut = 30000
logger: ManageLogger
constructor (accessKeyId: string, accessKeySecret: string, logger: ManageLogger) {
constructor(accessKeyId: string, accessKeySecret: string, logger: ManageLogger) {
this.ctx = new OSS({
accessKeyId,
accessKeySecret,
@@ -33,7 +39,7 @@ class AliyunApi {
this.logger = logger
}
formatFolder (item: string, slicedPrefix: string, urlPrefix: string): any {
formatFolder(item: string, slicedPrefix: string, urlPrefix: string): any {
return {
key: item,
url: `${urlPrefix}/${item}`,
@@ -48,7 +54,7 @@ class AliyunApi {
}
}
formatFile (item: OSS.ObjectMeta, slicedPrefix: string, urlPrefix: string): any {
formatFile(item: OSS.ObjectMeta, slicedPrefix: string, urlPrefix: string): any {
const fileName = item.name.replace(slicedPrefix, '')
return {
...item,
@@ -65,26 +71,32 @@ class AliyunApi {
}
}
getCanonicalizedOSSHeaders (headers: IStringKeyMap) {
getCanonicalizedOSSHeaders(headers: IStringKeyMap) {
const lowerCaseHeaders = Object.keys(headers).reduce((acc, key) => {
acc[key.toLowerCase()] = headers[key]
return acc
}, {} as IStringKeyMap)
let canonicalizedOSSHeaders = ''
const headerKeys = Object.keys(lowerCaseHeaders).sort()
headerKeys.forEach((key) => {
headerKeys.forEach(key => {
key.startsWith('x-oss-') && (canonicalizedOSSHeaders += `${key}:${lowerCaseHeaders[key]}\n`)
})
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 stringToSign = `${method.toUpperCase()}\n${contentMd5}\n${contentType}\n${date}\n${this.getCanonicalizedOSSHeaders(headers)}${canonicalizedResource}`
return `OSS ${this.accessKeyId}:${hmacSha1Base64(this.accessKeySecret, stringToSign)}`
}
getNewCtx (region: string, bucket: string) {
getNewCtx(region: string, bucket: string) {
return new OSS({
accessKeyId: this.accessKeyId,
accessKeySecret: this.accessKeySecret,
@@ -95,21 +107,25 @@ class AliyunApi {
}
/**
* 获取存储桶列表
*/
async getBucketList (): Promise<any> {
* 获取存储桶列表
*/
async getBucketList(): Promise<any> {
const getBuckets = async (marker?: string) => {
const res = await this.ctx.listBuckets({
const res = (await this.ctx.listBuckets({
marker,
'max-keys': 1000
}) as IStringKeyMap
})) as IStringKeyMap
if (res?.res?.statusCode !== 200 || !res?.buckets) return { result: [], isTruncated: false }
const formattedBuckets = res.buckets.map((item: OSS.Bucket) => ({
Name: item.name,
Location: item.region,
CreationDate: item.creationDate
}))
return { result: formattedBuckets, isTruncated: res.isTruncated, nextMarker: res.nextMarker }
return {
result: formattedBuckets,
isTruncated: res.isTruncated,
nextMarker: res.nextMarker
}
}
const result: IStringKeyMap[] = []
let NextMarker: string | undefined
@@ -127,7 +143,7 @@ class AliyunApi {
/**
* 获取自定义域名
*/
async getBucketDomain (param: IStringKeyMap): Promise<any> {
async getBucketDomain(param: IStringKeyMap): Promise<any> {
const headers = {
Date: new Date().toUTCString()
}
@@ -160,17 +176,17 @@ class AliyunApi {
}
/**
* 创建存储桶
* @param {Object} configMap
* configMap = {
* BucketName: string,
* region: string,
* acl: string
* }
* @description
* acl: private | publicRead | publicReadWrite
*/
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
* 创建存储桶
* @param {Object} configMap
* configMap = {
* BucketName: string,
* region: string,
* acl: string
* }
* @description
* acl: private | publicRead | publicReadWrite
*/
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
const client = new OSS({
accessKeyId: this.accessKeyId,
accessKeySecret: this.accessKeySecret,
@@ -191,9 +207,14 @@ class AliyunApi {
return res?.res?.status === 200
}
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
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 urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
let marker
@@ -212,13 +233,16 @@ class AliyunApi {
}
const client = this.getNewCtx(region, bucket)
do {
res = await client.listV2({
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
'max-keys': '1000',
'continuation-token': marker
}, {
timeout: this.timeOut
})
res = await client.listV2(
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
'max-keys': '1000',
'continuation-token': marker
},
{
timeout: this.timeOut
}
)
if (res?.res?.statusCode === 200) {
res?.objects?.forEach((item: OSS.ObjectMeta) => {
item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
@@ -238,9 +262,14 @@ class AliyunApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
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 urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
let marker
@@ -259,14 +288,17 @@ class AliyunApi {
}
const client = this.getNewCtx(region, bucket)
do {
res = await client.listV2({
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
delimiter: '/',
'max-keys': '1000',
'continuation-token': marker
}, {
timeout: this.timeOut
})
res = await client.listV2(
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
delimiter: '/',
'max-keys': '1000',
'continuation-token': marker
},
{
timeout: this.timeOut
}
)
if (res?.res?.statusCode === 200) {
res?.prefixes?.forEach((item: string) => {
result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
@@ -303,21 +335,30 @@ class AliyunApi {
* itemsPerPage: number,
* customUrl: string
* }
*/
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, marker, itemsPerPage } = configMap
*/
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
marker,
itemsPerPage
} = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
const client = this.getNewCtx(region, bucket)
const res = await client.listV2({
prefix: slicedPrefix || undefined,
delimiter: '/',
'max-keys': itemsPerPage.toString(),
'continuation-token': marker
}, {
timeout: this.timeOut
}) as any
const res = (await client.listV2(
{
prefix: slicedPrefix || undefined,
delimiter: '/',
'max-keys': itemsPerPage.toString(),
'continuation-token': marker
},
{
timeout: this.timeOut
}
)) as any
// prefixes can be null
// objects will be [] when no file
if (res?.res.statusCode !== 200) {
@@ -330,7 +371,9 @@ class AliyunApi {
}
const fullList = [
...(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 {
fullList,
@@ -341,50 +384,47 @@ class AliyunApi {
}
/**
* 重命名文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* oldKey: string,
* newKey: string
* }
*/
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
* 重命名文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* oldKey: string,
* newKey: string
* }
*/
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, oldKey, newKey } = configMap
const client = this.getNewCtx(region, bucketName)
const copyRes = await client.copy(
newKey,
oldKey
) as any
const copyRes = (await client.copy(newKey, oldKey)) as any
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 false
}
/**
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string
* }
*/
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string
* }
*/
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
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
}
/**
* 删除文件夹
* @param configMap
*/
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
* 删除文件夹
* @param configMap
*/
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
const client = this.getNewCtx(region, bucketName)
let marker
@@ -394,14 +434,17 @@ class AliyunApi {
Contents: [] as any[]
}
do {
const res = await client.listV2({
prefix: key,
delimiter: '/',
'max-keys': '1000',
'continuation-token': marker
}, {
timeout: this.timeOut
}) as any
const res = (await client.listV2(
{
prefix: key,
delimiter: '/',
'max-keys': '1000',
'continuation-token': marker
},
{
timeout: this.timeOut
}
)) as any
if (res?.res.statusCode !== 200) return false
res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes)
@@ -423,8 +466,9 @@ class AliyunApi {
if (allFileList.Contents.length > 0) {
const cycle = Math.ceil(allFileList.Contents.length / 1000)
for (let i = 0; i < cycle; i++) {
const deleteRes = await client.deleteMulti(
allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => item.name)) as any
const deleteRes = (await client.deleteMulti(
allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => item.name)
)) as any
if (deleteRes?.res.statusCode !== 200) return false
}
}
@@ -432,17 +476,17 @@ class AliyunApi {
}
/**
* 获取预签名url
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string,
* expires: number,
* customUrl: string
* }
*/
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
* 获取预签名url
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string,
* expires: number,
* customUrl: string
* }
*/
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName, region, key, expires, customUrl } = configMap
const client = this.getNewCtx(region, bucketName)
const res = client.signatureUrl(key, {
@@ -455,7 +499,7 @@ class AliyunApi {
* 上传文件
* @param configMap
*/
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
// fileArray = [{
// bucketName: string,
@@ -485,10 +529,8 @@ class AliyunApi {
targetFileBucket: bucketName,
targetFileRegion: region
})
client.multipartUpload(
key,
filePath,
{
client
.multipartUpload(key, filePath, {
partSize: 1 * 1024 * 1024,
mime: getFileMimeType(fileName),
progress: (p: number) => {
@@ -499,37 +541,43 @@ class AliyunApi {
status: uploadTaskSpecialStatus.uploading
})
}
}
).then((res: any) => {
const id = `${bucketName}-${region}-${key}-${filePath}`
if (res?.res?.statusCode === 200) {
instance.updateUploadTask({
id,
progress: 100,
status: uploadTaskSpecialStatus.uploaded,
response: JSON.stringify(res),
finishTime: new Date().toLocaleString()
})
} else {
})
.then((res: any) => {
const id = `${bucketName}-${region}-${key}-${filePath}`
if (res?.res?.statusCode === 200) {
instance.updateUploadTask({
id,
progress: 100,
status: uploadTaskSpecialStatus.uploaded,
response: JSON.stringify(res),
finishTime: new Date().toLocaleString()
})
} else {
instance.updateUploadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
response: JSON.stringify(res),
finishTime: new Date().toLocaleString()
})
}
})
.catch((err: any) => {
this.logger.error(
formatError(err, {
class: 'AliyunApi',
method: 'uploadBucketFile'
})
)
const id = `${bucketName}-${region}-${key}-${filePath}`
instance.updateUploadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
response: JSON.stringify(res),
response: JSON.stringify(err),
finishTime: new Date().toLocaleString()
})
}
}).catch((err: any) => {
this.logger.error(formatError(err, { class: 'AliyunApi', method: 'uploadBucketFile' }))
const id = `${bucketName}-${region}-${key}-${filePath}`
instance.updateUploadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
response: JSON.stringify(err),
finishTime: new Date().toLocaleString()
})
})
}
return true
}
@@ -538,10 +586,10 @@ class AliyunApi {
* 新建文件夹
* @param configMap
*/
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
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
}
@@ -549,7 +597,7 @@ class AliyunApi {
* 下载文件
* @param configMap
*/
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance()
const promises = [] as any
@@ -571,20 +619,27 @@ class AliyunApi {
const preSignedUrl = client.signatureUrl(key, {
expires: 60 * 60 * 48
})
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger)
.then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
})
}))
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
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
}

View File

@@ -6,7 +6,14 @@ import path from 'path'
import windowManager from 'apis/app/window/windowManager'
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 { commonTaskStatus, IWindowList } from '#/types/enum'
@@ -20,9 +27,9 @@ class GithubApi {
proxy: any
proxyStr: string | undefined
baseUrl = 'https://api.github.com'
commonHeaders : IStringKeyMap
commonHeaders: IStringKeyMap
constructor (token: string, username: string, proxy: string | undefined, logger: ManageLogger) {
constructor(token: string, username: string, proxy: string | undefined, logger: ManageLogger) {
this.logger = logger
this.token = token.startsWith('Bearer ') ? token : `Bearer ${token}`.trim()
this.username = username
@@ -34,16 +41,25 @@ class GithubApi {
}
}
formatFolder (item: any, slicedPrefix: string, branch: string, repo: string, cdnUrl: string | undefined) {
formatFolder(item: any, slicedPrefix: string, branch: string, repo: string, cdnUrl: string | undefined) {
const key = `${slicedPrefix ? `${slicedPrefix}/` : ''}${item.path}/`
let rawUrl = ''
const placeholders = ['{username}', '{repo}', '{branch}', '{path}']
rawUrl = cdnUrl
? placeholders.some(item => cdnUrl.includes(item))
? placeholders.reduce((url, ph) => {
const value = ph === '{username}' ? this.username : ph === '{repo}' ? repo : ph === '{branch}' ? branch : ph === '{path}' ? key : ''
return url.replaceAll(ph, value)
}, cdnUrl)
const value =
ph === '{username}'
? this.username
: ph === '{repo}'
? repo
: ph === '{branch}'
? branch
: ph === '{path}'
? key
: ''
return url.replaceAll(ph, value)
}, cdnUrl)
: `${cdnUrl}/${key}`
: `https://raw.githubusercontent.com/${this.username}/${repo}/${branch}/${key}`
rawUrl = rawUrl.replace(/(?<!https?:)\/{2,}/g, '/')
@@ -62,16 +78,25 @@ class GithubApi {
}
}
formatFile (item: any, slicedPrefix: string, branch: string, repo: string, cdnUrl: string | undefined) {
formatFile(item: any, slicedPrefix: string, branch: string, repo: string, cdnUrl: string | undefined) {
let rawUrl = ''
const placeholders = ['{username}', '{repo}', '{branch}', '{path}']
const key = slicedPrefix === '' ? item.path : `${slicedPrefix}/${item.path}`
rawUrl = cdnUrl
? placeholders.some(item => cdnUrl.includes(item))
? placeholders.reduce((url, ph) => {
const value = ph === '{username}' ? this.username : ph === '{repo}' ? repo : ph === '{branch}' ? branch : ph === '{path}' ? `${slicedPrefix}/${item.path}` : ''
return url.replaceAll(ph, value)
}, cdnUrl)
const value =
ph === '{username}'
? this.username
: ph === '{repo}'
? repo
: ph === '{branch}'
? branch
: ph === '{path}'
? `${slicedPrefix}/${item.path}`
: ''
return url.replaceAll(ph, value)
}, cdnUrl)
: `${cdnUrl}/${key}`
: `https://raw.githubusercontent.com/${this.username}/${repo}/${branch}/${key}`
rawUrl = rawUrl.replace(/(?<!https?:)\/{2,}/g, '/')
@@ -92,17 +117,25 @@ class GithubApi {
}
/**
* get repo list
*/
async getBucketList (): Promise<any> {
* get repo list
*/
async getBucketList(): Promise<any> {
let initPage = 1
let res
const result = [] as any[]
do {
res = await got(
res = (await got(
`${this.baseUrl}/user/repos`,
getOptions('GET', this.commonHeaders, { page: initPage, per_page: 100 }, 'json', undefined, undefined, this.proxy)
) as any
getOptions(
'GET',
this.commonHeaders,
{ page: initPage, per_page: 100 },
'json',
undefined,
undefined,
this.proxy
)
)) as any
if (res.statusCode === 200) {
res.body.forEach((item: any) => {
result.push({
@@ -123,16 +156,24 @@ class GithubApi {
/**
* 获取branch列表
*/
async getBucketDomain (param: IStringKeyMap): Promise<any> {
async getBucketDomain(param: IStringKeyMap): Promise<any> {
const { bucketName: repo } = param
let initPage = 1
let res
const result = [] as string[]
do {
res = await got(
res = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/branches`,
getOptions('GET', this.commonHeaders, { page: initPage, per_page: 100 }, 'json', undefined, undefined, this.proxy)
) as any
getOptions(
'GET',
this.commonHeaders,
{ page: initPage, per_page: 100 },
'json',
undefined,
undefined,
this.proxy
)
)) as any
if (res.statusCode === 200) {
res.body.forEach((item: any) => result.push(item.name))
} else {
@@ -143,7 +184,7 @@ class GithubApi {
return result
}
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap
const slicedPrefix = prefix.replace(/(^\/+|\/+$)/g, '')
@@ -167,10 +208,10 @@ class GithubApi {
return result
}
const currentPrefix = treeQueue[0]
res = await got(
res = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${treeQueue.shift()}`,
getOptions('GET', this.commonHeaders, {}, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (res && res.statusCode === 200) {
const { tree } = res.body
tree.forEach((item: any) => {
@@ -194,7 +235,7 @@ class GithubApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap
const slicedPrefix = prefix.replace(/(^\/+|\/+$)/g, '')
@@ -236,15 +277,15 @@ class GithubApi {
}
/**
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string
* }
*/
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string
* }
*/
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName: repo, githubBranch: branch, key, DeleteHash: sha } = configMap
const body = {
message: 'deleted by PicList',
@@ -259,94 +300,135 @@ class GithubApi {
}
/**
* create a new tree to delete a folder
* @param configMap
*/
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
* create a new tree to delete a folder
* @param configMap
*/
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName: repo, githubBranch: branch, key } = configMap
// get sha of the branch
const refRes = await got(
const refRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`,
getOptions('GET', this.commonHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (refRes.statusCode !== 200) return false
const refSha = refRes.body.object.sha
// get sha of the root tree
const rootRes = await got(
const rootRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/branches/${branch}`,
getOptions('GET', undefined, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (rootRes.statusCode !== 200) return false
const rootSha = rootRes.body.commit.commit.tree.sha
// TODO: if there are more than 10000 files in the folder, it will be truncated
// Rare cases, not considered for now
// 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, '')}`,
getOptions('GET', this.commonHeaders, {
recursive: true
}, 'json', undefined, undefined, this.proxy)
) as any
getOptions(
'GET',
this.commonHeaders,
{
recursive: true
},
'json',
undefined,
undefined,
this.proxy
)
)) as any
if (treeRes.statusCode !== 200) return false
const oldTree = treeRes.body.tree
// create a new tree
const newTree = oldTree.filter((item: any) => item.type === 'blob')
.map((item:any) => ({
const newTree = oldTree
.filter((item: any) => item.type === 'blob')
.map((item: any) => ({
path: `${key.replace(/(^\/+|\/+$)/g, '')}/${item.path}`,
mode: item.mode,
type: item.type,
sha: null
}))
const newTreeShaRes = await got(
const newTreeShaRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/trees`,
getOptions('POST', this.commonHeaders, undefined, 'json', JSON.stringify({
base_tree: rootSha,
tree: newTree
}), undefined, this.proxy)
) as any
getOptions(
'POST',
this.commonHeaders,
undefined,
'json',
JSON.stringify({
base_tree: rootSha,
tree: newTree
}),
undefined,
this.proxy
)
)) as any
if (newTreeShaRes.statusCode !== 201) return false
const newTreeSha = newTreeShaRes.body.sha
// create a new commit
const commitRes = await got(
const commitRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/commits`,
getOptions('POST', this.commonHeaders, undefined, 'json', JSON.stringify({
message: 'deleted by PicList',
tree: newTreeSha,
parents: [refSha]
}), undefined, this.proxy)
) as any
getOptions(
'POST',
this.commonHeaders,
undefined,
'json',
JSON.stringify({
message: 'deleted by PicList',
tree: newTreeSha,
parents: [refSha]
}),
undefined,
this.proxy
)
)) as any
if (commitRes.statusCode !== 201) return false
const commitSha = commitRes.body.sha
// update the branch
const updateRefRes = await got(
const updateRefRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`,
getOptions('PATCH', this.commonHeaders, undefined, 'json', JSON.stringify({
sha: commitSha
}), undefined, this.proxy)
) as any
getOptions(
'PATCH',
this.commonHeaders,
undefined,
'json',
JSON.stringify({
sha: commitSha
}),
undefined,
this.proxy
)
)) as any
return updateRefRes.statusCode === 200
}
/**
* 获取预签名url
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string,
* expires: number,
* customUrl: string
* }
*/
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
* 获取预签名url
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string,
* expires: number,
* customUrl: string
* }
*/
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName: repo, customUrl: branch, key, rawUrl, githubPrivate: isPrivate } = configMap
if (!isPrivate) return rawUrl
const res = await got(
const res = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/contents/${key}`,
getOptions('GET', this.commonHeaders, {
ref: branch
}, 'json', undefined, undefined, this.proxy)
) as any
getOptions(
'GET',
this.commonHeaders,
{
ref: branch
},
'json',
undefined,
undefined,
this.proxy
)
)) as any
return res.statusCode === 200 ? res.body.download_url : ''
}
@@ -354,7 +436,7 @@ class GithubApi {
* 新建文件夹
* @param configMap
*/
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName: repo, githubBranch: branch, key } = configMap
const newFileKey = `${trimPath(key)}/.gitkeep`
const base64Content = Buffer.from('created by PicList').toString('base64')
@@ -374,7 +456,7 @@ class GithubApi {
* 上传文件
* @param configMap
*/
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
fileArray.forEach((item: any) => {
@@ -423,7 +505,7 @@ class GithubApi {
* 下载文件
* @param configMap
*/
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance()
const promises = [] as any
@@ -454,27 +536,27 @@ class GithubApi {
} else {
downloadUrl = githubUrl
}
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(
instance,
downloadUrl,
id,
savedFilePath,
this.logger,
this.proxyStr
)
.then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, downloadUrl, id, savedFilePath, this.logger, this.proxyStr).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
})
}))
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
this.logger.error(formatError(error, { class: 'GithubApi', method: 'downloadBucketFile' }))
pool.all(promises).catch(error => {
this.logger.error(
formatError(error, {
class: 'GithubApi',
method: 'downloadBucketFile'
})
)
})
return true
}

View File

@@ -31,7 +31,7 @@ class ImgurApi {
idHeaders: any
baseUrl = 'https://api.imgur.com/3'
constructor (userName: string, accessToken: string, proxy: any, logger: ManageLogger) {
constructor(userName: string, accessToken: string, proxy: any, logger: ManageLogger) {
this.userName = userName
this.accessToken = accessToken.startsWith('Bearer ') ? accessToken : `Bearer ${accessToken}`
this.proxy = proxy
@@ -42,7 +42,7 @@ class ImgurApi {
}
}
formatFile (item: any) {
formatFile(item: any) {
const fileName = path.basename(item.link)
const isImg = isImage(fileName)
return {
@@ -62,17 +62,17 @@ class ImgurApi {
}
/**
* get repo list
*/
async getBucketList (): Promise<any> {
* get repo list
*/
async getBucketList(): Promise<any> {
let initPage = 0
let res
const result = [] as any[]
do {
res = await got(
res = (await got(
`${this.baseUrl}/account/${this.userName}/albums/${initPage}`,
getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (!(res.statusCode === 200 && res.body.success)) {
return []
}
@@ -93,9 +93,12 @@ class ImgurApi {
return finalResult
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketConfig: { Location: albumHash }, cancelToken } = configMap
const {
bucketConfig: { Location: albumHash },
cancelToken
} = configMap
const cancelTask = [false]
ipcMain.on('cancelLoadingFileList', (_: IpcMainEvent, token: string) => {
if (token === cancelToken) {
@@ -110,10 +113,10 @@ class ImgurApi {
finished: false
}
if (albumHash !== 'unclassified') {
res = await got(
res = (await got(
`${this.baseUrl}/account/${this.userName}/album/${albumHash}`,
getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (res.statusCode === 200 && res.body.success) {
res.body.data.images.forEach((item: any) => {
result.fullList.push(this.formatFile(item))
@@ -127,10 +130,10 @@ class ImgurApi {
} else {
let initPage = 0
do {
res = await got(
res = (await got(
`${this.baseUrl}/account/${this.userName}/images/${initPage}`,
getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (res.statusCode === 200 && res.body.success) {
res.body.data.forEach((item: any) => {
result.fullList.push(this.formatFile(item))
@@ -150,12 +153,12 @@ class ImgurApi {
ipcMain.removeAllListeners('cancelLoadingFileList')
}
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { DeleteHash: deleteHash } = configMap
const res = await got(
const res = (await got(
`${this.baseUrl}/account/${this.userName}/image/${deleteHash}`,
getOptions('DELETE', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
return res.statusCode === 200 && res.body.success
}
@@ -163,7 +166,7 @@ class ImgurApi {
* 上传文件
* @param configMap
*/
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
fileArray.forEach((item: any) => {
@@ -223,7 +226,7 @@ class ImgurApi {
* 下载文件
* @param configMap
*/
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance()
const promises = [] as any
@@ -241,26 +244,21 @@ class ImgurApi {
sourceFileName: fileName,
targetFilePath: savedFilePath
})
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(
instance,
url,
id,
savedFilePath,
this.logger,
this.proxyStr
)
.then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, url, id, savedFilePath, this.logger, this.proxyStr).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
})
}))
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'ImgurApi', method: 'downloadBucketFile' }))
})
return true

View File

@@ -16,28 +16,30 @@ class LocalApi {
logger: ManageLogger
isWindows: boolean
constructor (logger: ManageLogger) {
constructor(logger: ManageLogger) {
this.logger = logger
this.isWindows = process.platform === 'win32'
}
logParam = (error:any, method: string) =>
this.logger.error(formatError(error, { class: 'LocalApi', method }))
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'LocalApi', method }))
// windows 系统下将路径转换为 unix 风格
transPathToUnix (filePath: string | undefined) {
transPathToUnix(filePath: string | undefined) {
if (!filePath) return ''
return this.isWindows ? filePath.split(path.sep).join(path.posix.sep) : filePath.replace(/^\/+/, '')
}
transBack (filePath: string | undefined) {
transBack(filePath: string | undefined) {
if (!filePath) return ''
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, '')}`
}
formatFolder (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) {
formatFolder(item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) {
const key = `${this.transPathToUnix(filePath)}/`.replace(/\/+$/, '/')
return {
...item,
@@ -54,7 +56,7 @@ class LocalApi {
}
}
formatFile (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string, isDownload = false) {
formatFile(item: fs.Stats, urlPrefix: string, fileName: string, filePath: string, isDownload = false) {
const key = isDownload ? filePath : this.transPathToUnix(filePath)
return {
...item,
@@ -71,7 +73,7 @@ class LocalApi {
}
}
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl = '', cancelToken } = configMap
const urlPrefix = customUrl.replace(/\/+$/, '')
@@ -111,7 +113,7 @@ class LocalApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { customUrl = '', cancelToken, baseDir } = configMap
let prefix = configMap.prefix
@@ -167,7 +169,7 @@ class LocalApi {
ipcMain.removeAllListeners('cancelLoadingFileList')
}
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { oldKey, newKey } = configMap
let result = false
try {
@@ -179,7 +181,7 @@ class LocalApi {
return result
}
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -191,7 +193,7 @@ class LocalApi {
return result
}
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -205,7 +207,7 @@ class LocalApi {
return result
}
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) {
@@ -247,7 +249,7 @@ class LocalApi {
return true
}
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -261,7 +263,7 @@ class LocalApi {
return result
}
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) {

View File

@@ -1,4 +1,3 @@
import axios from 'axios'
import { ipcMain, IpcMainEvent } from 'electron'
import path from 'path'
@@ -7,7 +6,13 @@ import qiniu from 'qiniu/index'
import windowManager from 'apis/app/window/windowManager'
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 { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
@@ -28,14 +33,14 @@ class QiniuApi {
getBucketDomain: 'https://uc.qiniuapi.com/v2/domains'
}
constructor (accessKey: string, secretKey: string, logger: ManageLogger) {
constructor(accessKey: string, secretKey: string, logger: ManageLogger) {
this.mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
this.accessKey = accessKey
this.secretKey = secretKey
this.logger = logger
}
formatFolder (item: string, slicedPrefix: string, urlPrefix: string) {
formatFolder(item: string, slicedPrefix: string, urlPrefix: string) {
return {
Key: item,
key: item,
@@ -49,7 +54,7 @@ class QiniuApi {
}
}
formatFile (item: any, slicedPrefix: string, urlPrefix: string) {
formatFile(item: any, slicedPrefix: string, urlPrefix: string) {
const fileName = item.key.replace(slicedPrefix, '')
return {
...item,
@@ -64,7 +69,7 @@ class QiniuApi {
}
}
authorization (
authorization(
method: string,
urlPath: string,
host: string,
@@ -79,7 +84,7 @@ class QiniuApi {
if (xQiniuHeaders) {
const xQiniuHeaderStr = Object.keys(xQiniuHeaders)
.sort()
.map((key) => `\n${key}:${xQiniuHeaders[key]}`)
.map(key => `\n${key}:${xQiniuHeaders[key]}`)
.join('')
signStr += xQiniuHeaderStr
}
@@ -92,8 +97,8 @@ class QiniuApi {
/**
* 获取存储桶列表
*/
async getBucketList (): Promise<any> {
*/
async getBucketList(): Promise<any> {
const host = this.hostList.getBucketList
const authorization = qiniu.util.generateAccessToken(this.mac, host, undefined)
const res = await axios.get(host, {
@@ -122,8 +127,8 @@ class QiniuApi {
/**
* 获取存储桶详细信息
*/
async getBucketInfo (param: IStringKeyMap): Promise<any> {
*/
async getBucketInfo(param: IStringKeyMap): Promise<any> {
const { bucketName } = param
const urlPath = `/v2/bucketInfo?bucket=${bucketName}&fs=true`
const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
@@ -143,19 +148,19 @@ class QiniuApi {
})
return res?.status === 200
? {
success: true,
private: res.data.private,
zone: res.data.zone
}
success: true,
private: res.data.private,
zone: res.data.zone
}
: {
success: false
}
success: false
}
}
/**
* 获取自定义域名
*/
async getBucketDomain (param: IStringKeyMap): Promise<any> {
async getBucketDomain(param: IStringKeyMap): Promise<any> {
const { bucketName } = param
const host = this.hostList.getBucketDomain
const authorization = qiniu.util.generateAccessToken(this.mac, `${host}?tbl=${bucketName}`, undefined)
@@ -175,7 +180,7 @@ class QiniuApi {
/**
* 修改存储桶权限
*/
async setBucketAclPolicy (param: IStringKeyMap): Promise<boolean> {
async setBucketAclPolicy(param: IStringKeyMap): Promise<boolean> {
// 0: 公开访问 1: 私有访问
const { bucketName } = param
let { isPrivate } = param
@@ -207,8 +212,8 @@ class QiniuApi {
* region: string,
* acl: boolean // 是否公开访问
* }
*/
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
*/
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
const { BucketName, region, acl } = configMap
const urlPath = `/mkbucketv3/${BucketName}/region/${region}`
const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
@@ -224,13 +229,13 @@ class QiniuApi {
})
return res?.status === 200
? await this.setBucketAclPolicy({
bucketName: BucketName,
isPrivate: !acl
})
bucketName: BucketName,
isPrivate: !acl
})
: false
}
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap
let marker = undefined as any
@@ -252,25 +257,31 @@ class QiniuApi {
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
do {
res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucket, {
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
marker,
limit: 1000
}, (err: any, respBody: any, respInfo: any) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
bucketManager.listPrefix(
bucket,
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
marker,
limit: 1000
},
(err: any, respBody: any, respInfo: any) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
}
}
})
)
})
if (res && res.respInfo.statusCode === 200) {
res.respBody && res.respBody.items && res.respBody.items.forEach((item: any) => {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
res.respBody &&
res.respBody.items &&
res.respBody.items.forEach((item: any) => {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
window.webContents.send(refreshDownloadFileTransferList, result)
} else {
result.finished = true
@@ -286,7 +297,7 @@ class QiniuApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap
let marker = undefined as any
@@ -308,29 +319,37 @@ class QiniuApi {
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
do {
res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucket, {
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
delimiter: '/',
marker,
limit: 1000
}, (err: any, respBody: any, respInfo: any) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
bucketManager.listPrefix(
bucket,
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
delimiter: '/',
marker,
limit: 1000
},
(err: any, respBody: any, respInfo: any) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
}
}
})
)
})
if (res && res.respInfo.statusCode === 200) {
res.respBody && res.respBody.commonPrefixes && res.respBody.commonPrefixes.forEach((item: any) => {
result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
})
res.respBody && res.respBody.items && res.respBody.items.forEach((item: any) => {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
res.respBody &&
res.respBody.commonPrefixes &&
res.respBody.commonPrefixes.forEach((item: any) => {
result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
})
res.respBody &&
res.respBody.items &&
res.respBody.items.forEach((item: any) => {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
window.webContents.send('refreshFileTransferList', result)
} else {
result.finished = true
@@ -360,8 +379,8 @@ class QiniuApi {
* itemsPerPage: number,
* customUrl: string
* }
*/
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
*/
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, prefix, marker, itemsPerPage, customUrl: urlPrefix } = configMap
const slicedPrefix = prefix.slice(1)
const config = new qiniu.conf.Config()
@@ -374,21 +393,25 @@ class QiniuApi {
success: false
}
res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucket, {
limit: itemsPerPage,
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
marker,
delimiter: '/'
}, (err, respBody, respInfo) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
bucketManager.listPrefix(
bucket,
{
limit: itemsPerPage,
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
marker,
delimiter: '/'
},
(err, respBody, respInfo) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
}
}
})
)
})
if (res?.respInfo?.statusCode === 200) {
if (res.respBody?.commonPrefixes) {
@@ -401,7 +424,7 @@ class QiniuApi {
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.success = true
}
@@ -409,19 +432,19 @@ class QiniuApi {
}
/**
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string
* }
*/
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string
* }
*/
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, key } = configMap
const config = new qiniu.conf.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) => {
if (err) {
reject(err)
@@ -432,7 +455,7 @@ class QiniuApi {
})
}
})
}) as any
})) as any
return res?.respInfo?.statusCode === 200
}
@@ -440,7 +463,7 @@ class QiniuApi {
* 删除文件夹
* @param configMap
*/
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, key } = configMap
const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
@@ -450,27 +473,31 @@ class QiniuApi {
Contents: [] as any[]
}
do {
const res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucketName, {
prefix: key,
marker,
limit: 1000
}, (err, respBody, respInfo) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
const res = (await new Promise((resolve, reject) => {
bucketManager.listPrefix(
bucketName,
{
prefix: key,
marker,
limit: 1000
},
(err, respBody, respInfo) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
}
}
})
}) as any
)
})) as any
if (res?.respInfo?.statusCode === 200) {
if (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 : ''
} else {
return false
@@ -481,7 +508,7 @@ class QiniuApi {
const deleteOps = allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => {
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) => {
if (err) {
reject(err)
@@ -492,40 +519,47 @@ class QiniuApi {
})
}
})
}) as any
})) as any
if (res?.respInfo?.statusCode !== 200) return false
}
return true
}
/**
* 重命名文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* oldKey: string,
* newKey: string
* }
*/
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
* 重命名文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* oldKey: string,
* newKey: string
* }
*/
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, oldKey, newKey } = configMap
const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
const res = await new Promise((resolve, reject) => {
bucketManager.move(bucketName, oldKey, bucketName, newKey, {
force: true
}, (err, respBody, respInfo) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
const res = (await new Promise((resolve, reject) => {
bucketManager.move(
bucketName,
oldKey,
bucketName,
newKey,
{
force: true
},
(err, respBody, respInfo) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
}
}
})
}) as any
)
})) as any
return res?.respInfo?.statusCode === 200
}
@@ -540,7 +574,7 @@ class QiniuApi {
* customUrl: string
* }
*/
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { key, expires, customUrl } = configMap
const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
@@ -554,7 +588,7 @@ class QiniuApi {
* 上传文件
* @param configMap
*/
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
fileArray.forEach((item: any) => {
@@ -585,7 +619,7 @@ class QiniuApi {
putExtra.version = 'v2'
putExtra.partSize = 4 * 1024 * 1024
putExtra.progressCallback = (uploadBytes, totalBytes) => {
const progress = Math.floor(uploadBytes / totalBytes * 100)
const progress = Math.floor((uploadBytes / totalBytes) * 100)
instance.updateUploadTask({
id: `${bucketName}-${region}-${key}-${filePath}`,
progress,
@@ -594,7 +628,12 @@ class QiniuApi {
}
resumeUploader.putFile(uploadToken, key, filePath, putExtra, (respErr, respBody, respInfo) => {
if (respErr) {
this.logger.error(formatError(respErr, { class: 'Qiniu', method: 'uploadBucketFile' }))
this.logger.error(
formatError(respErr, {
class: 'Qiniu',
method: 'uploadBucketFile'
})
)
instance.updateUploadTask({
id: `${bucketName}-${region}-${key}-${filePath}`,
progress: 0,
@@ -628,7 +667,7 @@ class QiniuApi {
* 新建文件夹
* @param configMap
*/
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, key } = configMap
const putPolicy = new qiniu.rs.PutPolicy({
scope: `${bucketName}:${key}`
@@ -636,7 +675,7 @@ class QiniuApi {
const uploadToken = putPolicy.uploadToken(this.mac)
const FormUploader = new qiniu.form_up.FormUploader()
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) => {
if (err) {
reject(err)
@@ -647,7 +686,7 @@ class QiniuApi {
})
}
})
}) as any
})) as any
return res?.respInfo?.statusCode === 200
}
@@ -655,7 +694,7 @@ class QiniuApi {
* 下载文件
* @param configMap
*/
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance()
const promises = [] as any
@@ -673,20 +712,26 @@ class QiniuApi {
sourceFileName: fileName,
targetFilePath: savedFilePath
})
const preSignedUrl = await this.getPreSignedUrl({ key, expires: 36000, customUrl })
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger)
.then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
const preSignedUrl = await this.getPreSignedUrl({
key,
expires: 36000,
customUrl
})
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
})
}))
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'QiniuApi', method: 'downloadBucketFile' }))
})
return true

View File

@@ -46,7 +46,7 @@ class S3plistApi {
secretAccessKey: string
bucketName: string
constructor (
constructor(
accessKeyId: string,
secretAccessKey: string,
endpoint: string | undefined,
@@ -75,9 +75,9 @@ class S3plistApi {
this.proxy = formatHttpProxy(proxy, 'string') as string | undefined
}
async getDogeCloudToken () {
async getDogeCloudToken() {
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) {
throw new Error('manage.setting.dogeCloudTokenError')
}
@@ -88,7 +88,7 @@ class S3plistApi {
}
}
setAgent (proxy: string | undefined, sslEnabled: boolean) : NodeHttpHandler {
setAgent(proxy: string | undefined, sslEnabled: boolean): NodeHttpHandler {
const agent = getAgent(proxy, sslEnabled)
const commonOptions: AgentOptions = {
keepAlive: true,
@@ -98,27 +98,26 @@ class S3plistApi {
const extraOptions = sslEnabled ? { rejectUnauthorized: false } : {}
return sslEnabled
? new NodeHttpHandler({
httpsAgent: agent.https
? agent.https
: new https.Agent({
...commonOptions,
...extraOptions
})
})
httpsAgent: agent.https
? agent.https
: new https.Agent({
...commonOptions,
...extraOptions
})
})
: new NodeHttpHandler({
httpAgent: agent.http
? agent.http
: new http.Agent({
...commonOptions,
...extraOptions
})
})
httpAgent: agent.http
? agent.http
: new http.Agent({
...commonOptions,
...extraOptions
})
})
}
logParam = (error:any, method: string) =>
this.logger.error(formatError(error, { class: 'S3plistApi', method }))
logParam = (error: any, method: string) => 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 {
Key: item.Prefix,
url: `${urlPrefix}/${item.Prefix}`,
@@ -133,7 +132,7 @@ class S3plistApi {
}
}
formatFile (item: _Object, slicedPrefix: string, urlPrefix: string): any {
formatFile(item: _Object, slicedPrefix: string, urlPrefix: string): any {
const fileName = item.Key?.replace(slicedPrefix, '')
return {
...item,
@@ -149,7 +148,7 @@ class S3plistApi {
}
}
async putPublicAccess (bucketName: string, client: S3Client) {
async putPublicAccess(bucketName: string, client: S3Client) {
const input = {
Bucket: bucketName,
PublicAccessBlockConfiguration: {
@@ -170,13 +169,13 @@ class S3plistApi {
/**
* 新建存储桶
* @param {Object} configMap
* configMap = {
* BucketName: string,
* region: string,
* acl: string
* }
* configMap = {
* BucketName: string,
* region: string,
* acl: string
* }
*/
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
const { BucketName, region, acl, endpoint } = configMap
try {
await this.getDogeCloudToken()
@@ -186,7 +185,7 @@ class S3plistApi {
const command = new ListBucketsCommand({})
const data = await client.send(command)
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)) {
return true
}
@@ -233,9 +232,9 @@ class S3plistApi {
}
/**
* 获取存储桶列表
*/
async getBucketList (): Promise<any> {
* 获取存储桶列表
*/
async getBucketList(): Promise<any> {
if (this.dogeCloudSupport) {
try {
const res = await dogecloudApi('/oss/bucket/list.json', {}, false, this.accessKeyId, this.secretAccessKey)
@@ -258,7 +257,7 @@ class S3plistApi {
}
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
const result: IStringKeyMap[] = []
const endpoint = options.endpoint as string || ''
const endpoint = (options.endpoint as string) || ''
options.region = endpoint.includes('cloudflarestorage') ? 'auto' : 'us-east-1'
try {
const client = new S3Client(options)
@@ -271,23 +270,28 @@ class S3plistApi {
if (data.Buckets) {
if (endpoint.includes('cloudflarestorage')) {
result.push(...data.Buckets.map(bucket => ({
Name: bucket.Name,
CreationDate: bucket.CreationDate,
Location: 'auto'
})))
result.push(
...data.Buckets.map(bucket => ({
Name: bucket.Name,
CreationDate: bucket.CreationDate,
Location: 'auto'
}))
)
} else {
for (const bucket of data.Buckets) {
const bucketName = bucket.Name
const bucketConfig = await client.send(new GetBucketLocationCommand({
Bucket: bucketName
}))
const bucketConfig = await client.send(
new GetBucketLocationCommand({
Bucket: bucketName
})
)
result.push({
Name: bucketName,
CreationDate: bucket.CreationDate,
Location: bucketConfig.$metadata.httpStatusCode === 200
? bucketConfig.LocationConstraint?.toLowerCase() || 'us-east-1'
: 'us-east-1'
Location:
bucketConfig.$metadata.httpStatusCode === 200
? bucketConfig.LocationConstraint?.toLowerCase() || 'us-east-1'
: 'us-east-1'
})
if (bucketConfig.$metadata.httpStatusCode !== 200) {
this.logParam(bucketConfig, 'getBucketList')
@@ -301,9 +305,14 @@ class S3plistApi {
return result
}
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
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 urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
let marker
@@ -333,9 +342,10 @@ class S3plistApi {
})
res = await client.send(command)
if (res.$metadata.httpStatusCode === 200) {
res.Contents && res.Contents.forEach((item: _Object) => {
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
res.Contents &&
res.Contents.forEach((item: _Object) => {
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
window.webContents.send(refreshDownloadFileTransferList, result)
} else {
this.logParam(res, 'getBucketListRecursively')
@@ -359,9 +369,14 @@ class S3plistApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
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 urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
let marker
@@ -393,12 +408,14 @@ class S3plistApi {
})
res = await client.send(command)
if (res.$metadata.httpStatusCode === 200) {
res.CommonPrefixes && res.CommonPrefixes.forEach((item: CommonPrefix) => {
result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
})
res.Contents && res.Contents.forEach((item: _Object) => {
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
res.CommonPrefixes &&
res.CommonPrefixes.forEach((item: CommonPrefix) => {
result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
})
res.Contents &&
res.Contents.forEach((item: _Object) => {
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
window.webContents.send('refreshFileTransferList', result)
} else {
this.logParam(res, 'getBucketListBackstage')
@@ -422,8 +439,14 @@ class S3plistApi {
ipcMain.removeAllListeners('cancelLoadingFileList')
}
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, marker, itemsPerPage } = configMap
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
marker,
itemsPerPage
} = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
const result = {
@@ -434,7 +457,10 @@ class S3plistApi {
}
try {
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 command = new ListObjectsV2Command({
Bucket: bucket,
@@ -460,21 +486,24 @@ class S3plistApi {
}
/**
* 重命名文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* oldKey: string,
* newKey: string
* }
*/
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
* 重命名文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* oldKey: string,
* newKey: string
* }
*/
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, oldKey, newKey } = configMap
let result = false
try {
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 command = new CopyObjectCommand({
Bucket: bucketName,
@@ -503,15 +532,15 @@ class S3plistApi {
}
/**
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string
* }
*/
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string
* }
*/
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
let result = false
try {
@@ -536,10 +565,10 @@ class S3plistApi {
}
/**
* 删除文件夹
* @param configMap
*/
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
* 删除文件夹
* @param configMap
*/
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
let marker
let result = false
@@ -562,7 +591,7 @@ class S3plistApi {
Delimiter: '/',
MaxKeys: 1000
})
res = await client.send(command) as ListObjectsV2CommandOutput
res = (await client.send(command)) as ListObjectsV2CommandOutput
if (res.$metadata.httpStatusCode === 200) {
res.CommonPrefixes && allFileList.CommonPrefixes.push(...res.CommonPrefixes)
res.Contents && allFileList.Contents.push(...res.Contents)
@@ -595,7 +624,7 @@ class S3plistApi {
const deleteCommand = new DeleteObjectsCommand({
Bucket: bucketName,
Delete: {
Objects: deleteList.map((item) => {
Objects: deleteList.map(item => {
return {
Key: item.Key
}
@@ -618,29 +647,33 @@ class S3plistApi {
}
/**
* 获取预签名url
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string,
* expires: number,
* customUrl: string
* }
*/
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
* 获取预签名url
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string,
* expires: number,
* customUrl: string
* }
*/
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName, region, key, expires } = configMap
try {
await this.getDogeCloudToken()
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
options.region = String(region) || 'us-east-1'
const client = new S3Client(options)
const signedUrl = await getSignedUrl(client, new GetObjectCommand({
Bucket: bucketName,
Key: key
}), {
expiresIn: expires || 3600
})
const signedUrl = await getSignedUrl(
client,
new GetObjectCommand({
Bucket: bucketName,
Key: key
}),
{
expiresIn: expires || 3600
}
)
return signedUrl
} catch (error) {
this.logParam(error, 'getPreSignedUrl')
@@ -652,7 +685,7 @@ class S3plistApi {
* 新建文件夹
* @param configMap
*/
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
let result = false
try {
@@ -680,7 +713,7 @@ class S3plistApi {
* upload file
* @param configMap
*/
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
// fileArray = [{
// bucketName: string,
@@ -693,7 +726,15 @@ class S3plistApi {
fileArray.forEach((item: any) => {
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) {
const { bucketName, region, key, filePath, fileName, aclForUpload } = item
const id = `${bucketName}-${String(region)}-${key}-${filePath}`
@@ -743,36 +784,39 @@ class S3plistApi {
parallelUploads3.on('httpUploadProgress', (progress: Progress) => {
instance.updateUploadTask({
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
})
})
parallelUploads3.done().then((data) => {
if (data.$metadata.httpStatusCode === 200) {
instance.updateUploadTask({
id,
progress: 100,
status: uploadTaskSpecialStatus.uploaded,
finishTime: new Date().toLocaleString()
})
} else {
parallelUploads3
.done()
.then(data => {
if (data.$metadata.httpStatusCode === 200) {
instance.updateUploadTask({
id,
progress: 100,
status: uploadTaskSpecialStatus.uploaded,
finishTime: new Date().toLocaleString()
})
} else {
instance.updateUploadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
finishTime: new Date().toLocaleString()
})
}
})
.catch(error => {
this.logParam(error, 'uploadBucketFile')
instance.updateUploadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
response: JSON.stringify(error),
finishTime: new Date().toLocaleString()
})
}
}).catch((error) => {
this.logParam(error, 'uploadBucketFile')
instance.updateUploadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
response: JSON.stringify(error),
finishTime: new Date().toLocaleString()
})
})
}
return true
}
@@ -781,7 +825,7 @@ class S3plistApi {
* 下载文件
* @param configMap
*/
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance()
const promises = [] as any
@@ -806,19 +850,21 @@ class S3plistApi {
expires: 36000,
customUrl
})
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxy)
.then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxy).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
})
}))
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logParam(error, 'downloadBucketFile')
})
return true

View File

@@ -45,7 +45,7 @@ class SftpApi {
passphrase: string
}
constructor (
constructor(
host: string,
port: Undefinable<number>,
username: Undefinable<string>,
@@ -76,8 +76,7 @@ class SftpApi {
}
}
logParam = (error:any, method: string) =>
this.logger.error(formatError(error, { class: 'SftpApi', method }))
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'SftpApi', method }))
transFormPermission = (permissionsStr: string) => {
const permissions = permissionsStr.length === 10 ? permissionsStr.slice(1) : permissionsStr
@@ -96,7 +95,7 @@ class SftpApi {
return `0${result}`
}
formatFolder (item: listDirResult, urlPrefix: string, isWebPath = false) {
formatFolder(item: listDirResult, urlPrefix: string, isWebPath = false) {
const key = item.key
let url: string
if (isWebPath) {
@@ -123,7 +122,7 @@ class SftpApi {
}
}
formatFile (item: listDirResult, urlPrefix: string, isWebPath = false) {
formatFile(item: listDirResult, urlPrefix: string, isWebPath = false) {
const key = item.key
return {
...item,
@@ -153,7 +152,7 @@ class SftpApi {
}
}
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken } = configMap
const urlPrefix = customUrl || `${this.host}:${this.port}`
@@ -193,7 +192,7 @@ class SftpApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
formatLSResult (res: string, cwd: string): listDirResult[] {
formatLSResult(res: string, cwd: string): listDirResult[] {
const result = [] as listDirResult[]
const resArray = res.trim().split('\n')
resArray.slice(resArray[0].startsWith('total') ? 1 : 0).forEach((item: string) => {
@@ -219,7 +218,7 @@ class SftpApi {
return result
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken, baseDir } = configMap
let urlPrefix = customUrl || `${this.host}:${this.port}`
@@ -250,7 +249,8 @@ class SftpApi {
if (formatedLSRes.length) {
formatedLSRes.forEach((item: listDirResult) => {
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) {
result.fullList.push(this.formatFolder(item, webPath ? relative : urlPrefix, !!webPath))
} else {
@@ -277,7 +277,7 @@ class SftpApi {
ipcMain.removeAllListeners('cancelLoadingFileList')
}
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { oldKey, newKey } = configMap
let result = false
try {
@@ -291,7 +291,7 @@ class SftpApi {
return result
}
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -305,7 +305,7 @@ class SftpApi {
return result
}
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -322,7 +322,7 @@ class SftpApi {
return result
}
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) {
@@ -377,7 +377,7 @@ class SftpApi {
return true
}
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -391,7 +391,7 @@ class SftpApi {
return result
}
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) {

View File

@@ -20,7 +20,7 @@ class SmmsApi {
logger: ManageLogger
timeout = 30000
constructor (token: string, logger: ManageLogger) {
constructor(token: string, logger: ManageLogger) {
this.token = token
this.axiosInstance = axios.create({
baseURL: this.baseUrl,
@@ -36,7 +36,7 @@ class SmmsApi {
this.logger = logger
}
formatFile (item: any) {
formatFile(item: any) {
return {
...item,
Key: item.path,
@@ -53,7 +53,7 @@ class SmmsApi {
}
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { cancelToken } = configMap
let marker = 1
@@ -71,17 +71,15 @@ class SmmsApi {
finished: false
}
do {
res = await this.axiosInstance(
'/upload_history',
{
method: 'GET',
headers: {
'Content-Type': 'multipart/form-data'
},
params: {
page: marker
}
})
res = await this.axiosInstance('/upload_history', {
method: 'GET',
headers: {
'Content-Type': 'multipart/form-data'
},
params: {
page: marker
}
})
if (res && res.status === 200 && res.data && res.data.success) {
if (res.data.Count === 0) {
result.success = true
@@ -123,26 +121,23 @@ class SmmsApi {
* itemsPerPage: number,
* customUrl: string
* }
*/
async getBucketFileList ({ currentPage }: IStringKeyMap): Promise<any> {
*/
async getBucketFileList({ currentPage }: IStringKeyMap): Promise<any> {
const result = {
fullList: <any>[],
isTruncated: false,
nextMarker: '',
success: false
}
const res = await this.axiosInstance(
'/upload_history',
{
method: 'GET',
headers: {
'Content-Type': 'multipart/form-data'
},
params: {
page: currentPage
}
const res = await this.axiosInstance('/upload_history', {
method: 'GET',
headers: {
'Content-Type': 'multipart/form-data'
},
params: {
page: currentPage
}
)
})
if (res?.status !== 200 || !res?.data?.success) return result
if (res.data.Count === 0) return { ...result, success: true }
@@ -157,26 +152,23 @@ class SmmsApi {
}
/**
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string,
* DeleteHash: string
* }
*/
async deleteBucketFile ({ DeleteHash }: IStringKeyMap): Promise<boolean> {
const res = await this.axiosInstance(
`/delete/${DeleteHash}`,
{
method: 'GET',
params: {
hash: DeleteHash,
format: 'json'
}
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string,
* DeleteHash: string
* }
*/
async deleteBucketFile({ DeleteHash }: IStringKeyMap): Promise<boolean> {
const res = await this.axiosInstance(`/delete/${DeleteHash}`, {
method: 'GET',
params: {
hash: DeleteHash,
format: 'json'
}
)
})
return res?.status === 200 && res?.data?.success
}
@@ -184,7 +176,7 @@ class SmmsApi {
* 上传文件
* @param configMap
*/
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) {
@@ -221,7 +213,7 @@ class SmmsApi {
* 下载文件
* @param configMap
*/
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance()
const promises = [] as any
@@ -239,19 +231,21 @@ class SmmsApi {
sourceFileName: fileName,
targetFilePath: savedFilePath
})
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger)
.then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
})
}))
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'SmmsApi', method: 'downloadBucketFile' }))
})
return true

View File

@@ -1,4 +1,3 @@
import COS from 'cos-nodejs-sdk-v5'
import { ipcMain, IpcMainEvent } from 'electron'
import fs from 'fs-extra'
@@ -18,7 +17,7 @@ class TcyunApi {
ctx: COS
logger: ManageLogger
constructor (secretId: string, secretKey: string, logger: ManageLogger) {
constructor(secretId: string, secretKey: string, logger: ManageLogger) {
this.ctx = new COS({
SecretId: secretId,
SecretKey: secretKey
@@ -26,7 +25,7 @@ class TcyunApi {
this.logger = logger
}
formatFolder (item: {Prefix: string}, slicedPrefix: string, urlPrefix: string) {
formatFolder(item: { Prefix: string }, slicedPrefix: string, urlPrefix: string) {
return {
...item,
key: item.Prefix,
@@ -41,7 +40,7 @@ class TcyunApi {
}
}
formatFile (item: COS.CosObject, slicedPrefix: string, urlPrefix: string): any {
formatFile(item: COS.CosObject, slicedPrefix: string, urlPrefix: string): any {
return {
...item,
key: item.Key,
@@ -58,8 +57,8 @@ class TcyunApi {
/**
* 获取存储桶列表
*/
async getBucketList (): Promise<any> {
*/
async getBucketList(): Promise<any> {
const res = await this.ctx.getService({})
return res?.Buckets || []
}
@@ -67,7 +66,7 @@ class TcyunApi {
/**
* 获取自定义域名
*/
async getBucketDomain (param: IStringKeyMap): Promise<any> {
async getBucketDomain(param: IStringKeyMap): Promise<any> {
const { bucketName, region } = param
const res = await this.ctx.getBucketDomain({
Bucket: bucketName,
@@ -87,8 +86,8 @@ class TcyunApi {
* }
* @description
* acl: private | publicRead | publicReadWrite
*/
async createBucket (configMap: IStringKeyMap): Promise < boolean > {
*/
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
const res = await this.ctx.putBucket({
ACL: configMap.acl,
Bucket: configMap.BucketName,
@@ -97,9 +96,15 @@ class TcyunApi {
return res?.statusCode === 200
}
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
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 urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
const cancelTask = [false]
@@ -125,8 +130,11 @@ class TcyunApi {
Marker: marker
})
if (res?.statusCode === 200) {
result.fullList.push(...res.Contents.filter(item => parseInt(item.Size) !== 0)
.map(item => this.formatFile(item, slicedPrefix, urlPrefix)))
result.fullList.push(
...res.Contents.filter(item => parseInt(item.Size) !== 0).map(item =>
this.formatFile(item, slicedPrefix, urlPrefix)
)
)
window.webContents.send(refreshDownloadFileTransferList, result)
} else {
result.finished = true
@@ -142,9 +150,15 @@ class TcyunApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise < any > {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
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 urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
const cancelTask = [false]
@@ -173,8 +187,9 @@ class TcyunApi {
if (res?.statusCode === 200) {
result.fullList.push(
...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix, urlPrefix)),
...res.Contents.filter(item => parseInt(item.Size) !== 0)
.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('refreshFileTransferList', result)
} else {
@@ -205,19 +220,26 @@ class TcyunApi {
* itemsPerPage: number,
* customUrl: string
* }
*/
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, customUrl, marker, itemsPerPage } = configMap
*/
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
customUrl,
marker,
itemsPerPage
} = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
const res = await this.ctx.getBucket({
const res = (await this.ctx.getBucket({
Bucket: bucket,
Region: region,
Prefix: slicedPrefix === '' ? undefined : slicedPrefix,
Delimiter: '/',
Marker: marker,
MaxKeys: itemsPerPage
}) as COS.GetBucketResult
})) as COS.GetBucketResult
if (res?.statusCode !== 200) {
return {
fullList: [],
@@ -229,8 +251,9 @@ class TcyunApi {
const result = {
fullList: [
...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix, urlPrefix)),
...res.Contents.filter(item => parseInt(item.Size) !== 0)
.map(item => this.formatFile(item, slicedPrefix, urlPrefix))
...res.Contents.filter(item => parseInt(item.Size) !== 0).map(item =>
this.formatFile(item, slicedPrefix, urlPrefix)
)
],
isTruncated: res.IsTruncated === 'true',
nextMarker: res.NextMarker || '',
@@ -248,8 +271,8 @@ class TcyunApi {
* oldKey: string,
* newKey: string
* }
*/
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
*/
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, oldKey, newKey } = configMap
const copyRes = await this.ctx.putObjectCopy({
Bucket: bucketName,
@@ -277,8 +300,8 @@ class TcyunApi {
* region: string,
* key: string
* }
*/
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
*/
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
const res = await this.ctx.deleteObject({
Bucket: bucketName,
@@ -292,7 +315,7 @@ class TcyunApi {
* 删除文件夹
* @param configMap
*/
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
let marker
let res: any
@@ -317,7 +340,14 @@ class TcyunApi {
marker = res.NextMarker
} while (res.IsTruncated === 'true')
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)
for (let i = 0; i < cycles; i++) {
@@ -342,16 +372,18 @@ class TcyunApi {
* customUrl: string
* }
*/
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName, region, key, expires, customUrl } = configMap
const res = this.ctx.getObjectUrl({
Bucket: bucketName,
Region: region,
Key: key,
Expires: expires,
Sign: true
}, () => {
})
const res = this.ctx.getObjectUrl(
{
Bucket: bucketName,
Region: region,
Key: key,
Expires: expires,
Sign: true
},
() => {}
)
return customUrl ? `${customUrl.replace(/\/+$/, '')}/${key}${res.slice(res.indexOf('?'))}` : res
}
@@ -359,7 +391,7 @@ class TcyunApi {
* 高级上传文件
* @param configMap
*/
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
// fileArray = [{
// bucketName: string,
@@ -412,7 +444,12 @@ class TcyunApi {
finishTime: new Date().toLocaleString()
})
} else {
this.logger.error(formatError(err, { method: 'uploadBucketFile', class: 'TcyunApi' }))
this.logger.error(
formatError(err, {
method: 'uploadBucketFile',
class: 'TcyunApi'
})
)
instance.updateUploadTask({
id,
progress: 0,
@@ -434,7 +471,7 @@ class TcyunApi {
* 新建文件夹
* @param configMap
*/
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
const res = await this.ctx.putObject({
Bucket: bucketName,
@@ -449,7 +486,7 @@ class TcyunApi {
* 下载文件
* @param configMap
*/
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray } = configMap
// fileArray = [{
// bucketName: string,
@@ -472,38 +509,46 @@ class TcyunApi {
targetFilePath: path.join(downloadPath, fileName)
})
fs.ensureDirSync(path.dirname(path.join(downloadPath, fileName)))
this.ctx.downloadFile({
Bucket: bucketName,
Region: region,
Key: key,
RetryTimes: 3,
ChunkSize: 1024 * 1024 * 1,
FilePath: path.join(downloadPath, fileName),
onProgress: (progress: any) => {
this.ctx
.downloadFile({
Bucket: bucketName,
Region: region,
Key: key,
RetryTimes: 3,
ChunkSize: 1024 * 1024 * 1,
FilePath: path.join(downloadPath, fileName),
onProgress: (progress: any) => {
instance.updateDownloadTask({
id,
progress: Math.floor(progress.percent * 100),
status: downloadTaskSpecialStatus.downloading
})
}
})
.then((res: any) => {
instance.updateDownloadTask({
id,
progress: Math.floor(progress.percent * 100),
status: downloadTaskSpecialStatus.downloading
progress: res && res.statusCode === 200 ? 100 : 0,
status: res && res.statusCode === 200 ? downloadTaskSpecialStatus.downloaded : commonTaskStatus.failed,
response: typeof res === 'object' ? JSON.stringify(res) : String(res),
finishTime: new Date().toLocaleString()
})
}
}).then((res: any) => {
instance.updateDownloadTask({
id,
progress: res && res.statusCode === 200 ? 100 : 0,
status: res && res.statusCode === 200 ? downloadTaskSpecialStatus.downloaded : commonTaskStatus.failed,
response: typeof res === 'object' ? JSON.stringify(res) : String(res),
finishTime: new Date().toLocaleString()
})
}).catch((err: any) => {
this.logger.error(formatError(err, { method: 'downloadBucketFile', class: 'TcyunApi' }))
instance.updateDownloadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
response: typeof err === 'object' ? JSON.stringify(err) : String(err),
finishTime: new Date().toLocaleString()
.catch((err: any) => {
this.logger.error(
formatError(err, {
method: 'downloadBucketFile',
class: 'TcyunApi'
})
)
instance.updateDownloadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
response: typeof err === 'object' ? JSON.stringify(err) : String(err),
finishTime: new Date().toLocaleString()
})
})
})
}
return true
}

View File

@@ -7,7 +7,15 @@ import Upyun from 'upyun'
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 UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
@@ -26,7 +34,14 @@ class UpyunApi {
stopMarker = 'g2gCZAAEbmV4dGQAA2VvZg'
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.cli = new Upyun.Client(this.ser)
this.bucket = bucket
@@ -37,7 +52,7 @@ class UpyunApi {
this.expireTime = expireTime || 24 * 60 * 60
}
getAntiLeechParam (key: string): string {
getAntiLeechParam(key: string): string {
const uri = `/${key}`.replace(/%2F/g, '/').replace(/^\/+/g, '/')
const now = Math.round(new Date().getTime() / 1000)
const expire = this.expireTime ? now + parseInt(this.expireTime.toString(), 10) : now + 1800
@@ -46,7 +61,7 @@ class UpyunApi {
return `_upt=${upt}`
}
formatFolder (item: any, slicedPrefix: string, urlPrefix: string) {
formatFolder(item: any, slicedPrefix: string, urlPrefix: string) {
const key = `${slicedPrefix}${item.name}/`
let url = `${urlPrefix}/${key}`
if (this.antiLeechToken) {
@@ -67,7 +82,7 @@ class UpyunApi {
}
}
formatFile (item: any, slicedPrefix: string, urlPrefix: string) {
formatFile(item: any, slicedPrefix: string, urlPrefix: string) {
const key = `${slicedPrefix}${item.name}`
let url = `${urlPrefix}/${key}`
if (this.antiLeechToken) {
@@ -87,13 +102,7 @@ class UpyunApi {
}
}
authorization (
method: string,
uri: string,
contentMd5: string,
operator: string,
password: string
) {
authorization(method: string, uri: string, contentMd5: string, operator: string, password: string) {
return `UPYUN ${operator}:${hmacSha1Base64(
md5(password, 'hex'),
`${method.toUpperCase()}&${encodeURI(uri)}&${new Date().toUTCString()}${contentMd5 ? `&${contentMd5}` : ''}`
@@ -103,11 +112,11 @@ class UpyunApi {
/**
* 获取空间列表
*/
async getBucketList (): Promise<any> {
async getBucketList(): Promise<any> {
return this.bucket
}
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, prefix, cancelToken } = configMap
const slicedPrefix = prefix.slice(1)
@@ -159,7 +168,7 @@ class UpyunApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, prefix, cancelToken } = configMap
const slicedPrefix = prefix.slice(1)
@@ -217,8 +226,8 @@ class UpyunApi {
* itemsPerPage: number,
* customUrl: string
* }
*/
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
*/
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, prefix, marker, itemsPerPage } = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `http://${bucket}.test.upcdn.net`
@@ -246,16 +255,16 @@ class UpyunApi {
}
/**
* 重命名文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* oldKey: string,
* newKey: string
* }
*/
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
* 重命名文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* oldKey: string,
* newKey: string
* }
*/
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const oldKey = configMap.oldKey
let newKey = configMap.newKey
const method = 'PUT'
@@ -280,25 +289,25 @@ class UpyunApi {
}
/**
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string
* }
*/
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
* 删除文件
* @param configMap
* configMap = {
* bucketName: string,
* region: string,
* key: string
* }
*/
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
const res = await this.cli.deleteFile(key)
return res
}
/**
* delete bucket folder
* @param configMap
*/
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
* delete bucket folder
* @param configMap
*/
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let marker = ''
let isTruncated
@@ -313,14 +322,16 @@ class UpyunApi {
})
if (res) {
res.files.forEach((item: any) => {
item.type === 'N' && allFileList.Contents.push({
...item,
key: `${key}${item.name}`
})
item.type === 'F' && allFileList.CommonPrefixes.push({
...item,
key: `${key}${item.name}/`
})
item.type === 'N' &&
allFileList.Contents.push({
...item,
key: `${key}${item.name}`
})
item.type === 'F' &&
allFileList.CommonPrefixes.push({
...item,
key: `${key}${item.name}/`
})
})
marker = res.next
isTruncated = res.next !== this.stopMarker
@@ -360,7 +371,7 @@ class UpyunApi {
* axiso:onUploadProgress not work in nodejs , use got instead
* @param configMap
*/
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
fileArray.forEach((item: any) => {
@@ -416,7 +427,7 @@ class UpyunApi {
* 新建文件夹
* @param configMap
*/
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
const res = await this.cli.makeDir(`/${key}`)
return res
@@ -426,7 +437,7 @@ class UpyunApi {
* 下载文件
* @param configMap
*/
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance()
const promises = [] as any
@@ -445,19 +456,21 @@ class UpyunApi {
targetFilePath: savedFilePath
})
const preSignedUrl = `${customUrl}/${key}`
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger)
.then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
})
}))
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'UpyunApi', method: 'downloadBucketFile' }))
})
return true

View File

@@ -29,7 +29,15 @@ class WebdavplistApi {
agent: https.Agent | http.Agent
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.username = username
this.password = password
@@ -50,16 +58,12 @@ class WebdavplistApi {
if (this.authType === 'digest') {
options.authType = AuthType.Digest
}
this.ctx = createClient(
this.endpoint,
options
)
this.ctx = createClient(this.endpoint, options)
}
logParam = (error:any, method: string) =>
this.logger.error(formatError(error, { class: 'WebdavplistApi', method }))
logParam = (error: any, method: string) => 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(/^\/+/, '')
return {
...item,
@@ -76,7 +80,7 @@ class WebdavplistApi {
}
}
formatFile (item: FileStat, urlPrefix: string, isWebPath = false) {
formatFile(item: FileStat, urlPrefix: string, isWebPath = false) {
const key = item.filename.replace(/^\/+/, '')
return {
...item,
@@ -95,7 +99,7 @@ class WebdavplistApi {
isRequestSuccess = (code: number) => code >= 200 && code < 300
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken } = configMap
const urlPrefix = customUrl || this.endpoint
@@ -135,7 +139,7 @@ class WebdavplistApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken, baseDir } = configMap
let urlPrefix = customUrl || this.endpoint
@@ -166,7 +170,8 @@ class WebdavplistApi {
if (res.data?.length) {
res.data.forEach((item: FileStat) => {
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') {
result.fullList.push(this.formatFolder(item, webPath ? relative : urlPrefix, !!webPath))
} else {
@@ -193,7 +198,7 @@ class WebdavplistApi {
ipcMain.removeAllListeners('cancelLoadingFileList')
}
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { oldKey, newKey } = configMap
let result = false
try {
@@ -205,7 +210,7 @@ class WebdavplistApi {
return result
}
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -217,7 +222,7 @@ class WebdavplistApi {
return result
}
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -229,7 +234,7 @@ class WebdavplistApi {
return result
}
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { key } = configMap
let result = ''
try {
@@ -241,7 +246,7 @@ class WebdavplistApi {
return result
}
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) {
@@ -261,10 +266,8 @@ class WebdavplistApi {
targetFileRegion: region,
noProgress: true
})
this.ctx.putFileContents(
key,
this.authType === 'digest' ? fs.readFileSync(filePath) : fs.createReadStream(filePath),
{
this.ctx
.putFileContents(key, this.authType === 'digest' ? fs.readFileSync(filePath) : fs.createReadStream(filePath), {
overwrite: true,
onUploadProgress: (progressEvent: ProgressEvent) => {
instance.updateUploadTask({
@@ -273,37 +276,38 @@ class WebdavplistApi {
status: uploadTaskSpecialStatus.uploading
})
}
}
).then((res: boolean) => {
if (res) {
instance.updateUploadTask({
id,
progress: 100,
status: uploadTaskSpecialStatus.uploaded,
finishTime: new Date().toLocaleString()
})
} else {
})
.then((res: boolean) => {
if (res) {
instance.updateUploadTask({
id,
progress: 100,
status: uploadTaskSpecialStatus.uploaded,
finishTime: new Date().toLocaleString()
})
} else {
instance.updateUploadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
finishTime: new Date().toLocaleString()
})
}
})
.catch((error: any) => {
this.logParam(error, 'uploadBucketFile')
instance.updateUploadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
finishTime: new Date().toLocaleString()
})
}
}).catch((error: any) => {
this.logParam(error, 'uploadBucketFile')
instance.updateUploadTask({
id,
progress: 0,
status: commonTaskStatus.failed,
finishTime: new Date().toLocaleString()
})
})
}
return true
}
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -317,7 +321,7 @@ class WebdavplistApi {
return result
}
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance()
const promises = [] as any
@@ -345,25 +349,35 @@ class WebdavplistApi {
Authorization: `Basic ${base64Str}`
}
} 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 = {
Authorization: authHeader
}
preSignedUrl = `${this.endpoint}/${key.replace(/^\/+/, '')}`
}
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxyStr, headers)
.then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxyStr, headers).then(
(res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
}
)
})
}))
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logParam(error, 'downloadBucketFile')
})
return true

View File

@@ -6,7 +6,7 @@ import { IManageApiType, IManageConfigType } from '#/types/manage'
class ManageDB {
readonly #ctx: IManageApiType
readonly #db: JSONStore
constructor (ctx: IManageApiType) {
constructor(ctx: IManageApiType) {
this.#ctx = ctx
this.#db = new JSONStore(this.#ctx.configPath)
const initParams: IStringKeyMap = {
@@ -25,37 +25,37 @@ class ManageDB {
}
}
read (flush?: boolean): IJSON {
read(flush?: boolean): IJSON {
return this.#db.read(flush)
}
get (key: string = ''): any {
get(key: string = ''): any {
this.read(true)
return this.#db.get(key)
}
set (key: string, value: any): void {
set(key: string, value: any): void {
this.read(true)
return this.#db.set(key, value)
}
has (key: string): boolean {
has(key: string): boolean {
this.read(true)
return this.#db.has(key)
}
unset (key: string, value: any): boolean {
unset(key: string, value: any): boolean {
this.read(true)
return this.#db.unset(key, value)
}
saveConfig (config: Partial<IManageConfigType>): void {
saveConfig(config: Partial<IManageConfigType>): void {
Object.keys(config).forEach((name: string) => {
this.set(name, config[name])
})
}
removeConfig (config: IManageConfigType): void {
removeConfig(config: IManageConfigType): void {
Object.keys(config).forEach((name: string) => {
this.unset(name, config[name])
})

View File

@@ -23,7 +23,7 @@ const errorMsg = {
/** ensure notification list */
if (!global.notificationList) global.notificationList = []
function manageDbChecker () {
function manageDbChecker() {
if (process.type !== 'renderer') {
const manageConfigFilePath = managePathChecker()
if (!fs.existsSync(manageConfigFilePath)) {
@@ -42,9 +42,13 @@ function manageDbChecker () {
fs.unlinkSync(manageConfigFilePath)
if (fs.existsSync(manageConfigFileBackupPath)) {
try {
configFile = fs.readFileSync(manageConfigFileBackupPath, { encoding: 'utf-8' })
configFile = fs.readFileSync(manageConfigFileBackupPath, {
encoding: 'utf-8'
})
JSON.parse(configFile)
writeFile.sync(manageConfigFilePath, configFile, { encoding: 'utf-8' })
writeFile.sync(manageConfigFilePath, configFile, {
encoding: 'utf-8'
})
const stats = fs.statSync(manageConfigFileBackupPath)
optionsTpl.body = `${errorMsg.brokenButBackup}\n${T('TIPS_PICGO_BACKUP_FILE_VERSION', {
v: dayjs(stats.mtime).format('YYYY-MM-DD HH:mm:ss')
@@ -61,14 +65,16 @@ function manageDbChecker () {
global.notificationList.push(optionsTpl)
return
}
writeFile.sync(manageConfigFileBackupPath, configFile, { encoding: 'utf-8' })
writeFile.sync(manageConfigFileBackupPath, configFile, {
encoding: 'utf-8'
})
}
}
/**
* Get manage config path
*/
function managePathChecker (): string {
function managePathChecker(): string {
if (_configFilePath) {
return _configFilePath
}
@@ -80,7 +86,9 @@ function managePathChecker (): string {
return _configFilePath
}
try {
const configString = fs.readFileSync(defaultManageConfigPath, { encoding: 'utf-8' })
const configString = fs.readFileSync(defaultManageConfigPath, {
encoding: 'utf-8'
})
const config = JSON.parse(configString)
const userConfigPath: string = config.configPath || ''
if (userConfigPath) {
@@ -107,12 +115,8 @@ function managePathChecker (): string {
}
}
function managePathDir () {
function managePathDir() {
return path.dirname(managePathChecker())
}
export {
managePathChecker,
managePathDir,
manageDbChecker
}
export { managePathChecker, managePathDir, manageDbChecker }

View File

@@ -9,8 +9,8 @@ import { commonTaskStatus, downloadTaskSpecialStatus, uploadTaskSpecialStatus }
import { IDownloadTask, IUploadTask } from '#/types/manage'
class UpDownTaskQueue {
/* eslint-disable */
private static instance: UpDownTaskQueue
/* eslint-disable */
private static instance: UpDownTaskQueue
/* eslint-enable */
private uploadTaskQueue = <IUploadTask[]>[]
@@ -18,38 +18,38 @@ class UpDownTaskQueue {
private persistPath = path.join(app.getPath('userData'), 'UpDownTaskQueue.json')
private constructor () {
private constructor() {
this.restore()
}
static getInstance () {
static getInstance() {
if (!UpDownTaskQueue.instance) {
UpDownTaskQueue.instance = new UpDownTaskQueue()
}
return UpDownTaskQueue.instance
}
getUploadTaskQueue () {
getUploadTaskQueue() {
return UpDownTaskQueue.getInstance().uploadTaskQueue
}
getDownloadTaskQueue () {
getDownloadTaskQueue() {
return UpDownTaskQueue.getInstance().downloadTaskQueue
}
getUploadTask (taskId: string) {
getUploadTask(taskId: string) {
return UpDownTaskQueue.getInstance().uploadTaskQueue.find(item => item.id === taskId)
}
getAllUploadTask () {
getAllUploadTask() {
return UpDownTaskQueue.getInstance().uploadTaskQueue
}
addUploadTask (task: IUploadTask) {
addUploadTask(task: IUploadTask) {
UpDownTaskQueue.getInstance().uploadTaskQueue.push(task)
}
updateUploadTask (task: Partial<IUploadTask>) {
updateUploadTask(task: Partial<IUploadTask>) {
const taskIndex = UpDownTaskQueue.getInstance().uploadTaskQueue.findIndex(item => item.id === task.id)
if (taskIndex !== -1) {
const taskKeys = Object.keys(task)
@@ -61,33 +61,33 @@ class UpDownTaskQueue {
}
}
removeUploadTask (taskId: string) {
removeUploadTask(taskId: string) {
const taskIndex = UpDownTaskQueue.getInstance().uploadTaskQueue.findIndex(item => item.id === taskId)
if (taskIndex !== -1) {
UpDownTaskQueue.getInstance().uploadTaskQueue.splice(taskIndex, 1)
}
}
removeDownloadTask (taskId: string) {
removeDownloadTask(taskId: string) {
const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === taskId)
if (taskIndex !== -1) {
UpDownTaskQueue.getInstance().downloadTaskQueue.splice(taskIndex, 1)
}
}
getDownloadTask (taskId: string) {
getDownloadTask(taskId: string) {
return UpDownTaskQueue.getInstance().downloadTaskQueue.find(item => item.id === taskId)
}
getAllDownloadTask () {
getAllDownloadTask() {
return UpDownTaskQueue.getInstance().downloadTaskQueue
}
addDownloadTask (task: IDownloadTask) {
addDownloadTask(task: IDownloadTask) {
UpDownTaskQueue.getInstance().downloadTaskQueue.push(task)
}
updateDownloadTask (task: Partial<IDownloadTask>) {
updateDownloadTask(task: Partial<IDownloadTask>) {
const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === task.id)
if (taskIndex !== -1) {
const taskKeys = Object.keys(task)
@@ -99,40 +99,53 @@ class UpDownTaskQueue {
}
}
clearUploadTaskQueue () {
clearUploadTaskQueue() {
UpDownTaskQueue.getInstance().uploadTaskQueue = []
}
removeUploadedTask () {
UpDownTaskQueue.getInstance().uploadTaskQueue = UpDownTaskQueue.getInstance().uploadTaskQueue.filter(item => item.status !== uploadTaskSpecialStatus.uploaded && item.status !== commonTaskStatus.canceled && item.status !== commonTaskStatus.failed)
removeUploadedTask() {
UpDownTaskQueue.getInstance().uploadTaskQueue = UpDownTaskQueue.getInstance().uploadTaskQueue.filter(
item =>
item.status !== uploadTaskSpecialStatus.uploaded &&
item.status !== commonTaskStatus.canceled &&
item.status !== commonTaskStatus.failed
)
}
removeDownloadedTask () {
UpDownTaskQueue.getInstance().downloadTaskQueue = UpDownTaskQueue.getInstance().downloadTaskQueue.filter(item => item.status !== downloadTaskSpecialStatus.downloaded && item.status !== commonTaskStatus.canceled && item.status !== commonTaskStatus.failed)
removeDownloadedTask() {
UpDownTaskQueue.getInstance().downloadTaskQueue = UpDownTaskQueue.getInstance().downloadTaskQueue.filter(
item =>
item.status !== downloadTaskSpecialStatus.downloaded &&
item.status !== commonTaskStatus.canceled &&
item.status !== commonTaskStatus.failed
)
}
clearDownloadTaskQueue () {
clearDownloadTaskQueue() {
UpDownTaskQueue.getInstance().downloadTaskQueue = []
}
clearAllTaskQueue () {
clearAllTaskQueue() {
this.clearUploadTaskQueue()
this.clearDownloadTaskQueue()
}
persist () {
persist() {
try {
this.checkPersistPath()
fs.writeFileSync(this.persistPath, JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue
}))
fs.writeFileSync(
this.persistPath,
JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue
})
)
} catch (e) {
console.log(e)
}
}
private restore () {
private restore() {
try {
this.checkPersistPath()
const persistData = JSON.parse(fs.readFileSync(this.persistPath, { encoding: 'utf-8' }))
@@ -144,20 +157,26 @@ class UpDownTaskQueue {
}
}
private checkPersistPath () {
private checkPersistPath() {
if (!fs.existsSync(this.persistPath)) {
fs.writeFileSync(this.persistPath, JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue
}))
fs.writeFileSync(
this.persistPath,
JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue
})
)
}
try {
JSON.parse(fs.readFileSync(this.persistPath, { encoding: 'utf-8' }))
} catch (e) {
fs.writeFileSync(this.persistPath, JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue
}))
fs.writeFileSync(
this.persistPath,
JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue
})
)
}
}
}

View File

@@ -10,7 +10,7 @@ import { downloadFileFromUrl } from '~/manage/utils/common'
import { selectDownloadFolder } from '#/utils/static'
export const manageIpcList = {
listen () {
listen() {
manageCoreIPC.listen()
ipcMain.handle('getBucketList', async (_: IpcMainInvokeEvent, currentPicBed: string) => {
@@ -59,10 +59,13 @@ export const manageIpcList = {
return manage.getBucketListBackstage(param)
})
ipcMain.on('getBucketListRecursively', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.getBucketListRecursively(param)
})
ipcMain.on(
'getBucketListRecursively',
async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.getBucketListRecursively(param)
}
)
ipcMain.handle('convertPathToBase64', async (_: IpcMainInvokeEvent, filePath: string) => {
const res = fs.readFileSync(filePath, 'base64')

View File

@@ -1,11 +1,11 @@
import {
IpcMainEvent,
IpcMainInvokeEvent,
ipcMain
} from 'electron'
import { IpcMainEvent, IpcMainInvokeEvent, ipcMain } from 'electron'
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()
@@ -28,7 +28,7 @@ const handleManageRemoveConfig = () => {
}
export default {
listen () {
listen() {
handleManageGetConfig()
handleManageSaveConfig()
handleManageRemoveConfig()

View File

@@ -1,4 +1,3 @@
import { ipcMain } from 'electron'
import { EventEmitter } from 'events'
import fs from 'fs-extra'
@@ -15,12 +14,7 @@ import { isInputConfigValid, formatError } from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { IWindowList } from '#/types/enum'
import {
IManageApiType,
IManageConfigType,
IManageError,
IPicBedMangeConfig
} from '#/types/manage'
import { IManageApiType, IManageConfigType, IManageError, IPicBedMangeConfig } from '#/types/manage'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
export class ManageApi extends EventEmitter implements IManageApiType {
@@ -32,7 +26,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
logger: ManageLogger
currentPicBedConfig: IPicBedMangeConfig
constructor (currentPicBed: string = '') {
constructor(currentPicBed: string = '') {
super()
this.currentPicBed = currentPicBed || 'placeholder'
this.configPath = managePathChecker()
@@ -42,7 +36,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
this.currentPicBedConfig = this.getPicBedConfig(this.currentPicBed)
}
getMsgParam (method: string) {
getMsgParam(method: string) {
return {
class: 'ManageApi',
method,
@@ -50,19 +44,33 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
errorMsg (err: any, param: IStringKeyMap) {
errorMsg(err: any, param: IStringKeyMap) {
this.logger.error(formatError(err, param))
}
createClient () {
createClient() {
const name = this.currentPicBedConfig.picBedName
switch (name) {
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':
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':
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':
return new API.LocalApi(this.logger)
case 'qiniu':
@@ -70,25 +78,60 @@ export class ManageApi extends EventEmitter implements IManageApiType {
case 'smms':
return new API.SmmsApi(this.currentPicBedConfig.token, this.logger)
case 's3plist':
return new API.S3plistApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.secretAccessKey, this.currentPicBedConfig.endpoint, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.s3ForcePathStyle, this.currentPicBedConfig.proxy, this.logger, 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':
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':
return new API.TcyunApi(this.currentPicBedConfig.secretId, this.currentPicBedConfig.secretKey, this.logger)
case 'upyun':
return new API.UpyunApi(this.currentPicBedConfig.bucketName, this.currentPicBedConfig.operator, this.currentPicBedConfig.password, this.logger, 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':
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:
return {} as any
}
}
private getPicBedConfig (picBedName: string): IPicBedMangeConfig {
private getPicBedConfig(picBedName: string): IPicBedMangeConfig {
return this.getConfig<IPicBedMangeConfig>(`picBed.${picBedName}`)
}
private initConfigPath (): void {
private initConfigPath(): void {
if (this.configPath === '') {
this.configPath = `${homedir()}/.piclist/manage.json`
}
@@ -103,30 +146,28 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
private initconfig (): void {
private initconfig(): void {
this.db = new ManageDB(this)
this._config = this.db.read(true) as IManageConfigType
}
getConfig<T> (name?: string): T {
getConfig<T>(name?: string): T {
if (!name) {
return this._config as unknown as T
}
return get(this._config, name)
}
saveConfig (config: IStringKeyMap): void {
saveConfig(config: IStringKeyMap): void {
if (!isInputConfigValid(config)) {
this.logger.warn(
'the format of config is invalid, please provide object'
)
this.logger.warn('the format of config is invalid, please provide object')
return
}
this.setConfig(config)
this.db.saveConfig(config)
}
removeConfig (key: string, propName: string): void {
removeConfig(key: string, propName: string): void {
if (!key || !propName) {
return
}
@@ -134,11 +175,9 @@ export class ManageApi extends EventEmitter implements IManageApiType {
this.db.unset(key, propName)
}
setConfig (config: IStringKeyMap): void {
setConfig(config: IStringKeyMap): void {
if (!isInputConfigValid(config)) {
this.logger.warn(
'the format of config is invalid, please provide object'
)
this.logger.warn('the format of config is invalid, please provide object')
return
}
Object.keys(config).forEach((name: string) => {
@@ -146,14 +185,12 @@ export class ManageApi extends EventEmitter implements IManageApiType {
})
}
unsetConfig (key: string, propName: string): void {
unsetConfig(key: string, propName: string): void {
if (!key || !propName) return
unset(this.getConfig(key), propName)
}
async getBucketList (
param?: IStringKeyMap | undefined
): Promise<any> {
async getBucketList(param?: IStringKeyMap | undefined): Promise<any> {
let client
const name = this.currentPicBedConfig.picBedName.replace('plist', '')
switch (this.currentPicBedConfig.picBedName) {
@@ -171,36 +208,36 @@ export class ManageApi extends EventEmitter implements IManageApiType {
return []
}
case 'upyun':
return [{
Name: this.currentPicBedConfig.bucketName,
Location: 'upyun',
CreationDate: new Date().toISOString()
}]
return [
{
Name: this.currentPicBedConfig.bucketName,
Location: 'upyun',
CreationDate: new Date().toISOString()
}
]
case 'smms':
case 'webdavplist':
case 'local':
case 'sftp':
return [{
Name: name,
Location: name,
CreationDate: new Date().toISOString()
}]
return [
{
Name: name,
Location: name,
CreationDate: new Date().toISOString()
}
]
default:
console.log(param)
return []
}
}
async getBucketInfo (
param?: IStringKeyMap | undefined
): Promise<IStringKeyMap | IManageError> {
async getBucketInfo(param?: IStringKeyMap | undefined): Promise<IStringKeyMap | IManageError> {
console.log(param)
return {}
}
async getBucketDomain (
param: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
async getBucketDomain(param: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -225,9 +262,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async createBucket (
param?: IStringKeyMap
): Promise<boolean> {
async createBucket(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -246,44 +281,32 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async deleteBucket (
param?: IStringKeyMap
): Promise<boolean> {
async deleteBucket(param?: IStringKeyMap): Promise<boolean> {
console.log(param)
return false
}
async getOperatorList (
param?: IStringKeyMap
): Promise<string[] | IManageError> {
async getOperatorList(param?: IStringKeyMap): Promise<string[] | IManageError> {
console.log(param)
return []
}
async addOperator (
param?: IStringKeyMap
): Promise<boolean> {
async addOperator(param?: IStringKeyMap): Promise<boolean> {
console.log(param)
return false
}
async deleteOperator (
param?: IStringKeyMap
): Promise<boolean> {
async deleteOperator(param?: IStringKeyMap): Promise<boolean> {
console.log(param)
return false
}
async getBucketAclPolicy (
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
async getBucketAclPolicy(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
console.log(param)
return {}
}
async setBucketAclPolicy (
param?: IStringKeyMap
): Promise<boolean> {
async setBucketAclPolicy(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'qiniu':
@@ -299,9 +322,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async getBucketListRecursively (
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
async getBucketListRecursively(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
let client
let window
const defaultResult = {
@@ -344,9 +365,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
* @param param
* @returns
*/
async getBucketListBackstage (
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
async getBucketListBackstage(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
let client
let window
const defaultResult = {
@@ -385,17 +404,15 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
/**
* 获取文件夹列表
* 结果统一进行格式化 文件夹提取到最前
* key: 完整路径
* fileName: 文件名
* formatedTime: 格式化时间
* isDir: 是否是文件夹
* fileSize: 文件大小
**/
async getBucketFileList (
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
* 获取文件夹列表
* 结果统一进行格式化 文件夹提取到最前
* key: 完整路径
* fileName: 文件名
* formatedTime: 格式化时间
* isDir: 是否是文件夹
* fileSize: 文件大小
**/
async getBucketFileList(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
const defaultResponse = {
fullList: <any>[],
isTruncated: false,
@@ -422,9 +439,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async deleteBucketFile (
param?: IStringKeyMap
): Promise<boolean> {
async deleteBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -451,9 +466,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async deleteBucketFolder (
param?: IStringKeyMap
): Promise<boolean> {
async deleteBucketFolder(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -477,9 +490,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async renameBucketFile (
param?: IStringKeyMap
): Promise<boolean> {
async renameBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -502,9 +513,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async downloadBucketFile (
param?: IStringKeyMap
): Promise<boolean> {
async downloadBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -531,16 +540,12 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async copyMoveBucketFile (
param?: IStringKeyMap
): Promise<boolean> {
async copyMoveBucketFile(param?: IStringKeyMap): Promise<boolean> {
console.log(param)
return false
}
async createBucketFolder (
param?: IStringKeyMap
): Promise<boolean> {
async createBucketFolder(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -564,9 +569,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async uploadBucketFile (
param?: IStringKeyMap
): Promise<boolean> {
async uploadBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -592,9 +595,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async getPreSignedUrl (
param?: IStringKeyMap
): Promise<string> {
async getPreSignedUrl(param?: IStringKeyMap): Promise<string> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':

View File

@@ -18,17 +18,12 @@ import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, downloadTaskSpecialStatus, uploadTaskSpecialStatus } from '#/types/enum'
import { formatHttpProxy } from '#/utils/common'
export const getFSFile = async (
filePath: string,
stream: boolean = false
): Promise<IStringKeyMap> => {
export const getFSFile = async (filePath: string, stream: boolean = false): Promise<IStringKeyMap> => {
try {
return {
extension: path.extname(filePath),
fileName: path.basename(filePath),
buffer: stream
? fs.createReadStream(filePath)
: await fs.readFile(filePath),
buffer: stream ? fs.createReadStream(filePath) : await fs.readFile(filePath),
success: true
}
} catch (e) {
@@ -38,10 +33,8 @@ export const getFSFile = async (
}
}
export function isInputConfigValid (config: any): boolean {
return typeof config === 'object' &&
!Array.isArray(config) &&
Object.keys(config).length > 0
export function isInputConfigValid(config: any): boolean {
return typeof config === 'object' && !Array.isArray(config) && Object.keys(config).length > 0
}
export const getFileMimeType = (filePath: string): string => mime.lookup(filePath) || 'application/octet-stream'
@@ -83,17 +76,18 @@ export const clearTempFolder = () => fs.emptyDirSync(getTempDirPath())
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 (
instance: UpDownTaskQueue,
preSignedUrl: string,
id : string,
id: string,
savedFilePath: string,
logger?: ManageLogger,
proxy?: string,
headers?: any
) : Promise<boolean> => {
): Promise<boolean> => {
const options = {
url: preSignedUrl,
directory: path.dirname(savedFilePath),
@@ -150,19 +144,16 @@ export const gotUpload = async (
throwHttpErrors: boolean = false,
agent: any = {}
) => {
got(
url,
{
headers,
method,
body,
timeout: {
lookup: timeout
},
throwHttpErrors,
agent
}
)
got(url, {
headers,
method,
body,
timeout: {
lookup: timeout
},
throwHttpErrors,
agent
})
.on('uploadProgress', (progress: any) => {
instance.updateUploadTask({
id,
@@ -174,7 +165,10 @@ export const gotUpload = async (
instance.updateUploadTask({
id,
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()
})
})
@@ -190,7 +184,7 @@ export const gotUpload = async (
})
}
export const formatError = (err: any, params:IStringKeyMap) => {
export const formatError = (err: any, params: IStringKeyMap) => {
if (err instanceof RequestError) {
return {
...params,
@@ -220,7 +214,10 @@ const commonOptions = {
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined
} as any
export const getAgent = (proxy:any, https: boolean = true): {
export const getAgent = (
proxy: any,
https: boolean = true
): {
https?: HttpsProxyAgent
http?: HttpProxyAgent
} => {
@@ -253,36 +250,36 @@ export const getInnerAgent = (proxy: any, sslEnabled: boolean = true) => {
if (sslEnabled) {
return formatProxy
? {
agent: new https.Agent({
agent: new https.Agent({
...commonOptions,
rejectUnauthorized: false,
host: formatProxy.host,
port: formatProxy.port
})
}
: {
agent: new https.Agent({
rejectUnauthorized: false,
keepAlive: true
})
}
}
return formatProxy
? {
agent: new http.Agent({
...commonOptions,
rejectUnauthorized: false,
host: formatProxy.host,
port: formatProxy.port
})
}
: {
agent: new https.Agent({
rejectUnauthorized: false,
keepAlive: true
: {
agent: new http.Agent({
...commonOptions
})
}
}
return formatProxy
? {
agent: new http.Agent({
...commonOptions,
host: formatProxy.host,
port: formatProxy.port
})
}
: {
agent: new http.Agent({
...commonOptions
})
}
}
export function getOptions (
export function getOptions(
method?: string,
headers?: IStringKeyMap,
searchParams?: IStringKeyMap,
@@ -298,7 +295,9 @@ export function getOptions (
...(body && { body }),
...(responseType && { responseType }),
...(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
}
}
@@ -309,14 +308,14 @@ export class ConcurrencyPromisePool {
runningNum: number
results: any[]
constructor (limit: number) {
constructor(limit: number) {
this.limit = limit
this.queue = []
this.runningNum = 0
this.results = []
}
all (promises: any[] = []) {
all(promises: any[] = []) {
return new Promise((resolve, reject) => {
for (const promise of promises) {
this._run(promise, resolve, reject)
@@ -324,7 +323,7 @@ export class ConcurrencyPromisePool {
})
}
_run (promise: any, resolve: any, reject: any) {
_run(promise: any, resolve: any, reject: any) {
if (this.runningNum >= this.limit) {
this.queue.push(promise)
return

View File

@@ -1,4 +1,4 @@
const AliyunAreaCodeName : IStringKeyMap = {
const AliyunAreaCodeName: IStringKeyMap = {
'oss-cn-hangzhou': '华东1(杭州)',
'oss-cn-shanghai': '华东2(上海)',
'oss-cn-nanjing': '华东5(南京)',
@@ -31,7 +31,7 @@ const AliyunAreaCodeName : IStringKeyMap = {
'oss-rg-china-mainland': '无地域属性'
}
const QiniuAreaCodeName : IStringKeyMap = {
const QiniuAreaCodeName: IStringKeyMap = {
z0: '华东-浙江',
'cn-east-2': '华东 浙江2',
z1: '华北-河北',
@@ -42,7 +42,7 @@ const QiniuAreaCodeName : IStringKeyMap = {
'ap-southeast-2': '亚太-河内'
}
const TencentAreaCodeName : IStringKeyMap = {
const TencentAreaCodeName: IStringKeyMap = {
'ap-beijing-1': '北京一区',
'ap-beijing': '北京',
'ap-nanjing': '南京',

View File

@@ -10,7 +10,7 @@ export interface DogecloudToken {
sessionToken: string
}
export async function dogecloudApi (
export async function dogecloudApi(
apiPath: string,
data = {},
jsonMode: boolean = false,
@@ -18,7 +18,10 @@ export async function dogecloudApi (
secretKey: string
) {
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}`
try {
const res = await axios.request({
@@ -40,16 +43,22 @@ export async function dogecloudApi (
}
}
export async function getTempToken (accessKey: string, secretKey: string): Promise<{} | DogecloudToken> {
const dogeTempToken = await picgo.getConfig('Credentials.doge-token') || {} as any
export async function getTempToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudToken> {
const dogeTempToken = (await picgo.getConfig('Credentials.doge-token')) || ({} as any)
if (dogeTempToken.token && dogeTempToken.expires > Date.now() + 7200000) {
return dogeTempToken.token
}
try {
const data = await dogecloudApi('/auth/tmp_token.json', {
channel: 'OSS_FULL',
scopes: ['*']
}, true, accessKey, secretKey)
const data = await dogecloudApi(
'/auth/tmp_token.json',
{
channel: 'OSS_FULL',
scopes: ['*']
},
true,
accessKey,
secretKey
)
const token = data.Credentials
picgo.saveConfig({
Credentials: {

View File

@@ -22,14 +22,12 @@ export class ManageLogger implements ILogger {
#logLevel!: string
#logPath!: string
constructor (ctx: IManageApiType) {
constructor(ctx: IManageApiType) {
this.#ctx = ctx
}
#handleLog (type: ILogType, ...msg: ILogArgvTypeWithError[]): void {
const logHeader = chalk[this.#level[type] as ILogColor](
`[PicList ${type.toUpperCase()}]`
)
#handleLog(type: ILogType, ...msg: ILogArgvTypeWithError[]): void {
const logHeader = chalk[this.#level[type] as ILogColor](`[PicList ${type.toUpperCase()}]`)
console.log(logHeader, ...msg)
this.#logLevel = this.#ctx.getConfig(configPaths.settings.logLevel)
this.#logPath =
@@ -53,7 +51,7 @@ export class ManageLogger implements ILogger {
}, 0)
}
#checkLogFileIsLarge (logPath: string): {
#checkLogFileIsLarge(logPath: string): {
isLarge: boolean
logFileSize?: number
logFileSizeLimit?: number
@@ -61,11 +59,7 @@ export class ManageLogger implements ILogger {
if (fs.existsSync(logPath)) {
const logFileSize = fs.statSync(logPath).size
const logFileSizeLimit =
enforceNumber(
this.#ctx.getConfig<Undefinable<number>>(
configPaths.settings.logFileSizeLimit
) || 10
) *
enforceNumber(this.#ctx.getConfig<Undefinable<number>>(configPaths.settings.logFileSizeLimit) || 10) *
1024 *
1024
return {
@@ -80,18 +74,14 @@ export class ManageLogger implements ILogger {
}
}
#recreateLogFile (logPath: string): void {
#recreateLogFile(logPath: string): void {
if (fs.existsSync(logPath)) {
fs.unlinkSync(logPath)
fs.createFileSync(logPath)
}
}
#handleWriteLog (
logPath: string,
type: string,
...msg: ILogArgvTypeWithError[]
): void {
#handleWriteLog(logPath: string, type: string, ...msg: ILogArgvTypeWithError[]): void {
try {
if (this.#checkLogLevel(type, this.#logLevel)) {
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicList ${type.toUpperCase()}] `
@@ -106,7 +96,7 @@ export class ManageLogger implements ILogger {
}
}
#formatLogItem (item: ILogArgvTypeWithError, type: string): string {
#formatLogItem(item: ILogArgvTypeWithError, type: string): string {
let result = ''
if (item instanceof Error && type === 'error') {
result += `\n------Error Stack Begin------\n${util.format(item?.stack)}\n-------Error Stack End------- `
@@ -122,10 +112,7 @@ export class ManageLogger implements ILogger {
return result
}
#checkLogLevel (
type: string,
level: undefined | string | string[]
): boolean {
#checkLogLevel(type: string, level: undefined | string | string[]): boolean {
if (level === undefined || level === 'all') {
return true
}
@@ -135,23 +122,23 @@ export class ManageLogger implements ILogger {
return type === level
}
success (...msq: ILogArgvType[]): void {
success(...msq: ILogArgvType[]): void {
return this.#handleLog(ILogType.success, ...msq)
}
info (...msq: ILogArgvType[]): void {
info(...msq: ILogArgvType[]): void {
return this.#handleLog(ILogType.info, ...msq)
}
error (...msq: ILogArgvTypeWithError[]): void {
error(...msq: ILogArgvTypeWithError[]): void {
return this.#handleLog(ILogType.error, ...msq)
}
warn (...msq: ILogArgvType[]): void {
warn(...msq: ILogArgvType[]): void {
return this.#handleLog(ILogType.warn, ...msq)
}
debug (...msq: ILogArgvType[]): void {
debug(...msq: ILogArgvType[]): void {
if (isDev) {
this.#handleLog(ILogType.info, ...msq)
}

View File

@@ -9,10 +9,7 @@ import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import routers from '~/server/routerManager'
import {
handleResponse,
ensureHTTPLink
} from '~/server/utils'
import { handleResponse, ensureHTTPLink } from '~/server/utils'
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) {
// eslint-disable-next-line no-control-regex
if (!/[^\u0000-\u00ff]/.test(file.originalname)) {
file.originalname = Buffer.from(file.originalname, 'latin1').toString(
'utf8'
)
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
}
cb(null, file.originalname)
}
@@ -47,12 +42,12 @@ class Server {
#httpServer: http.Server
#config: IServerConfig
constructor () {
constructor() {
this.#config = this.getConfigWithDefaults()
this.#httpServer = http.createServer(this.#handleRequest)
}
getConfigWithDefaults () {
getConfigWithDefaults() {
let config = picgo.getConfig<IServerConfig>(configPaths.settings.server)
if (!this.#isValidConfig(config)) {
config = { port: DEFAULT_PORT, host: DEFAULT_HOST, enable: true }
@@ -62,8 +57,8 @@ class Server {
return config
}
#isValidConfig (config: IObj | undefined) {
return config && config.port && config.host && (config.enable !== undefined)
#isValidConfig(config: IObj | undefined) {
return config && config.port && config.host && config.enable !== undefined
}
#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')) {
// @ts-ignore
// @ts-expect-error since the multer type is not correct
uploadMulter.any()(request, response, (err: any) => {
if (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)
logger.info('[PicList Server] get a formData request')
const handler = routers.getHandler(url!, 'POST')?.handler
@@ -140,7 +135,7 @@ class Server {
})
request.on('end', () => {
try {
postObj = (body === '') ? {} : JSON.parse(body)
postObj = body === '' ? {} : JSON.parse(body)
} catch (err: any) {
logger.error('[PicList Server]', err)
return handleResponse({
@@ -205,20 +200,20 @@ class Server {
})
}
startup () {
startup() {
if (this.#config.enable) {
this.#listen(this.#config.port)
}
}
shutdown (hasStarted?: boolean) {
shutdown(hasStarted?: boolean) {
this.#httpServer.close()
if (!hasStarted) {
logger.info('[PicList Server] shutdown')
}
}
restart () {
restart() {
this.shutdown()
this.#config = this.getConfigWithDefaults()
this.startup()

View File

@@ -1,29 +1,29 @@
type HttpMethod = 'GET' | 'POST'
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)) {
this.#router.set(url, new Map())
}
this.#router.get(url)!.set(method, { handler: callback, urlparams })
}
get (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
get(url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
this.#addRoute('GET', url, callback, urlparams)
}
post (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
post(url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
this.#addRoute('POST', url, callback, urlparams)
}
any (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
any(url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
this.#addRoute('GET', url, callback, urlparams)
this.#addRoute('POST', url, callback, urlparams)
}
getHandler (url: string, method: HttpMethod) {
getHandler(url: string, method: HttpMethod) {
if (this.#router.has(url)) {
const methods = this.#router.get(url)!
if (methods.has(method)) {

View File

@@ -16,10 +16,7 @@ import windowManager from 'apis/app/window/windowManager'
import { markdownContent } from '~/server/apiDoc'
import router from '~/server/router'
import {
deleteChoosedFiles,
handleResponse
} from '~/server/utils'
import { deleteChoosedFiles, handleResponse } from '~/server/utils'
import { configPaths } from '#/utils/configPaths'
@@ -32,7 +29,7 @@ const LOG_PATH = path.join(STORE_PATH, 'piclist.log')
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
const deleteErrorMessage = `delete error. see ${LOG_PATH} for more detail.`
async function responseForGet ({ response } : {response: http.ServerResponse}) {
async function responseForGet({ response }: { response: http.ServerResponse }) {
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
const htmlContent = marked(markdownContent)
response.write(htmlContent)
@@ -42,192 +39,188 @@ async function responseForGet ({ response } : {response: http.ServerResponse}) {
router.get('/', responseForGet)
router.get('/upload', responseForGet)
router.post('/upload', async ({
response,
list = [],
urlparams
} : {
response: IHttpResponse,
list?: string[],
urlparams?: URLSearchParams
}): Promise<void> => {
try {
const picbed = urlparams?.get('picbed')
const passedKey = urlparams?.get('key')
const serverKey = picgo.getConfig<string>(configPaths.settings.serverKey) || ''
if (serverKey && passedKey !== serverKey) {
router.post(
'/upload',
async ({
response,
list = [],
urlparams
}: {
response: IHttpResponse
list?: string[]
urlparams?: URLSearchParams
}): Promise<void> => {
try {
const picbed = urlparams?.get('picbed')
const passedKey = urlparams?.get('key')
const serverKey = picgo.getConfig<string>(configPaths.settings.serverKey) || ''
if (serverKey && passedKey !== serverKey) {
handleResponse({
response,
body: {
success: false,
message: 'server key is uncorrect'
}
})
return
}
let currentPicBedType = ''
let currentPicBedConfig = {} as IStringKeyMap
let currentPicBedConfigId = ''
let needRestore = false
if (picbed) {
const currentPicBed = picgo.getConfig<IStringKeyMap>('picBed') || ({} as IStringKeyMap)
currentPicBedType = currentPicBed.current || ''
currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
currentPicBedConfigId = currentPicBedConfig._id
const configName = urlparams?.get('configName') || currentPicBed[picbed]?._configName
if (picbed === currentPicBedType && configName === currentPicBedConfig._configName) {
// do nothing
} else {
needRestore = true
const picBeds = picgo.getConfig<IStringKeyMap>('uploader')
const currentPicBedList = picBeds?.[picbed]?.configList
if (currentPicBedList) {
const currentConfig = currentPicBedList?.find((item: any) => item._configName === configName)
if (currentConfig) {
changeCurrentUploader(picbed, currentConfig, currentConfig._id)
}
}
}
}
if (list.length === 0) {
// upload with clipboard
logger.info('[PicList Server] upload clipboard file')
const result = await uploadClipboardFiles()
const res = result.url
const fullResult = result.fullResult
logger.info('[PicList Server] upload result:', res)
if (res) {
const treatedFullResult = {
isEncrypted: 1,
EncryptedData: new AESHelper().encrypt(JSON.stringify(fullResult)),
...fullResult
}
delete treatedFullResult.config
handleResponse({
response,
body: {
success: true,
result: [res],
fullResult: [treatedFullResult]
}
})
} else {
handleResponse({
response,
body: {
success: false,
message: errorMessage
}
})
}
} else {
logger.info('[PicList Server] upload files in list')
// upload with files
const pathList = list.map(item => {
return {
path: item
}
})
const win = windowManager.getAvailableWindow()
const result = await uploadChoosedFiles(win.webContents, pathList)
const res = result.map(item => {
return item.url
})
const fullResult = result.map((item: any) => {
const treatedItem = {
isEncrypted: 1,
EncryptedData: new AESHelper().encrypt(JSON.stringify(item.fullResult)),
...item.fullResult
}
delete treatedItem.config
return treatedItem
})
logger.info('[PicList Server] upload result', res.join(' ; '))
if (res.length) {
handleResponse({
response,
body: {
success: true,
result: res,
fullResult
}
})
} else {
handleResponse({
response,
body: {
success: false,
message: errorMessage
}
})
}
}
fs.emptyDirSync(serverTempDir)
if (needRestore) {
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
}
} catch (err: any) {
logger.error(err)
handleResponse({
response,
body: {
success: false,
message: 'server key is uncorrect'
message: errorMessage
}
})
}
}
)
router.post(
'/delete',
async ({ response, list = [] }: { response: IHttpResponse; list?: IStringKeyMap[] }): Promise<void> => {
if (list.length === 0) {
handleResponse({
response,
body: {
success: false,
message: 'no file to delete'
}
})
return
}
let currentPicBedType = ''
let currentPicBedConfig = {} as IStringKeyMap
let currentPicBedConfigId = ''
let needRestore = false
if (picbed) {
const currentPicBed = picgo.getConfig<IStringKeyMap>('picBed') || {} as IStringKeyMap
currentPicBedType = currentPicBed.current || ''
currentPicBedConfig = currentPicBed[currentPicBedType] || {} as IStringKeyMap
currentPicBedConfigId = currentPicBedConfig._id
const configName = urlparams?.get('configName') || currentPicBed[picbed]?._configName
if (picbed === currentPicBedType && configName === currentPicBedConfig._configName) {
// do nothing
} else {
needRestore = true
const picBeds = picgo.getConfig<IStringKeyMap>('uploader')
const currentPicBedList = picBeds?.[picbed]?.configList
if (currentPicBedList) {
const currentConfig = currentPicBedList?.find((item: any) => item._configName === configName)
if (currentConfig) {
changeCurrentUploader(picbed, currentConfig, currentConfig._id)
}
}
}
}
if (list.length === 0) {
// upload with clipboard
logger.info('[PicList Server] upload clipboard file')
const result = await uploadClipboardFiles()
const res = result.url
const fullResult = result.fullResult
logger.info('[PicList Server] upload result:', res)
if (res) {
const treatedFullResult = {
isEncrypted: 1,
EncryptedData: new AESHelper().encrypt(JSON.stringify(fullResult)),
...fullResult
}
delete treatedFullResult.config
handleResponse({
response,
body: {
success: true,
result: [res],
fullResult: [treatedFullResult]
}
})
} else {
handleResponse({
response,
body: {
success: false,
message: errorMessage
}
})
}
} else {
logger.info('[PicList Server] upload files in list')
// upload with files
const pathList = list.map(item => {
return {
path: item
try {
const aesHelper = new AESHelper()
const treatList = list.map(item => {
if (!item.isEncrypted) return item
return JSON.parse(aesHelper.decrypt(item.EncryptedData))
})
const result = await deleteChoosedFiles(treatList)
const successCount = result.filter(item => item).length
const failCount = result.length - successCount
handleResponse({
response,
body: {
success: !!successCount,
message: successCount ? `delete success: ${successCount}, fail: ${failCount}` : deleteErrorMessage
}
})
const win = windowManager.getAvailableWindow()
const result = await uploadChoosedFiles(win.webContents, pathList)
const res = result.map(item => {
return item.url
})
const fullResult = result.map((item: any) => {
const treatedItem = {
isEncrypted: 1,
EncryptedData: new AESHelper().encrypt(JSON.stringify(item.fullResult)),
...item.fullResult
} catch (err: any) {
logger.error(err)
handleResponse({
response,
body: {
success: false,
message: deleteErrorMessage
}
delete treatedItem.config
return treatedItem
})
logger.info('[PicList Server] upload result', res.join(' ; '))
if (res.length) {
handleResponse({
response,
body: {
success: true,
result: res,
fullResult
}
})
} else {
handleResponse({
response,
body: {
success: false,
message: errorMessage
}
})
}
}
fs.emptyDirSync(serverTempDir)
if (needRestore) {
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
}
} catch (err: any) {
logger.error(err)
handleResponse({
response,
body: {
success: false,
message: errorMessage
}
})
}
})
)
router.post('/delete', async ({
response,
list = []
} : {
response: IHttpResponse,
list?: IStringKeyMap[]
}): Promise<void> => {
if (list.length === 0) {
handleResponse({
response,
body: {
success: false,
message: 'no file to delete'
}
})
return
}
try {
const aesHelper = new AESHelper()
const treatList = list.map(item => {
if (!item.isEncrypted) return item
return JSON.parse(aesHelper.decrypt(item.EncryptedData))
})
const result = await deleteChoosedFiles(treatList)
const successCount = result.filter(item => item).length
const failCount = result.length - successCount
handleResponse({
response,
body: {
success: !!successCount,
message: successCount ? `delete success: ${successCount}, fail: ${failCount}` : deleteErrorMessage
}
})
} catch (err: any) {
logger.error(err)
handleResponse({
response,
body: {
success: false,
message: deleteErrorMessage
}
})
}
})
router.any('/heartbeat', async ({
response
} : {
response: IHttpResponse,
}) => {
router.any('/heartbeat', async ({ response }: { response: IHttpResponse }) => {
handleResponse({
response,
body: {

View File

@@ -1,6 +1,4 @@
import {
Notification
} from 'electron'
import { Notification } from 'electron'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
@@ -28,10 +26,10 @@ export const handleResponse = ({
body = {
success: false
}
} : {
response: IHttpResponse,
statusCode?: number,
header?: IObj,
}: {
response: IHttpResponse
statusCode?: number
header?: IObj
body?: any
}) => {
if (body?.success === false) {
@@ -43,9 +41,7 @@ export const handleResponse = ({
}
export const ensureHTTPLink = (url: string): string => {
return url.startsWith('http')
? url
: `http://${url}`
return url.startsWith('http') ? url : `http://${url}`
}
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 notification = new Notification({
title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'),
body: T(value
? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED'
: 'GALLERY_SYNC_DELETE_NOTICE_FAILED'
)
body: T(value ? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED' : 'GALLERY_SYNC_DELETE_NOTICE_FAILED')
})
notification.show()
}

View File

@@ -10,7 +10,7 @@ import { configPaths } from '#/utils/configPaths'
const defaultPath = process.platform === 'win32' ? 'C:\\Users' : '/'
function generateDirectoryListingHtml (files: any[], requestPath: any) {
function generateDirectoryListingHtml(files: any[], requestPath: any) {
let html = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><h1>Directory Listing</h1><ul>'
files.forEach((file: string) => {
const filePath = path.join(requestPath, file)
@@ -20,7 +20,7 @@ function generateDirectoryListingHtml (files: any[], requestPath: any) {
return html
}
function serveDirectory (res: http.ServerResponse, filePath: fs.PathLike, requestPath: any) {
function serveDirectory(res: http.ServerResponse, filePath: fs.PathLike, requestPath: any) {
fs.readdir(filePath, (err, files) => {
if (err) {
res.writeHead(500)
@@ -32,7 +32,7 @@ function serveDirectory (res: http.ServerResponse, filePath: fs.PathLike, reques
})
}
function serveFile (res:http.ServerResponse, filePath: fs.PathLike) {
function serveFile(res: http.ServerResponse, filePath: fs.PathLike) {
const readStream = fs.createReadStream(filePath)
readStream.pipe(res)
readStream.on('error', () => {
@@ -45,12 +45,12 @@ class WebServer {
#server!: http.Server
#config!: IStringKeyMap
constructor () {
constructor() {
this.loadConfig()
this.initServer()
}
loadConfig (): void {
loadConfig(): void {
this.#config = {
enableWebServer: picgo.getConfig<boolean>(configPaths.settings.enableWebServer) || false,
webServerHost: picgo.getConfig<string>(configPaths.settings.webServerHost) || '0.0.0.0',
@@ -59,7 +59,7 @@ class WebServer {
}
}
initServer (): void {
initServer(): void {
this.#server = http.createServer((req, res) => {
const requestPath = req.url?.split('?')[0]
const filePath = path.join(this.#config.webServerPath, decodeURIComponent(requestPath || ''))
@@ -78,15 +78,19 @@ class WebServer {
})
}
start () {
start() {
if (this.#config.enableWebServer) {
this.#server
.listen(
this.#config.webServerPort === 36699 ? 37777 : this.#config.webServerPort,
this.#config.webServerHost, () => {
logger.info(`Web server is running at http://${this.#config.webServerHost}:${this.#config.webServerPort}, root path is ${this.#config.webServerPath}`)
})
.on('error', (err) => {
this.#config.webServerHost,
() => {
logger.info(
`Web server is running at http://${this.#config.webServerHost}:${this.#config.webServerPort}, root path is ${this.#config.webServerPath}`
)
}
)
.on('error', err => {
logger.error(err)
})
} else {
@@ -94,13 +98,13 @@ class WebServer {
}
}
stop () {
stop() {
this.#server.close(() => {
logger.info('Web server is stopped')
})
}
restart () {
restart() {
this.stop()
this.loadConfig()
this.initServer()

View File

@@ -8,7 +8,7 @@ import { DEFAULT_AES_PASSWORD } from '#/utils/static'
export class AESHelper {
key: Buffer
constructor () {
constructor() {
const userPassword = picgo.getConfig<string>(configPaths.settings.aesPassword) || DEFAULT_AES_PASSWORD
const fixedSalt = Buffer.from('a8b3c4d2e4f5098712345678feedc0de', 'hex')
const fixedIterations = 100000
@@ -16,7 +16,7 @@ export class AESHelper {
this.key = crypto.pbkdf2Sync(userPassword, fixedSalt, fixedIterations, keyLength, 'sha512')
}
encrypt (plainText: string) {
encrypt(plainText: string) {
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv('aes-256-cbc', this.key, iv)
let encrypted = cipher.update(plainText, 'utf8', 'hex')
@@ -24,7 +24,7 @@ export class AESHelper {
return `${iv.toString('hex')}:${encrypted}`
}
decrypt (encryptedData: string) {
decrypt(encryptedData: string) {
const [ivHex, encryptedText] = encryptedData.split(':')
if (!ivHex || !encryptedText) {
return '{}'

View File

@@ -10,7 +10,7 @@ import { i18nManager } from '~/i18n'
const configPath = dbPathChecker()
const CONFIG_DIR = path.dirname(configPath)
function beforeOpen () {
function beforeOpen() {
if (process.platform === 'darwin') {
resolveMacWorkFlow()
}
@@ -18,10 +18,7 @@ function beforeOpen () {
resolveOtherI18nFiles()
}
function copyFileOutsideOfElectronAsar (
sourceInAsarArchive: string,
destOutsideAsarArchive: string
) {
function copyFileOutsideOfElectronAsar(sourceInAsarArchive: string, destOutsideAsarArchive: string) {
if (fs.existsSync(sourceInAsarArchive)) {
// file will be copied
if (fs.statSync(sourceInAsarArchive).isFile()) {
@@ -45,7 +42,7 @@ function copyFileOutsideOfElectronAsar (
/**
* macOS 右键菜单
*/
function resolveMacWorkFlow () {
function resolveMacWorkFlow() {
const dest = `${os.homedir()}/Library/Services/Upload pictures with PicList.workflow`
if (fs.existsSync(dest)) return true
try {
@@ -55,7 +52,7 @@ function resolveMacWorkFlow () {
}
}
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
function diffFilesAndUpdate(filePath1: string, filePath2: string) {
try {
const file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
const file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
@@ -72,7 +69,7 @@ function diffFilesAndUpdate (filePath1: string, filePath2: string) {
/**
* 初始化剪贴板生成图片的脚本
*/
function resolveClipboardImageGenerator () {
function resolveClipboardImageGenerator() {
const clipboardFiles = getClipboardFiles()
if (!fs.pathExistsSync(path.join(CONFIG_DIR, 'windows10.ps1'))) {
clipboardFiles.forEach(item => {
@@ -84,14 +81,8 @@ function resolveClipboardImageGenerator () {
})
}
function getClipboardFiles () {
const files = [
'/linux.sh',
'/mac.applescript',
'/windows.ps1',
'/windows10.ps1',
'/wsl.sh'
]
function getClipboardFiles() {
const files = ['/linux.sh', '/mac.applescript', '/windows.ps1', '/windows10.ps1', '/wsl.sh']
return files.map(item => {
return {
@@ -105,7 +96,7 @@ function resolveClipboardImageGenerator () {
/**
* 初始化其他语言文件
*/
function resolveOtherI18nFiles () {
function resolveOtherI18nFiles() {
const i18nFolder = path.join(CONFIG_DIR, 'i18n')
if (!fs.pathExistsSync(i18nFolder)) {
fs.mkdirSync(i18nFolder)

View File

@@ -8,13 +8,13 @@ class ClipboardWatcher extends EventEmitter {
timer: NodeJS.Timeout | null
lastImageHash: string | null
constructor () {
constructor() {
super()
this.lastImageHash = null
this.timer = null
}
startListening (watchDelay = 500) {
startListening(watchDelay = 500) {
this.stopListening(false)
this.timer = setInterval(() => {
@@ -33,7 +33,7 @@ class ClipboardWatcher extends EventEmitter {
logger.info('Start to watch clipboard')
}
stopListening (isLog = true) {
stopListening(isLog = true) {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
@@ -42,7 +42,7 @@ class ClipboardWatcher extends EventEmitter {
isLog && logger.info('Stop to watch clipboard')
}
getImageHash (image: Electron.NativeImage): string {
getImageHash(image: Electron.NativeImage): string {
const buffer = image.toBitmap()
return crypto.createHash('md5').update(buffer).digest('hex')
}

View File

@@ -12,11 +12,13 @@ import { configPaths } from '#/utils/configPaths'
export let tray: Tray
export const setTray = (t: Tray) => { tray = t }
export const setTray = (t: Tray) => {
tray = t
}
export const getTray = () => tray
export function setTrayToolTip (title: string): void {
export function setTrayToolTip(title: string): void {
if (tray) {
tray.setToolTip(title)
}
@@ -32,13 +34,15 @@ export const handleCopyUrl = (str: string): void => {
* show notification
* @param options
*/
export const showNotification = (options: IPrivateShowNotificationOption = {
title: '',
body: '',
clickToCopy: false,
copyContent: '',
clickFn: () => {}
}) => {
export const showNotification = (
options: IPrivateShowNotificationOption = {
title: '',
body: '',
clickToCopy: false,
copyContent: '',
clickFn: () => {}
}
) => {
const notification = new Notification({
title: options.title,
body: options.body
@@ -60,10 +64,8 @@ export const showNotification = (options: IPrivateShowNotificationOption = {
}
export const showMessageBox = (options: any) => {
return new Promise<IShowMessageBoxResult>(async (resolve) => {
dialog.showMessageBox(
options
).then((res) => {
return new Promise<IShowMessageBoxResult>(async resolve => {
dialog.showMessageBox(options).then(res => {
resolve({
result: res.response,
checkboxChecked: res.checkboxChecked
@@ -118,14 +120,18 @@ export const getClipboardFilePath = (): string => {
}
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 ''
}
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'
@@ -163,7 +169,12 @@ const generateYOURLSShortUrl = async (url: string) => {
if (!/^https?:\/\//.test(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 {
const res = await axios.get(`${domain}/yourls-api.php?${params.toString()}`)
if (res.data?.shorturl) {

View File

@@ -32,7 +32,7 @@ const dogeRegionMap: IStringKeyMap = {
'ap-chengdu': '3'
}
async function dogecloudApi (
async function dogecloudApi(
apiPath: string,
data = {},
jsonMode: boolean = false,
@@ -40,7 +40,10 @@ async function dogecloudApi (
secretKey: string
) {
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}`
try {
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 {
const data = await dogecloudApi('/auth/tmp_token.json', {
channel: 'OSS_FULL',
scopes: ['*']
}, true, accessKey, secretKey)
const data = await dogecloudApi(
'/auth/tmp_token.json',
{
channel: 'OSS_FULL',
scopes: ['*']
},
true,
accessKey,
secretKey
)
return data
} catch (err: any) {
console.log(err)
@@ -75,10 +84,17 @@ 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 {
const { url: rawUrl, type, config: { accessKeyID, secretAccessKey, bucketName, endpoint, pathStyleAccess, rejectUnauthorized, proxy } } = configMap
let { imgUrl, config: { region } } = configMap
const {
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') {
imgUrl = rawUrl || imgUrl || ''
}
@@ -105,21 +121,21 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
const extraOptions = sslEnabled ? { rejectUnauthorized: !!rejectUnauthorized } : {}
const handler = sslEnabled
? new NodeHttpHandler({
httpsAgent: agent.https
? agent.https
: new https.Agent({
...commonOptions,
...extraOptions
})
})
httpsAgent: agent.https
? agent.https
: new https.Agent({
...commonOptions,
...extraOptions
})
})
: new NodeHttpHandler({
httpAgent: agent.http
? agent.http
: new http.Agent({
...commonOptions,
...extraOptions
})
})
httpAgent: agent.http
? agent.http
: new http.Agent({
...commonOptions,
...extraOptions
})
})
const s3Options: S3ClientConfig = {
credentials: {
accessKeyId: accessKeyID,
@@ -162,10 +178,12 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
}
}
export async function removeFileFromDogeInMain (configMap: IStringKeyMap) {
export async function removeFileFromDogeInMain(configMap: IStringKeyMap) {
try {
const { config: { bucketName, AccessKey, SecretKey } } = configMap
const token = await getDogeToken(AccessKey, SecretKey) as DogecloudTokenFull
const {
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 newConfigMap = Object.assign({}, configMap)
newConfigMap.config = {
@@ -184,7 +202,7 @@ export async function removeFileFromDogeInMain (configMap: IStringKeyMap) {
}
}
function createHuaweiAuthorization (
function createHuaweiAuthorization(
bucketName: string,
path: string,
fileName: string,
@@ -197,7 +215,7 @@ function createHuaweiAuthorization (
return `OBS ${accessKey}:${singature}`
}
export async function removeFileFromHuaweiInMain (configMap: IStringKeyMap) {
export async function removeFileFromHuaweiInMain(configMap: IStringKeyMap) {
const { fileName, config } = configMap
const { accessKeyId, accessKeySecret, bucketName, endpoint } = config
let path = config.path || '/'
@@ -223,11 +241,11 @@ export async function removeFileFromHuaweiInMain (configMap: IStringKeyMap) {
}
}
export async function removeFileFromSFTPInMain (config: ISftpPlistConfig, fileName: string) {
export async function removeFileFromSFTPInMain(config: ISftpPlistConfig, fileName: string) {
try {
const client = SSHClient.instance
await client.connect(config)
const uploadPath = `/${(config.uploadPath || '')}/`.replace(/\/+/g, '/')
const uploadPath = `/${config.uploadPath || ''}/`.replace(/\/+/g, '/')
const remote = path.join(uploadPath, fileName)
const deleteResult = await client.deleteFileSFTP(config, remote)
client.close()

View File

@@ -9,11 +9,8 @@ export const isMacOS = process.platform === 'darwin'
let version: string | undefined
const clean = (version: string) => version.split('.').length === 1
? `${version}.0.0`
: version.split('.').length === 2
? `${version}.0`
: version
const clean = (version: string) =>
version.split('.').length === 1 ? `${version}.0.0` : version.split('.').length === 2 ? `${version}.0` : version
const parseVersion = (plist: string) => {
const matches = /<key>ProductVersion<\/key>\s*<string>([\d.]+)<\/string>/.exec(plist)
@@ -24,7 +21,7 @@ const parseVersion = (plist: string) => {
return matches[1].replace('10.16', '11')
}
export function macOSVersion (): string {
export function macOSVersion(): string {
if (!isMacOS) return ''
if (!version) {
@@ -45,7 +42,7 @@ if (process.env.NODE_ENV === 'test') {
macOSVersion._parseVersion = parseVersion
}
export function isMacOSVersion (semverRange: string) {
export function isMacOSVersion(semverRange: string) {
if (!isMacOS) {
return false
}
@@ -55,7 +52,7 @@ export function isMacOSVersion (semverRange: string) {
return semver.satisfies(macOSVersion(), clean(semverRange))
}
export function isMacOSVersionGreaterThanOrEqualTo (version: string) {
export function isMacOSVersionGreaterThanOrEqualTo(version: string) {
if (!isMacOS) {
return false
}
@@ -65,7 +62,7 @@ export function isMacOSVersionGreaterThanOrEqualTo (version: string) {
return semver.gte(macOSVersion(), clean(version))
}
export function assertMacOSVersion (semverRange: string) {
export function assertMacOSVersion(semverRange: string) {
semverRange = semverRange.replace('10.16', '11')
if (!isMacOSVersion(semverRange)) {
@@ -73,7 +70,7 @@ export function assertMacOSVersion (semverRange: string) {
}
}
export function assertMacOSVersionGreaterThanOrEqualTo (version: string) {
export function assertMacOSVersionGreaterThanOrEqualTo(version: string) {
version = version.replace('10.16', '11')
if (!isMacOSVersionGreaterThanOrEqualTo(version)) {
@@ -81,7 +78,7 @@ export function assertMacOSVersionGreaterThanOrEqualTo (version: string) {
}
}
export function assertMacOS () {
export function assertMacOS() {
if (!isMacOS) {
throw new Error('Requires macOS')
}

View File

@@ -4,19 +4,21 @@ import { configPaths } from '#/utils/configPaths'
const getPicBeds = () => {
const picBedTypes = picgo.helper.uploader.getIdList()
const picBedFromDB = picgo.getConfig<IPicBedType[]>(configPaths.picBed.list) || []
const picBeds = picBedTypes.map((item: string) => {
const visible = picBedFromDB.find((i: IPicBedType) => i.type === item) // object or undefined
return {
type: item,
name: picgo.helper.uploader.get(item)!.name || item,
visible: visible ? visible.visible : true
}
}).sort((a) => {
if (a.type === 'tcyun') {
return -1
}
return 0
}) as IPicBedType[]
const picBeds = picBedTypes
.map((item: string) => {
const visible = picBedFromDB.find((i: IPicBedType) => i.type === item) // object or undefined
return {
type: item,
name: picgo.helper.uploader.get(item)!.name || item,
visible: visible ? visible.visible : true
}
})
.sort(a => {
if (a.type === 'tcyun') {
return -1
}
return 0
}) as IPicBedType[]
return picBeds
}

View File

@@ -36,34 +36,34 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd(), logger: Logger
if (fileList?.length === 0) {
return null // for uploading images in clipboard
} else if ((fileList?.length || 0) > 0) {
const result = fileList!.map(item => {
if (isUrl(item)) {
return {
path: item
}
}
if (path.isAbsolute(item)) {
return {
path: item
}
} else {
const tempPath = path.join(cwd, item)
if (fs.existsSync(tempPath)) {
const result = fileList!
.map(item => {
if (isUrl(item)) {
return {
path: tempPath
path: item
}
}
if (path.isAbsolute(item)) {
return {
path: item
}
} else {
logger.warn(`cli -> can't get file: ${tempPath}, invalid path`)
return null
const tempPath = path.join(cwd, item)
if (fs.existsSync(tempPath)) {
return {
path: tempPath
}
} else {
logger.warn(`cli -> can't get file: ${tempPath}, invalid path`)
return null
}
}
}
}).filter(item => item !== null) as Result
})
.filter(item => item !== null) as Result
return result
}
}
return []
}
export {
getUploadFiles
}
export { getUploadFiles }

View File

@@ -13,6 +13,7 @@ export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IP
config[i].default = config[i].default()
}
if (typeof config[i].choices === 'function') {
// eslint-disable-next-line @typescript-eslint/ban-types
config[i].choices = (config[i].choices as Function)()
}
}
@@ -20,13 +21,17 @@ export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IP
}
export const completeUploaderMetaConfig = (originData: IStringKeyMap): IUploaderConfigListItem => {
return Object.assign({
_configName: 'Default'
}, trimValues(originData), {
_id: uuid(),
_createdAt: Date.now(),
_updatedAt: Date.now()
})
return Object.assign(
{
_configName: 'Default'
},
trimValues(originData),
{
_id: uuid(),
_createdAt: Date.now(),
_updatedAt: Date.now()
}
)
}
/**
@@ -130,7 +135,9 @@ export const deleteUploaderConfig = (type: string, id: string): IUploaderConfigI
/**
* upgrade old uploader config to new format
*/
export const upgradeUploaderConfig = (type: string): {
export const upgradeUploaderConfig = (
type: string
): {
configList: IStringKeyMap[]
defaultId: string
} => {

View File

@@ -1,4 +1,3 @@
// @ts-nocheck
import fs from 'fs-extra'
import { NodeSSH, Config, SSHExecCommandResponse } from 'node-ssh-no-cpu-features'
import path from 'path'
@@ -11,22 +10,26 @@ class SSHClient {
private static _client: NodeSSH
private _isConnected = false
static get instance (): SSHClient {
static get instance(): SSHClient {
return this._instance || (this._instance = new this())
}
static get client (): NodeSSH {
static get client(): NodeSSH {
return this._client || (this._client = new NodeSSH())
}
private changeWinStylePathToUnix (path: string): string {
private changeWinStylePathToUnix(path: string): string {
return path.replace(/\\/g, '/')
}
async connect (config: ISftpPlistConfig): Promise<boolean> {
async connect(config: ISftpPlistConfig): Promise<boolean> {
const { username, password, privateKey, passphrase } = config
const loginInfo: Config = privateKey
? { username, privateKeyPath: privateKey, passphrase: passphrase || undefined }
? {
username,
privateKeyPath: privateKey,
passphrase: passphrase || undefined
}
: { username, password }
try {
await SSHClient.client.connect({
@@ -41,52 +44,64 @@ class SSHClient {
}
}
async deleteFileSFTP (config: ISftpPlistConfig, remote: string): Promise<boolean> {
async deleteFileSFTP(config: ISftpPlistConfig, remote: string): Promise<boolean> {
try {
const client = new Client()
const { username, password, privateKey, passphrase } = config
const loginInfo: Config = privateKey
? { username, privateKey: fs.readFileSync(privateKey), passphrase: passphrase || undefined }
? {
username,
privateKey: fs.readFileSync(privateKey),
passphrase: passphrase || undefined
}
: { username, password }
remote = this.changeWinStylePathToUnix(remote)
if (remote === '/' || remote.includes('*')) return false
const promise = new Promise((resolve, reject) => {
client.on('ready', () => {
client.sftp((err, sftp) => {
// eslint-disable-next-line prefer-promise-reject-errors
if (err) reject(false)
sftp.unlink(remote, (err) => {
// eslint-disable-next-line prefer-promise-reject-errors
if (err) reject(false)
client.end()
resolve(true)
})
client
.on('ready', () => {
client.sftp(
(
err: any,
sftp: {
unlink: (arg0: string, arg1: (err: any) => void) => void
}
) => {
// eslint-disable-next-line prefer-promise-reject-errors
if (err) reject(false)
sftp.unlink(remote, (err: any) => {
// eslint-disable-next-line prefer-promise-reject-errors
if (err) reject(false)
client.end()
resolve(true)
})
}
)
})
}).connect({
host: config.host,
port: Number(config.port) || 22,
...loginInfo
})
}
)
return await promise
.connect({
host: config.host,
port: Number(config.port) || 22,
...loginInfo
})
})
return (await promise) as boolean
} catch (err: any) {
console.log(err)
return false
}
}
private async exec (script: string): Promise<boolean> {
private async exec(script: string): Promise<boolean> {
const execResult = await SSHClient.client.execCommand(script)
return execResult.code === 0
}
async execCommand (script: string): Promise<SSHExecCommandResponse> {
async execCommand(script: string): Promise<SSHExecCommandResponse> {
const execResult = await SSHClient.client.execCommand(script)
return execResult || { code: 1, stdout: '', stderr: '' }
}
async getFile (local: string, remote: string): Promise<boolean> {
async getFile(local: string, remote: string): Promise<boolean> {
if (!this._isConnected) {
throw new Error('SSH 未连接')
}
@@ -103,10 +118,14 @@ class SSHClient {
}
}
async putFile (local: string, remote: string, config: {
fileMode?: string
dirMode?: string
} = {}): Promise<boolean> {
async putFile(
local: string,
remote: string,
config: {
fileMode?: string
dirMode?: string
} = {}
): Promise<boolean> {
if (!this._isConnected) {
throw new Error('SSH 未连接')
}
@@ -126,9 +145,12 @@ class SSHClient {
}
}
async mkdir (dirPath: string, config: {
dirMode?: string
} = {}): Promise<boolean> {
async mkdir(
dirPath: string,
config: {
dirMode?: string
} = {}
): Promise<boolean> {
if (!this._isConnected) {
throw new Error('SSH 未连接')
}
@@ -158,11 +180,11 @@ class SSHClient {
}
}
get isConnected (): boolean {
get isConnected(): boolean {
return SSHClient.client.isConnected()
}
close (): void {
close(): void {
SSHClient.client.dispose()
this._isConnected = false
}

View File

@@ -15,32 +15,35 @@ const STORE_PATH = app.getPath('userData')
const readFileAsBase64 = (filePath: string) => fs.readFileSync(filePath, { encoding: 'base64' })
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 = () => {
return db.get(configPaths.settings.sync) || {
type: 'github',
username: '',
repo: '',
branch: '',
token: '',
proxy: ''
}
return (
db.get(configPaths.settings.sync) || {
type: 'github',
username: '',
repo: '',
branch: '',
token: '',
proxy: ''
}
)
}
const getProxyagent = (proxy: string | undefined) => {
return proxy
? new HttpsProxyAgent({
keepAlive: true,
keepAliveMsecs: 1000,
rejectUnauthorized: false,
proxy: proxy.replace('127.0.0.1', 'localhost'),
scheduling: 'lifo'
})
keepAlive: true,
keepAliveMsecs: 1000,
rejectUnauthorized: false,
proxy: proxy.replace('127.0.0.1', 'localhost'),
scheduling: 'lifo'
})
: undefined
}
function getOctokit (syncConfig: ISyncConfig) {
function getOctokit(syncConfig: ISyncConfig) {
const { token, proxy } = syncConfig
return new Octokit({
auth: token,
@@ -50,17 +53,11 @@ function getOctokit (syncConfig: ISyncConfig) {
})
}
const isSyncConfigValidate = ({
type,
username,
repo,
branch,
token
}: ISyncConfig) => {
const isSyncConfigValidate = ({ type, username, repo, branch, token }: ISyncConfig) => {
return type && username && repo && branch && token
}
async function uploadLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
async function uploadLocalToRemote(syncConfig: ISyncConfig, fileName: string) {
const localFilePath = path.join(STORE_PATH, fileName)
if (!fs.existsSync(localFilePath)) {
return false
@@ -109,7 +106,7 @@ async function uploadLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
}
}
async function updateLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
async function updateLocalToRemote(syncConfig: ISyncConfig, fileName: string) {
const localFilePath = path.join(STORE_PATH, fileName)
if (!fs.existsSync(localFilePath)) {
return false
@@ -179,12 +176,16 @@ async function updateLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
}
const data = shaRes.data as any
const sha = data.sha
const res = await axios.put(apiUrl, {
...defaultConfig,
sha
}, {
headers
})
const res = await axios.put(
apiUrl,
{
...defaultConfig,
sha
},
{
headers
}
)
return isHttpResSuccess(res)
}
default:
@@ -192,7 +193,7 @@ async function updateLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
}
}
async function uploadFile (fileName: string[]): Promise<number> {
async function uploadFile(fileName: string[]): Promise<number> {
const syncConfig = getSyncConfig()
if (!isSyncConfigValidate(syncConfig)) {
logger.error('sync config is invalid')
@@ -217,16 +218,19 @@ async function uploadFile (fileName: string[]): Promise<number> {
return count
}
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)
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 false
}
async function downloadRemoteToLocal (syncConfig: ISyncConfig, fileName: string) {
async function downloadRemoteToLocal(syncConfig: ISyncConfig, fileName: string) {
const localFilePath = path.join(STORE_PATH, fileName)
const { username, repo, branch, token, proxy, type } = syncConfig
try {
@@ -251,9 +255,14 @@ async function downloadRemoteToLocal (syncConfig: ISyncConfig, fileName: string)
if (res.status === 200) {
const data = res.data as any
const downloadUrl = data.download_url
return downloadAndWriteFile(downloadUrl, localFilePath, {
httpsAgent: getProxyagent(proxy)
}, true)
return downloadAndWriteFile(
downloadUrl,
localFilePath,
{
httpsAgent: getProxyagent(proxy)
},
true
)
}
return false
}
@@ -278,7 +287,7 @@ async function downloadRemoteToLocal (syncConfig: ISyncConfig, fileName: string)
}
}
async function downloadFile (fileName: string[]): Promise<number> {
async function downloadFile(fileName: string[]): Promise<number> {
const syncConfig = getSyncConfig()
if (!isSyncConfigValidate(syncConfig)) {
logger.error('sync config is invalid')
@@ -294,7 +303,4 @@ async function downloadFile (fileName: string[]): Promise<number> {
return (await Promise.all(fileName.map(downloadFunc))).reduce((a, b) => a + b, 0)
}
export {
uploadFile,
downloadFile
}
export { uploadFile, downloadFile }

View File

@@ -7,7 +7,7 @@ import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
export function openMiniWindow (hideSettingWindow:boolean = true) {
export function openMiniWindow(hideSettingWindow: boolean = true) {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
miniWindow.removeAllListeners()
if (db.get(configPaths.settings.miniWindowOntop)) {

View File

@@ -1,8 +1,5 @@
<template>
<div
id="app"
:key="pageReloadCount"
>
<div id="app" :key="pageReloadCount">
<router-view />
</div>
</template>
@@ -25,7 +22,6 @@ onBeforeMount(async () => {
store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
}
})
</script>
<script lang="ts">
@@ -35,18 +31,18 @@ export default {
</script>
<style lang="stylus">
body,
html
padding 0
margin 0
height 100%
font-family "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif
#app
height 100%
user-select none
overflow hidden
.el-button-group
width 100%
.el-button
width 50%
body,
html
padding 0
margin 0
height 100%
font-family "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif
#app
height 100%
user-select none
overflow hidden
.el-button-group
width 100%
.el-button
width 50%
</style>

View File

@@ -14,7 +14,7 @@ interface IConfigMap {
}
export default class AlistApi {
static async delete (configMap: IConfigMap): Promise<boolean> {
static async delete(configMap: IConfigMap): Promise<boolean> {
const { fileName, config } = configMap
try {
const { version, url, uploadPath, token } = config

View File

@@ -8,13 +8,11 @@ interface IConfigMap {
}
export default class AliyunApi {
static #getKey (fileName: string, path?: string): string {
return path && path !== '/'
? `${path.replace(/^\/+|\/+$/, '')}/${fileName}`
: fileName
static #getKey(fileName: string, path?: string): string {
return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${fileName}` : fileName
}
static async delete (configMap: IConfigMap): Promise<boolean> {
static async delete(configMap: IConfigMap): Promise<boolean> {
const { fileName, config } = configMap
try {
const client = new OSS({ ...config, region: config.area })

View File

@@ -36,7 +36,7 @@ const apiMap: IStringKeyMap = {
}
export default class ALLApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
static async delete(configMap: IStringKeyMap): Promise<boolean> {
const api = apiMap[configMap.type]
return api ? await api.delete(configMap) : false
}

View File

@@ -6,10 +6,10 @@ import { deleteFailedLog } from '#/utils/deleteLog'
import { IRPCActionType } from '#/types/enum'
export default class AwsS3Api {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
static async delete(configMap: IStringKeyMap): Promise<boolean> {
try {
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))
} catch (error: any) {
deleteFailedLog(configMap.fileName, 'AWS S3', error)

View File

@@ -7,10 +7,10 @@ import { deleteFailedLog } from '#/utils/deleteLog'
import { IRPCActionType } from '#/types/enum'
export default class AwsS3Api {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
static async delete(configMap: IStringKeyMap): Promise<boolean> {
try {
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))
} catch (error: any) {
deleteFailedLog(configMap.fileName, 'DogeCloud', error)

View File

@@ -9,21 +9,23 @@ interface IConfigMap {
}
export default class GithubApi {
static #createOctokit (token: string) {
static #createOctokit(token: string) {
return new Octokit({
auth: token
})
}
static #createKey (path: string | undefined, fileName: string): string {
static #createKey(path: string | undefined, fileName: string): string {
const formatedFileName = fileName.replace(/%2F/g, '/')
return path && path !== '/'
? `${path.replace(/^\/+|\/+$/, '')}/${formatedFileName}`
: formatedFileName
return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${formatedFileName}` : formatedFileName
}
static async delete (configMap: IConfigMap): Promise<boolean> {
const { fileName, hash, config: { repo, token, branch, path } } = configMap
static async delete(configMap: IConfigMap): Promise<boolean> {
const {
fileName,
hash,
config: { repo, token, branch, path }
} = configMap
const [owner, repoName] = repo.split('/')
const octokit = GithubApi.#createOctokit(token)
const key = GithubApi.#createKey(path, fileName)

View File

@@ -7,10 +7,10 @@ import { deleteFailedLog } from '#/utils/deleteLog'
import { IRPCActionType } from '#/types/enum'
export default class HuaweicloudApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
static async delete(configMap: IStringKeyMap): Promise<boolean> {
try {
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))
} catch (error: any) {
deleteFailedLog(configMap.fileName, 'HuaweiCloud', error)

View File

@@ -10,11 +10,8 @@ interface IConfigMap {
export default class ImgurApi {
static #baseUrl = 'https://api.imgur.com/3'
static async delete (configMap: IConfigMap): Promise<boolean> {
const {
config: { clientId = '', username = '', accessToken = '' } = {},
hash = ''
} = configMap
static async delete(configMap: IConfigMap): Promise<boolean> {
const { config: { clientId = '', username = '', accessToken = '' } = {}, hash = '' } = configMap
let Authorization: string, apiUrl: string
if (username && accessToken) {

View File

@@ -7,7 +7,7 @@ interface IConfigMap {
}
export default class LocalApi {
static async delete (configMap: IConfigMap): Promise<boolean> {
static async delete(configMap: IConfigMap): Promise<boolean> {
const { hash } = configMap
if (!hash) {
deleteLog(hash, 'Local', false, 'Local.delete: invalid params')

View File

@@ -4,7 +4,7 @@ import https from 'https'
import { deleteFailedLog, deleteLog } from '#/utils/deleteLog'
export default class LskyplistApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
static async delete(configMap: IStringKeyMap): Promise<boolean> {
const { hash, config } = configMap
if (!hash || !config || !config.token) {
deleteLog(hash, 'Lskyplist', false, 'LskyplistApi.delete: invalid params')
@@ -26,12 +26,11 @@ export default class LskyplistApi {
rejectUnauthorized: false
})
try {
const response: AxiosResponse = await axios.delete(
`${host}/api/v1/images/${hash}`, {
headers: v2Headers,
timeout: 30000,
httpsAgent: requestAgent
})
const response: AxiosResponse = await axios.delete(`${host}/api/v1/images/${hash}`, {
headers: v2Headers,
timeout: 30000,
httpsAgent: requestAgent
})
if (response.status === 200 && response.data.status === true) {
deleteLog(hash, 'Lskyplist')
return true

View File

@@ -3,7 +3,7 @@ import axios, { AxiosResponse } from 'axios'
import { deleteFailedLog, deleteLog } from '#/utils/deleteLog'
export default class PiclistApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
static async delete(configMap: IStringKeyMap): Promise<boolean> {
const { config, fullResult } = configMap
const { host, port } = config
if (!host) {
@@ -14,12 +14,9 @@ export default class PiclistApi {
const url = `http://${host || '127.0.0.1'}:${port || 36677}/delete`
try {
const response: AxiosResponse = await axios.post(
url,
{
list: [fullResult]
}
)
const response: AxiosResponse = await axios.post(url, {
list: [fullResult]
})
if (response.status === 200 && response.data?.success) {
deleteLog(fullResult, 'Piclist')
return true

View File

@@ -8,15 +8,18 @@ interface IConfigMap {
}
export default class QiniuApi {
static async delete (configMap: IConfigMap): Promise<boolean> {
const { fileName, config: { accessKey, secretKey, bucket, path } } = configMap
static async delete(configMap: IConfigMap): Promise<boolean> {
const {
fileName,
config: { accessKey, secretKey, bucket, path }
} = configMap
const mac = new Qiniu.auth.digest.Mac(accessKey, secretKey)
const qiniuConfig = new Qiniu.conf.Config()
try {
const bucketManager = new Qiniu.rs.BucketManager(mac, qiniuConfig)
const formattedPath = path?.replace(/^\/+|\/+$/, '') || ''
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) => {
if (err) {
reject(err)
@@ -27,7 +30,7 @@ export default class QiniuApi {
})
}
})
}) as any
})) as any
if (res?.respInfo?.statusCode === 200) {
deleteLog(fileName, 'Qiniu')
return true

View File

@@ -7,12 +7,11 @@ import { deleteFailedLog } from '#/utils/deleteLog'
import { IRPCActionType } from '#/types/enum'
export default class SftpPlistApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
static async delete(configMap: IStringKeyMap): Promise<boolean> {
const { fileName, config } = configMap
try {
return ipcRenderer
? await triggerRPC(IRPCActionType.GALLERY_DELETE_SFTP_FILE, getRawData(config),
fileName) || false
? (await triggerRPC(IRPCActionType.GALLERY_DELETE_SFTP_FILE, getRawData(config), fileName)) || false
: await removeFileFromSFTPInMain(getRawData(config), fileName)
} catch (error: any) {
deleteFailedLog(fileName, 'SFTP', error)

View File

@@ -10,7 +10,7 @@ interface IConfigMap {
export default class SmmsApi {
static readonly #baseUrl = 'https://smms.app/api/v2'
static async delete (configMap: IConfigMap): Promise<boolean> {
static async delete(configMap: IConfigMap): Promise<boolean> {
const { hash, config } = configMap
if (!hash || !config || !config.token) {
deleteLog(hash, 'Smms', false, 'SmmsApi.delete: invalid params')
@@ -20,17 +20,16 @@ export default class SmmsApi {
const { token } = config
try {
const response: AxiosResponse = await axios.get(
`${SmmsApi.#baseUrl}/delete/${hash}`, {
headers: {
Authorization: token
},
params: {
hash,
format: 'json'
},
timeout: 30000
})
const response: AxiosResponse = await axios.get(`${SmmsApi.#baseUrl}/delete/${hash}`, {
headers: {
Authorization: token
},
params: {
hash,
format: 'json'
},
timeout: 30000
})
if (response.status === 200) {
deleteLog(hash, 'Smms')
return true

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