mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
🎨 Style(custom): format with prettier
This commit is contained in:
@@ -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
|
||||
|
||||
23
.eslintrc.js
23
.eslintrc.js
@@ -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
18
.prettierrc
Normal 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"
|
||||
}
|
||||
@@ -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"],
|
||||
};
|
||||
|
||||
314
package.json
314
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -21,7 +21,7 @@ picgo.GUI_VERSION = global.PICGO_GUI_VERSION
|
||||
|
||||
const originPicGoSaveConfig = picgo.saveConfig.bind(picgo)
|
||||
|
||||
function flushDB () {
|
||||
function flushDB() {
|
||||
db.read(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,4 @@ const getLogger = (logPath: string, logType: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getLogger
|
||||
}
|
||||
export { getLogger }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -20,7 +20,7 @@ export class RPCRouter implements IRPCRouter {
|
||||
return this
|
||||
}
|
||||
|
||||
routes () {
|
||||
routes() {
|
||||
return this.routeMap
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -94,6 +94,4 @@ const picbedRoutes = [
|
||||
|
||||
picbedRouter.addBatch(picbedRoutes)
|
||||
|
||||
export {
|
||||
picbedRouter
|
||||
}
|
||||
export { picbedRouter }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import { IRPCActionType } from '#/types/enum'
|
||||
import server from '~/server'
|
||||
import webServer from '~/server/webServer'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import {
|
||||
Notification
|
||||
} from 'electron'
|
||||
import { Notification } from 'electron'
|
||||
|
||||
import bus from '@core/bus'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -30,6 +30,4 @@ const uploadRoutes = [
|
||||
|
||||
uploadRouter.addBatch(uploadRoutes)
|
||||
|
||||
export {
|
||||
uploadRouter
|
||||
}
|
||||
export { uploadRouter }
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(':')
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
})
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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': '南京',
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 '{}'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
} => {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user