🎨 Style(custom): lint code

This commit is contained in:
Kuingsmile
2025-08-15 13:29:09 +08:00
parent 0ae27cfeef
commit f11a4264d0
160 changed files with 18208 additions and 20414 deletions

View File

@@ -1,57 +1,56 @@
/// <reference types="vitest/config" /> /// <reference types="vitest/config" />
import { dirname, resolve } from 'node:path' import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite' import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
export default defineConfig({ export default defineConfig({
main: { main: {
plugins: [ plugins: [externalizeDepsPlugin()],
externalizeDepsPlugin() resolve: {
], alias: {
resolve: { '@': resolve('src/renderer'),
alias: { '~': resolve('src/main'),
'@': resolve('src/renderer'), root: resolve('./'),
'~': resolve('src/main'), '#': resolve('src/universal'),
root: resolve('./'), apis: resolve('src/main/apis'),
'#': resolve('src/universal'), '@core': resolve('src/main/apis/core')
apis: resolve('src/main/apis'), }
'@core': resolve('src/main/apis/core') }
} },
} preload: {
}, plugins: [
preload: { externalizeDepsPlugin(),
plugins: [externalizeDepsPlugin(), VueI18nPlugin({
VueI18nPlugin({ /* options */
/* options */ // locale messages resource pre-compile option
// locale messages resource pre-compile option include: resolve(dirname(fileURLToPath(import.meta.url)), './src/renderer/i18n/locales/**')
include: resolve(dirname(fileURLToPath(import.meta.url)), './src/renderer/i18n/locales/**') })
}) ],
], resolve: {
resolve: { alias: {
alias: { '@': resolve('src/renderer'),
'@': resolve('src/renderer'), '~': resolve('src/main'),
'~': resolve('src/main'), root: resolve('./'),
root: resolve('./'), '#': resolve('src/universal')
'#': resolve('src/universal') }
} }
} },
}, renderer: {
renderer: { root: resolve('src/renderer'),
root: resolve('src/renderer'), base: './',
base: './', resolve: {
resolve: { alias: {
alias: { '@': resolve('src/renderer'),
'@': resolve('src/renderer'), '~': resolve('src/main'),
'~': resolve('src/main'), root: resolve('./'),
root: resolve('./'), '#': resolve('src/universal')
'#': resolve('src/universal') }
} },
}, plugins: [vue()],
plugins: [vue()], server: {
server: { port: 3000
port: 3000 }
} }
} })
})

View File

@@ -1,5 +1,6 @@
import eslint from '@eslint/js' import eslint from '@eslint/js'
import standard from '@vue/eslint-config-standard' import standard from '@vue/eslint-config-standard'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import simpleImportSort from 'eslint-plugin-simple-import-sort' import simpleImportSort from 'eslint-plugin-simple-import-sort'
import eslintPluginUnicorn from 'eslint-plugin-unicorn' import eslintPluginUnicorn from 'eslint-plugin-unicorn'
import pluginVue from 'eslint-plugin-vue' import pluginVue from 'eslint-plugin-vue'
@@ -18,6 +19,7 @@ export default tseslint.config(
...tseslint.configs.stylistic, ...tseslint.configs.stylistic,
...pluginVue.configs['flat/recommended'], ...pluginVue.configs['flat/recommended'],
...standard, ...standard,
eslintPluginPrettierRecommended,
{ {
plugins: { plugins: {
'simple-import-sort': simpleImportSort, 'simple-import-sort': simpleImportSort,

View File

@@ -25,10 +25,10 @@
"dev": "electron-vite dev --watch", "dev": "electron-vite dev --watch",
"i18n": "node ./scripts/gen-i18n-types.js", "i18n": "node ./scripts/gen-i18n-types.js",
"link": "node ./scripts/link.js", "link": "node ./scripts/link.js",
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.vue src/", "lint": "eslint --ext .js,.jsx,.ts,.tsx,.vue src/ scripts/ .",
"lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/main/index.ts", "lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/main/index.ts",
"lint:dpdm:renderer": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/renderer/main.ts", "lint:dpdm:renderer": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/renderer/main.ts",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/", "lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/ scripts/ .",
"ncu": "node ./scripts/check-dep.js", "ncu": "node ./scripts/check-dep.js",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps", "postuninstall": "electron-builder install-app-deps",
@@ -44,7 +44,6 @@
"@aws-sdk/client-s3": "^3.864.0", "@aws-sdk/client-s3": "^3.864.0",
"@aws-sdk/lib-storage": "^3.864.0", "@aws-sdk/lib-storage": "^3.864.0",
"@aws-sdk/s3-request-presigner": "^3.864.0", "@aws-sdk/s3-request-presigner": "^3.864.0",
"@electron-toolkit/preload": "^3.0.2",
"@headlessui/vue": "^1.7.23", "@headlessui/vue": "^1.7.23",
"@highlightjs/vue-plugin": "^2.1.2", "@highlightjs/vue-plugin": "^2.1.2",
"@nodelib/fs.walk": "^3.0.1", "@nodelib/fs.walk": "^3.0.1",
@@ -53,11 +52,12 @@
"@piclist/store": "^3.0.0", "@piclist/store": "^3.0.0",
"@smithy/node-http-handler": "^4.1.1", "@smithy/node-http-handler": "^4.1.1",
"@videojs-player/vue": "^1.0.0", "@videojs-player/vue": "^1.0.0",
"@vueuse/core": "^13.6.0",
"ali-oss": "^6.23.0", "ali-oss": "^6.23.0",
"axios": "^1.11.0", "axios": "^1.11.0",
"chalk": "^5.5.0",
"compare-versions": "^6.1.1", "compare-versions": "^6.1.1",
"cos-nodejs-sdk-v5": "^2.15.4", "cos-nodejs-sdk-v5": "^2.15.4",
"dayjs": "^1.11.13",
"dexie": "^3.2.4", "dexie": "^3.2.4",
"electron-updater": "^6.6.2", "electron-updater": "^6.6.2",
"fast-xml-parser": "^5.2.5", "fast-xml-parser": "^5.2.5",
@@ -78,13 +78,12 @@
"piclist": "^2.0.0", "piclist": "^2.0.0",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0", "pinia-plugin-persistedstate": "^4.5.0",
"proxy-agent": "^6.5.0",
"qiniu": "7.14.0", "qiniu": "7.14.0",
"qrcode.vue": "^3.6.0", "qrcode.vue": "^3.6.0",
"querystring": "^0.2.1",
"semver": "^7.7.2", "semver": "^7.7.2",
"shell-path": "3.0.0", "shell-path": "3.0.0",
"ssh2-no-cpu-features": "^2.0.0", "ssh2-no-cpu-features": "^2.0.0",
"tunnel": "^0.0.6",
"upyun": "^3.4.6", "upyun": "^3.4.6",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"video.js": "^8.23.4", "video.js": "^8.23.4",
@@ -112,9 +111,7 @@
"@types/video.js": "^7.3.58", "@types/video.js": "^7.3.58",
"@types/write-file-atomic": "^4.0.3", "@types/write-file-atomic": "^4.0.3",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-standard": "^9.0.1", "@vue/eslint-config-standard": "^9.0.1",
"@vue/eslint-config-typescript": "^14.6.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"dpdm": "^3.14.0", "dpdm": "^3.14.0",
"electron": "^36.7.4", "electron": "^36.7.4",
@@ -122,10 +119,12 @@
"electron-devtools-installer": "^4.0.0", "electron-devtools-installer": "^4.0.0",
"electron-vite": "^4.0.0", "electron-vite": "^4.0.0",
"eslint": "^9.33.0", "eslint": "^9.33.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4", "eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unicorn": "^60.0.0", "eslint-plugin-unicorn": "^60.0.0",
"eslint-plugin-vue": "^10.4.0", "eslint-plugin-vue": "^10.4.0",
"globals": "^16.3.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"node-bump-version": "^2.0.0", "node-bump-version": "^2.0.0",
"npm-check-updates": "^18.0.2", "npm-check-updates": "^18.0.2",

View File

@@ -1,7 +1,7 @@
import axios from 'axios' import axios from 'axios'
import { run } from 'npm-check-updates' import { run } from 'npm-check-updates'
async function getRepositoryInfo (packageName) { async function getRepositoryInfo(packageName) {
try { try {
const { data } = await axios.get(`https://registry.npmjs.org/${packageName}`) const { data } = await axios.get(`https://registry.npmjs.org/${packageName}`)
const repository = data.repository const repository = data.repository
@@ -17,7 +17,7 @@ async function getRepositoryInfo (packageName) {
return null return null
} }
async function checkUpdates () { async function checkUpdates() {
const updated = await run({ const updated = await run({
packageFile: './package.json', packageFile: './package.json',
upgrade: false upgrade: false

View File

@@ -1,66 +1,66 @@
// different platform has different format // different platform has different format
// macos // macos
const darwin = [ const darwin = [
{ {
appNameWithPrefix: 'PicList-', appNameWithPrefix: 'PicList-',
ext: '.dmg', ext: '.dmg',
arch: '-arm64', arch: '-arm64',
'version-file': 'latest-mac.yml' 'version-file': 'latest-mac.yml'
}, },
{ {
appNameWithPrefix: 'PicList-', appNameWithPrefix: 'PicList-',
ext: '.dmg', ext: '.dmg',
arch: '-x64', arch: '-x64',
'version-file': 'latest-mac.yml' 'version-file': 'latest-mac.yml'
} }
] ]
const linux = [ const linux = [
{ {
appNameWithPrefix: 'PicList-', appNameWithPrefix: 'PicList-',
ext: '.AppImage', ext: '.AppImage',
arch: '', arch: '',
'version-file': 'latest-linux.yml' 'version-file': 'latest-linux.yml'
}, },
{ {
appNameWithPrefix: 'piclist_', appNameWithPrefix: 'piclist_',
ext: '.snap', ext: '.snap',
arch: '_amd64', arch: '_amd64',
'version-file': 'latest-linux.yml' 'version-file': 'latest-linux.yml'
} }
] ]
// windows // windows
const win32 = [ const win32 = [
{ {
appNameWithPrefix: 'PicList-Setup-', appNameWithPrefix: 'PicList-Setup-',
ext: '.exe', ext: '.exe',
arch: '-ia32', arch: '-ia32',
'version-file': 'latest.yml' 'version-file': 'latest.yml'
}, },
{ {
appNameWithPrefix: 'PicList-Setup-', appNameWithPrefix: 'PicList-Setup-',
ext: '.exe', ext: '.exe',
arch: '-x64', arch: '-x64',
'version-file': 'latest.yml' 'version-file': 'latest.yml'
}, },
{ {
appNameWithPrefix: 'PicList-Setup-', appNameWithPrefix: 'PicList-Setup-',
ext: '.exe', ext: '.exe',
arch: '', // 32 & 64 arch: '', // 32 & 64
'version-file': 'latest.yml' 'version-file': 'latest.yml'
}, },
{ {
appNameWithPrefix: 'PicList-Setup-', appNameWithPrefix: 'PicList-Setup-',
ext: '.exe', ext: '.exe',
arch: '-arm64', arch: '-arm64',
'version-file': 'latest.yml' 'version-file': 'latest.yml'
} }
] ]
export default { export default {
darwin, darwin,
linux, linux,
win32 win32
} }

View File

@@ -5,7 +5,7 @@ import path from 'node:path'
import axios from 'axios' import axios from 'axios'
import fs from 'fs-extra' import fs from 'fs-extra'
import pkg from '../package.json' with { type: 'json'} import pkg from '../package.json' with { type: 'json' }
const version = process.argv[2] || pkg.version const version = process.argv[2] || pkg.version
// Configuration // Configuration
@@ -26,7 +26,7 @@ const files = [
/** /**
* Create progress bar string * Create progress bar string
*/ */
function getProgressBar (current, total, length = 20) { function getProgressBar(current, total, length = 20) {
const progress = Math.round((current / total) * length) const progress = Math.round((current / total) * length)
const percentage = Math.round((current / total) * 100) const percentage = Math.round((current / total) * 100)
const bar = '█'.repeat(progress) + '░'.repeat(length - progress) const bar = '█'.repeat(progress) + '░'.repeat(length - progress)
@@ -36,7 +36,7 @@ function getProgressBar (current, total, length = 20) {
/** /**
* Format bytes to human-readable format * Format bytes to human-readable format
*/ */
function formatBytes (bytes, decimals = 2) { function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes' if (bytes === 0) return '0 Bytes'
const k = 1024 const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB'] const sizes = ['Bytes', 'KB', 'MB', 'GB']
@@ -47,7 +47,7 @@ function formatBytes (bytes, decimals = 2) {
/** /**
* Download file and calculate SHA256 hash * Download file and calculate SHA256 hash
*/ */
async function downloadAndHash (fileInfo) { async function downloadAndHash(fileInfo) {
const { url, name } = fileInfo const { url, name } = fileInfo
const filePath = path.join(DOWNLOAD_DIR, name) const filePath = path.join(DOWNLOAD_DIR, name)
@@ -103,7 +103,7 @@ async function downloadAndHash (fileInfo) {
/** /**
* Main function * Main function
*/ */
async function main () { async function main() {
console.log(`Generating SHA256 hashes for PicList v${version}`) console.log(`Generating SHA256 hashes for PicList v${version}`)
console.log(`Download directory: ${DOWNLOAD_DIR}`) console.log(`Download directory: ${DOWNLOAD_DIR}`)

View File

@@ -1,26 +1,26 @@
import pkg from '../package.json' with { type: 'json' } import pkg from '../package.json' with { type: 'json' }
const version = pkg.version const version = pkg.version
// TODO: use the same name format // TODO: use the same name format
const generateURL = (platform, ext, prefix = 'PicList-') => { const generateURL = (platform, ext, prefix = 'PicList-') => {
return `https://release.piclist.cn/latest/${prefix}${version}${platform}${ext}` return `https://release.piclist.cn/latest/${prefix}${version}${platform}${ext}`
} }
const template = ` const template = `
### 加速下载地址 ### 加速下载地址
#### MacOS #### MacOS
[PicList-${version}-arm64.dmg](${generateURL('-arm64', '.dmg', 'PicList-')}) [PicList-${version}-arm64.dmg](${generateURL('-arm64', '.dmg', 'PicList-')})
[PicList-${version}-x64.dmg](${generateURL('-x64', '.dmg', 'PicList-')}) [PicList-${version}-x64.dmg](${generateURL('-x64', '.dmg', 'PicList-')})
[PicList-${version}-universal.dmg](${generateURL('-universal', '.dmg', 'PicList-')}) [PicList-${version}-universal.dmg](${generateURL('-universal', '.dmg', 'PicList-')})
#### Windows #### Windows
[PicList-Setup-${version}-ia32.exe](${generateURL('-ia32', '.exe', 'PicList-Setup-')}) [PicList-Setup-${version}-ia32.exe](${generateURL('-ia32', '.exe', 'PicList-Setup-')})
[PicList-Setup-${version}-x64.exe](${generateURL('-x64', '.exe', 'PicList-Setup-')}) [PicList-Setup-${version}-x64.exe](${generateURL('-x64', '.exe', 'PicList-Setup-')})
[PicList-Setup-${version}-arm64.exe](${generateURL('-arm64', '.exe', 'PicList-Setup-')}) [PicList-Setup-${version}-arm64.exe](${generateURL('-arm64', '.exe', 'PicList-Setup-')})
[PicList-Setup-${version}.exe](${generateURL('', '.exe', 'PicList-Setup-')}) [PicList-Setup-${version}.exe](${generateURL('', '.exe', 'PicList-Setup-')})
#### Linux #### Linux
[PicList-${version}.AppImage](${generateURL('', '.AppImage', 'PicList-')}) [PicList-${version}.AppImage](${generateURL('', '.AppImage', 'PicList-')})
[piclist_${version}_amd64.snap](${generateURL('_amd64', '.snap', 'piclist_')})` [piclist_${version}_amd64.snap](${generateURL('_amd64', '.snap', 'piclist_')})`
console.log(template) console.log(template)

View File

@@ -5,7 +5,7 @@ require('dotenv').config()
const { notarize } = require('@electron/notarize') const { notarize } = require('@electron/notarize')
const { ELECTRON_SKIP_NOTARIZATION, XCODE_APP_LOADER_EMAIL, XCODE_APP_LOADER_PASSWORD, XCODE_TEAM_ID } = process.env const { ELECTRON_SKIP_NOTARIZATION, XCODE_APP_LOADER_EMAIL, XCODE_APP_LOADER_PASSWORD, XCODE_TEAM_ID } = process.env
async function main (context) { async function main(context) {
const { electronPlatformName, appOutDir } = context const { electronPlatformName, appOutDir } = context
if ( if (

View File

@@ -23,12 +23,12 @@ class RemoteNoticeHandler {
private remoteNotice: IRemoteNotice | null = null private remoteNotice: IRemoteNotice | null = null
private remoteNoticeLocalCountStorage: IRemoteNoticeLocalCountStorage | null = null private remoteNoticeLocalCountStorage: IRemoteNoticeLocalCountStorage | null = null
async init () { async init() {
this.remoteNotice = await this.getRemoteNoticeInfo() this.remoteNotice = await this.getRemoteNoticeInfo()
this.initLocalCountStorage() this.initLocalCountStorage()
} }
private initLocalCountStorage () { private initLocalCountStorage() {
const localCountStorage = {} const localCountStorage = {}
if (!fs.existsSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH)) { if (!fs.existsSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH)) {
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({})) fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({}))
@@ -44,14 +44,14 @@ class RemoteNoticeHandler {
} }
} }
private saveLocalCountStorage (newData?: IRemoteNoticeLocalCountStorage) { private saveLocalCountStorage(newData?: IRemoteNoticeLocalCountStorage) {
if (newData) { if (newData) {
this.remoteNoticeLocalCountStorage = newData this.remoteNoticeLocalCountStorage = newData
} }
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify(this.remoteNoticeLocalCountStorage)) fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify(this.remoteNoticeLocalCountStorage))
} }
private async getRemoteNoticeInfo (): Promise<IRemoteNotice | null> { private async getRemoteNoticeInfo(): Promise<IRemoteNotice | null> {
try { try {
const noticeInfo = (await axios({ const noticeInfo = (await axios({
method: 'get', method: 'get',
@@ -68,7 +68,7 @@ class RemoteNoticeHandler {
* if the notice is not shown or is always shown, then show the notice * if the notice is not shown or is always shown, then show the notice
* @param action * @param action
*/ */
private checkActionCount (action: IRemoteNoticeAction) { private checkActionCount(action: IRemoteNoticeAction) {
try { try {
if (!this.remoteNoticeLocalCountStorage) { if (!this.remoteNoticeLocalCountStorage) {
return true return true
@@ -102,7 +102,7 @@ class RemoteNoticeHandler {
} }
} }
private async doActions (actions: IRemoteNoticeAction[]) { private async doActions(actions: IRemoteNoticeAction[]) {
for (const action of actions) { for (const action of actions) {
if (this.checkActionCount(action)) { if (this.checkActionCount(action)) {
switch (action.type) { switch (action.type) {
@@ -117,7 +117,7 @@ class RemoteNoticeHandler {
body: action.data?.content || '', body: action.data?.content || '',
clickToCopy: !!action.data?.copyToClipboard, clickToCopy: !!action.data?.copyToClipboard,
copyContent: action.data?.copyToClipboard || '', copyContent: action.data?.copyToClipboard || '',
clickFn () { clickFn() {
if (action.data?.url) { if (action.data?.url) {
shell.openExternal(action.data.url) shell.openExternal(action.data.url)
} }
@@ -163,7 +163,7 @@ class RemoteNoticeHandler {
} }
} }
triggerHook (hook: string) { triggerHook(hook: string) {
if (!this.remoteNotice || !this.remoteNotice.list) { if (!this.remoteNotice || !this.remoteNotice.list) {
return return
} }

View File

@@ -6,24 +6,30 @@ import shortKeyService from 'apis/app/shortKey/shortKeyService'
import GuiApi from 'apis/gui' import GuiApi from 'apis/gui'
import { globalShortcut } from 'electron' import { globalShortcut } from 'electron'
import type { IKeyCommandType, IPluginShortKeyConfig, IShortKeyConfig, IShortKeyConfigs, IShortKeyHandler } from '#/types/types' import type {
IKeyCommandType,
IPluginShortKeyConfig,
IShortKeyConfig,
IShortKeyConfigs,
IShortKeyHandler
} from '#/types/types'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '~/events/constant' import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '~/events/constant'
import { configPaths } from '~/utils/configPaths' import { configPaths } from '~/utils/configPaths'
class ShortKeyHandler { class ShortKeyHandler {
private isInModifiedMode: boolean = false private isInModifiedMode: boolean = false
constructor () { constructor() {
bus.on(TOGGLE_SHORTKEY_MODIFIED_MODE, flag => { bus.on(TOGGLE_SHORTKEY_MODIFIED_MODE, flag => {
this.isInModifiedMode = flag this.isInModifiedMode = flag
}) })
} }
async init () { async init() {
this.initBuiltInShortKey() this.initBuiltInShortKey()
await this.initPluginsShortKey() await this.initPluginsShortKey()
} }
private initBuiltInShortKey () { private initBuiltInShortKey() {
const commands = db.get(configPaths.settings.shortKey._path) as IShortKeyConfigs const commands = db.get(configPaths.settings.shortKey._path) as IShortKeyConfigs
Object.keys(commands) Object.keys(commands)
.filter(item => item.includes('picgo:')) .filter(item => item.includes('picgo:'))
@@ -38,7 +44,7 @@ class ShortKeyHandler {
}) })
} }
private async initPluginsShortKey () { private async initPluginsShortKey() {
// get enabled plugin // get enabled plugin
const pluginList = picgo.pluginLoader.getList() const pluginList = picgo.pluginLoader.getList()
for (const item of pluginList) { for (const item of pluginList) {
@@ -68,7 +74,7 @@ class ShortKeyHandler {
} }
} }
private registerShortKey ( private registerShortKey(
config: IShortKeyConfig | IPluginShortKeyConfig, config: IShortKeyConfig | IPluginShortKeyConfig,
command: string, command: string,
handler: IShortKeyHandler, handler: IShortKeyHandler,
@@ -97,7 +103,7 @@ class ShortKeyHandler {
} }
// enable or disable shortKey // enable or disable shortKey
bindOrUnbindShortKey (item: IShortKeyConfig, from: string): boolean { bindOrUnbindShortKey(item: IShortKeyConfig, from: string): boolean {
const command = `${from}:${item.name}` const command = `${from}:${item.name}`
if (item.enable === false) { if (item.enable === false) {
globalShortcut.unregister(item.key) globalShortcut.unregister(item.key)
@@ -121,7 +127,7 @@ class ShortKeyHandler {
} }
// update shortKey bindings // update shortKey bindings
updateShortKey (item: IShortKeyConfig, oldKey: string, from: string): boolean { updateShortKey(item: IShortKeyConfig, oldKey: string, from: string): boolean {
const command = `${from}:${item.name}` const command = `${from}:${item.name}`
if (globalShortcut.isRegistered(item.key)) return false if (globalShortcut.isRegistered(item.key)) return false
globalShortcut.unregister(oldKey) globalShortcut.unregister(oldKey)
@@ -134,7 +140,7 @@ class ShortKeyHandler {
return true return true
} }
private async handler (command: string) { private async handler(command: string) {
if (this.isInModifiedMode) { if (this.isInModifiedMode) {
return return
} }
@@ -150,7 +156,7 @@ class ShortKeyHandler {
} }
} }
async registerPluginShortKey (pluginName: string) { async registerPluginShortKey(pluginName: string) {
const plugin = await picgo.pluginLoader.getPlugin(pluginName) const plugin = await picgo.pluginLoader.getPlugin(pluginName)
if (plugin && plugin.commands) { if (plugin && plugin.commands) {
if (typeof plugin.commands !== 'function') { if (typeof plugin.commands !== 'function') {
@@ -170,7 +176,7 @@ class ShortKeyHandler {
} }
} }
unregisterPluginShortKey (pluginName: string) { unregisterPluginShortKey(pluginName: string) {
const commands = db.get(configPaths.settings.shortKey._path) as IShortKeyConfigs const commands = db.get(configPaths.settings.shortKey._path) as IShortKeyConfigs
const keyList = Object.keys(commands) const keyList = Object.keys(commands)
.filter(command => command.includes(pluginName)) .filter(command => command.includes(pluginName))

View File

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

View File

@@ -36,7 +36,7 @@ import uploadPng from '../../../../../resources/upload.png?asset&asarUnpack'
import uploadDarkPng from '../../../../../resources/upload-dark.png?asset&asarUnpack' import uploadDarkPng from '../../../../../resources/upload-dark.png?asset&asarUnpack'
let contextMenu: Menu | null let contextMenu: Menu | null
export function setDockMenu () { export function setDockMenu() {
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const dockMenu = Menu.buildFromTemplate([ const dockMenu = Menu.buildFromTemplate([
{ {
@@ -45,7 +45,7 @@ export function setDockMenu () {
}, },
{ {
label: $t('START_WATCH_CLIPBOARD'), label: $t('START_WATCH_CLIPBOARD'),
click () { click() {
db.set(configPaths.settings.isListeningClipboard, true) db.set(configPaths.settings.isListeningClipboard, true)
clipboardPoll.startListening() clipboardPoll.startListening()
clipboardPoll.on('change', () => { clipboardPoll.on('change', () => {
@@ -58,7 +58,7 @@ export function setDockMenu () {
}, },
{ {
label: $t('STOP_WATCH_CLIPBOARD'), label: $t('STOP_WATCH_CLIPBOARD'),
click () { click() {
db.set(configPaths.settings.isListeningClipboard, false) db.set(configPaths.settings.isListeningClipboard, false)
clipboardPoll.stopListening() clipboardPoll.stopListening()
clipboardPoll.removeAllListeners() clipboardPoll.removeAllListeners()
@@ -70,7 +70,7 @@ export function setDockMenu () {
app.dock?.setMenu(dockMenu) app.dock?.setMenu(dockMenu)
} }
export function createMenu () { export function createMenu() {
const submenu = buildPicBedListMenu() const submenu = buildPicBedListMenu()
const appMenu = Menu.buildFromTemplate([ const appMenu = Menu.buildFromTemplate([
{ {
@@ -79,7 +79,7 @@ export function createMenu () {
{ label: $t('OPEN_MAIN_WINDOW'), click: openMainWindow }, { label: $t('OPEN_MAIN_WINDOW'), click: openMainWindow },
{ {
label: $t('RELOAD_APP'), label: $t('RELOAD_APP'),
click () { click() {
app.relaunch() app.relaunch()
app.exit(0) app.exit(0)
} }
@@ -107,7 +107,7 @@ export function createMenu () {
Menu.setApplicationMenu(appMenu) Menu.setApplicationMenu(appMenu)
} }
export function createContextMenu () { export function createContextMenu() {
const ClipboardWatcher = clipboardPoll const ClipboardWatcher = clipboardPoll
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const isMiniWindowVisible = const isMiniWindowVisible =
@@ -147,7 +147,7 @@ export function createContextMenu () {
}, },
{ {
label: $t('RELOAD_APP'), label: $t('RELOAD_APP'),
click () { click() {
app.relaunch() app.relaunch()
app.exit(0) app.exit(0)
} }
@@ -160,7 +160,7 @@ export function createContextMenu () {
0, 0,
{ {
label: $t('OPEN_MINI_WINDOW'), label: $t('OPEN_MINI_WINDOW'),
click () { click() {
openMiniWindow(false) openMiniWindow(false)
}, },
visible: !isMiniWindowVisible visible: !isMiniWindowVisible
@@ -185,7 +185,7 @@ export function createContextMenu () {
{ label: $t('OPEN_MAIN_WINDOW'), click: openMainWindow }, { label: $t('OPEN_MAIN_WINDOW'), click: openMainWindow },
{ {
label: $t('OPEN_MINI_WINDOW'), label: $t('OPEN_MINI_WINDOW'),
click () { click() {
openMiniWindow(false) openMiniWindow(false)
}, },
visible: !isMiniWindowVisible visible: !isMiniWindowVisible
@@ -207,7 +207,7 @@ export function createContextMenu () {
}, },
{ {
label: $t('ABOUT'), label: $t('ABOUT'),
click () { click() {
dialog.showMessageBox({ dialog.showMessageBox({
title: 'PicList', title: 'PicList',
message: 'PicList', message: 'PicList',
@@ -230,7 +230,7 @@ const getTrayIcon = () => {
} }
} }
export function createTray (tooltip: string) { export function createTray(tooltip: string) {
const menubarPic = getTrayIcon() const menubarPic = getTrayIcon()
setTray(new Tray(menubarPic)) setTray(new Tray(menubarPic))
tray.setToolTip(tooltip) tray.setToolTip(tooltip)
@@ -309,7 +309,7 @@ export function createTray (tooltip: string) {
// drop-files only be supported in macOS // drop-files only be supported in macOS
// so the tray window must be available // so the tray window must be available
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
(tray as any).on('drop-files', async (_: Event, files: string[]) => { ;(tray as any).on('drop-files', async (_: Event, files: string[]) => {
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const rawInput = cloneDeep(files) const rawInput = cloneDeep(files)
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)! const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
@@ -338,14 +338,14 @@ export function createTray (tooltip: string) {
imgs[i].shortUrl = shortUrl imgs[i].shortUrl = shortUrl
pasteText.push(pasteTextItem) pasteText.push(pasteTextItem)
const isShowResultNotification = const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined db.get(configPaths.settings.uploadResultNotification) === undefined
? true ? true
: !!db.get(configPaths.settings.uploadResultNotification) : !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) { if (isShowResultNotification) {
const notification = new Notification({ const notification = new Notification({
title: $t('UPLOAD_SUCCEED'), title: $t('UPLOAD_SUCCEED'),
body: shortUrl || imgs[i].imgUrl! body: shortUrl || imgs[i].imgUrl!
// icon: files[i] // icon: files[i]
}) })
setTimeout(() => { setTimeout(() => {
notification.show() notification.show()

View File

@@ -1,236 +1,236 @@
import db, { GalleryDB } from '@core/datastore' import db, { GalleryDB } from '@core/datastore'
import picgo from '@core/picgo' import picgo from '@core/picgo'
import uploader from 'apis/app/uploader' import uploader from 'apis/app/uploader'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import { Notification, WebContents } from 'electron' import { Notification, WebContents } from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import type { IPicGo } from 'piclist' import type { IPicGo } from 'piclist'
import type { IFileWithPath, ImgInfo, IStringKeyMap, IUploadOption } from '#/types/types' import type { IFileWithPath, ImgInfo, IStringKeyMap, IUploadOption } from '#/types/types'
import { T as $t } from '~/i18n/index' import { T as $t } from '~/i18n/index'
import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common' import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common'
import { configPaths } from '~/utils/configPaths' import { configPaths } from '~/utils/configPaths'
import { IPasteStyle, IWindowList } from '~/utils/enum' import { IPasteStyle, IWindowList } from '~/utils/enum'
import { changeCurrentUploader } from '~/utils/handleUploaderConfig' import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
import pasteTemplate from '~/utils/pasteTemplate' import pasteTemplate from '~/utils/pasteTemplate'
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => { const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
const useBuiltinClipboard = const useBuiltinClipboard =
db.get(configPaths.settings.useBuiltinClipboard) === undefined db.get(configPaths.settings.useBuiltinClipboard) === undefined
? true ? true
: !!db.get(configPaths.settings.useBuiltinClipboard) : !!db.get(configPaths.settings.useBuiltinClipboard)
const win = windowManager.getAvailableWindow() const win = windowManager.getAvailableWindow()
if (useBuiltinClipboard) { if (useBuiltinClipboard) {
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard() return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard()
} }
return await uploader.setWebContents(win!.webContents).upload() return await uploader.setWebContents(win!.webContents).upload()
} }
const handleClipboardUploadingReturnCtx = async (img?: IUploadOption, skipProcess = false): Promise<false | IPicGo> => { const handleClipboardUploadingReturnCtx = async (img?: IUploadOption, skipProcess = false): Promise<false | IPicGo> => {
const useBuiltinClipboard = const useBuiltinClipboard =
db.get(configPaths.settings.useBuiltinClipboard) === undefined db.get(configPaths.settings.useBuiltinClipboard) === undefined
? true ? true
: !!db.get(configPaths.settings.useBuiltinClipboard) : !!db.get(configPaths.settings.useBuiltinClipboard)
const win = windowManager.getAvailableWindow() const win = windowManager.getAvailableWindow()
if (useBuiltinClipboard) { if (useBuiltinClipboard) {
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboardReturnCtx(img, skipProcess) return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboardReturnCtx(img, skipProcess)
} }
return await uploader.setWebContents(win!.webContents).uploadReturnCtx(img, skipProcess) return await uploader.setWebContents(win!.webContents).uploadReturnCtx(img, skipProcess)
} }
export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => { export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
const { needRestore, ctx } = await handleSecondaryUpload(undefined, undefined, 'clipboard') const { needRestore, ctx } = await handleSecondaryUpload(undefined, undefined, 'clipboard')
let img: ImgInfo[] | false = false let img: ImgInfo[] | false = false
if (needRestore) { if (needRestore) {
const res = await handleClipboardUploadingReturnCtx(ctx ? ctx.processedInput : undefined, true) const res = await handleClipboardUploadingReturnCtx(ctx ? ctx.processedInput : undefined, true)
img = res ? res.output : false img = res ? res.output : false
} else { } else {
img = await handleClipboardUploading() img = await handleClipboardUploading()
} }
if (img !== false) { if (img !== false) {
if (img.length > 0) { if (img.length > 0) {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW) const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const [pastedText, shortUrl] = await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink)) const [pastedText, shortUrl] = await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink))
img[0].shortUrl = shortUrl img[0].shortUrl = shortUrl
handleCopyUrl(pastedText) handleCopyUrl(pastedText)
const isShowResultNotification = const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined db.get(configPaths.settings.uploadResultNotification) === undefined
? true ? true
: !!db.get(configPaths.settings.uploadResultNotification) : !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) { if (isShowResultNotification) {
const notification = new Notification({ const notification = new Notification({
title: $t('UPLOAD_SUCCEED'), title: $t('UPLOAD_SUCCEED'),
body: shortUrl || img[0].imgUrl! body: shortUrl || img[0].imgUrl!
// icon: img[0].imgUrl // icon: img[0].imgUrl
}) })
setTimeout(() => { setTimeout(() => {
notification.show() notification.show()
}, 100) }, 100)
} }
const inserted = await GalleryDB.getInstance().insert(img[0]) const inserted = await GalleryDB.getInstance().insert(img[0])
// trayWindow just be created in mac/windows, not in linux // trayWindow just be created in mac/windows, not in linux
trayWindow?.webContents?.send('clipboardFiles', []) trayWindow?.webContents?.send('clipboardFiles', [])
trayWindow?.webContents?.send('uploadFiles', img) trayWindow?.webContents?.send('uploadFiles', img)
if (windowManager.has(IWindowList.SETTING_WINDOW)) { if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery') windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
} }
return { return {
url: handleUrlEncodeWithSetting(inserted.imgUrl as string), url: handleUrlEncodeWithSetting(inserted.imgUrl as string),
fullResult: inserted fullResult: inserted
} }
} else { } else {
const notification = new Notification({ const notification = new Notification({
title: $t('UPLOAD_FAILED'), title: $t('UPLOAD_FAILED'),
body: $t('TIPS_UPLOAD_NOT_PICTURES') body: $t('TIPS_UPLOAD_NOT_PICTURES')
}) })
notification.show() notification.show()
return { return {
url: '', url: '',
fullResult: {} fullResult: {}
} }
} }
} else { } else {
return { return {
url: '', url: '',
fullResult: {} fullResult: {}
} }
} }
} }
export const uploadChoosedFiles = async ( export const uploadChoosedFiles = async (
webContents: WebContents, webContents: WebContents,
files: IFileWithPath[] files: IFileWithPath[]
): Promise<IStringKeyMap[]> => { ): Promise<IStringKeyMap[]> => {
const input = files.map(item => item.path) const input = files.map(item => item.path)
const rawInput = cloneDeep(input) const rawInput = cloneDeep(input)
const { needRestore, ctx } = await handleSecondaryUpload(webContents, input) const { needRestore, ctx } = await handleSecondaryUpload(webContents, input)
let imgs: ImgInfo[] | false = false let imgs: ImgInfo[] | false = false
if (needRestore) { if (needRestore) {
const res = await uploader.setWebContents(webContents).uploadReturnCtx(ctx ? ctx.processedInput : input, true) const res = await uploader.setWebContents(webContents).uploadReturnCtx(ctx ? ctx.processedInput : input, true)
imgs = res ? res.output : false imgs = res ? res.output : false
} else { } else {
imgs = await uploader.setWebContents(webContents).upload(input) imgs = await uploader.setWebContents(webContents).upload(input)
} }
const result = [] const result = []
if (imgs !== false) { if (imgs !== false) {
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false
const pasteText: string[] = [] const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) { for (let i = 0; i < imgs.length; i++) {
if (deleteLocalFile) { if (deleteLocalFile) {
fs.remove(rawInput[i]) fs.remove(rawInput[i])
.then(() => { .then(() => {
picgo.log.info(`delete local file: ${rawInput[i]}`) picgo.log.info(`delete local file: ${rawInput[i]}`)
}) })
.catch((err: Error) => { .catch((err: Error) => {
picgo.log.error(err) picgo.log.error(err)
}) })
} }
const [pasteTextItem, shortUrl] = await pasteTemplate( const [pasteTextItem, shortUrl] = await pasteTemplate(
pasteStyle, pasteStyle,
imgs[i], imgs[i],
db.get(configPaths.settings.customLink) db.get(configPaths.settings.customLink)
) )
imgs[i].shortUrl = shortUrl imgs[i].shortUrl = shortUrl
pasteText.push(pasteTextItem) pasteText.push(pasteTextItem)
const isShowResultNotification = const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined db.get(configPaths.settings.uploadResultNotification) === undefined
? true ? true
: !!db.get(configPaths.settings.uploadResultNotification) : !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) { if (isShowResultNotification) {
const notification = new Notification({ const notification = new Notification({
title: $t('UPLOAD_SUCCEED'), title: $t('UPLOAD_SUCCEED'),
body: shortUrl || imgs[i].imgUrl! body: shortUrl || imgs[i].imgUrl!
// icon: files[i].path // icon: files[i].path
}) })
setTimeout(() => { setTimeout(() => {
notification.show() notification.show()
}, i * 100) }, i * 100)
} }
const inserted = await GalleryDB.getInstance().insert(imgs[i]) const inserted = await GalleryDB.getInstance().insert(imgs[i])
result.push({ result.push({
url: handleUrlEncodeWithSetting(inserted.imgUrl!), url: handleUrlEncodeWithSetting(inserted.imgUrl!),
fullResult: inserted fullResult: inserted
}) })
} }
handleCopyUrl(pasteText.join('\n')) handleCopyUrl(pasteText.join('\n'))
// trayWindow just be created in mac/windows, not in linux // trayWindow just be created in mac/windows, not in linux
windowManager.get(IWindowList.TRAY_WINDOW)?.webContents?.send('uploadFiles', imgs) windowManager.get(IWindowList.TRAY_WINDOW)?.webContents?.send('uploadFiles', imgs)
if (windowManager.has(IWindowList.SETTING_WINDOW)) { if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery') windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
} }
return result return result
} else { } else {
return [] return []
} }
} }
export const handleSecondaryUpload = async ( export const handleSecondaryUpload = async (
webContents?: WebContents, webContents?: WebContents,
input?: string[], input?: string[],
uploadType: 'clipboard' | 'file' | 'tray' = 'file' uploadType: 'clipboard' | 'file' | 'tray' = 'file'
): Promise<{ needRestore: boolean; ctx: IPicGo | false }> => { ): Promise<{ needRestore: boolean; ctx: IPicGo | false }> => {
const enableSecondUploader = db.get(configPaths.settings.enableSecondUploader) || false const enableSecondUploader = db.get(configPaths.settings.enableSecondUploader) || false
let currentPicBedType = '' let currentPicBedType = ''
let currentPicBedConfig = {} as IStringKeyMap let currentPicBedConfig = {} as IStringKeyMap
let currentPicBedConfigId = '' let currentPicBedConfigId = ''
let needRestore = false let needRestore = false
let ctx: IPicGo | false = false let ctx: IPicGo | false = false
if (enableSecondUploader) { if (enableSecondUploader) {
const secondUploader = db.get(configPaths.picBed.secondUploader) const secondUploader = db.get(configPaths.picBed.secondUploader)
const secondUploaderConfig = db.get(configPaths.picBed.secondUploaderConfig) const secondUploaderConfig = db.get(configPaths.picBed.secondUploaderConfig)
const secondUploaderId = db.get(configPaths.picBed.secondUploaderId) const secondUploaderId = db.get(configPaths.picBed.secondUploaderId)
const currentPicBed = db.get('picBed') || ({} as IStringKeyMap) const currentPicBed = db.get('picBed') || ({} as IStringKeyMap)
currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms' currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms'
currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap) currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
currentPicBedConfigId = currentPicBedConfig._id currentPicBedConfigId = currentPicBedConfig._id
if ( if (
secondUploader === currentPicBedType && secondUploader === currentPicBedType &&
secondUploaderConfig._configName === currentPicBedConfig._configName && secondUploaderConfig._configName === currentPicBedConfig._configName &&
secondUploaderId === currentPicBedConfigId secondUploaderId === currentPicBedConfigId
) { ) {
picgo.log.info('second uploader is the same as current uploader') picgo.log.info('second uploader is the same as current uploader')
} else { } else {
needRestore = true needRestore = true
let secondImgs: ImgInfo[] | false = false let secondImgs: ImgInfo[] | false = false
changeCurrentUploader(secondUploader, secondUploaderConfig, secondUploaderId) changeCurrentUploader(secondUploader, secondUploaderConfig, secondUploaderId)
if (uploadType === 'clipboard') { if (uploadType === 'clipboard') {
ctx = await handleClipboardUploadingReturnCtx(undefined) ctx = await handleClipboardUploadingReturnCtx(undefined)
} else { } else {
ctx = await uploader.setWebContents(webContents!).uploadReturnCtx(input) ctx = await uploader.setWebContents(webContents!).uploadReturnCtx(input)
} }
secondImgs = ctx ? ctx.output : false secondImgs = ctx ? ctx.output : false
if (secondImgs !== false) { if (secondImgs !== false) {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW) const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
if (uploadType === 'clipboard') { if (uploadType === 'clipboard') {
if (secondImgs.length > 0) { if (secondImgs.length > 0) {
await GalleryDB.getInstance().insert(secondImgs[0]) await GalleryDB.getInstance().insert(secondImgs[0])
trayWindow?.webContents?.send('clipboardFiles', []) trayWindow?.webContents?.send('clipboardFiles', [])
trayWindow?.webContents?.send('uploadFiles', secondImgs) trayWindow?.webContents?.send('uploadFiles', secondImgs)
} }
} else { } else {
for (const secondImgsItem of secondImgs) { for (const secondImgsItem of secondImgs) {
await GalleryDB.getInstance().insert(secondImgsItem) await GalleryDB.getInstance().insert(secondImgsItem)
} }
if (uploadType === 'tray') { if (uploadType === 'tray') {
trayWindow?.webContents?.send('dragFiles', secondImgs) trayWindow?.webContents?.send('dragFiles', secondImgs)
} else { } else {
trayWindow?.webContents?.send('uploadFiles', secondImgs) trayWindow?.webContents?.send('uploadFiles', secondImgs)
} }
} }
if (windowManager.has(IWindowList.SETTING_WINDOW) && uploadType !== 'tray') { if (windowManager.has(IWindowList.SETTING_WINDOW) && uploadType !== 'tray') {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery') windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
} }
} }
} }
} }
if (needRestore) { if (needRestore) {
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId) changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
} }
return { return {
needRestore, needRestore,
ctx ctx
} }
} }

View File

@@ -36,11 +36,11 @@ const waitForRename = (window: BrowserWindow, id: number): Promise<string | null
class Uploader { class Uploader {
private webContents: WebContents | null = null private webContents: WebContents | null = null
constructor () { constructor() {
this.init() this.init()
} }
init () { init() {
picgo.on(ICOREBuildInEvent.NOTIFICATION, (message: any) => { picgo.on(ICOREBuildInEvent.NOTIFICATION, (message: any) => {
new Notification(message).show() new Notification(message).show()
}) })
@@ -92,12 +92,12 @@ class Uploader {
}) })
} }
setWebContents (webContents: WebContents) { setWebContents(webContents: WebContents) {
this.webContents = webContents this.webContents = webContents
return this return this
} }
private async getClipboardImagePath (): Promise<string | false> { private async getClipboardImagePath(): Promise<string | false> {
const imgPath = getClipboardFilePath() const imgPath = getClipboardFilePath()
if (imgPath) return imgPath if (imgPath) return imgPath
@@ -115,7 +115,7 @@ class Uploader {
/** /**
* use electron's clipboard image to upload * use electron's clipboard image to upload
*/ */
async uploadWithBuildInClipboard (): Promise<ImgInfo[] | false> { async uploadWithBuildInClipboard(): Promise<ImgInfo[] | false> {
let imgPath: string | false = false let imgPath: string | false = false
try { try {
imgPath = await this.getClipboardImagePath() imgPath = await this.getClipboardImagePath()
@@ -131,7 +131,7 @@ class Uploader {
} }
} }
async uploadWithBuildInClipboardReturnCtx (img?: IUploadOption, skipProcess = false): Promise<IPicGo | false> { async uploadWithBuildInClipboardReturnCtx(img?: IUploadOption, skipProcess = false): Promise<IPicGo | false> {
let imgPath: string | false = false let imgPath: string | false = false
try { try {
imgPath = await this.getClipboardImagePath() imgPath = await this.getClipboardImagePath()
@@ -147,7 +147,7 @@ class Uploader {
} }
} }
async uploadReturnCtx (img?: IUploadOption, skipProcess = false): Promise<IPicGo | false> { async uploadReturnCtx(img?: IUploadOption, skipProcess = false): Promise<IPicGo | false> {
try { try {
const ctx = await picgo.uploadReturnCtx(img, skipProcess) const ctx = await picgo.uploadReturnCtx(img, skipProcess)
if (!Array.isArray(ctx.output) || !ctx.output.some((item: ImgInfo) => item.imgUrl)) return false if (!Array.isArray(ctx.output) || !ctx.output.some((item: ImgInfo) => item.imgUrl)) return false
@@ -172,7 +172,7 @@ class Uploader {
} }
} }
async upload (img?: IUploadOption): Promise<ImgInfo[] | false> { async upload(img?: IUploadOption): Promise<ImgInfo[] | false> {
try { try {
const output = await picgo.upload(img) const output = await picgo.upload(img)
if (!Array.isArray(output) || !output.some((item: ImgInfo) => item.imgUrl)) return false if (!Array.isArray(output) || !output.some((item: ImgInfo) => item.imgUrl)) return false

View File

@@ -1,277 +1,277 @@
import path from 'node:path' import path from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import bus from '@core/bus' import bus from '@core/bus'
import { CREATE_APP_MENU } from '@core/bus/constants' import { CREATE_APP_MENU } from '@core/bus/constants'
import db from '@core/datastore' import db from '@core/datastore'
import { app, BrowserWindow, Rectangle } from 'electron' import { app, BrowserWindow, Rectangle } from 'electron'
import type { IWindowListItem } from '#/types/electron' import type { IWindowListItem } from '#/types/electron'
import type { IBrowserWindowOptions } from '#/types/types' import type { IBrowserWindowOptions } from '#/types/types'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '~/events/constant' import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '~/events/constant'
import { T as $t } from '~/i18n' import { T as $t } from '~/i18n'
import { configPaths } from '~/utils/configPaths' import { configPaths } from '~/utils/configPaths'
import { IWindowList } from '~/utils/enum' import { IWindowList } from '~/utils/enum'
import logo from '../../../../../resources/logo.png?asset&asarUnpack' import logo from '../../../../../resources/logo.png?asset&asarUnpack'
const windowList = new Map<string, IWindowListItem>() const windowList = new Map<string, IWindowListItem>()
const getDefaultWindowSizes = (): { width: number; height: number } => { const getDefaultWindowSizes = (): { width: number; height: number } => {
const [mainWindowWidth, mainWindowHeight] = db.get([ const [mainWindowWidth, mainWindowHeight] = db.get([
configPaths.settings.mainWindowWidth, configPaths.settings.mainWindowWidth,
configPaths.settings.mainWindowHeight configPaths.settings.mainWindowHeight
]) ])
return { return {
width: mainWindowWidth || 1200, width: mainWindowWidth || 1200,
height: mainWindowHeight || 800 height: mainWindowHeight || 800
} }
} }
function setMiniWindowShape (win: BrowserWindow) { function setMiniWindowShape(win: BrowserWindow) {
const radius = 32 const radius = 32
const shape: Rectangle[] = [] const shape: Rectangle[] = []
for (let y = -radius; y <= radius; y++) { for (let y = -radius; y <= radius; y++) {
for (let x = -radius; x <= radius; x++) { for (let x = -radius; x <= radius; x++) {
if (x * x + y * y <= radius * radius) { if (x * x + y * y <= radius * radius) {
shape.push({ x: radius + x, y: radius + y, width: 1, height: 1 }) shape.push({ x: radius + x, y: radius + y, width: 1, height: 1 })
} }
} }
} }
win.setShape(shape) win.setShape(shape)
} }
const __dirname = path.dirname(fileURLToPath(import.meta.url)) const __dirname = path.dirname(fileURLToPath(import.meta.url))
const preloadPath = fileURLToPath(new URL('../preload/index.mjs', import.meta.url)) const preloadPath = fileURLToPath(new URL('../preload/index.mjs', import.meta.url))
const { width: defaultWindowWidth, height: defaultWindowHeight } = getDefaultWindowSizes() const { width: defaultWindowWidth, height: defaultWindowHeight } = getDefaultWindowSizes()
const trayWindowOptions = { const trayWindowOptions = {
height: 350, height: 350,
width: 196, width: 196,
show: false, show: false,
frame: false, frame: false,
fullscreenable: false, fullscreenable: false,
resizable: false, resizable: false,
transparent: true, transparent: true,
vibrancy: 'ultra-dark', vibrancy: 'ultra-dark',
webPreferences: { webPreferences: {
sandbox: false, sandbox: false,
preload: preloadPath, preload: preloadPath,
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
nodeIntegrationInWorker: false, nodeIntegrationInWorker: false,
backgroundThrottling: true, backgroundThrottling: true,
webSecurity: false webSecurity: false
} }
} }
const settingWindowOptions = { const settingWindowOptions = {
height: defaultWindowHeight, height: defaultWindowHeight,
width: defaultWindowWidth, width: defaultWindowWidth,
show: false, show: false,
frame: true, frame: true,
center: true, center: true,
fullscreenable: true, fullscreenable: true,
resizable: true, resizable: true,
title: 'PicList', title: 'PicList',
transparent: false, transparent: false,
backgroundColor: '#ebeef5', backgroundColor: '#ebeef5',
titleBarStyle: 'hidden', titleBarStyle: 'hidden',
webPreferences: { webPreferences: {
sandbox: false, sandbox: false,
webviewTag: true, webviewTag: true,
backgroundThrottling: true, backgroundThrottling: true,
preload: preloadPath, preload: preloadPath,
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
nodeIntegrationInWorker: false, nodeIntegrationInWorker: false,
webSecurity: false webSecurity: false
} }
} as IBrowserWindowOptions } as IBrowserWindowOptions
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
settingWindowOptions.frame = false settingWindowOptions.frame = false
settingWindowOptions.icon = logo settingWindowOptions.icon = logo
} }
const miniWindowOptions = { const miniWindowOptions = {
height: 64, height: 64,
width: 64, width: 64,
show: process.platform === 'linux', show: process.platform === 'linux',
frame: false, frame: false,
fullscreenable: false, fullscreenable: false,
skipTaskbar: true, skipTaskbar: true,
resizable: false, resizable: false,
transparent: process.platform !== 'linux', transparent: process.platform !== 'linux',
icon: logo, icon: logo,
webPreferences: { webPreferences: {
sandbox: false, sandbox: false,
preload: preloadPath, preload: preloadPath,
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
backgroundThrottling: true, backgroundThrottling: true,
nodeIntegrationInWorker: false nodeIntegrationInWorker: false
} }
} as IBrowserWindowOptions } as IBrowserWindowOptions
if (db.get(configPaths.settings.miniWindowOntop)) { if (db.get(configPaths.settings.miniWindowOntop)) {
miniWindowOptions.alwaysOnTop = true miniWindowOptions.alwaysOnTop = true
} }
const renameWindowOptions = { const renameWindowOptions = {
height: 270, height: 270,
width: 350, width: 350,
show: true, show: true,
fullscreenable: false, fullscreenable: false,
icon: logo, icon: logo,
resizable: true, resizable: true,
webPreferences: { webPreferences: {
sandbox: false, sandbox: false,
preload: preloadPath, preload: preloadPath,
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
nodeIntegrationInWorker: false, nodeIntegrationInWorker: false,
backgroundThrottling: false backgroundThrottling: false
} }
} as IBrowserWindowOptions } as IBrowserWindowOptions
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
renameWindowOptions.show = true renameWindowOptions.show = true
renameWindowOptions.backgroundColor = '#3f3c37' renameWindowOptions.backgroundColor = '#3f3c37'
renameWindowOptions.autoHideMenuBar = true renameWindowOptions.autoHideMenuBar = true
renameWindowOptions.transparent = false renameWindowOptions.transparent = false
} }
const toolboxWindowOptions = { const toolboxWindowOptions = {
height: 450, height: 450,
width: 800, width: 800,
show: false, show: false,
frame: true, frame: true,
center: true, center: true,
fullscreenable: false, fullscreenable: false,
resizable: false, resizable: false,
title: `PicList ${$t('TOOLBOX')}`, title: `PicList ${$t('TOOLBOX')}`,
backgroundColor: '#ebeef5', backgroundColor: '#ebeef5',
icon: logo, icon: logo,
webPreferences: { webPreferences: {
sandbox: false, sandbox: false,
backgroundThrottling: true, backgroundThrottling: true,
preload: preloadPath, preload: preloadPath,
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
nodeIntegrationInWorker: false, nodeIntegrationInWorker: false,
webSecurity: false webSecurity: false
} }
} as IBrowserWindowOptions } as IBrowserWindowOptions
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
toolboxWindowOptions.backgroundColor = '#3f3c37' toolboxWindowOptions.backgroundColor = '#3f3c37'
toolboxWindowOptions.autoHideMenuBar = true toolboxWindowOptions.autoHideMenuBar = true
toolboxWindowOptions.transparent = false toolboxWindowOptions.transparent = false
} }
windowList.set(IWindowList.TRAY_WINDOW, { windowList.set(IWindowList.TRAY_WINDOW, {
isValid: process.platform !== 'linux', isValid: process.platform !== 'linux',
multiple: false, multiple: false,
options: () => trayWindowOptions, options: () => trayWindowOptions,
callback (window) { callback(window) {
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) { if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
window.loadURL(process.env.ELECTRON_RENDERER_URL) window.loadURL(process.env.ELECTRON_RENDERER_URL)
} else { } else {
window.loadFile(path.join(__dirname, '../renderer/index.html')) window.loadFile(path.join(__dirname, '../renderer/index.html'))
} }
window.on('blur', () => { window.on('blur', () => {
window.hide() window.hide()
}) })
} }
}) })
windowList.set(IWindowList.SETTING_WINDOW, { windowList.set(IWindowList.SETTING_WINDOW, {
isValid: true, isValid: true,
multiple: false, multiple: false,
options: () => settingWindowOptions, options: () => settingWindowOptions,
callback (window, windowManager) { callback(window, windowManager) {
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) { if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#main-page/upload`) window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#main-page/upload`)
} else { } else {
window.loadFile(path.join(__dirname, '../renderer/index.html'), { window.loadFile(path.join(__dirname, '../renderer/index.html'), {
hash: 'main-page/upload' hash: 'main-page/upload'
}) })
} }
window.on('closed', () => { window.on('closed', () => {
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false) bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
if (process.platform === 'linux') { if (process.platform === 'linux') {
process.nextTick(() => { process.nextTick(() => {
app.quit() app.quit()
}) })
} }
}) })
bus.emit(CREATE_APP_MENU) bus.emit(CREATE_APP_MENU)
windowManager.create(IWindowList.MINI_WINDOW) windowManager.create(IWindowList.MINI_WINDOW)
} }
}) })
windowList.set(IWindowList.MINI_WINDOW, { windowList.set(IWindowList.MINI_WINDOW, {
isValid: process.platform !== 'darwin', isValid: process.platform !== 'darwin',
multiple: false, multiple: false,
options: () => miniWindowOptions, options: () => miniWindowOptions,
callback (window) { callback(window) {
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) { if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#mini-page`) window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#mini-page`)
} else { } else {
window.loadFile(path.join(__dirname, '../renderer/index.html'), { window.loadFile(path.join(__dirname, '../renderer/index.html'), {
hash: 'mini-page' hash: 'mini-page'
}) })
} }
setMiniWindowShape(window) setMiniWindowShape(window)
} }
}) })
windowList.set(IWindowList.RENAME_WINDOW, { windowList.set(IWindowList.RENAME_WINDOW, {
isValid: true, isValid: true,
multiple: true, multiple: true,
options: () => renameWindowOptions, options: () => renameWindowOptions,
async callback (window, windowManager) { async callback(window, windowManager) {
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) { if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#rename-page`) window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#rename-page`)
} else { } else {
window.loadFile(path.join(__dirname, '../renderer/index.html'), { window.loadFile(path.join(__dirname, '../renderer/index.html'), {
hash: 'rename-page' hash: 'rename-page'
}) })
} }
const currentWindow = windowManager.getAvailableWindow(true) const currentWindow = windowManager.getAvailableWindow(true)
if (currentWindow && currentWindow.isVisible()) { if (currentWindow && currentWindow.isVisible()) {
const { x, y, width, height } = currentWindow.getBounds() const { x, y, width, height } = currentWindow.getBounds()
const positionX = Math.floor(x + width / 2 - 150) const positionX = Math.floor(x + width / 2 - 150)
const positionY = Math.floor(y + height / 2 - (height > 400 ? 88 : 0)) const positionY = Math.floor(y + height / 2 - (height > 400 ? 88 : 0))
window.setPosition(positionX, positionY, false) window.setPosition(positionX, positionY, false)
} }
} }
}) })
windowList.set(IWindowList.TOOLBOX_WINDOW, { windowList.set(IWindowList.TOOLBOX_WINDOW, {
isValid: true, isValid: true,
multiple: false, multiple: false,
options: () => toolboxWindowOptions, options: () => toolboxWindowOptions,
async callback (window, windowManager) { async callback(window, windowManager) {
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) { if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#toolbox-page`) window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#toolbox-page`)
} else { } else {
window.loadFile(path.join(__dirname, '../renderer/index.html'), { window.loadFile(path.join(__dirname, '../renderer/index.html'), {
hash: 'toolbox-page' hash: 'toolbox-page'
}) })
} }
const currentWindow = windowManager.getAvailableWindow(true) const currentWindow = windowManager.getAvailableWindow(true)
if (currentWindow && currentWindow.isVisible()) { if (currentWindow && currentWindow.isVisible()) {
const { x, y, width, height } = currentWindow.getBounds() const { x, y, width, height } = currentWindow.getBounds()
const positionX = Math.floor(x + width / 2 - 400) const positionX = Math.floor(x + width / 2 - 400)
const positionY = Math.floor(y + height / 2 - (height > 400 ? 225 : 0)) const positionY = Math.floor(y + height / 2 - (height > 400 ? 225 : 0))
window.setPosition(positionX, positionY, false) window.setPosition(positionX, positionY, false)
} }
} }
}) })
export default windowList export default windowList

View File

@@ -5,10 +5,10 @@ import type { IWindowListItem, IWindowManager } from '#/types/electron'
import { IWindowList } from '~/utils/enum' import { IWindowList } from '~/utils/enum'
class WindowManager implements IWindowManager { class WindowManager implements IWindowManager {
#windowMap: Map< string, BrowserWindow> = new Map() #windowMap: Map<string, BrowserWindow> = new Map()
#windowIdMap: Map<number, string> = new Map() #windowIdMap: Map<number, string> = new Map()
create (name: string) { create(name: string) {
const windowConfig: IWindowListItem = windowList.get(name)! const windowConfig: IWindowListItem = windowList.get(name)!
if (!windowConfig.isValid) return null if (!windowConfig.isValid) return null
@@ -30,14 +30,14 @@ class WindowManager implements IWindowManager {
return window return window
} }
get (name: string) { get(name: string) {
if (this.has(name)) { if (this.has(name)) {
return this.#windowMap.get(name)! return this.#windowMap.get(name)!
} }
return this.create(name) return this.create(name)
} }
has (name: string) { has(name: string) {
return this.#windowMap.has(name) return this.#windowMap.has(name)
} }
@@ -49,7 +49,7 @@ class WindowManager implements IWindowManager {
} }
} }
getAvailableWindow (isSkipMiniWindow = false) { getAvailableWindow(isSkipMiniWindow = false) {
const miniWindow = this.#windowMap.get(IWindowList.MINI_WINDOW) const miniWindow = this.#windowMap.get(IWindowList.MINI_WINDOW)
if (miniWindow && miniWindow.isVisible() && !isSkipMiniWindow) { if (miniWindow && miniWindow.isVisible() && !isSkipMiniWindow) {
return miniWindow return miniWindow

View File

@@ -1,31 +1,31 @@
import { EventEmitter } from 'node:events' import { EventEmitter } from 'node:events'
class OptimizedBus extends EventEmitter { class OptimizedBus extends EventEmitter {
constructor () { constructor() {
super() super()
this.setMaxListeners(50) this.setMaxListeners(50)
} }
once (event: string | symbol, listener: (...args: any[]) => void): this { once(event: string | symbol, listener: (...args: any[]) => void): this {
const wrappedListener = (...args: any[]) => { const wrappedListener = (...args: any[]) => {
try { try {
listener(...args) listener(...args)
} finally { } finally {
this.removeListener(event, wrappedListener) this.removeListener(event, wrappedListener)
} }
} }
return super.once(event, wrappedListener) return super.once(event, wrappedListener)
} }
cleanupListeners () { cleanupListeners() {
const events = this.eventNames() const events = this.eventNames()
events.forEach(event => { events.forEach(event => {
const listenerCount = this.listenerCount(event) const listenerCount = this.listenerCount(event)
console.log(` listener count (${listenerCount}) for event: ${String(event)}`) console.log(` listener count (${listenerCount}) for event: ${String(event)}`)
}) })
} }
} }
const bus = new OptimizedBus() const bus = new OptimizedBus()
export default bus export default bus

View File

@@ -23,7 +23,7 @@ const errorMsg = {
brokenButBackup: $t('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP') brokenButBackup: $t('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP')
} }
function dbChecker () { function dbChecker() {
if (process.type !== 'renderer') { if (process.type !== 'renderer') {
// db save bak // db save bak
try { try {
@@ -80,7 +80,7 @@ function dbChecker () {
/** /**
* Get config path * Get config path
*/ */
function dbPathChecker (): string { function dbPathChecker(): string {
if (_configFilePath) { if (_configFilePath) {
return _configFilePath return _configFilePath
} }
@@ -120,11 +120,11 @@ function dbPathChecker (): string {
} }
} }
function dbPathDir () { function dbPathDir() {
return path.dirname(dbPathChecker()) return path.dirname(dbPathChecker())
} }
function getGalleryDBPath (): { function getGalleryDBPath(): {
dbPath: string dbPath: string
dbBackupPath: string dbBackupPath: string
} { } {

View File

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

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ const getAListToken = async (url: string, username: string, password: string) =>
} }
export default class AListplistApi { export default class AListplistApi {
static async delete (configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { fileName, config } = configMap const { fileName, config } = configMap
try { try {
const { url, username, password, uploadPath } = config const { url, username, password, uploadPath } = config

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import { removeFileFromS3InMain } from '~/utils/deleteFunc'
import { deleteFailedLog } from '~/utils/deleteLog' import { deleteFailedLog } from '~/utils/deleteLog'
export default class AwsS3Api { export default class AwsS3Api {
static async delete (configMap: IStringKeyMap): Promise<boolean> { static async delete(configMap: IStringKeyMap): Promise<boolean> {
try { try {
return await removeFileFromS3InMain(getRawData(configMap)) return await removeFileFromS3InMain(getRawData(configMap))
} catch (error: any) { } catch (error: any) {

View File

@@ -4,7 +4,7 @@ import { removeFileFromDogeInMain } from '~/utils/deleteFunc'
import { deleteFailedLog } from '~/utils/deleteLog' import { deleteFailedLog } from '~/utils/deleteLog'
export default class AwsS3Api { export default class AwsS3Api {
static async delete (configMap: IStringKeyMap): Promise<boolean> { static async delete(configMap: IStringKeyMap): Promise<boolean> {
try { try {
return await removeFileFromDogeInMain(getRawData(configMap)) return await removeFileFromDogeInMain(getRawData(configMap))
} catch (error: any) { } catch (error: any) {

View File

@@ -10,18 +10,18 @@ interface IConfigMap {
} }
export default class GithubApi { export default class GithubApi {
static #createOctokit (token: string) { static #createOctokit(token: string) {
return new Octokit({ return new Octokit({
auth: token auth: token
}) })
} }
static #createKey (path: string | undefined, fileName: string): string { static #createKey(path: string | undefined, fileName: string): string {
const formatedFileName = fileName.replace(/%2F/g, '/') const formatedFileName = fileName.replace(/%2F/g, '/')
return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${formatedFileName}` : formatedFileName return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${formatedFileName}` : formatedFileName
} }
static async delete (configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { const {
fileName, fileName,
hash, hash,

View File

@@ -4,7 +4,7 @@ import { removeFileFromHuaweiInMain } from '~/utils/deleteFunc'
import { deleteFailedLog } from '~/utils/deleteLog' import { deleteFailedLog } from '~/utils/deleteLog'
export default class HuaweicloudApi { export default class HuaweicloudApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> { static async delete(configMap: IStringKeyMap): Promise<boolean> {
try { try {
return await removeFileFromHuaweiInMain(getRawData(configMap)) return await removeFileFromHuaweiInMain(getRawData(configMap))
} catch (error: any) { } catch (error: any) {

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ import type { IStringKeyMap } from '#/types/types'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog' import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
export default class LskyplistApi { export default class LskyplistApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> { static async delete(configMap: IStringKeyMap): Promise<boolean> {
const { hash, config } = configMap const { hash, config } = configMap
if (!hash || !config || !config.token) { if (!hash || !config || !config.token) {
deleteLog(hash, 'Lskyplist', false, 'LskyplistApi.delete: invalid params') deleteLog(hash, 'Lskyplist', false, 'LskyplistApi.delete: invalid params')

View File

@@ -4,7 +4,7 @@ import type { IStringKeyMap } from '#/types/types'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog' import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
export default class PiclistApi { export default class PiclistApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> { static async delete(configMap: IStringKeyMap): Promise<boolean> {
const { config, fullResult } = configMap const { config, fullResult } = configMap
const { host, port } = config const { host, port } = config
if (!fullResult) return true if (!fullResult) return true

View File

@@ -8,7 +8,7 @@ interface IConfigMap {
} }
export default class QiniuApi { export default class QiniuApi {
static async delete (configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { const {
fileName, fileName,
config: { accessKey, secretKey, bucket, path } config: { accessKey, secretKey, bucket, path }

View File

@@ -4,7 +4,7 @@ import { removeFileFromSFTPInMain } from '~/utils/deleteFunc'
import { deleteFailedLog } from '~/utils/deleteLog' import { deleteFailedLog } from '~/utils/deleteLog'
export default class SftpPlistApi { export default class SftpPlistApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> { static async delete(configMap: IStringKeyMap): Promise<boolean> {
const { fileName, config } = configMap const { fileName, config } = configMap
try { try {
return await removeFileFromSFTPInMain(getRawData(config), fileName) return await removeFileFromSFTPInMain(getRawData(config), fileName)

View File

@@ -11,7 +11,7 @@ interface IConfigMap {
export default class SmmsApi { export default class SmmsApi {
static readonly #baseUrl = 'https://smms.app/api/v2' 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 const { hash, config } = configMap
if (!hash || !config || !config.token) { if (!hash || !config || !config.token) {
deleteLog(hash, 'Smms', false, 'SmmsApi.delete: invalid params') deleteLog(hash, 'Smms', false, 'SmmsApi.delete: invalid params')

View File

@@ -7,14 +7,14 @@ interface IConfigMap {
config: PartialKeys<ITcYunConfig, 'path'> config: PartialKeys<ITcYunConfig, 'path'>
} }
export default class TcyunApi { export default class TcyunApi {
static #createCOS (SecretId: string, SecretKey: string): COS { static #createCOS(SecretId: string, SecretKey: string): COS {
return new COS({ return new COS({
SecretId, SecretId,
SecretKey SecretKey
}) })
} }
static async delete (configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { const {
fileName, fileName,
config: { secretId, secretKey, bucket, area, path } config: { secretId, secretKey, bucket, area, path }

View File

@@ -9,7 +9,7 @@ interface IConfigMap {
} }
export default class UpyunApi { export default class UpyunApi {
static async delete (configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { const {
fileName, fileName,
config: { bucket, operator, password, path } config: { bucket, operator, password, path }

View File

@@ -10,7 +10,7 @@ interface IConfigMap {
} }
export default class WebdavApi { export default class WebdavApi {
static async delete (configMap: IConfigMap): Promise<boolean> { static async delete(configMap: IConfigMap): Promise<boolean> {
const { const {
fileName, fileName,
config: { host, username, password, path, sslEnabled, authType } config: { host, username, password, path, sslEnabled, authType }

View File

@@ -8,7 +8,16 @@ import { BrowserWindow, dialog, ipcMain, IpcMainEvent, MessageBoxOptions, Notifi
import fs from 'fs-extra' import fs from 'fs-extra'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import type { IGuiApi, ImgInfo, IShowFileExplorerOption, IShowInputBoxOption, IShowMessageBoxOption, IShowMessageBoxResult, IShowNotificationOption, IUploadOption } from '#/types/types' import type {
IGuiApi,
ImgInfo,
IShowFileExplorerOption,
IShowInputBoxOption,
IShowMessageBoxOption,
IShowMessageBoxResult,
IShowNotificationOption,
IUploadOption
} from '#/types/types'
import { SHOW_INPUT_BOX } from '~/events/constant' import { SHOW_INPUT_BOX } from '~/events/constant'
import { T as $t } from '~/i18n' import { T as $t } from '~/i18n'
import { handleCopyUrl } from '~/utils/common' import { handleCopyUrl } from '~/utils/common'
@@ -21,18 +30,18 @@ class GuiApi implements IGuiApi {
private static instance: GuiApi private static instance: GuiApi
private windowId: number = -1 private windowId: number = -1
private settingWindowId: number = -1 private settingWindowId: number = -1
private constructor () { private constructor() {
console.log('init guiapi') console.log('init guiapi')
} }
static getInstance (): GuiApi { static getInstance(): GuiApi {
if (!GuiApi.instance) { if (!GuiApi.instance) {
GuiApi.instance = new GuiApi() GuiApi.instance = new GuiApi()
} }
return GuiApi.instance return GuiApi.instance
} }
private async showSettingWindow () { private async showSettingWindow() {
this.settingWindowId = await getSettingWindowId() this.settingWindowId = await getSettingWindowId()
const settingWindow = BrowserWindow.fromId(this.settingWindowId) const settingWindow = BrowserWindow.fromId(this.settingWindowId)
if (settingWindow?.isVisible()) { if (settingWindow?.isVisible()) {
@@ -46,11 +55,11 @@ class GuiApi implements IGuiApi {
}) })
} }
private getWebcontentsByWindowId (id: number) { private getWebcontentsByWindowId(id: number) {
return BrowserWindow.fromId(id)?.webContents return BrowserWindow.fromId(id)?.webContents
} }
async showInputBox ( async showInputBox(
options: IShowInputBoxOption = { options: IShowInputBoxOption = {
title: '', title: '',
placeholder: '' placeholder: ''
@@ -65,13 +74,13 @@ class GuiApi implements IGuiApi {
}) })
} }
async showFileExplorer (options: IShowFileExplorerOption = {}) { async showFileExplorer(options: IShowFileExplorerOption = {}) {
this.windowId = await getWindowId() this.windowId = await getWindowId()
const res = await dialog.showOpenDialog(BrowserWindow.fromId(this.windowId)!, options) const res = await dialog.showOpenDialog(BrowserWindow.fromId(this.windowId)!, options)
return res.filePaths || [] return res.filePaths || []
} }
async upload (input: IUploadOption) { async upload(input: IUploadOption) {
this.windowId = await getWindowId() this.windowId = await getWindowId()
const webContents = this.getWebcontentsByWindowId(this.windowId) const webContents = this.getWebcontentsByWindowId(this.windowId)
const rawInput = cloneDeep(input) const rawInput = cloneDeep(input)
@@ -122,7 +131,7 @@ class GuiApi implements IGuiApi {
return [] return []
} }
showNotification ( showNotification(
options: IShowNotificationOption = { options: IShowNotificationOption = {
title: '', title: '',
body: '' body: ''
@@ -135,7 +144,7 @@ class GuiApi implements IGuiApi {
notification.show() notification.show()
} }
showMessageBox ( showMessageBox(
options: IShowMessageBoxOption = { options: IShowMessageBoxOption = {
title: '', title: '',
message: '', message: '',
@@ -159,7 +168,7 @@ class GuiApi implements IGuiApi {
/** /**
* get picgo config/data path * get picgo config/data path
*/ */
async getConfigPath () { async getConfigPath() {
const currentConfigPath = dbPathChecker() const currentConfigPath = dbPathChecker()
const galleryDBPath = getGalleryDBPath().dbPath const galleryDBPath = getGalleryDBPath().dbPath
return { return {
@@ -169,12 +178,12 @@ class GuiApi implements IGuiApi {
} }
} }
get galleryDB (): DBStore { get galleryDB(): DBStore {
return new Proxy<DBStore>(GalleryDB.getInstance(), { return new Proxy<DBStore>(GalleryDB.getInstance(), {
get (target, prop: keyof DBStore) { get(target, prop: keyof DBStore) {
if (prop === 'overwrite') { if (prop === 'overwrite') {
return new Proxy(GalleryDB.getInstance().overwrite, { return new Proxy(GalleryDB.getInstance().overwrite, {
apply (target, ctx, args) { apply(target, ctx, args) {
return new Promise(resolve => { return new Promise(resolve => {
const guiApi = GuiApi.getInstance() const guiApi = GuiApi.getInstance()
guiApi guiApi
@@ -197,7 +206,7 @@ class GuiApi implements IGuiApi {
} }
if (prop === 'removeById') { if (prop === 'removeById') {
return new Proxy(GalleryDB.getInstance().removeById, { return new Proxy(GalleryDB.getInstance().removeById, {
apply (target, ctx, args) { apply(target, ctx, args) {
return new Promise(resolve => { return new Promise(resolve => {
const guiApi = GuiApi.getInstance() const guiApi = GuiApi.getInstance()
guiApi guiApi

View File

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

View File

@@ -1,15 +1,15 @@
export const SHOW_INPUT_BOX = 'SHOW_INPUT_BOX' export const SHOW_INPUT_BOX = 'SHOW_INPUT_BOX'
export const SHOW_INPUT_BOX_RESPONSE = 'SHOW_INPUT_BOX_RESPONSE' export const SHOW_INPUT_BOX_RESPONSE = 'SHOW_INPUT_BOX_RESPONSE'
export const TOGGLE_SHORTKEY_MODIFIED_MODE = 'TOGGLE_SHORTKEY_MODIFIED_MODE' export const TOGGLE_SHORTKEY_MODIFIED_MODE = 'TOGGLE_SHORTKEY_MODIFIED_MODE'
// picgo plugin // picgo plugin
export const PICGO_CONFIG_PLUGIN = 'PICGO_CONFIG_PLUGIN' export const PICGO_CONFIG_PLUGIN = 'PICGO_CONFIG_PLUGIN'
export const PICGO_HANDLE_PLUGIN_ING = 'PICGO_HANDLE_PLUGIN_ING' export const PICGO_HANDLE_PLUGIN_ING = 'PICGO_HANDLE_PLUGIN_ING'
export const PICGO_HANDLE_PLUGIN_DONE = 'PICGO_HANDLE_PLUGIN_DONE' export const PICGO_HANDLE_PLUGIN_DONE = 'PICGO_HANDLE_PLUGIN_DONE'
export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN' export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN'
// picgo uploader // picgo uploader
export const RENAME_FILE_NAME = 'RENAME_FILE_NAME' export const RENAME_FILE_NAME = 'RENAME_FILE_NAME'
export const GET_RENAME_FILE_NAME = 'GET_RENAME_FILE_NAME' export const GET_RENAME_FILE_NAME = 'GET_RENAME_FILE_NAME'
export const SHOW_MAIN_PAGE_QRCODE = 'SHOW_MAIN_PAGE_QRCODE' export const SHOW_MAIN_PAGE_QRCODE = 'SHOW_MAIN_PAGE_QRCODE'
// rpc // rpc
export const RPC_ACTIONS = 'RPC_ACTIONS' export const RPC_ACTIONS = 'RPC_ACTIONS'
export const RPC_ACTIONS_INVOKE = 'RPC_ACTIONS_INVOKE' export const RPC_ACTIONS_INVOKE = 'RPC_ACTIONS_INVOKE'

View File

@@ -46,19 +46,19 @@ const buildMiniPageMenu = () => {
}, },
{ {
label: $t('UPLOAD_BY_CLIPBOARD'), label: $t('UPLOAD_BY_CLIPBOARD'),
click () { click() {
uploadClipboardFiles() uploadClipboardFiles()
} }
}, },
{ {
label: $t('HIDE_MINI_WINDOW'), label: $t('HIDE_MINI_WINDOW'),
click () { click() {
BrowserWindow.getFocusedWindow()!.hide() BrowserWindow.getFocusedWindow()!.hide()
} }
}, },
{ {
label: $t('START_WATCH_CLIPBOARD'), label: $t('START_WATCH_CLIPBOARD'),
click () { click() {
db.set(configPaths.settings.isListeningClipboard, true) db.set(configPaths.settings.isListeningClipboard, true)
ClipboardWatcher.startListening() ClipboardWatcher.startListening()
ClipboardWatcher.on('change', () => { ClipboardWatcher.on('change', () => {
@@ -71,7 +71,7 @@ const buildMiniPageMenu = () => {
}, },
{ {
label: $t('STOP_WATCH_CLIPBOARD'), label: $t('STOP_WATCH_CLIPBOARD'),
click () { click() {
db.set(configPaths.settings.isListeningClipboard, false) db.set(configPaths.settings.isListeningClipboard, false)
ClipboardWatcher.stopListening() ClipboardWatcher.stopListening()
ClipboardWatcher.removeAllListeners() ClipboardWatcher.removeAllListeners()
@@ -81,7 +81,7 @@ const buildMiniPageMenu = () => {
}, },
{ {
label: $t('RELOAD_APP'), label: $t('RELOAD_APP'),
click () { click() {
app.relaunch() app.relaunch()
app.exit(0) app.exit(0)
} }
@@ -98,7 +98,7 @@ const buildMainPageMenu = (win: BrowserWindow) => {
const template = [ const template = [
{ {
label: $t('ABOUT'), label: $t('ABOUT'),
click () { click() {
dialog.showMessageBox({ dialog.showMessageBox({
title: 'PicList', title: 'PicList',
message: 'PicList', message: 'PicList',
@@ -108,26 +108,26 @@ const buildMainPageMenu = (win: BrowserWindow) => {
}, },
{ {
label: $t('SHOW_PICBED_QRCODE'), label: $t('SHOW_PICBED_QRCODE'),
click () { click() {
win?.webContents?.send(SHOW_MAIN_PAGE_QRCODE) win?.webContents?.send(SHOW_MAIN_PAGE_QRCODE)
} }
}, },
{ {
label: $t('OPEN_TOOLBOX'), label: $t('OPEN_TOOLBOX'),
click () { click() {
const window = windowManager.create(IWindowList.TOOLBOX_WINDOW) const window = windowManager.create(IWindowList.TOOLBOX_WINDOW)
window?.show() window?.show()
} }
}, },
{ {
label: $t('SHOW_DEVTOOLS'), label: $t('SHOW_DEVTOOLS'),
click () { click() {
win?.webContents?.openDevTools({ mode: 'detach' }) win?.webContents?.openDevTools({ mode: 'detach' })
} }
}, },
{ {
label: $t('FEEDBACK'), label: $t('FEEDBACK'),
click () { click() {
const url = 'https://github.com/Kuingsmile/PicList/issues' const url = 'https://github.com/Kuingsmile/PicList/issues'
shell.openExternal(url) shell.openExternal(url)
} }
@@ -176,10 +176,10 @@ const buildSecondPicBedMenu = () => {
: undefined, : undefined,
click: !hasSubmenu click: !hasSubmenu
? function () { ? function () {
picgo.saveConfig({ picgo.saveConfig({
[configPaths.picBed.secondUploader]: item.type [configPaths.picBed.secondUploader]: item.type
}) })
} }
: undefined : undefined
} }
}) })
@@ -233,15 +233,15 @@ const buildPicBedListMenu = () => {
: undefined, : undefined,
click: !hasSubmenu click: !hasSubmenu
? function () { ? function () {
picgo.saveConfig({ picgo.saveConfig({
[configPaths.picBed.current]: item.type, [configPaths.picBed.current]: item.type,
[configPaths.picBed.uploader]: item.type [configPaths.picBed.uploader]: item.type
}) })
if (windowManager.has(IWindowList.SETTING_WINDOW)) { if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed') windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
setTrayToolTip(item.type)
} }
setTrayToolTip(item.type)
}
: undefined : undefined
} }
}) })
@@ -278,7 +278,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
{ {
label: $t('ENABLE_PLUGIN'), label: $t('ENABLE_PLUGIN'),
enabled: !plugin.enabled, enabled: !plugin.enabled,
click () { click() {
picgo.saveConfig({ picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: true [`picgoPlugins.${plugin.fullName}`]: true
}) })
@@ -289,7 +289,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
{ {
label: $t('DISABLE_PLUGIN'), label: $t('DISABLE_PLUGIN'),
enabled: plugin.enabled, enabled: plugin.enabled,
click () { click() {
picgo.saveConfig({ picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: false [`picgoPlugins.${plugin.fullName}`]: false
}) })
@@ -307,7 +307,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
}, },
{ {
label: $t('UNINSTALL_PLUGIN'), label: $t('UNINSTALL_PLUGIN'),
click () { click() {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName) window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
handlePluginUninstall(plugin.fullName) handlePluginUninstall(plugin.fullName)
@@ -315,7 +315,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
}, },
{ {
label: $t('UPDATE_PLUGIN'), label: $t('UPDATE_PLUGIN'),
click () { click() {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName) window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
handlePluginUpdate(plugin.fullName) handlePluginUpdate(plugin.fullName)
@@ -328,7 +328,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
label: $t('CONFIG_THING', { label: $t('CONFIG_THING', {
c: `${i} - ${plugin.config[i].fullName || plugin.config[i].name}` c: `${i} - ${plugin.config[i].fullName || plugin.config[i].name}`
}), }),
click () { click() {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const currentType = i const currentType = i
const configName = plugin.config[i].fullName || plugin.config[i].name const configName = plugin.config[i].fullName || plugin.config[i].name
@@ -346,7 +346,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const pluginTransformer = plugin.config.transformer.name const pluginTransformer = plugin.config.transformer.name
const obj = { const obj = {
label: `${currentTransformer === pluginTransformer ? $t('DISABLE') : $t('ENABLE')}transformer - ${plugin.config.transformer.name}`, label: `${currentTransformer === pluginTransformer ? $t('DISABLE') : $t('ENABLE')}transformer - ${plugin.config.transformer.name}`,
click () { click() {
const transformer = plugin.config.transformer.name const transformer = plugin.config.transformer.name
const currentTransformer = picgo.getConfig<string>(configPaths.picBed.transformer) || 'path' const currentTransformer = picgo.getConfig<string>(configPaths.picBed.transformer) || 'path'
if (currentTransformer === transformer) { if (currentTransformer === transformer) {
@@ -371,7 +371,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
for (const i of plugin.guiMenu) { for (const i of plugin.guiMenu) {
menu.push({ menu.push({
label: i.label, label: i.label,
async click () { async click() {
const picgPlugin = await picgo.pluginLoader.getPlugin(plugin.fullName) const picgPlugin = await picgo.pluginLoader.getPlugin(plugin.fullName)
if (picgPlugin?.guiMenu?.(picgo)?.length) { if (picgPlugin?.guiMenu?.(picgo)?.length) {
const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo) const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo)

View File

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

View File

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

View File

@@ -1,15 +1,15 @@
import ALLApi from 'apis/delete/allApi' import ALLApi from 'apis/delete/allApi'
import type { IIPCEvent } from '#/types/rpc' import type { IIPCEvent } from '#/types/rpc'
import type { ImgInfo } from '#/types/types' import type { ImgInfo } from '#/types/types'
import { IRPCActionType, IRPCType } from '~/utils/enum' import { IRPCActionType, IRPCType } from '~/utils/enum'
export default [ export default [
{ {
action: IRPCActionType.DELETE_ALL_API, action: IRPCActionType.DELETE_ALL_API,
handler: async (_: IIPCEvent, args:[item: ImgInfo]) => { handler: async (_: IIPCEvent, args: [item: ImgInfo]) => {
return await ALLApi.delete(args[0]) return await ALLApi.delete(args[0])
}, },
type: IRPCType.INVOKE type: IRPCType.INVOKE
} }
] ]

View File

@@ -85,11 +85,15 @@ const getPluginList = async (): Promise<IPicGoPlugin[]> => {
}, },
uploader: { uploader: {
name: uploaderName, name: uploaderName,
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.uploader as keyof typeof IPicGoHelperType, picgo)) config: handleConfigWithFunction(
getConfig(uploaderName, IPicGoHelperType.uploader as keyof typeof IPicGoHelperType, picgo)
)
}, },
transformer: { transformer: {
name: transformerName, name: transformerName,
config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.transformer as keyof typeof IPicGoHelperType, picgo)) config: handleConfigWithFunction(
getConfig(uploaderName, IPicGoHelperType.transformer as keyof typeof IPicGoHelperType, picgo)
)
} }
}, },
enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`), enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`),

View File

@@ -10,9 +10,7 @@ import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
import { T as $t } from '~/i18n' import { T as $t } from '~/i18n'
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/utils/enum' import { IToolboxItemCheckStatus, IToolboxItemType } from '~/utils/enum'
export const checkFileMap: IToolboxCheckerMap< export const checkFileMap: IToolboxCheckerMap<string> = {
string
> = {
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async (event: IpcMainEvent) => { [IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async (event: IpcMainEvent) => {
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_CONFIG_FILE_BROKEN) const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_CONFIG_FILE_BROKEN)
sendToolboxRes(event, { sendToolboxRes(event, {
@@ -62,9 +60,7 @@ export const checkFileMap: IToolboxCheckerMap<
} }
} }
export const fixFileMap: IToolboxFixMap< export const fixFileMap: IToolboxFixMap<string> = {
string
> = {
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async () => { [IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async () => {
try { try {
fs.unlinkSync(dbPathChecker()) fs.unlinkSync(dbPathChecker())

View File

@@ -1,89 +1,89 @@
import { dbPathChecker } from '@core/datastore/dbChecker' import { dbPathChecker } from '@core/datastore/dbChecker'
import axios, { AxiosRequestConfig } from 'axios' import axios, { AxiosRequestConfig } from 'axios'
import fs from 'fs-extra' import fs from 'fs-extra'
import { IConfig } from 'piclist' import { IConfig } from 'piclist'
import tunnel from 'tunnel' import tunnel from 'tunnel'
import type { IToolboxCheckerMap } from '#/types/rpc' import type { IToolboxCheckerMap } from '#/types/rpc'
import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils' import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
import { T as $t } from '~/i18n' import { T as $t } from '~/i18n'
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/utils/enum' import { IToolboxItemCheckStatus, IToolboxItemType } from '~/utils/enum'
function getProxy (proxyStr: string): AxiosRequestConfig['proxy'] | null { function getProxy(proxyStr: string): AxiosRequestConfig['proxy'] | null {
if (proxyStr) { if (proxyStr) {
try { try {
const proxyOptions = new URL(proxyStr) const proxyOptions = new URL(proxyStr)
return { return {
host: proxyOptions.hostname, host: proxyOptions.hostname,
port: parseInt(proxyOptions.port || '0', 10), port: parseInt(proxyOptions.port || '0', 10),
protocol: proxyOptions.protocol protocol: proxyOptions.protocol
} }
} catch (e) {} } catch (e) {}
} }
return null return null
} }
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY) const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
export const checkProxyMap: IToolboxCheckerMap<string> = { export const checkProxyMap: IToolboxCheckerMap<string> = {
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async event => { [IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async event => {
sendToolboxRes(event, { sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING status: IToolboxItemCheckStatus.LOADING
}) })
const configFilePath = dbPathChecker() const configFilePath = dbPathChecker()
if (fs.existsSync(configFilePath)) { if (fs.existsSync(configFilePath)) {
let config: IConfig | undefined let config: IConfig | undefined
try { try {
config = (await fs.readJSON(configFilePath)) as IConfig config = (await fs.readJSON(configFilePath)) as IConfig
} catch (e) {} } catch (e) {}
if (!config) { if (!config) {
return sendToolboxRes(event, { return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS, status: IToolboxItemCheckStatus.SUCCESS,
msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS') msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
}) })
} }
const proxy = config.picBed?.proxy const proxy = config.picBed?.proxy
if (!proxy) { if (!proxy) {
return sendToolboxRes(event, { return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS, status: IToolboxItemCheckStatus.SUCCESS,
msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS') msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
}) })
} else { } else {
const proxyOptions = getProxy(proxy) const proxyOptions = getProxy(proxy)
if (!proxyOptions) { if (!proxyOptions) {
return sendToolboxRes(event, { return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR, status: IToolboxItemCheckStatus.ERROR,
msg: $t('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT') msg: $t('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT')
}) })
} else { } else {
const httpsAgent = tunnel.httpsOverHttp({ const httpsAgent = tunnel.httpsOverHttp({
proxy: { proxy: {
host: proxyOptions.host, host: proxyOptions.host,
port: proxyOptions.port port: proxyOptions.port
} }
}) })
try { try {
await axios.get('https://www.google.com', { await axios.get('https://www.google.com', {
httpsAgent httpsAgent
}) })
return sendToolboxRes(event, { return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS, status: IToolboxItemCheckStatus.SUCCESS,
msg: $t('TOOLBOX_CHECK_PROXY_SUCCESS_TIPS') msg: $t('TOOLBOX_CHECK_PROXY_SUCCESS_TIPS')
}) })
} catch (e) { } catch (e) {
return sendToolboxRes(event, { return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.ERROR, status: IToolboxItemCheckStatus.ERROR,
msg: $t('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING') msg: $t('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING')
}) })
} }
} }
} }
} }
sendToolboxRes(event, { sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS, status: IToolboxItemCheckStatus.SUCCESS,
msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS') msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
}) })
} }
} }

View File

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

View File

@@ -12,7 +12,7 @@ const serverPort = 36699
let server: http.Server let server: http.Server
export function startFileServer () { export function startFileServer() {
server = http.createServer((req, res) => { server = http.createServer((req, res) => {
const requestPath = req.url?.split('?')[0] const requestPath = req.url?.split('?')[0]
const filePath = path.join(imgFilePath, decodeURIComponent(requestPath as string)) const filePath = path.join(imgFilePath, decodeURIComponent(requestPath as string))
@@ -36,7 +36,7 @@ export function startFileServer () {
}) })
} }
export function stopFileServer () { export function stopFileServer() {
server.close(() => { server.close(() => {
logger.info('File server is stopped') logger.info('File server is stopped')
}) })

View File

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

View File

@@ -1,3 +1,3 @@
import { lifeCycle } from '~/lifeCycle' import { lifeCycle } from '~/lifeCycle'
lifeCycle.launchApp() lifeCycle.launchApp()

View File

@@ -24,9 +24,9 @@ process.on('unhandledRejection', (error: any) => {
}) })
// acconrding to https://github.com/Molunerfinn/PicGo/commit/7363be798cfef11e980934e542817ff1d6c04389#diff-896d0db4fbd446798fbffec14d456b4cd98d4c72c46856c770a585fa7ab0926f // acconrding to https://github.com/Molunerfinn/PicGo/commit/7363be798cfef11e980934e542817ff1d6c04389#diff-896d0db4fbd446798fbffec14d456b4cd98d4c72c46856c770a585fa7ab0926f
function bootstrapEPIPESuppression () { function bootstrapEPIPESuppression() {
let suppressing = false let suppressing = false
function logEPIPEErrorOnce () { function logEPIPEErrorOnce() {
if (suppressing) { if (suppressing) {
return return
} }
@@ -41,11 +41,11 @@ function bootstrapEPIPESuppression () {
bootstrapEPIPESuppression() bootstrapEPIPESuppression()
function epipeBomb (stream: any, callback: any) { function epipeBomb(stream: any, callback: any) {
if (stream == null) stream = process.stdout if (stream == null) stream = process.stdout
if (callback == null) callback = process.exit if (callback == null) callback = process.exit
function epipeFilter (err: any) { function epipeFilter(err: any) {
if (err.code === 'EPIPE') return callback() if (err.code === 'EPIPE') return callback()
// If there's more than one error handler (ie, us), // If there's more than one error handler (ie, us),

View File

@@ -1,6 +1,6 @@
import { shellPathSync } from 'shell-path' import { shellPathSync } from 'shell-path'
export default function fixPath () { export default function fixPath() {
if (process.platform === 'win32') { if (process.platform === 'win32') {
return return
} }

View File

@@ -1,354 +1,360 @@
import '~/lifeCycle/errorHandler' import '~/lifeCycle/errorHandler'
import path from 'node:path' import path from 'node:path'
import bus from '@core/bus' import bus from '@core/bus'
import db from '@core/datastore' import db from '@core/datastore'
import picgo from '@core/picgo' import picgo from '@core/picgo'
import logger from '@core/picgo/logger' import logger from '@core/picgo/logger'
import { remoteNoticeHandler } from 'apis/app/remoteNotice' import { remoteNoticeHandler } from 'apis/app/remoteNotice'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import { createTray, setDockMenu } from 'apis/app/system' import { createTray, setDockMenu } from 'apis/app/system'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis' import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import axios from 'axios' import axios from 'axios'
import { app, dialog, globalShortcut, Notification, protocol, screen, shell } from 'electron' import { app, dialog, globalShortcut, Notification, protocol, screen, shell } from 'electron'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer' import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import updater from 'electron-updater' import updater from 'electron-updater'
import fs from 'fs-extra' import fs from 'fs-extra'
import busEventList from '~/events/busEventList' import busEventList from '~/events/busEventList'
import { rpcServer } from '~/events/rpc' import { rpcServer } from '~/events/rpc'
import { startFileServer, stopFileServer } from '~/fileServer' import { startFileServer, stopFileServer } from '~/fileServer'
import { T as $t } from '~/i18n' import { T as $t } from '~/i18n'
import fixPath from '~/lifeCycle/fixPath' import fixPath from '~/lifeCycle/fixPath'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue' import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import getManageApi from '~/manage/Main' import getManageApi from '~/manage/Main'
import { clearTempFolder } from '~/manage/utils/common' import { clearTempFolder } from '~/manage/utils/common'
import server from '~/server/index' import server from '~/server/index'
import webServer from '~/server/webServer' import webServer from '~/server/webServer'
import beforeOpen from '~/utils/beforeOpen' import beforeOpen from '~/utils/beforeOpen'
import clipboardPoll from '~/utils/clipboardPoll' import clipboardPoll from '~/utils/clipboardPoll'
import { configPaths } from '~/utils/configPaths' import { configPaths } from '~/utils/configPaths'
import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '~/utils/enum' import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '~/utils/enum'
import { getUploadFiles } from '~/utils/handleArgv' import { getUploadFiles } from '~/utils/handleArgv'
import { initI18n } from '~/utils/handleI18n' import { initI18n } from '~/utils/handleI18n'
import { notificationList } from '~/utils/notification' import { notificationList } from '~/utils/notification'
import { MemoryMonitor } from '~/utils/performanceOptimizer' import { MemoryMonitor } from '~/utils/performanceOptimizer'
import { CLIPBOARD_IMAGE_FOLDER } from '~/utils/static' import { CLIPBOARD_IMAGE_FOLDER } from '~/utils/static'
import updateChecker from '~/utils/updateChecker' import updateChecker from '~/utils/updateChecker'
const isDevelopment = process.env.NODE_ENV !== 'production' const isDevelopment = process.env.NODE_ENV !== 'production'
const handleStartUpFiles = (argv: string[], cwd: string) => { const handleStartUpFiles = (argv: string[], cwd: string) => {
const files = getUploadFiles(argv, cwd, logger) const files = getUploadFiles(argv, cwd, logger)
if (files === null) { if (files === null) {
logger.info('cli -> uploading file from clipboard') logger.info('cli -> uploading file from clipboard')
uploadClipboardFiles() uploadClipboardFiles()
return true return true
} }
if (files.length > 0) { if (files.length > 0) {
logger.info('cli -> uploading files from cli', ...files.map(file => file.path)) logger.info('cli -> uploading files from cli', ...files.map(file => file.path))
const win = windowManager.getAvailableWindow() const win = windowManager.getAvailableWindow()
uploadChoosedFiles(win.webContents, files) uploadChoosedFiles(win.webContents, files)
return true return true
} }
return false return false
} }
updater.autoUpdater.setFeedURL({ updater.autoUpdater.setFeedURL({
provider: 'generic', provider: 'generic',
url: 'https://release.piclist.cn/latest', url: 'https://release.piclist.cn/latest',
channel: 'latest' channel: 'latest'
}) })
updater.autoUpdater.autoDownload = false updater.autoUpdater.autoDownload = false
updater.autoUpdater.on('update-available', async (info: updater.UpdateInfo) => { updater.autoUpdater.on('update-available', async (info: updater.UpdateInfo) => {
const lang = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN const lang = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN
let updateLog = '' let updateLog = ''
try { try {
const url = const url =
lang === II18nLanguage.ZH_CN lang === II18nLanguage.ZH_CN
? 'https://release.piclist.cn/currentVersion.md' ? 'https://release.piclist.cn/currentVersion.md'
: 'https://release.piclist.cn/currentVersion_en.md' : 'https://release.piclist.cn/currentVersion_en.md'
const res = await axios.get(url) const res = await axios.get(url)
updateLog = res.data updateLog = res.data
} catch (e: any) { } catch (e: any) {
logger.error(e) logger.error(e)
} }
const maxLogLength = 800 const maxLogLength = 800
let displayLog = updateLog let displayLog = updateLog
let truncatedNote = '' let truncatedNote = ''
if (updateLog.length > maxLogLength) { if (updateLog.length > maxLogLength) {
const truncatePoint = updateLog.lastIndexOf('\n', maxLogLength) const truncatePoint = updateLog.lastIndexOf('\n', maxLogLength)
displayLog = updateLog.substring(0, truncatePoint > 0 ? truncatePoint : maxLogLength) displayLog = updateLog.substring(0, truncatePoint > 0 ? truncatePoint : maxLogLength)
truncatedNote = truncatedNote =
lang === II18nLanguage.ZH_CN lang === II18nLanguage.ZH_CN
? '\n\n... (更多详情请查看完整更新日志)' ? '\n\n... (更多详情请查看完整更新日志)'
: '\n\n... (See full changelog for more details)' : '\n\n... (See full changelog for more details)'
} }
dialog dialog
.showMessageBox({ .showMessageBox({
type: 'info', type: 'info',
title: $t('FIND_NEW_VERSION'), title: $t('FIND_NEW_VERSION'),
buttons: ['Yes', 'Go to download page'], buttons: ['Yes', 'Go to download page'],
message: message:
$t('TIPS_FIND_NEW_VERSION', { $t('TIPS_FIND_NEW_VERSION', {
v: info.version v: info.version
}) + }) +
'\n\n' + '\n\n' +
displayLog + displayLog +
truncatedNote, truncatedNote,
checkboxLabel: $t('NO_MORE_NOTICE'), checkboxLabel: $t('NO_MORE_NOTICE'),
checkboxChecked: false checkboxChecked: false
}) })
.then(result => { .then(result => {
if (result.response === 0) { if (result.response === 0) {
updater.autoUpdater.downloadUpdate() updater.autoUpdater.downloadUpdate()
} else { } else {
shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest') shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest')
} }
db.set(configPaths.settings.showUpdateTip, !result.checkboxChecked) db.set(configPaths.settings.showUpdateTip, !result.checkboxChecked)
}) })
.catch(err => { .catch(err => {
logger.error(err) logger.error(err)
}) })
}) })
updater.autoUpdater.on('download-progress', progressObj => { updater.autoUpdater.on('download-progress', progressObj => {
const percent = { const percent = {
progress: progressObj.percent progress: progressObj.percent
} }
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send('updateProgress', percent) window.webContents.send('updateProgress', percent)
}) })
updater.autoUpdater.on('update-downloaded', () => { updater.autoUpdater.on('update-downloaded', () => {
dialog dialog
.showMessageBox({ .showMessageBox({
type: 'info', type: 'info',
title: $t('UPDATE_DOWNLOADED'), title: $t('UPDATE_DOWNLOADED'),
buttons: ['Yes', 'No'], buttons: ['Yes', 'No'],
message: $t('TIPS_UPDATE_DOWNLOADED') message: $t('TIPS_UPDATE_DOWNLOADED')
}) })
.then(result => { .then(result => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send('updateProgress', { progress: 100 }) window.webContents.send('updateProgress', { progress: 100 })
if (result.response === 0) { if (result.response === 0) {
updater.autoUpdater.quitAndInstall() updater.autoUpdater.quitAndInstall()
} }
}) })
.catch(err => { .catch(err => {
logger.error(err) logger.error(err)
}) })
}) })
updater.autoUpdater.on('error', err => { updater.autoUpdater.on('error', err => {
logger.error(err) logger.error(err)
}) })
class LifeCycle { class LifeCycle {
async #beforeReady () { async #beforeReady() {
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }]) protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
// fix the $PATH in macOS & linux // fix the $PATH in macOS & linux
fixPath() fixPath()
beforeOpen() beforeOpen()
getManageApi() getManageApi()
UpDownTaskQueue.getInstance() UpDownTaskQueue.getInstance()
initI18n() initI18n()
rpcServer.start() rpcServer.start()
busEventList.listen() busEventList.listen()
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
MemoryMonitor.start(30000) MemoryMonitor.start(30000)
} }
} }
#onReady () { #onReady() {
const readyFunction = async () => { const readyFunction = async () => {
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
installExtension(VUEJS_DEVTOOLS).catch(err => { installExtension(VUEJS_DEVTOOLS).catch(err => {
logger.error('An error occurred: ', err) logger.error('An error occurred: ', err)
}) })
} }
windowManager.create(IWindowList.TRAY_WINDOW) windowManager.create(IWindowList.TRAY_WINDOW)
windowManager.create(IWindowList.SETTING_WINDOW) windowManager.create(IWindowList.SETTING_WINDOW)
const isAutoListenClipboard = db.get(configPaths.settings.isAutoListenClipboard) || false const isAutoListenClipboard = db.get(configPaths.settings.isAutoListenClipboard) || false
const ClipboardWatcher = clipboardPoll const ClipboardWatcher = clipboardPoll
if (isAutoListenClipboard) { if (isAutoListenClipboard) {
db.set(configPaths.settings.isListeningClipboard, true) db.set(configPaths.settings.isListeningClipboard, true)
ClipboardWatcher.startListening() ClipboardWatcher.startListening()
ClipboardWatcher.on('change', () => { ClipboardWatcher.on('change', () => {
picgo.log.info('clipboard changed') picgo.log.info('clipboard changed')
uploadClipboardFiles() uploadClipboardFiles()
}) })
} else { } else {
db.set(configPaths.settings.isListeningClipboard, false) db.set(configPaths.settings.isListeningClipboard, false)
} }
const isHideDock = db.get(configPaths.settings.isHideDock) || false const isHideDock = db.get(configPaths.settings.isHideDock) || false
let startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET let startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET
if (process.platform === 'darwin' && startMode === ISartMode.MINI) { if (process.platform === 'darwin' && startMode === ISartMode.MINI) {
startMode = ISartMode.QUIET startMode = ISartMode.QUIET
} }
const currentPicBed = db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms' const currentPicBed = db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms'
const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default' const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default'
const tooltip = `${currentPicBed} ${currentPicBedConfig}` const tooltip = `${currentPicBed} ${currentPicBedConfig}`
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
isHideDock ? app.dock?.hide() : setDockMenu() isHideDock ? app.dock?.hide() : setDockMenu()
startMode !== ISartMode.NO_TRAY && createTray(tooltip) startMode !== ISartMode.NO_TRAY && createTray(tooltip)
} else { } else {
createTray(tooltip) createTray(tooltip)
} }
db.set(configPaths.needReload, false) db.set(configPaths.needReload, false)
updateChecker() updateChecker()
// 不需要阻塞 // 不需要阻塞
process.nextTick(() => { process.nextTick(() => {
shortKeyHandler.init() shortKeyHandler.init()
}) })
server.startup() server.startup()
webServer.start() webServer.start()
startFileServer() startFileServer()
if (process.env.NODE_ENV !== 'development') { if (process.env.NODE_ENV !== 'development') {
handleStartUpFiles(process.argv, process.cwd()) handleStartUpFiles(process.argv, process.cwd())
} }
if (notificationList && notificationList.length > 0) { if (notificationList && notificationList.length > 0) {
while (notificationList.length) { while (notificationList.length) {
const option = notificationList.pop() const option = notificationList.pop()
const notice = new Notification(option!) const notice = new Notification(option!)
notice.show() notice.show()
} }
} }
await remoteNoticeHandler.init() await remoteNoticeHandler.init()
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START) remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
if (startMode === ISartMode.MINI && process.platform !== 'darwin') { if (startMode === ISartMode.MINI && process.platform !== 'darwin') {
windowManager.create(IWindowList.MINI_WINDOW) windowManager.create(IWindowList.MINI_WINDOW)
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
miniWindow.removeAllListeners() miniWindow.removeAllListeners()
if (db.get(configPaths.settings.miniWindowOntop)) { if (db.get(configPaths.settings.miniWindowOntop)) {
miniWindow.setAlwaysOnTop(true) miniWindow.setAlwaysOnTop(true)
} }
const { width, height } = screen.getPrimaryDisplay().workAreaSize const { width, height } = screen.getPrimaryDisplay().workAreaSize
const lastPosition = db.get(configPaths.settings.miniWindowPosition) const lastPosition = db.get(configPaths.settings.miniWindowPosition)
if (lastPosition) { if (lastPosition) {
if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) { if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) {
miniWindow.setPosition(width - 100, height - 100) miniWindow.setPosition(width - 100, height - 100)
db.set(configPaths.settings.miniWindowPosition, [width - 100, height - 100]) db.set(configPaths.settings.miniWindowPosition, [width - 100, height - 100])
} else if (lastPosition[0] + miniWindow.getSize()[0] > width || lastPosition[1] + miniWindow.getSize()[1] > height) { } else if (
miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]) lastPosition[0] + miniWindow.getSize()[0] > width ||
db.set(configPaths.settings.miniWindowPosition, [width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]]) lastPosition[1] + miniWindow.getSize()[1] > height
} else { ) {
miniWindow.setPosition(lastPosition[0], lastPosition[1]) miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1])
} db.set(configPaths.settings.miniWindowPosition, [
} else { width - miniWindow.getSize()[0],
miniWindow.setPosition(width - 100, height - 100) height - miniWindow.getSize()[1]
} ])
const setPositionFunc = () => { } else {
const position = miniWindow.getPosition() miniWindow.setPosition(lastPosition[0], lastPosition[1])
db.set(configPaths.settings.miniWindowPosition, position) }
} } else {
miniWindow.on('close', setPositionFunc) miniWindow.setPosition(width - 100, height - 100)
miniWindow.on('move', setPositionFunc) }
miniWindow.show() const setPositionFunc = () => {
miniWindow.focus() const position = miniWindow.getPosition()
} else if (startMode === ISartMode.MAIN) { db.set(configPaths.settings.miniWindowPosition, position)
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)! }
settingWindow.show() miniWindow.on('close', setPositionFunc)
settingWindow.focus() miniWindow.on('move', setPositionFunc)
} miniWindow.show()
const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER) miniWindow.focus()
fs.emptyDir(clipboardDir) } else if (startMode === ISartMode.MAIN) {
} const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
app.whenReady().then(readyFunction) settingWindow.show()
} settingWindow.focus()
}
#onRunning () { const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER)
app.on('second-instance', (_, commandLine, workingDirectory) => { fs.emptyDir(clipboardDir)
logger.info('detect second instance') }
const result = handleStartUpFiles(commandLine, workingDirectory) app.whenReady().then(readyFunction)
if (!result) { }
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)! #onRunning() {
if (settingWindow.isMinimized()) { app.on('second-instance', (_, commandLine, workingDirectory) => {
settingWindow.restore() logger.info('detect second instance')
} const result = handleStartUpFiles(commandLine, workingDirectory)
settingWindow.focus() if (!result) {
} if (windowManager.has(IWindowList.SETTING_WINDOW)) {
} const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
}) if (settingWindow.isMinimized()) {
app.on('activate', () => { settingWindow.restore()
if (!windowManager.has(IWindowList.TRAY_WINDOW)) { }
windowManager.create(IWindowList.TRAY_WINDOW) settingWindow.focus()
} }
if (!windowManager.has(IWindowList.SETTING_WINDOW)) { }
windowManager.create(IWindowList.SETTING_WINDOW) })
} app.on('activate', () => {
}) if (!windowManager.has(IWindowList.TRAY_WINDOW)) {
app.setLoginItemSettings({ windowManager.create(IWindowList.TRAY_WINDOW)
openAtLogin: db.get(configPaths.settings.autoStart) || false }
}) if (!windowManager.has(IWindowList.SETTING_WINDOW)) {
if (process.platform === 'win32') { windowManager.create(IWindowList.SETTING_WINDOW)
app.setAppUserModelId('com.kuingsmile.piclist') }
} })
app.setLoginItemSettings({
if (process.env.XDG_CURRENT_DESKTOP && process.env.XDG_CURRENT_DESKTOP.includes('Unity')) { openAtLogin: db.get(configPaths.settings.autoStart) || false
process.env.XDG_CURRENT_DESKTOP = 'Unity' })
} if (process.platform === 'win32') {
} app.setAppUserModelId('com.kuingsmile.piclist')
}
#onQuit () {
app.on('window-all-closed', () => { if (process.env.XDG_CURRENT_DESKTOP && process.env.XDG_CURRENT_DESKTOP.includes('Unity')) {
if (process.platform !== 'darwin') { process.env.XDG_CURRENT_DESKTOP = 'Unity'
app.quit() }
} }
})
#onQuit() {
app.on('will-quit', () => { app.on('window-all-closed', () => {
UpDownTaskQueue.getInstance().persist() if (process.platform !== 'darwin') {
clearTempFolder() app.quit()
globalShortcut.unregisterAll() }
bus.removeAllListeners() })
server.shutdown()
webServer.stop() app.on('will-quit', () => {
stopFileServer() UpDownTaskQueue.getInstance().persist()
MemoryMonitor.stop() clearTempFolder()
}) globalShortcut.unregisterAll()
// Exit cleanly on request from parent process in development mode. bus.removeAllListeners()
if (isDevelopment) { server.shutdown()
if (process.platform === 'win32') { webServer.stop()
process.on('message', data => { stopFileServer()
if (data === 'graceful-exit') { MemoryMonitor.stop()
app.quit() })
} // Exit cleanly on request from parent process in development mode.
}) if (isDevelopment) {
} else { if (process.platform === 'win32') {
process.on('SIGTERM', () => { process.on('message', data => {
app.quit() if (data === 'graceful-exit') {
}) app.quit()
} }
} })
} } else {
process.on('SIGTERM', () => {
async launchApp () { app.quit()
const gotTheLock = app.requestSingleInstanceLock() })
if (!gotTheLock) { }
app.quit() }
} else { }
await this.#beforeReady()
this.#onReady() async launchApp() {
this.#onRunning() const gotTheLock = app.requestSingleInstanceLock()
this.#onQuit() if (!gotTheLock) {
} app.quit()
} } else {
} await this.#beforeReady()
this.#onReady()
const lifeCycle = new LifeCycle() this.#onRunning()
this.#onQuit()
export { lifeCycle } }
}
}
const lifeCycle = new LifeCycle()
export { lifeCycle }

View File

@@ -28,7 +28,7 @@ class AliyunApi {
timeOut = 30000 timeOut = 30000
logger: ManageLogger logger: ManageLogger
constructor (accessKeyId: string, accessKeySecret: string, logger: ManageLogger) { constructor(accessKeyId: string, accessKeySecret: string, logger: ManageLogger) {
this.ctx = new OSS({ this.ctx = new OSS({
accessKeyId, accessKeyId,
accessKeySecret, accessKeySecret,
@@ -39,7 +39,7 @@ class AliyunApi {
this.logger = logger this.logger = logger
} }
formatFolder (item: string, slicedPrefix: string, urlPrefix: string): any { formatFolder(item: string, slicedPrefix: string, urlPrefix: string): any {
return { return {
key: item, key: item,
url: `${urlPrefix}/${item}`, url: `${urlPrefix}/${item}`,
@@ -54,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, '') const fileName = item.name.replace(slicedPrefix, '')
return { return {
...item, ...item,
@@ -71,7 +71,7 @@ class AliyunApi {
} }
} }
getCanonicalizedOSSHeaders (headers: IStringKeyMap) { getCanonicalizedOSSHeaders(headers: IStringKeyMap) {
const lowerCaseHeaders = Object.keys(headers).reduce((acc, key) => { const lowerCaseHeaders = Object.keys(headers).reduce((acc, key) => {
acc[key.toLowerCase()] = headers[key] acc[key.toLowerCase()] = headers[key]
return acc return acc
@@ -84,7 +84,7 @@ class AliyunApi {
return canonicalizedOSSHeaders return canonicalizedOSSHeaders
} }
authorization ( authorization(
method: string, method: string,
canonicalizedResource: string, canonicalizedResource: string,
headers: IStringKeyMap, headers: IStringKeyMap,
@@ -96,7 +96,7 @@ class AliyunApi {
return `OSS ${this.accessKeyId}:${hmacSha1Base64(this.accessKeySecret, stringToSign)}` return `OSS ${this.accessKeyId}:${hmacSha1Base64(this.accessKeySecret, stringToSign)}`
} }
getNewCtx (region: string, bucket: string) { getNewCtx(region: string, bucket: string) {
return new OSS({ return new OSS({
accessKeyId: this.accessKeyId, accessKeyId: this.accessKeyId,
accessKeySecret: this.accessKeySecret, accessKeySecret: this.accessKeySecret,
@@ -109,7 +109,7 @@ class AliyunApi {
/** /**
* 获取存储桶列表 * 获取存储桶列表
*/ */
async getBucketList (): Promise<any> { async getBucketList(): Promise<any> {
const getBuckets = async (marker?: string) => { const getBuckets = async (marker?: string) => {
const res = (await this.ctx.listBuckets({ const res = (await this.ctx.listBuckets({
marker, marker,
@@ -143,7 +143,7 @@ class AliyunApi {
/** /**
* 获取自定义域名 * 获取自定义域名
*/ */
async getBucketDomain (param: IStringKeyMap): Promise<any> { async getBucketDomain(param: IStringKeyMap): Promise<any> {
const headers = { const headers = {
Date: new Date().toUTCString() Date: new Date().toUTCString()
} }
@@ -186,7 +186,7 @@ class AliyunApi {
* @description * @description
* acl: private | publicRead | publicReadWrite * acl: private | publicRead | publicReadWrite
*/ */
async createBucket (configMap: IStringKeyMap): Promise<boolean> { async createBucket(configMap: IStringKeyMap): Promise<boolean> {
const client = new OSS({ const client = new OSS({
accessKeyId: this.accessKeyId, accessKeyId: this.accessKeyId,
accessKeySecret: this.accessKeySecret, accessKeySecret: this.accessKeySecret,
@@ -207,7 +207,7 @@ class AliyunApi {
return res?.res?.status === 200 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 window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { const {
bucketName: bucket, bucketName: bucket,
@@ -262,7 +262,7 @@ class AliyunApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList) ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
} }
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { const {
bucketName: bucket, bucketName: bucket,
@@ -336,7 +336,7 @@ class AliyunApi {
* customUrl: string * customUrl: string
* } * }
*/ */
async getBucketFileList (configMap: IStringKeyMap): Promise<any> { async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { const {
bucketName: bucket, bucketName: bucket,
bucketConfig: { Location: region }, bucketConfig: { Location: region },
@@ -393,7 +393,7 @@ class AliyunApi {
* newKey: string * newKey: string
* } * }
*/ */
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> { async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, oldKey, newKey } = configMap const { bucketName, region, oldKey, newKey } = configMap
const client = this.getNewCtx(region, bucketName) const client = this.getNewCtx(region, bucketName)
const copyRes = (await client.copy(newKey, oldKey)) as any const copyRes = (await client.copy(newKey, oldKey)) as any
@@ -413,7 +413,7 @@ class AliyunApi {
* key: string * key: string
* } * }
*/ */
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
const client = this.getNewCtx(region, bucketName) const client = this.getNewCtx(region, bucketName)
const res = (await client.delete(key)) as any const res = (await client.delete(key)) as any
@@ -424,7 +424,7 @@ class AliyunApi {
* 删除文件夹 * 删除文件夹
* @param configMap * @param configMap
*/ */
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
const client = this.getNewCtx(region, bucketName) const client = this.getNewCtx(region, bucketName)
let marker let marker
@@ -486,7 +486,7 @@ class AliyunApi {
* customUrl: string * customUrl: string
* } * }
*/ */
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> { async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName, region, key, expires, customUrl } = configMap const { bucketName, region, key, expires, customUrl } = configMap
const client = this.getNewCtx(region, bucketName) const client = this.getNewCtx(region, bucketName)
const res = client.signatureUrl(key, { const res = client.signatureUrl(key, {
@@ -499,7 +499,7 @@ class AliyunApi {
* 上传文件 * 上传文件
* @param configMap * @param configMap
*/ */
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap const { fileArray } = configMap
// fileArray = [{ // fileArray = [{
// bucketName: string, // bucketName: string,
@@ -586,7 +586,7 @@ class AliyunApi {
* 新建文件夹 * 新建文件夹
* @param configMap * @param configMap
*/ */
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
const client = this.getNewCtx(region, bucketName) const client = this.getNewCtx(region, bucketName)
const res = (await client.put(key, Buffer.from(''))) as any const res = (await client.put(key, Buffer.from(''))) as any
@@ -597,7 +597,7 @@ class AliyunApi {
* 下载文件 * 下载文件
* @param configMap * @param configMap
*/ */
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
const promises = [] as any const promises = [] as any

View File

@@ -29,7 +29,7 @@ class GithubApi {
baseUrl = 'https://api.github.com' 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.logger = logger
this.token = token.startsWith('Bearer ') ? token : `Bearer ${token}`.trim() this.token = token.startsWith('Bearer ') ? token : `Bearer ${token}`.trim()
this.username = username this.username = username
@@ -41,7 +41,7 @@ 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}/` const key = `${slicedPrefix ? `${slicedPrefix}/` : ''}${item.path}/`
let rawUrl = '' let rawUrl = ''
const placeholders = ['{username}', '{repo}', '{branch}', '{path}'] const placeholders = ['{username}', '{repo}', '{branch}', '{path}']
@@ -78,7 +78,7 @@ 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 = '' let rawUrl = ''
const placeholders = ['{username}', '{repo}', '{branch}', '{path}'] const placeholders = ['{username}', '{repo}', '{branch}', '{path}']
const key = slicedPrefix === '' ? item.path : `${slicedPrefix}/${item.path}` const key = slicedPrefix === '' ? item.path : `${slicedPrefix}/${item.path}`
@@ -119,7 +119,7 @@ class GithubApi {
/** /**
* get repo list * get repo list
*/ */
async getBucketList (): Promise<any> { async getBucketList(): Promise<any> {
let initPage = 1 let initPage = 1
let res let res
const result = [] as any[] const result = [] as any[]
@@ -156,7 +156,7 @@ class GithubApi {
/** /**
* 获取branch列表 * 获取branch列表
*/ */
async getBucketDomain (param: IStringKeyMap): Promise<any> { async getBucketDomain(param: IStringKeyMap): Promise<any> {
const { bucketName: repo } = param const { bucketName: repo } = param
let initPage = 1 let initPage = 1
let res let res
@@ -184,7 +184,7 @@ class GithubApi {
return result return result
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> { async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap
const slicedPrefix = prefix.replace(/(^\/+|\/+$)/g, '') const slicedPrefix = prefix.replace(/(^\/+|\/+$)/g, '')
@@ -235,7 +235,7 @@ class GithubApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList) ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
} }
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap
const slicedPrefix = prefix.replace(/(^\/+|\/+$)/g, '') const slicedPrefix = prefix.replace(/(^\/+|\/+$)/g, '')
@@ -285,7 +285,7 @@ class GithubApi {
* key: string * key: string
* } * }
*/ */
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName: repo, githubBranch: branch, key, DeleteHash: sha } = configMap const { bucketName: repo, githubBranch: branch, key, DeleteHash: sha } = configMap
const body = { const body = {
message: 'deleted by PicList', message: 'deleted by PicList',
@@ -303,7 +303,7 @@ class GithubApi {
* create a new tree to delete a folder * create a new tree to delete a folder
* @param configMap * @param configMap
*/ */
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName: repo, githubBranch: branch, key } = configMap const { bucketName: repo, githubBranch: branch, key } = configMap
// get sha of the branch // get sha of the branch
const refRes = (await got( const refRes = (await got(
@@ -412,7 +412,7 @@ class GithubApi {
* customUrl: string * customUrl: string
* } * }
*/ */
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> { async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName: repo, customUrl: branch, key, rawUrl, githubPrivate: isPrivate } = configMap const { bucketName: repo, customUrl: branch, key, rawUrl, githubPrivate: isPrivate } = configMap
if (!isPrivate) return rawUrl if (!isPrivate) return rawUrl
const res = (await got( const res = (await got(
@@ -436,7 +436,7 @@ class GithubApi {
* 新建文件夹 * 新建文件夹
* @param configMap * @param configMap
*/ */
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName: repo, githubBranch: branch, key } = configMap const { bucketName: repo, githubBranch: branch, key } = configMap
const newFileKey = `${trimPath(key)}/.gitkeep` const newFileKey = `${trimPath(key)}/.gitkeep`
const base64Content = Buffer.from('created by PicList').toString('base64') const base64Content = Buffer.from('created by PicList').toString('base64')
@@ -456,7 +456,7 @@ class GithubApi {
* 上传文件 * 上传文件
* @param configMap * @param configMap
*/ */
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
fileArray.forEach((item: any) => { fileArray.forEach((item: any) => {
@@ -505,7 +505,7 @@ class GithubApi {
* 下载文件 * 下载文件
* @param configMap * @param configMap
*/ */
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
const promises = [] as any const promises = [] as any

View File

@@ -31,7 +31,7 @@ class ImgurApi {
idHeaders: any idHeaders: any
baseUrl = 'https://api.imgur.com/3' 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.userName = userName
this.accessToken = accessToken.startsWith('Bearer ') ? accessToken : `Bearer ${accessToken}` this.accessToken = accessToken.startsWith('Bearer ') ? accessToken : `Bearer ${accessToken}`
this.proxy = proxy this.proxy = proxy
@@ -42,7 +42,7 @@ class ImgurApi {
} }
} }
formatFile (item: any) { formatFile(item: any) {
const fileName = path.basename(item.link) const fileName = path.basename(item.link)
const isImg = isImage(fileName) const isImg = isImage(fileName)
return { return {
@@ -64,7 +64,7 @@ class ImgurApi {
/** /**
* get repo list * get repo list
*/ */
async getBucketList (): Promise<any> { async getBucketList(): Promise<any> {
let initPage = 0 let initPage = 0
let res let res
const result = [] as any[] const result = [] as any[]
@@ -93,7 +93,7 @@ class ImgurApi {
return finalResult return finalResult
} }
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { const {
bucketConfig: { Location: albumHash }, bucketConfig: { Location: albumHash },
@@ -153,7 +153,7 @@ class ImgurApi {
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
} }
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { DeleteHash: deleteHash } = configMap const { DeleteHash: deleteHash } = configMap
const res = (await got( const res = (await got(
`${this.baseUrl}/account/${this.userName}/image/${deleteHash}`, `${this.baseUrl}/account/${this.userName}/image/${deleteHash}`,
@@ -166,7 +166,7 @@ class ImgurApi {
* 上传文件 * 上传文件
* @param configMap * @param configMap
*/ */
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
fileArray.forEach((item: any) => { fileArray.forEach((item: any) => {
@@ -226,7 +226,7 @@ class ImgurApi {
* 下载文件 * 下载文件
* @param configMap * @param configMap
*/ */
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
const promises = [] as any const promises = [] as any

View File

@@ -17,7 +17,7 @@ class LocalApi {
logger: ManageLogger logger: ManageLogger
isWindows: boolean isWindows: boolean
constructor (logger: ManageLogger) { constructor(logger: ManageLogger) {
this.logger = logger this.logger = logger
this.isWindows = process.platform === 'win32' this.isWindows = process.platform === 'win32'
} }
@@ -25,12 +25,12 @@ class LocalApi {
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 风格 // windows 系统下将路径转换为 unix 风格
transPathToUnix (filePath: string | undefined) { transPathToUnix(filePath: string | undefined) {
if (!filePath) return '' if (!filePath) return ''
return this.isWindows ? filePath.split(path.sep).join(path.posix.sep) : filePath.replace(/^\/+/, '') return this.isWindows ? filePath.split(path.sep).join(path.posix.sep) : filePath.replace(/^\/+/, '')
} }
transBack (filePath: string | undefined) { transBack(filePath: string | undefined) {
if (!filePath) return '' if (!filePath) return ''
return this.isWindows return this.isWindows
? filePath ? filePath
@@ -40,7 +40,7 @@ class LocalApi {
: `/${filePath.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(/\/+$/, '/') const key = `${this.transPathToUnix(filePath)}/`.replace(/\/+$/, '/')
return { return {
...item, ...item,
@@ -57,7 +57,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) const key = isDownload ? filePath : this.transPathToUnix(filePath)
return { return {
...item, ...item,
@@ -74,7 +74,7 @@ class LocalApi {
} }
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> { async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl = '', cancelToken } = configMap const { prefix, customUrl = '', cancelToken } = configMap
const urlPrefix = customUrl.replace(/\/+$/, '') const urlPrefix = customUrl.replace(/\/+$/, '')
@@ -114,7 +114,7 @@ class LocalApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList) ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
} }
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { customUrl = '', cancelToken, baseDir } = configMap const { customUrl = '', cancelToken, baseDir } = configMap
let prefix = configMap.prefix let prefix = configMap.prefix
@@ -170,7 +170,7 @@ class LocalApi {
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
} }
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> { async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { oldKey, newKey } = configMap const { oldKey, newKey } = configMap
let result = false let result = false
try { try {
@@ -182,7 +182,7 @@ class LocalApi {
return result return result
} }
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
let result = false let result = false
try { try {
@@ -194,7 +194,7 @@ class LocalApi {
return result return result
} }
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
let result = false let result = false
try { try {
@@ -208,7 +208,7 @@ class LocalApi {
return result return result
} }
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) { for (const item of fileArray) {
@@ -250,7 +250,7 @@ class LocalApi {
return true return true
} }
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
let result = false let result = false
try { try {
@@ -264,7 +264,7 @@ class LocalApi {
return result return result
} }
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray } = configMap const { downloadPath, fileArray } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) { for (const item of fileArray) {

View File

@@ -33,14 +33,14 @@ class QiniuApi {
getBucketDomain: 'https://uc.qiniuapi.com/v2/domains' 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.mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
this.accessKey = accessKey this.accessKey = accessKey
this.secretKey = secretKey this.secretKey = secretKey
this.logger = logger this.logger = logger
} }
formatFolder (item: string, slicedPrefix: string, urlPrefix: string) { formatFolder(item: string, slicedPrefix: string, urlPrefix: string) {
return { return {
Key: item, Key: item,
key: item, key: item,
@@ -54,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, '') const fileName = item.key.replace(slicedPrefix, '')
return { return {
...item, ...item,
@@ -69,7 +69,7 @@ class QiniuApi {
} }
} }
authorization ( authorization(
method: string, method: string,
urlPath: string, urlPath: string,
host: string, host: string,
@@ -98,7 +98,7 @@ class QiniuApi {
/** /**
* 获取存储桶列表 * 获取存储桶列表
*/ */
async getBucketList (): Promise<any> { async getBucketList(): Promise<any> {
const host = this.hostList.getBucketList const host = this.hostList.getBucketList
const authorization = qiniu.util.generateAccessToken(this.mac, host, undefined) const authorization = qiniu.util.generateAccessToken(this.mac, host, undefined)
const res = await axios.get(host, { const res = await axios.get(host, {
@@ -128,7 +128,7 @@ class QiniuApi {
/** /**
* 获取存储桶详细信息 * 获取存储桶详细信息
*/ */
async getBucketInfo (param: IStringKeyMap): Promise<any> { async getBucketInfo(param: IStringKeyMap): Promise<any> {
const { bucketName } = param const { bucketName } = param
const urlPath = `/v2/bucketInfo?bucket=${bucketName}&fs=true` const urlPath = `/v2/bucketInfo?bucket=${bucketName}&fs=true`
const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json') const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
@@ -160,7 +160,7 @@ class QiniuApi {
/** /**
* 获取自定义域名 * 获取自定义域名
*/ */
async getBucketDomain (param: IStringKeyMap): Promise<any> { async getBucketDomain(param: IStringKeyMap): Promise<any> {
const { bucketName } = param const { bucketName } = param
const host = this.hostList.getBucketDomain const host = this.hostList.getBucketDomain
const authorization = qiniu.util.generateAccessToken(this.mac, `${host}?tbl=${bucketName}`, undefined) const authorization = qiniu.util.generateAccessToken(this.mac, `${host}?tbl=${bucketName}`, undefined)
@@ -180,7 +180,7 @@ class QiniuApi {
/** /**
* 修改存储桶权限 * 修改存储桶权限
*/ */
async setBucketAclPolicy (param: IStringKeyMap): Promise<boolean> { async setBucketAclPolicy(param: IStringKeyMap): Promise<boolean> {
// 0: 公开访问 1: 私有访问 // 0: 公开访问 1: 私有访问
const { bucketName } = param const { bucketName } = param
let { isPrivate } = param let { isPrivate } = param
@@ -213,7 +213,7 @@ class QiniuApi {
* acl: boolean // 是否公开访问 * acl: boolean // 是否公开访问
* } * }
*/ */
async createBucket (configMap: IStringKeyMap): Promise<boolean> { async createBucket(configMap: IStringKeyMap): Promise<boolean> {
const { BucketName, region, acl } = configMap const { BucketName, region, acl } = configMap
const urlPath = `/mkbucketv3/${BucketName}/region/${region}` const urlPath = `/mkbucketv3/${BucketName}/region/${region}`
const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json') const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
@@ -235,7 +235,7 @@ class QiniuApi {
: false : false
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> { async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap
let marker = undefined as any let marker = undefined as any
@@ -297,7 +297,7 @@ class QiniuApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList) ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
} }
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap
let marker = undefined as any let marker = undefined as any
@@ -380,7 +380,7 @@ class QiniuApi {
* customUrl: string * customUrl: string
* } * }
*/ */
async getBucketFileList (configMap: IStringKeyMap): Promise<any> { async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, prefix, marker, itemsPerPage, customUrl: urlPrefix } = configMap const { bucketName: bucket, prefix, marker, itemsPerPage, customUrl: urlPrefix } = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
const config = new qiniu.conf.Config() const config = new qiniu.conf.Config()
@@ -440,7 +440,7 @@ class QiniuApi {
* key: string * key: string
* } * }
*/ */
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, key } = configMap const { bucketName, key } = configMap
const config = new qiniu.conf.Config() const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config) const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
@@ -463,7 +463,7 @@ class QiniuApi {
* 删除文件夹 * 删除文件夹
* @param configMap * @param configMap
*/ */
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, key } = configMap const { bucketName, key } = configMap
const config = new qiniu.conf.Config() const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config) const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
@@ -535,7 +535,7 @@ class QiniuApi {
* newKey: string * newKey: string
* } * }
*/ */
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> { async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, oldKey, newKey } = configMap const { bucketName, oldKey, newKey } = configMap
const config = new qiniu.conf.Config() const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config) const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
@@ -574,7 +574,7 @@ class QiniuApi {
* customUrl: string * customUrl: string
* } * }
*/ */
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> { async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { key, expires, customUrl } = configMap const { key, expires, customUrl } = configMap
const config = new qiniu.conf.Config() const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config) const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
@@ -588,7 +588,7 @@ class QiniuApi {
* 上传文件 * 上传文件
* @param configMap * @param configMap
*/ */
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
fileArray.forEach((item: any) => { fileArray.forEach((item: any) => {
@@ -667,7 +667,7 @@ class QiniuApi {
* 新建文件夹 * 新建文件夹
* @param configMap * @param configMap
*/ */
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, key } = configMap const { bucketName, key } = configMap
const putPolicy = new qiniu.rs.PutPolicy({ const putPolicy = new qiniu.rs.PutPolicy({
scope: `${bucketName}:${key}` scope: `${bucketName}:${key}`
@@ -694,7 +694,7 @@ class QiniuApi {
* 下载文件 * 下载文件
* @param configMap * @param configMap
*/ */
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
const promises = [] as any const promises = [] as any

View File

@@ -46,7 +46,7 @@ class S3plistApi {
secretAccessKey: string secretAccessKey: string
bucketName: string bucketName: string
constructor ( constructor(
accessKeyId: string, accessKeyId: string,
secretAccessKey: string, secretAccessKey: string,
endpoint: string | undefined, endpoint: string | undefined,
@@ -75,7 +75,7 @@ class S3plistApi {
this.proxy = formatHttpProxy(proxy, 'string') as string | undefined this.proxy = formatHttpProxy(proxy, 'string') as string | undefined
} }
async getDogeCloudToken () { async getDogeCloudToken() {
if (!this.dogeCloudSupport) return if (!this.dogeCloudSupport) return
const token = (await getTempToken(this.accessKeyId, this.secretAccessKey)) as DogecloudToken const token = (await getTempToken(this.accessKeyId, this.secretAccessKey)) as DogecloudToken
if (Object.keys(token).length === 0) { if (Object.keys(token).length === 0) {
@@ -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 agent = getAgent(proxy, sslEnabled)
const commonOptions: AgentOptions = { const commonOptions: AgentOptions = {
keepAlive: true, keepAlive: true,
@@ -98,26 +98,26 @@ class S3plistApi {
const extraOptions = sslEnabled ? { rejectUnauthorized: false } : {} const extraOptions = sslEnabled ? { rejectUnauthorized: false } : {}
return sslEnabled return sslEnabled
? new NodeHttpHandler({ ? new NodeHttpHandler({
httpsAgent: agent.https httpsAgent: agent.https
? agent.https ? agent.https
: new https.Agent({ : new https.Agent({
...commonOptions, ...commonOptions,
...extraOptions ...extraOptions
}) })
}) })
: new NodeHttpHandler({ : new NodeHttpHandler({
httpAgent: agent.http httpAgent: agent.http
? agent.http ? agent.http
: new http.Agent({ : new http.Agent({
...commonOptions, ...commonOptions,
...extraOptions ...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 { return {
Key: item.Prefix, Key: item.Prefix,
url: `${urlPrefix}/${item.Prefix}`, url: `${urlPrefix}/${item.Prefix}`,
@@ -132,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, '') const fileName = item.Key?.replace(slicedPrefix, '')
return { return {
...item, ...item,
@@ -148,7 +148,7 @@ class S3plistApi {
} }
} }
async putPublicAccess (bucketName: string, client: S3Client) { async putPublicAccess(bucketName: string, client: S3Client) {
const input = { const input = {
Bucket: bucketName, Bucket: bucketName,
PublicAccessBlockConfiguration: { PublicAccessBlockConfiguration: {
@@ -175,11 +175,11 @@ class S3plistApi {
* acl: string * acl: string
* } * }
*/ */
async createBucket (configMap: IStringKeyMap): Promise<boolean> { async createBucket(configMap: IStringKeyMap): Promise<boolean> {
const { BucketName, region, acl, endpoint } = configMap const { BucketName, region, acl, endpoint } = configMap
try { try {
await this.getDogeCloudToken() await this.getDogeCloudToken()
const options = ({ ...this.baseOptions }) as S3ClientConfig const options = { ...this.baseOptions } as S3ClientConfig
options.region = String(region) || 'us-east-1' options.region = String(region) || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
const command = new ListBucketsCommand({}) const command = new ListBucketsCommand({})
@@ -234,7 +234,7 @@ class S3plistApi {
/** /**
* 获取存储桶列表 * 获取存储桶列表
*/ */
async getBucketList (): Promise<any> { async getBucketList(): Promise<any> {
if (this.dogeCloudSupport) { if (this.dogeCloudSupport) {
try { try {
const res = await dogecloudApi('/oss/bucket/list.json', {}, false, this.accessKeyId, this.secretAccessKey) const res = await dogecloudApi('/oss/bucket/list.json', {}, false, this.accessKeyId, this.secretAccessKey)
@@ -255,7 +255,7 @@ class S3plistApi {
} }
return [] return []
} }
const options = ({ ...this.baseOptions }) as S3ClientConfig const options = { ...this.baseOptions } as S3ClientConfig
const result: IStringKeyMap[] = [] const result: IStringKeyMap[] = []
const endpoint = (options.endpoint as string) || '' const endpoint = (options.endpoint as string) || ''
options.region = endpoint.includes('cloudflarestorage') ? 'auto' : 'us-east-1' options.region = endpoint.includes('cloudflarestorage') ? 'auto' : 'us-east-1'
@@ -305,7 +305,7 @@ class S3plistApi {
return result return result
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> { async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { const {
bucketName: bucket, bucketName: bucket,
@@ -331,7 +331,7 @@ class S3plistApi {
} }
try { try {
do { do {
const options = ({ ...this.baseOptions }) as S3ClientConfig const options = { ...this.baseOptions } as S3ClientConfig
options.region = String(region) || 'us-east-1' options.region = String(region) || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
const command = new ListObjectsV2Command({ const command = new ListObjectsV2Command({
@@ -369,7 +369,7 @@ class S3plistApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList) ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
} }
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { const {
bucketName: bucket, bucketName: bucket,
@@ -396,7 +396,7 @@ class S3plistApi {
try { try {
await this.getDogeCloudToken() await this.getDogeCloudToken()
do { do {
const options = ({ ...this.baseOptions }) as S3ClientConfig const options = { ...this.baseOptions } as S3ClientConfig
options.region = String(region) || 'us-east-1' options.region = String(region) || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
const command = new ListObjectsV2Command({ const command = new ListObjectsV2Command({
@@ -439,7 +439,7 @@ class S3plistApi {
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
} }
async getBucketFileList (configMap: IStringKeyMap): Promise<any> { async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { const {
bucketName: bucket, bucketName: bucket,
bucketConfig: { Location: region }, bucketConfig: { Location: region },
@@ -457,10 +457,10 @@ class S3plistApi {
} }
try { try {
await this.getDogeCloudToken() await this.getDogeCloudToken()
const options = ({ const options = {
...this.baseOptions,
...this.baseOptions, region: String(region) || 'us-east-1' region: String(region) || 'us-east-1'
}) as S3ClientConfig } as S3ClientConfig
const client = new S3Client(options) const client = new S3Client(options)
const command = new ListObjectsV2Command({ const command = new ListObjectsV2Command({
Bucket: bucket, Bucket: bucket,
@@ -495,15 +495,15 @@ class S3plistApi {
* newKey: string * newKey: string
* } * }
*/ */
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> { async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, oldKey, newKey } = configMap const { bucketName, region, oldKey, newKey } = configMap
let result = false let result = false
try { try {
await this.getDogeCloudToken() await this.getDogeCloudToken()
const options = ({ const options = {
...this.baseOptions,
...this.baseOptions, region: String(region) || 'us-east-1' region: String(region) || 'us-east-1'
}) as S3ClientConfig } as S3ClientConfig
const client = new S3Client(options) const client = new S3Client(options)
const command = new CopyObjectCommand({ const command = new CopyObjectCommand({
Bucket: bucketName, Bucket: bucketName,
@@ -540,12 +540,12 @@ class S3plistApi {
* key: string * key: string
* } * }
*/ */
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
let result = false let result = false
try { try {
await this.getDogeCloudToken() await this.getDogeCloudToken()
const options = ({ ...this.baseOptions }) as S3ClientConfig const options = { ...this.baseOptions } as S3ClientConfig
options.region = String(region) || 'us-east-1' options.region = String(region) || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
const command = new DeleteObjectCommand({ const command = new DeleteObjectCommand({
@@ -568,7 +568,7 @@ class S3plistApi {
* 删除文件夹 * 删除文件夹
* @param configMap * @param configMap
*/ */
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
let marker let marker
let result = false let result = false
@@ -581,7 +581,7 @@ class S3plistApi {
try { try {
await this.getDogeCloudToken() await this.getDogeCloudToken()
do { do {
const options = ({ ...this.baseOptions }) as S3ClientConfig const options = { ...this.baseOptions } as S3ClientConfig
options.region = String(region) || 'us-east-1' options.region = String(region) || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
const command = new ListObjectsV2Command({ const command = new ListObjectsV2Command({
@@ -616,7 +616,7 @@ class S3plistApi {
} }
if (allFileList.Contents.length > 0) { if (allFileList.Contents.length > 0) {
const cycle = Math.ceil(allFileList.Contents.length / 1000) const cycle = Math.ceil(allFileList.Contents.length / 1000)
const options = ({ ...this.baseOptions }) as S3ClientConfig const options = { ...this.baseOptions } as S3ClientConfig
options.region = String(region) || 'us-east-1' options.region = String(region) || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
for (let i = 0; i < cycle; i++) { for (let i = 0; i < cycle; i++) {
@@ -657,11 +657,11 @@ class S3plistApi {
* customUrl: string * customUrl: string
* } * }
*/ */
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> { async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName, region, key, expires } = configMap const { bucketName, region, key, expires } = configMap
try { try {
await this.getDogeCloudToken() await this.getDogeCloudToken()
const options = ({ ...this.baseOptions }) as S3ClientConfig const options = { ...this.baseOptions } as S3ClientConfig
options.region = String(region) || 'us-east-1' options.region = String(region) || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
const signedUrl = await getSignedUrl( const signedUrl = await getSignedUrl(
@@ -685,12 +685,12 @@ class S3plistApi {
* 新建文件夹 * 新建文件夹
* @param configMap * @param configMap
*/ */
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
let result = false let result = false
try { try {
await this.getDogeCloudToken() await this.getDogeCloudToken()
const options = ({ ...this.baseOptions }) as S3ClientConfig const options = { ...this.baseOptions } as S3ClientConfig
options.region = String(region) || 'us-east-1' options.region = String(region) || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
const command = new PutObjectCommand({ const command = new PutObjectCommand({
@@ -713,7 +713,7 @@ class S3plistApi {
* upload file * upload file
* @param configMap * @param configMap
*/ */
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap const { fileArray } = configMap
// fileArray = [{ // fileArray = [{
// bucketName: string, // bucketName: string,
@@ -764,7 +764,7 @@ class S3plistApi {
}) })
continue continue
} }
const options = ({ ...this.baseOptions }) as S3ClientConfig const options = { ...this.baseOptions } as S3ClientConfig
options.region = String(region) || 'us-east-1' options.region = String(region) || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
const fileStream = fs.createReadStream(filePath) const fileStream = fs.createReadStream(filePath)
@@ -825,7 +825,7 @@ class S3plistApi {
* 下载文件 * 下载文件
* @param configMap * @param configMap
*/ */
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
const promises = [] as any const promises = [] as any

View File

@@ -44,7 +44,7 @@ class SftpApi {
passphrase: string passphrase: string
} }
constructor ( constructor(
host: string, host: string,
port: Undefinable<number>, port: Undefinable<number>,
username: Undefinable<string>, username: Undefinable<string>,
@@ -94,7 +94,7 @@ class SftpApi {
return `0${result}` return `0${result}`
} }
formatFolder (item: listDirResult, urlPrefix: string, isWebPath = false) { formatFolder(item: listDirResult, urlPrefix: string, isWebPath = false) {
const key = item.key const key = item.key
let url: string let url: string
if (isWebPath) { if (isWebPath) {
@@ -121,7 +121,7 @@ class SftpApi {
} }
} }
formatFile (item: listDirResult, urlPrefix: string, isWebPath = false) { formatFile(item: listDirResult, urlPrefix: string, isWebPath = false) {
const key = item.key const key = item.key
return { return {
...item, ...item,
@@ -151,7 +151,7 @@ class SftpApi {
} }
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> { async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken } = configMap const { prefix, customUrl, cancelToken } = configMap
const urlPrefix = customUrl || `${this.host}:${this.port}` const urlPrefix = customUrl || `${this.host}:${this.port}`
@@ -191,7 +191,7 @@ class SftpApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList) ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
} }
formatLSResult (res: string, cwd: string): listDirResult[] { formatLSResult(res: string, cwd: string): listDirResult[] {
const result = [] as listDirResult[] const result = [] as listDirResult[]
const resArray = res.trim().split('\n') const resArray = res.trim().split('\n')
resArray.slice(resArray[0].startsWith('total') ? 1 : 0).forEach((item: string) => { resArray.slice(resArray[0].startsWith('total') ? 1 : 0).forEach((item: string) => {
@@ -217,7 +217,7 @@ class SftpApi {
return result return result
} }
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken, baseDir } = configMap const { prefix, customUrl, cancelToken, baseDir } = configMap
let urlPrefix = customUrl || `${this.host}:${this.port}` let urlPrefix = customUrl || `${this.host}:${this.port}`
@@ -276,7 +276,7 @@ class SftpApi {
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
} }
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> { async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { oldKey, newKey } = configMap const { oldKey, newKey } = configMap
let result = false let result = false
try { try {
@@ -290,7 +290,7 @@ class SftpApi {
return result return result
} }
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
let result = false let result = false
try { try {
@@ -304,7 +304,7 @@ class SftpApi {
return result return result
} }
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
let result = false let result = false
try { try {
@@ -321,7 +321,7 @@ class SftpApi {
return result return result
} }
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) { for (const item of fileArray) {
@@ -376,7 +376,7 @@ class SftpApi {
return true return true
} }
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
let result = false let result = false
try { try {
@@ -390,7 +390,7 @@ class SftpApi {
return result return result
} }
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray } = configMap const { downloadPath, fileArray } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) { for (const item of fileArray) {

View File

@@ -21,7 +21,7 @@ class SmmsApi {
logger: ManageLogger logger: ManageLogger
timeout = 30000 timeout = 30000
constructor (token: string, logger: ManageLogger) { constructor(token: string, logger: ManageLogger) {
this.token = token this.token = token
this.axiosInstance = axios.create({ this.axiosInstance = axios.create({
baseURL: this.baseUrl, baseURL: this.baseUrl,
@@ -37,7 +37,7 @@ class SmmsApi {
this.logger = logger this.logger = logger
} }
formatFile (item: any) { formatFile(item: any) {
return { return {
...item, ...item,
Key: item.path, Key: item.path,
@@ -54,7 +54,7 @@ class SmmsApi {
} }
} }
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { cancelToken } = configMap const { cancelToken } = configMap
let marker = 1 let marker = 1
@@ -123,7 +123,7 @@ class SmmsApi {
* customUrl: string * customUrl: string
* } * }
*/ */
async getBucketFileList ({ currentPage }: IStringKeyMap): Promise<any> { async getBucketFileList({ currentPage }: IStringKeyMap): Promise<any> {
const result = { const result = {
fullList: [] as any, fullList: [] as any,
isTruncated: false, isTruncated: false,
@@ -162,7 +162,7 @@ class SmmsApi {
* DeleteHash: string * DeleteHash: string
* } * }
*/ */
async deleteBucketFile ({ DeleteHash }: IStringKeyMap): Promise<boolean> { async deleteBucketFile({ DeleteHash }: IStringKeyMap): Promise<boolean> {
const res = await this.axiosInstance(`/delete/${DeleteHash}`, { const res = await this.axiosInstance(`/delete/${DeleteHash}`, {
method: 'GET', method: 'GET',
params: { params: {
@@ -177,7 +177,7 @@ class SmmsApi {
* 上传文件 * 上传文件
* @param configMap * @param configMap
*/ */
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) { for (const item of fileArray) {
@@ -214,7 +214,7 @@ class SmmsApi {
* 下载文件 * 下载文件
* @param configMap * @param configMap
*/ */
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
const promises = [] as any const promises = [] as any

View File

@@ -17,7 +17,7 @@ class TcyunApi {
ctx: COS ctx: COS
logger: ManageLogger logger: ManageLogger
constructor (secretId: string, secretKey: string, logger: ManageLogger) { constructor(secretId: string, secretKey: string, logger: ManageLogger) {
this.ctx = new COS({ this.ctx = new COS({
SecretId: secretId, SecretId: secretId,
SecretKey: secretKey SecretKey: secretKey
@@ -25,7 +25,7 @@ class TcyunApi {
this.logger = logger this.logger = logger
} }
formatFolder (item: { Prefix: string }, slicedPrefix: string, urlPrefix: string) { formatFolder(item: { Prefix: string }, slicedPrefix: string, urlPrefix: string) {
return { return {
...item, ...item,
key: item.Prefix, key: item.Prefix,
@@ -40,7 +40,7 @@ class TcyunApi {
} }
} }
formatFile (item: COS.CosObject, slicedPrefix: string, urlPrefix: string): any { formatFile(item: COS.CosObject, slicedPrefix: string, urlPrefix: string): any {
return { return {
...item, ...item,
key: item.Key, key: item.Key,
@@ -58,7 +58,7 @@ class TcyunApi {
/** /**
* 获取存储桶列表 * 获取存储桶列表
*/ */
async getBucketList (): Promise<any> { async getBucketList(): Promise<any> {
const res = await this.ctx.getService({}) const res = await this.ctx.getService({})
return res?.Buckets || [] return res?.Buckets || []
} }
@@ -66,7 +66,7 @@ class TcyunApi {
/** /**
* 获取自定义域名 * 获取自定义域名
*/ */
async getBucketDomain (param: IStringKeyMap): Promise<any> { async getBucketDomain(param: IStringKeyMap): Promise<any> {
const { bucketName, region } = param const { bucketName, region } = param
const res = await this.ctx.getBucketDomain({ const res = await this.ctx.getBucketDomain({
Bucket: bucketName, Bucket: bucketName,
@@ -87,7 +87,7 @@ class TcyunApi {
* @description * @description
* acl: private | publicRead | publicReadWrite * acl: private | publicRead | publicReadWrite
*/ */
async createBucket (configMap: IStringKeyMap): Promise<boolean> { async createBucket(configMap: IStringKeyMap): Promise<boolean> {
const res = await this.ctx.putBucket({ const res = await this.ctx.putBucket({
ACL: configMap.acl, ACL: configMap.acl,
Bucket: configMap.BucketName, Bucket: configMap.BucketName,
@@ -96,7 +96,7 @@ class TcyunApi {
return res?.statusCode === 200 return res?.statusCode === 200
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> { async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { const {
bucketName: bucket, bucketName: bucket,
@@ -150,7 +150,7 @@ class TcyunApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList) ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
} }
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { const {
bucketName: bucket, bucketName: bucket,
@@ -221,7 +221,7 @@ class TcyunApi {
* customUrl: string * customUrl: string
* } * }
*/ */
async getBucketFileList (configMap: IStringKeyMap): Promise<any> { async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { const {
bucketName: bucket, bucketName: bucket,
bucketConfig: { Location: region }, bucketConfig: { Location: region },
@@ -272,7 +272,7 @@ class TcyunApi {
* newKey: string * newKey: string
* } * }
*/ */
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> { async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, oldKey, newKey } = configMap const { bucketName, region, oldKey, newKey } = configMap
const copyRes = await this.ctx.putObjectCopy({ const copyRes = await this.ctx.putObjectCopy({
Bucket: bucketName, Bucket: bucketName,
@@ -301,7 +301,7 @@ class TcyunApi {
* key: string * key: string
* } * }
*/ */
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
const res = await this.ctx.deleteObject({ const res = await this.ctx.deleteObject({
Bucket: bucketName, Bucket: bucketName,
@@ -315,7 +315,7 @@ class TcyunApi {
* 删除文件夹 * 删除文件夹
* @param configMap * @param configMap
*/ */
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
let marker let marker
let res: any let res: any
@@ -346,7 +346,9 @@ class TcyunApi {
region, region,
key: item.Prefix key: item.Prefix
})) }))
) { return false } ) {
return false
}
} }
const cycles = Math.ceil(allFileList.Contents.length / 1000) const cycles = Math.ceil(allFileList.Contents.length / 1000)
for (let i = 0; i < cycles; i++) { for (let i = 0; i < cycles; i++) {
@@ -371,7 +373,7 @@ class TcyunApi {
* customUrl: string * customUrl: string
* } * }
*/ */
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> { async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName, region, key, expires, customUrl } = configMap const { bucketName, region, key, expires, customUrl } = configMap
const res = this.ctx.getObjectUrl( const res = this.ctx.getObjectUrl(
{ {
@@ -390,7 +392,7 @@ class TcyunApi {
* 高级上传文件 * 高级上传文件
* @param configMap * @param configMap
*/ */
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap const { fileArray } = configMap
// fileArray = [{ // fileArray = [{
// bucketName: string, // bucketName: string,
@@ -470,7 +472,7 @@ class TcyunApi {
* 新建文件夹 * 新建文件夹
* @param configMap * @param configMap
*/ */
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
const res = await this.ctx.putObject({ const res = await this.ctx.putObject({
Bucket: bucketName, Bucket: bucketName,
@@ -485,7 +487,7 @@ class TcyunApi {
* 下载文件 * 下载文件
* @param configMap * @param configMap
*/ */
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray } = configMap const { downloadPath, fileArray } = configMap
// fileArray = [{ // fileArray = [{
// bucketName: string, // bucketName: string,

View File

@@ -34,7 +34,7 @@ class UpyunApi {
stopMarker = 'g2gCZAAEbmV4dGQAA2VvZg' stopMarker = 'g2gCZAAEbmV4dGQAA2VvZg'
logger: ManageLogger logger: ManageLogger
constructor ( constructor(
bucket: string, bucket: string,
operator: string, operator: string,
password: string, password: string,
@@ -52,7 +52,7 @@ class UpyunApi {
this.expireTime = expireTime || 24 * 60 * 60 this.expireTime = expireTime || 24 * 60 * 60
} }
getAntiLeechParam (key: string): string { getAntiLeechParam(key: string): string {
const uri = `/${key}`.replace(/%2F/g, '/').replace(/^\/+/g, '/') const uri = `/${key}`.replace(/%2F/g, '/').replace(/^\/+/g, '/')
const now = Math.round(new Date().getTime() / 1000) const now = Math.round(new Date().getTime() / 1000)
const expire = this.expireTime ? now + parseInt(this.expireTime.toString(), 10) : now + 1800 const expire = this.expireTime ? now + parseInt(this.expireTime.toString(), 10) : now + 1800
@@ -61,7 +61,7 @@ class UpyunApi {
return `_upt=${upt}` return `_upt=${upt}`
} }
formatFolder (item: any, slicedPrefix: string, urlPrefix: string) { formatFolder(item: any, slicedPrefix: string, urlPrefix: string) {
const key = `${slicedPrefix}${item.name}/` const key = `${slicedPrefix}${item.name}/`
let url = `${urlPrefix}/${key}` let url = `${urlPrefix}/${key}`
if (this.antiLeechToken) { if (this.antiLeechToken) {
@@ -82,7 +82,7 @@ class UpyunApi {
} }
} }
formatFile (item: any, slicedPrefix: string, urlPrefix: string) { formatFile(item: any, slicedPrefix: string, urlPrefix: string) {
const key = `${slicedPrefix}${item.name}` const key = `${slicedPrefix}${item.name}`
let url = `${urlPrefix}/${key}` let url = `${urlPrefix}/${key}`
if (this.antiLeechToken) { if (this.antiLeechToken) {
@@ -102,7 +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( return `UPYUN ${operator}:${hmacSha1Base64(
md5(password, 'hex'), md5(password, 'hex'),
`${method.toUpperCase()}&${encodeURI(uri)}&${new Date().toUTCString()}${contentMd5 ? `&${contentMd5}` : ''}` `${method.toUpperCase()}&${encodeURI(uri)}&${new Date().toUTCString()}${contentMd5 ? `&${contentMd5}` : ''}`
@@ -112,11 +112,11 @@ class UpyunApi {
/** /**
* 获取空间列表 * 获取空间列表
*/ */
async getBucketList (): Promise<any> { async getBucketList(): Promise<any> {
return this.bucket return this.bucket
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> { async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, prefix, cancelToken } = configMap const { bucketName: bucket, prefix, cancelToken } = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
@@ -168,7 +168,7 @@ class UpyunApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList) ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
} }
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, prefix, cancelToken } = configMap const { bucketName: bucket, prefix, cancelToken } = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
@@ -227,7 +227,7 @@ class UpyunApi {
* customUrl: string * customUrl: string
* } * }
*/ */
async getBucketFileList (configMap: IStringKeyMap): Promise<any> { async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, prefix, marker, itemsPerPage } = configMap const { bucketName: bucket, prefix, marker, itemsPerPage } = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `http://${bucket}.test.upcdn.net` const urlPrefix = configMap.customUrl || `http://${bucket}.test.upcdn.net`
@@ -264,7 +264,7 @@ class UpyunApi {
* newKey: string * newKey: string
* } * }
*/ */
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> { async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const oldKey = configMap.oldKey const oldKey = configMap.oldKey
let newKey = configMap.newKey let newKey = configMap.newKey
const method = 'PUT' const method = 'PUT'
@@ -297,7 +297,7 @@ class UpyunApi {
* key: string * key: string
* } * }
*/ */
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
const res = await this.cli.deleteFile(key) const res = await this.cli.deleteFile(key)
return res return res
@@ -307,7 +307,7 @@ class UpyunApi {
* delete bucket folder * delete bucket folder
* @param configMap * @param configMap
*/ */
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
let marker = '' let marker = ''
let isTruncated let isTruncated
@@ -370,7 +370,7 @@ class UpyunApi {
* axiso:onUploadProgress not work in nodejs , use got instead * axiso:onUploadProgress not work in nodejs , use got instead
* @param configMap * @param configMap
*/ */
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
fileArray.forEach((item: any) => { fileArray.forEach((item: any) => {
@@ -426,7 +426,7 @@ class UpyunApi {
* 新建文件夹 * 新建文件夹
* @param configMap * @param configMap
*/ */
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
const res = await this.cli.makeDir(`/${key}`) const res = await this.cli.makeDir(`/${key}`)
return res return res
@@ -436,7 +436,7 @@ class UpyunApi {
* 下载文件 * 下载文件
* @param configMap * @param configMap
*/ */
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
const promises = [] as any const promises = [] as any

View File

@@ -28,7 +28,7 @@ class WebdavplistApi {
agent: https.Agent | http.Agent agent: https.Agent | http.Agent
ctx: WebDAVClient ctx: WebDAVClient
constructor ( constructor(
endpoint: string, endpoint: string,
username: string, username: string,
password: string, password: string,
@@ -62,7 +62,7 @@ class WebdavplistApi {
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(/^\/+/, '') const key = item.filename.replace(/^\/+/, '')
return { return {
...item, ...item,
@@ -79,7 +79,7 @@ class WebdavplistApi {
} }
} }
formatFile (item: FileStat, urlPrefix: string, isWebPath = false) { formatFile(item: FileStat, urlPrefix: string, isWebPath = false) {
const key = item.filename.replace(/^\/+/, '') const key = item.filename.replace(/^\/+/, '')
return { return {
...item, ...item,
@@ -98,7 +98,7 @@ class WebdavplistApi {
isRequestSuccess = (code: number) => code >= 200 && code < 300 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 window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken } = configMap const { prefix, customUrl, cancelToken } = configMap
const urlPrefix = customUrl || this.endpoint const urlPrefix = customUrl || this.endpoint
@@ -138,7 +138,7 @@ class WebdavplistApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList) ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
} }
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken, baseDir } = configMap const { prefix, customUrl, cancelToken, baseDir } = configMap
let urlPrefix = customUrl || this.endpoint let urlPrefix = customUrl || this.endpoint
@@ -197,7 +197,7 @@ class WebdavplistApi {
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
} }
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> { async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { oldKey, newKey } = configMap const { oldKey, newKey } = configMap
let result = false let result = false
try { try {
@@ -209,7 +209,7 @@ class WebdavplistApi {
return result return result
} }
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
let result = false let result = false
try { try {
@@ -221,7 +221,7 @@ class WebdavplistApi {
return result return result
} }
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
let result = false let result = false
try { try {
@@ -233,7 +233,7 @@ class WebdavplistApi {
return result return result
} }
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> { async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { key } = configMap const { key } = configMap
let result = '' let result = ''
try { try {
@@ -245,7 +245,7 @@ class WebdavplistApi {
return result return result
} }
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) { for (const item of fileArray) {
@@ -306,7 +306,7 @@ class WebdavplistApi {
return true return true
} }
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap const { key } = configMap
let result = false let result = false
try { try {
@@ -320,7 +320,7 @@ class WebdavplistApi {
return result return result
} }
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> { async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance() const instance = UpDownTaskQueue.getInstance()
const promises = [] as any const promises = [] as any

View File

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

View File

@@ -21,7 +21,7 @@ const errorMsg = {
brokenButBackup: $t('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP') brokenButBackup: $t('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP')
} }
function manageDbChecker () { function manageDbChecker() {
if (process.type !== 'renderer') { if (process.type !== 'renderer') {
const manageConfigFilePath = managePathChecker() const manageConfigFilePath = managePathChecker()
if (!fs.existsSync(manageConfigFilePath)) { if (!fs.existsSync(manageConfigFilePath)) {
@@ -72,7 +72,7 @@ function manageDbChecker () {
/** /**
* Get manage config path * Get manage config path
*/ */
function managePathChecker (): string { function managePathChecker(): string {
if (_configFilePath) { if (_configFilePath) {
return _configFilePath return _configFilePath
} }
@@ -113,7 +113,7 @@ function managePathChecker (): string {
} }
} }
function managePathDir () { function managePathDir() {
return path.dirname(managePathChecker()) return path.dirname(managePathChecker())
} }

View File

@@ -18,38 +18,38 @@ class UpDownTaskQueue {
private persistPath = path.join(app.getPath('userData'), 'UpDownTaskQueue.json') private persistPath = path.join(app.getPath('userData'), 'UpDownTaskQueue.json')
private constructor () { private constructor() {
this.restore() this.restore()
} }
static getInstance () { static getInstance() {
if (!UpDownTaskQueue.instance) { if (!UpDownTaskQueue.instance) {
UpDownTaskQueue.instance = new UpDownTaskQueue() UpDownTaskQueue.instance = new UpDownTaskQueue()
} }
return UpDownTaskQueue.instance return UpDownTaskQueue.instance
} }
getUploadTaskQueue () { getUploadTaskQueue() {
return UpDownTaskQueue.getInstance().uploadTaskQueue return UpDownTaskQueue.getInstance().uploadTaskQueue
} }
getDownloadTaskQueue () { getDownloadTaskQueue() {
return UpDownTaskQueue.getInstance().downloadTaskQueue return UpDownTaskQueue.getInstance().downloadTaskQueue
} }
getUploadTask (taskId: string) { getUploadTask(taskId: string) {
return UpDownTaskQueue.getInstance().uploadTaskQueue.find(item => item.id === taskId) return UpDownTaskQueue.getInstance().uploadTaskQueue.find(item => item.id === taskId)
} }
getAllUploadTask () { getAllUploadTask() {
return UpDownTaskQueue.getInstance().uploadTaskQueue return UpDownTaskQueue.getInstance().uploadTaskQueue
} }
addUploadTask (task: IUploadTask) { addUploadTask(task: IUploadTask) {
UpDownTaskQueue.getInstance().uploadTaskQueue.push(task) UpDownTaskQueue.getInstance().uploadTaskQueue.push(task)
} }
updateUploadTask (task: Partial<IUploadTask>) { updateUploadTask(task: Partial<IUploadTask>) {
const taskIndex = UpDownTaskQueue.getInstance().uploadTaskQueue.findIndex(item => item.id === task.id) const taskIndex = UpDownTaskQueue.getInstance().uploadTaskQueue.findIndex(item => item.id === task.id)
if (taskIndex !== -1) { if (taskIndex !== -1) {
const taskKeys = Object.keys(task) 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) const taskIndex = UpDownTaskQueue.getInstance().uploadTaskQueue.findIndex(item => item.id === taskId)
if (taskIndex !== -1) { if (taskIndex !== -1) {
UpDownTaskQueue.getInstance().uploadTaskQueue.splice(taskIndex, 1) UpDownTaskQueue.getInstance().uploadTaskQueue.splice(taskIndex, 1)
} }
} }
removeDownloadTask (taskId: string) { removeDownloadTask(taskId: string) {
const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === taskId) const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === taskId)
if (taskIndex !== -1) { if (taskIndex !== -1) {
UpDownTaskQueue.getInstance().downloadTaskQueue.splice(taskIndex, 1) UpDownTaskQueue.getInstance().downloadTaskQueue.splice(taskIndex, 1)
} }
} }
getDownloadTask (taskId: string) { getDownloadTask(taskId: string) {
return UpDownTaskQueue.getInstance().downloadTaskQueue.find(item => item.id === taskId) return UpDownTaskQueue.getInstance().downloadTaskQueue.find(item => item.id === taskId)
} }
getAllDownloadTask () { getAllDownloadTask() {
return UpDownTaskQueue.getInstance().downloadTaskQueue return UpDownTaskQueue.getInstance().downloadTaskQueue
} }
addDownloadTask (task: IDownloadTask) { addDownloadTask(task: IDownloadTask) {
UpDownTaskQueue.getInstance().downloadTaskQueue.push(task) UpDownTaskQueue.getInstance().downloadTaskQueue.push(task)
} }
updateDownloadTask (task: Partial<IDownloadTask>) { updateDownloadTask(task: Partial<IDownloadTask>) {
const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === task.id) const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === task.id)
if (taskIndex !== -1) { if (taskIndex !== -1) {
const taskKeys = Object.keys(task) const taskKeys = Object.keys(task)
@@ -99,11 +99,11 @@ class UpDownTaskQueue {
} }
} }
clearUploadTaskQueue () { clearUploadTaskQueue() {
UpDownTaskQueue.getInstance().uploadTaskQueue = [] UpDownTaskQueue.getInstance().uploadTaskQueue = []
} }
removeUploadedTask () { removeUploadedTask() {
UpDownTaskQueue.getInstance().uploadTaskQueue = UpDownTaskQueue.getInstance().uploadTaskQueue.filter( UpDownTaskQueue.getInstance().uploadTaskQueue = UpDownTaskQueue.getInstance().uploadTaskQueue.filter(
item => item =>
item.status !== uploadTaskSpecialStatus.uploaded && item.status !== uploadTaskSpecialStatus.uploaded &&
@@ -112,7 +112,7 @@ class UpDownTaskQueue {
) )
} }
removeDownloadedTask () { removeDownloadedTask() {
UpDownTaskQueue.getInstance().downloadTaskQueue = UpDownTaskQueue.getInstance().downloadTaskQueue.filter( UpDownTaskQueue.getInstance().downloadTaskQueue = UpDownTaskQueue.getInstance().downloadTaskQueue.filter(
item => item =>
item.status !== downloadTaskSpecialStatus.downloaded && item.status !== downloadTaskSpecialStatus.downloaded &&
@@ -121,16 +121,16 @@ class UpDownTaskQueue {
) )
} }
clearDownloadTaskQueue () { clearDownloadTaskQueue() {
UpDownTaskQueue.getInstance().downloadTaskQueue = [] UpDownTaskQueue.getInstance().downloadTaskQueue = []
} }
clearAllTaskQueue () { clearAllTaskQueue() {
this.clearUploadTaskQueue() this.clearUploadTaskQueue()
this.clearDownloadTaskQueue() this.clearDownloadTaskQueue()
} }
persist () { persist() {
try { try {
this.checkPersistPath() this.checkPersistPath()
fs.writeFileSync( fs.writeFileSync(
@@ -145,7 +145,7 @@ class UpDownTaskQueue {
} }
} }
private restore () { private restore() {
try { try {
this.checkPersistPath() this.checkPersistPath()
const persistData = JSON.parse(fs.readFileSync(this.persistPath, { encoding: 'utf-8' })) const persistData = JSON.parse(fs.readFileSync(this.persistPath, { encoding: 'utf-8' }))
@@ -157,7 +157,7 @@ class UpDownTaskQueue {
} }
} }
private checkPersistPath () { private checkPersistPath() {
if (!fs.existsSync(this.persistPath)) { if (!fs.existsSync(this.persistPath)) {
fs.writeFileSync( fs.writeFileSync(
this.persistPath, this.persistPath,

View File

@@ -26,7 +26,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
logger: ManageLogger logger: ManageLogger
currentPicBedConfig: IPicBedMangeConfig currentPicBedConfig: IPicBedMangeConfig
constructor (currentPicBed: string = '') { constructor(currentPicBed: string = '') {
super() super()
this.currentPicBed = currentPicBed || 'placeholder' this.currentPicBed = currentPicBed || 'placeholder'
this.configPath = managePathChecker() this.configPath = managePathChecker()
@@ -36,7 +36,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
this.currentPicBedConfig = this.getPicBedConfig(this.currentPicBed) this.currentPicBedConfig = this.getPicBedConfig(this.currentPicBed)
} }
getMsgParam (method: string) { getMsgParam(method: string) {
return { return {
class: 'ManageApi', class: 'ManageApi',
method, method,
@@ -44,11 +44,11 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
errorMsg (err: any, param: IStringKeyMap) { errorMsg(err: any, param: IStringKeyMap) {
this.logger.error(formatError(err, param)) this.logger.error(formatError(err, param))
} }
createClient () { createClient() {
const name = this.currentPicBedConfig.picBedName const name = this.currentPicBedConfig.picBedName
switch (name) { switch (name) {
case 'aliyun': case 'aliyun':
@@ -127,11 +127,11 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
private getPicBedConfig (picBedName: string): IPicBedMangeConfig { private getPicBedConfig(picBedName: string): IPicBedMangeConfig {
return this.getConfig<IPicBedMangeConfig>(`picBed.${picBedName}`) return this.getConfig<IPicBedMangeConfig>(`picBed.${picBedName}`)
} }
private initConfigPath (): void { private initConfigPath(): void {
if (this.configPath === '') { if (this.configPath === '') {
this.configPath = `${homedir()}/.piclist/manage.json` this.configPath = `${homedir()}/.piclist/manage.json`
} }
@@ -146,7 +146,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
private initconfig (): void { private initconfig(): void {
this.db = new ManageDB(this) this.db = new ManageDB(this)
this._config = this.db.read(true) as IManageConfigType this._config = this.db.read(true) as IManageConfigType
} }
@@ -158,7 +158,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
return get(this._config, name) return get(this._config, name)
} }
saveConfig (config: IStringKeyMap): void { saveConfig(config: IStringKeyMap): void {
if (!isInputConfigValid(config)) { 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 return
@@ -167,7 +167,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
this.db.saveConfig(config) this.db.saveConfig(config)
} }
removeConfig (key: string, propName: string): void { removeConfig(key: string, propName: string): void {
if (!key || !propName) { if (!key || !propName) {
return return
} }
@@ -175,7 +175,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
this.db.unset(key, propName) this.db.unset(key, propName)
} }
setConfig (config: IStringKeyMap): void { setConfig(config: IStringKeyMap): void {
if (!isInputConfigValid(config)) { 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 return
@@ -185,12 +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 if (!key || !propName) return
unset(this.getConfig(key), propName) unset(this.getConfig(key), propName)
} }
async getBucketList (param?: IStringKeyMap | undefined): Promise<any> { async getBucketList(param?: IStringKeyMap | undefined): Promise<any> {
let client let client
const name = this.currentPicBedConfig.picBedName.replace('plist', '') const name = this.currentPicBedConfig.picBedName.replace('plist', '')
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
@@ -232,12 +232,12 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async getBucketInfo (param?: IStringKeyMap | undefined): Promise<IStringKeyMap | IManageError> { async getBucketInfo(param?: IStringKeyMap | undefined): Promise<IStringKeyMap | IManageError> {
console.log(param) console.log(param)
return {} return {}
} }
async getBucketDomain (param: IStringKeyMap): Promise<IStringKeyMap | IManageError> { async getBucketDomain(param: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -262,7 +262,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async createBucket (param?: IStringKeyMap): Promise<boolean> { async createBucket(param?: IStringKeyMap): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -281,32 +281,32 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async deleteBucket (param?: IStringKeyMap): Promise<boolean> { async deleteBucket(param?: IStringKeyMap): Promise<boolean> {
console.log(param) console.log(param)
return false return false
} }
async getOperatorList (param?: IStringKeyMap): Promise<string[] | IManageError> { async getOperatorList(param?: IStringKeyMap): Promise<string[] | IManageError> {
console.log(param) console.log(param)
return [] return []
} }
async addOperator (param?: IStringKeyMap): Promise<boolean> { async addOperator(param?: IStringKeyMap): Promise<boolean> {
console.log(param) console.log(param)
return false return false
} }
async deleteOperator (param?: IStringKeyMap): Promise<boolean> { async deleteOperator(param?: IStringKeyMap): Promise<boolean> {
console.log(param) console.log(param)
return false return false
} }
async getBucketAclPolicy (param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> { async getBucketAclPolicy(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
console.log(param) console.log(param)
return {} return {}
} }
async setBucketAclPolicy (param?: IStringKeyMap): Promise<boolean> { async setBucketAclPolicy(param?: IStringKeyMap): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'qiniu': case 'qiniu':
@@ -322,7 +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 client
let window let window
const defaultResult = { const defaultResult = {
@@ -365,7 +365,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
* @param param * @param param
* @returns * @returns
*/ */
async getBucketListBackstage (param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> { async getBucketListBackstage(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
let client let client
let window let window
const defaultResult = { const defaultResult = {
@@ -412,7 +412,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
* isDir: 是否是文件夹 * isDir: 是否是文件夹
* fileSize: 文件大小 * fileSize: 文件大小
**/ **/
async getBucketFileList (param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> { async getBucketFileList(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
const defaultResponse = { const defaultResponse = {
fullList: [] as any, fullList: [] as any,
isTruncated: false, isTruncated: false,
@@ -439,7 +439,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async deleteBucketFile (param?: IStringKeyMap): Promise<boolean> { async deleteBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -466,7 +466,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async deleteBucketFolder (param?: IStringKeyMap): Promise<boolean> { async deleteBucketFolder(param?: IStringKeyMap): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -490,7 +490,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async renameBucketFile (param?: IStringKeyMap): Promise<boolean> { async renameBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -513,7 +513,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async downloadBucketFile (param?: IStringKeyMap): Promise<boolean> { async downloadBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -540,12 +540,12 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async copyMoveBucketFile (param?: IStringKeyMap): Promise<boolean> { async copyMoveBucketFile(param?: IStringKeyMap): Promise<boolean> {
console.log(param) console.log(param)
return false return false
} }
async createBucketFolder (param?: IStringKeyMap): Promise<boolean> { async createBucketFolder(param?: IStringKeyMap): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -569,7 +569,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async uploadBucketFile (param?: IStringKeyMap): Promise<boolean> { async uploadBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':
@@ -595,7 +595,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
} }
} }
async getPreSignedUrl (param?: IStringKeyMap): Promise<string> { async getPreSignedUrl(param?: IStringKeyMap): Promise<string> {
let client let client
switch (this.currentPicBedConfig.picBedName) { switch (this.currentPicBedConfig.picBedName) {
case 'tcyun': case 'tcyun':

View File

@@ -34,7 +34,7 @@ export const getFSFile = async (filePath: string, stream: boolean = false): Prom
} }
} }
export function isInputConfigValid (config: any): boolean { export function isInputConfigValid(config: any): boolean {
return typeof config === 'object' && !Array.isArray(config) && Object.keys(config).length > 0 return typeof config === 'object' && !Array.isArray(config) && Object.keys(config).length > 0
} }
@@ -280,7 +280,7 @@ export const getInnerAgent = (proxy: any, sslEnabled: boolean = true) => {
} }
} }
export function getOptions ( export function getOptions(
method?: string, method?: string,
headers?: IStringKeyMap, headers?: IStringKeyMap,
searchParams?: IStringKeyMap, searchParams?: IStringKeyMap,
@@ -309,14 +309,14 @@ export class ConcurrencyPromisePool {
runningNum: number runningNum: number
results: any[] results: any[]
constructor (limit: number) { constructor(limit: number) {
this.limit = limit this.limit = limit
this.queue = [] this.queue = []
this.runningNum = 0 this.runningNum = 0
this.results = [] this.results = []
} }
all (promises: any[] = []) { all(promises: any[] = []) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
for (const promise of promises) { for (const promise of promises) {
this._run(promise, resolve, reject) this._run(promise, resolve, reject)
@@ -324,7 +324,7 @@ export class ConcurrencyPromisePool {
}) })
} }
_run (promise: any, resolve: any, reject: any) { _run(promise: any, resolve: any, reject: any) {
if (this.runningNum >= this.limit) { if (this.runningNum >= this.limit) {
this.queue.push(promise) this.queue.push(promise)
return return

View File

@@ -12,7 +12,7 @@ export interface DogecloudToken {
sessionToken: string sessionToken: string
} }
export async function dogecloudApi ( export async function dogecloudApi(
apiPath: string, apiPath: string,
data = {}, data = {},
jsonMode: boolean = false, jsonMode: boolean = false,
@@ -45,7 +45,7 @@ export async function dogecloudApi (
} }
} }
export async function getTempToken (accessKey: string, secretKey: string): Promise<IObj | DogecloudToken> { export async function getTempToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudToken> {
const dogeTempToken = (await picgo.getConfig('Credentials.doge-token')) || ({} as any) const dogeTempToken = (await picgo.getConfig('Credentials.doge-token')) || ({} as any)
if (dogeTempToken.token && dogeTempToken.expires > Date.now() + 7200000) { if (dogeTempToken.token && dogeTempToken.expires > Date.now() + 7200000) {
return dogeTempToken.token return dogeTempToken.token

View File

@@ -24,11 +24,11 @@ export class ManageLogger implements ILogger {
#logLevel!: string #logLevel!: string
#logPath!: string #logPath!: string
constructor (ctx: IManageApiType) { constructor(ctx: IManageApiType) {
this.#ctx = ctx this.#ctx = ctx
} }
#handleLog (type: string, ...msg: ILogArgvTypeWithError[]): void { #handleLog(type: string, ...msg: ILogArgvTypeWithError[]): void {
const logHeader = chalk[this.#level[type] as ILogColor](`[PicList ${type.toUpperCase()}]`) const logHeader = chalk[this.#level[type] as ILogColor](`[PicList ${type.toUpperCase()}]`)
console.log(logHeader, ...msg) console.log(logHeader, ...msg)
this.#logLevel = this.#ctx.getConfig(configPaths.settings.logLevel) this.#logLevel = this.#ctx.getConfig(configPaths.settings.logLevel)
@@ -53,7 +53,7 @@ export class ManageLogger implements ILogger {
}, 0) }, 0)
} }
#checkLogFileIsLarge (logPath: string): { #checkLogFileIsLarge(logPath: string): {
isLarge: boolean isLarge: boolean
logFileSize?: number logFileSize?: number
logFileSizeLimit?: number logFileSizeLimit?: number
@@ -76,14 +76,14 @@ export class ManageLogger implements ILogger {
} }
} }
#recreateLogFile (logPath: string): void { #recreateLogFile(logPath: string): void {
if (fs.existsSync(logPath)) { if (fs.existsSync(logPath)) {
fs.unlinkSync(logPath) fs.unlinkSync(logPath)
fs.createFileSync(logPath) fs.createFileSync(logPath)
} }
} }
#handleWriteLog (logPath: string, type: string, ...msg: ILogArgvTypeWithError[]): void { #handleWriteLog(logPath: string, type: string, ...msg: ILogArgvTypeWithError[]): void {
try { try {
if (this.#checkLogLevel(type, this.#logLevel)) { if (this.#checkLogLevel(type, this.#logLevel)) {
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicList ${type.toUpperCase()}] ` let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicList ${type.toUpperCase()}] `
@@ -98,7 +98,7 @@ export class ManageLogger implements ILogger {
} }
} }
#formatLogItem (item: ILogArgvTypeWithError, type: string): string { #formatLogItem(item: ILogArgvTypeWithError, type: string): string {
let result = '' let result = ''
if (item instanceof Error && type === 'error') { if (item instanceof Error && type === 'error') {
result += `\n------Error Stack Begin------\n${util.format(item?.stack)}\n-------Error Stack End------- ` result += `\n------Error Stack Begin------\n${util.format(item?.stack)}\n-------Error Stack End------- `
@@ -114,7 +114,7 @@ export class ManageLogger implements ILogger {
return result return result
} }
#checkLogLevel (type: string, level: undefined | string | string[]): boolean { #checkLogLevel(type: string, level: undefined | string | string[]): boolean {
if (level === undefined || level === 'all') { if (level === undefined || level === 'all') {
return true return true
} }
@@ -124,23 +124,23 @@ export class ManageLogger implements ILogger {
return type === level return type === level
} }
success (...msq: ILogArgvType[]): void { success(...msq: ILogArgvType[]): void {
return this.#handleLog(ILogType.success, ...msq) return this.#handleLog(ILogType.success, ...msq)
} }
info (...msq: ILogArgvType[]): void { info(...msq: ILogArgvType[]): void {
return this.#handleLog(ILogType.info, ...msq) return this.#handleLog(ILogType.info, ...msq)
} }
error (...msq: ILogArgvTypeWithError[]): void { error(...msq: ILogArgvTypeWithError[]): void {
return this.#handleLog(ILogType.error, ...msq) return this.#handleLog(ILogType.error, ...msq)
} }
warn (...msq: ILogArgvType[]): void { warn(...msq: ILogArgvType[]): void {
return this.#handleLog(ILogType.warn, ...msq) return this.#handleLog(ILogType.warn, ...msq)
} }
debug (...msq: ILogArgvType[]): void { debug(...msq: ILogArgvType[]): void {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
this.#handleLog(ILogType.info, ...msq) this.#handleLog(ILogType.info, ...msq)
} }

View File

@@ -41,12 +41,12 @@ class Server {
#httpServer: http.Server #httpServer: http.Server
#config: IServerConfig #config: IServerConfig
constructor () { constructor() {
this.#config = this.getConfigWithDefaults() this.#config = this.getConfigWithDefaults()
this.#httpServer = http.createServer(this.#handleRequest) this.#httpServer = http.createServer(this.#handleRequest)
} }
getConfigWithDefaults () { getConfigWithDefaults() {
let config = picgo.getConfig<IServerConfig>(configPaths.settings.server) let config = picgo.getConfig<IServerConfig>(configPaths.settings.server)
if (!this.#isValidConfig(config)) { if (!this.#isValidConfig(config)) {
config = { port: DEFAULT_PORT, host: DEFAULT_HOST, enable: true } config = { port: DEFAULT_PORT, host: DEFAULT_HOST, enable: true }
@@ -55,7 +55,7 @@ class Server {
return config return config
} }
#isValidConfig (config: IObj | undefined) { #isValidConfig(config: IObj | undefined) {
return config && config.port && config.host && config.enable !== undefined return config && config.port && config.host && config.enable !== undefined
} }
@@ -198,20 +198,20 @@ class Server {
}) })
} }
startup () { startup() {
if (this.#config.enable) { if (this.#config.enable) {
this.#listen(this.#config.port) this.#listen(this.#config.port)
} }
} }
shutdown (hasStarted?: boolean) { shutdown(hasStarted?: boolean) {
this.#httpServer.close() this.#httpServer.close()
if (!hasStarted) { if (!hasStarted) {
logger.info('[PicList Server] shutdown') logger.info('[PicList Server] shutdown')
} }
} }
restart () { restart() {
this.shutdown() this.shutdown()
this.#config = this.getConfigWithDefaults() this.#config = this.getConfigWithDefaults()
this.startup() this.startup()

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ import { configPaths } from '~/utils/configPaths'
const defaultPath = process.platform === 'win32' ? 'C:\\Users' : '/' 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>' let html = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><h1>Directory Listing</h1><ul>'
files.forEach((file: string) => { files.forEach((file: string) => {
const filePath = path.join(requestPath, file) const filePath = path.join(requestPath, file)
@@ -21,7 +21,7 @@ function generateDirectoryListingHtml (files: any[], requestPath: any) {
return html 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) => { fs.readdir(filePath, (err, files) => {
if (err) { if (err) {
res.writeHead(500) res.writeHead(500)
@@ -33,7 +33,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) const readStream = fs.createReadStream(filePath)
readStream.pipe(res) readStream.pipe(res)
readStream.on('error', () => { readStream.on('error', () => {
@@ -46,12 +46,12 @@ class WebServer {
#server!: http.Server #server!: http.Server
#config!: IStringKeyMap #config!: IStringKeyMap
constructor () { constructor() {
this.loadConfig() this.loadConfig()
this.initServer() this.initServer()
} }
loadConfig (): void { loadConfig(): void {
this.#config = { this.#config = {
enableWebServer: picgo.getConfig<boolean>(configPaths.settings.enableWebServer) || false, enableWebServer: picgo.getConfig<boolean>(configPaths.settings.enableWebServer) || false,
webServerHost: picgo.getConfig<string>(configPaths.settings.webServerHost) || '0.0.0.0', webServerHost: picgo.getConfig<string>(configPaths.settings.webServerHost) || '0.0.0.0',
@@ -60,7 +60,7 @@ class WebServer {
} }
} }
initServer (): void { initServer(): void {
this.#server = http.createServer((req, res) => { this.#server = http.createServer((req, res) => {
const requestPath = req.url?.split('?')[0] const requestPath = req.url?.split('?')[0]
const filePath = path.join(this.#config.webServerPath, decodeURIComponent(requestPath || '')) const filePath = path.join(this.#config.webServerPath, decodeURIComponent(requestPath || ''))
@@ -79,7 +79,7 @@ class WebServer {
}) })
} }
start () { start() {
if (this.#config.enableWebServer) { if (this.#config.enableWebServer) {
this.#server this.#server
.listen( .listen(
@@ -99,13 +99,13 @@ class WebServer {
} }
} }
stop () { stop() {
this.#server.close(() => { this.#server.close(() => {
logger.info('Web server is stopped') logger.info('Web server is stopped')
}) })
} }
restart () { restart() {
this.stop() this.stop()
this.loadConfig() this.loadConfig()
this.initServer() this.initServer()

View File

@@ -1,75 +1,60 @@
import crypto from 'node:crypto' import crypto from 'node:crypto'
import picgo from '@core/picgo' import picgo from '@core/picgo'
import { configPaths } from '~/utils/configPaths' import { configPaths } from '~/utils/configPaths'
export class AESHelper { export class AESHelper {
static readonly #SALT = Buffer.from('a8b3c4d2e4f5098712345678feedc0de', 'hex') static readonly #SALT = Buffer.from('a8b3c4d2e4f5098712345678feedc0de', 'hex')
static readonly #ITERATIONS = 100_000 static readonly #ITERATIONS = 100_000
static readonly #KEYLEN = 32 static readonly #KEYLEN = 32
static readonly #DIGEST = 'sha512' as const static readonly #DIGEST = 'sha512' as const
static readonly #ALGO = 'aes-256-cbc' static readonly #ALGO = 'aes-256-cbc'
static readonly #IV_LENGTH = 16 static readonly #IV_LENGTH = 16
static readonly #SEP = ':' static readonly #SEP = ':'
static #keyCache = new Map<string, Buffer>() static #keyCache = new Map<string, Buffer>()
readonly key: Buffer readonly key: Buffer
constructor (password?: string) { constructor(password?: string) {
const pwd = const pwd = password ?? picgo.getConfig<string>(configPaths.settings.aesPassword) ?? 'aesPassword'
password ?? this.key = AESHelper.#deriveKey(pwd)
picgo.getConfig<string>(configPaths.settings.aesPassword) ?? }
'aesPassword'
this.key = AESHelper.#deriveKey(pwd) static #deriveKey(password: string): Buffer {
} const cached = this.#keyCache.get(password)
if (cached) return cached
static #deriveKey (password: string): Buffer { const key = crypto.pbkdf2Sync(password, this.#SALT, this.#ITERATIONS, this.#KEYLEN, this.#DIGEST)
const cached = this.#keyCache.get(password) this.#keyCache.set(password, key)
if (cached) return cached return key
const key = crypto.pbkdf2Sync( }
password,
this.#SALT, encrypt(plainText: string): string {
this.#ITERATIONS, const iv = crypto.randomBytes(AESHelper.#IV_LENGTH)
this.#KEYLEN, const cipher = crypto.createCipheriv(AESHelper.#ALGO, this.key, iv)
this.#DIGEST const encrypted = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()])
) return `${iv.toString('hex')}${AESHelper.#SEP}${encrypted.toString('hex')}`
this.#keyCache.set(password, key) }
return key
} decrypt(encryptedData: string): string {
if (!encryptedData) return '{}'
encrypt (plainText: string): string {
const iv = crypto.randomBytes(AESHelper.#IV_LENGTH) const sepIndex = encryptedData.indexOf(AESHelper.#SEP)
const cipher = crypto.createCipheriv(AESHelper.#ALGO, this.key, iv) if (sepIndex <= 0) return '{}'
const encrypted = Buffer.concat([
cipher.update(plainText, 'utf8'), const ivHex = encryptedData.slice(0, sepIndex)
cipher.final() const encryptedHex = encryptedData.slice(sepIndex + 1)
])
return `${iv.toString('hex')}${AESHelper.#SEP}${encrypted.toString('hex')}` try {
} const iv = Buffer.from(ivHex, 'hex')
if (iv.length !== AESHelper.#IV_LENGTH) return '{}'
decrypt (encryptedData: string): string {
if (!encryptedData) return '{}' const decipher = crypto.createDecipheriv(AESHelper.#ALGO, this.key, iv)
const decrypted = Buffer.concat([decipher.update(Buffer.from(encryptedHex, 'hex')), decipher.final()])
const sepIndex = encryptedData.indexOf(AESHelper.#SEP) return decrypted.toString('utf8')
if (sepIndex <= 0) return '{}' } catch {
return '{}'
const ivHex = encryptedData.slice(0, sepIndex) }
const encryptedHex = encryptedData.slice(sepIndex + 1) }
}
try {
const iv = Buffer.from(ivHex, 'hex')
if (iv.length !== AESHelper.#IV_LENGTH) return '{}'
const decipher = crypto.createDecipheriv(AESHelper.#ALGO, this.key, iv)
const decrypted = Buffer.concat([
decipher.update(Buffer.from(encryptedHex, 'hex')),
decipher.final()
])
return decrypted.toString('utf8')
} catch {
return '{}'
}
}
}

View File

@@ -13,7 +13,7 @@ const configPath = dbPathChecker()
const CONFIG_DIR = path.dirname(configPath) const CONFIG_DIR = path.dirname(configPath)
const __dirname = path.dirname(fileURLToPath(import.meta.url)) const __dirname = path.dirname(fileURLToPath(import.meta.url))
function beforeOpen () { function beforeOpen() {
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
resolveMacWorkFlow() resolveMacWorkFlow()
} }
@@ -21,7 +21,7 @@ function beforeOpen () {
resolveOtherI18nFiles() resolveOtherI18nFiles()
} }
function copyFileOutsideOfElectronAsar (sourceInAsarArchive: string, destOutsideAsarArchive: string) { function copyFileOutsideOfElectronAsar(sourceInAsarArchive: string, destOutsideAsarArchive: string) {
if (fs.existsSync(sourceInAsarArchive)) { if (fs.existsSync(sourceInAsarArchive)) {
// file will be copied // file will be copied
if (fs.statSync(sourceInAsarArchive).isFile()) { if (fs.statSync(sourceInAsarArchive).isFile()) {
@@ -45,16 +45,21 @@ function copyFileOutsideOfElectronAsar (sourceInAsarArchive: string, destOutside
/** /**
* macOS 右键菜单 * macOS 右键菜单
*/ */
function resolveMacWorkFlow () { function resolveMacWorkFlow() {
const dest = `${os.homedir()}/Library/Services/Upload pictures with PicList.workflow` const dest = `${os.homedir()}/Library/Services/Upload pictures with PicList.workflow`
try { try {
copyFileOutsideOfElectronAsar(path.join(__dirname, '../../resources', 'Upload pictures with PicList.workflow').replace('app.asar', 'app.asar.unpacked'), dest) copyFileOutsideOfElectronAsar(
path
.join(__dirname, '../../resources', 'Upload pictures with PicList.workflow')
.replace('app.asar', 'app.asar.unpacked'),
dest
)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
} }
function diffFilesAndUpdate (filePath1: string, filePath2: string) { function diffFilesAndUpdate(filePath1: string, filePath2: string) {
try { try {
const file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1) const file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
const file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2) const file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
@@ -71,7 +76,7 @@ function diffFilesAndUpdate (filePath1: string, filePath2: string) {
/** /**
* 初始化剪贴板生成图片的脚本 * 初始化剪贴板生成图片的脚本
*/ */
function resolveClipboardImageGenerator () { function resolveClipboardImageGenerator() {
const clipboardFiles = getClipboardFiles() const clipboardFiles = getClipboardFiles()
if (!fs.pathExistsSync(path.join(CONFIG_DIR, 'windows10.ps1'))) { if (!fs.pathExistsSync(path.join(CONFIG_DIR, 'windows10.ps1'))) {
clipboardFiles.forEach(item => { clipboardFiles.forEach(item => {
@@ -84,7 +89,7 @@ function resolveClipboardImageGenerator () {
}) })
} }
function getClipboardFiles () { function getClipboardFiles() {
const files = ['linux.sh', 'mac.applescript', 'windows.ps1', 'windows10.ps1', 'wsl.sh'] const files = ['linux.sh', 'mac.applescript', 'windows.ps1', 'windows10.ps1', 'wsl.sh']
return files.map(item => { return files.map(item => {
@@ -99,7 +104,7 @@ function resolveClipboardImageGenerator () {
/** /**
* 初始化其他语言文件 * 初始化其他语言文件
*/ */
function resolveOtherI18nFiles () { function resolveOtherI18nFiles() {
const i18nFolder = path.join(CONFIG_DIR, 'i18n') const i18nFolder = path.join(CONFIG_DIR, 'i18n')
if (!fs.pathExistsSync(i18nFolder)) { if (!fs.pathExistsSync(i18nFolder)) {
fs.mkdirSync(i18nFolder) fs.mkdirSync(i18nFolder)

View File

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

View File

@@ -1,323 +1,323 @@
import path from 'node:path' import path from 'node:path'
import db from '@core/datastore' import db from '@core/datastore'
import logger from '@core/picgo/logger' import logger from '@core/picgo/logger'
import axios from 'axios' import axios from 'axios'
import { clipboard, Notification, Tray } from 'electron' import { clipboard, Notification, Tray } from 'electron'
import FormData from 'form-data' import FormData from 'form-data'
import fs from 'fs-extra' import fs from 'fs-extra'
import { isReactive, isRef, toRaw, unref } from 'vue' import { isReactive, isRef, toRaw, unref } from 'vue'
import type { IHTTPProxy, IPrivateShowNotificationOption, IStringKeyMap } from '#/types/types' import type { IHTTPProxy, IPrivateShowNotificationOption, IStringKeyMap } from '#/types/types'
import { configPaths } from '~/utils/configPaths' import { configPaths } from '~/utils/configPaths'
import { IShortUrlServer } from '~/utils/enum' import { IShortUrlServer } from '~/utils/enum'
/** /**
* get raw data from reactive or ref * get raw data from reactive or ref
*/ */
export const getRawData = (args: any): any => { export const getRawData = (args: any): any => {
if (isRef(args)) return unref(args) if (isRef(args)) return unref(args)
if (isReactive(args)) return toRaw(args) if (isReactive(args)) return toRaw(args)
if (Array.isArray(args)) return args.map(getRawData) if (Array.isArray(args)) return args.map(getRawData)
if (typeof args === 'object' && args !== null) { if (typeof args === 'object' && args !== null) {
const data = {} as Record<string, any> const data = {} as Record<string, any>
for (const key in args) { for (const key in args) {
data[key] = getRawData(args[key]) data[key] = getRawData(args[key])
} }
return data return data
} }
return args return args
} }
const getExtension = (fileName: string) => path.extname(fileName).slice(1) const getExtension = (fileName: string) => path.extname(fileName).slice(1)
export const isImage = (fileName: string) => export const isImage = (fileName: string) =>
['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico', 'svg', 'avif'].includes(getExtension(fileName)) ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico', 'svg', 'avif'].includes(getExtension(fileName))
export let tray: Tray export let tray: Tray
export const setTray = (t: Tray) => { export const setTray = (t: Tray) => {
tray = t tray = t
} }
export const getTray = () => tray export const getTray = () => tray
export function setTrayToolTip (title: string): void { export function setTrayToolTip(title: string): void {
if (tray) { if (tray) {
tray.setToolTip(title) tray.setToolTip(title)
} }
} }
export const handleCopyUrl = (str: string): void => { export const handleCopyUrl = (str: string): void => {
if (db.get(configPaths.settings.autoCopy) !== false) { if (db.get(configPaths.settings.autoCopy) !== false) {
clipboard.writeText(str) clipboard.writeText(str)
} }
} }
/** /**
* show notification * show notification
* @param options * @param options
*/ */
export const showNotification = ( export const showNotification = (
options: IPrivateShowNotificationOption = { options: IPrivateShowNotificationOption = {
title: '', title: '',
body: '', body: '',
clickToCopy: false, clickToCopy: false,
copyContent: '', copyContent: '',
clickFn: () => {} clickFn: () => {}
} }
) => { ) => {
const notification = new Notification({ const notification = new Notification({
title: options.title, title: options.title,
body: options.body body: options.body
}) })
const handleClick = () => { const handleClick = () => {
if (options.clickToCopy) { if (options.clickToCopy) {
clipboard.writeText(options.copyContent || options.body) clipboard.writeText(options.copyContent || options.body)
} }
if (options.clickFn) { if (options.clickFn) {
options.clickFn() options.clickFn()
} }
} }
notification.once('click', handleClick) notification.once('click', handleClick)
notification.once('close', () => { notification.once('close', () => {
notification.removeListener('click', handleClick) notification.removeListener('click', handleClick)
}) })
notification.show() notification.show()
} }
/** /**
* macOS public.file-url will get encoded file path, * macOS public.file-url will get encoded file path,
* so we need to decode it * so we need to decode it
*/ */
export const ensureFilePath = (filePath: string, prefix = 'file://'): string => { export const ensureFilePath = (filePath: string, prefix = 'file://'): string => {
filePath = filePath.replace(prefix, '') filePath = filePath.replace(prefix, '')
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
return `${prefix}${filePath}` return `${prefix}${filePath}`
} }
filePath = decodeURIComponent(filePath) filePath = decodeURIComponent(filePath)
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
return `${prefix}${filePath}` return `${prefix}${filePath}`
} }
return '' return ''
} }
/** /**
* for builtin clipboard to get image path from clipboard * for builtin clipboard to get image path from clipboard
* @returns * @returns
*/ */
export const getClipboardFilePath = (): string => { export const getClipboardFilePath = (): string => {
// TODO: linux support // TODO: linux support
const img = clipboard.readImage() const img = clipboard.readImage()
const platform = process.platform const platform = process.platform
if (!img.isEmpty() && platform === 'darwin') { if (!img.isEmpty() && platform === 'darwin') {
let imgPath = clipboard.read('public.file-url') // will get file://xxx/xxx let imgPath = clipboard.read('public.file-url') // will get file://xxx/xxx
imgPath = ensureFilePath(imgPath) imgPath = ensureFilePath(imgPath)
return imgPath ? imgPath.replace('file://', '') : '' return imgPath ? imgPath.replace('file://', '') : ''
} }
if (img.isEmpty() && platform === 'win32') { if (img.isEmpty() && platform === 'win32') {
const imgPath = clipboard const imgPath = clipboard
.readBuffer('FileNameW') .readBuffer('FileNameW')
?.toString('ucs2') ?.toString('ucs2')
?.replace(RegExp(String.fromCharCode(0), 'g'), '') ?.replace(RegExp(String.fromCharCode(0), 'g'), '')
return imgPath || '' return imgPath || ''
} }
return '' return ''
} }
const c1nApi = 'https://c1n.cn/link/short' const c1nApi = 'https://c1n.cn/link/short'
const createC1NShortUrl = async (url: string) => { const createC1NShortUrl = async (url: string) => {
const c1nToken = db.get(configPaths.settings.c1nToken) || '' const c1nToken = db.get(configPaths.settings.c1nToken) || ''
if (!c1nToken) { if (!c1nToken) {
logger.warn('c1n token is not set') logger.warn('c1n token is not set')
return url return url
} }
try { try {
const form = new FormData() const form = new FormData()
form.append('url', url) form.append('url', url)
const res = await axios.post(c1nApi, form, { const res = await axios.post(c1nApi, form, {
headers: { headers: {
token: c1nToken token: c1nToken
} }
}) })
if (res.status >= 200 && res.status < 300 && res.data?.code === 0) { if (res.status >= 200 && res.status < 300 && res.data?.code === 0) {
return res.data.data return res.data.data
} }
} catch (e: any) { } catch (e: any) {
logger.error(e) logger.error(e)
} }
return url return url
} }
const createYOURLSShortLink = async (url: string) => { const createYOURLSShortLink = async (url: string) => {
let domain = db.get(configPaths.settings.yourlsDomain) || '' let domain = db.get(configPaths.settings.yourlsDomain) || ''
const signature = db.get(configPaths.settings.yourlsSignature) || '' const signature = db.get(configPaths.settings.yourlsSignature) || ''
if (!domain || !signature) { if (!domain || !signature) {
logger.warn('Yourls server or signature is not set') logger.warn('Yourls server or signature is not set')
return url return url
} }
if (!/^https?:\/\//.test(domain)) { if (!/^https?:\/\//.test(domain)) {
domain = `http://${domain}` domain = `http://${domain}`
} }
const params = new URLSearchParams({ const params = new URLSearchParams({
signature, signature,
action: 'shorturl', action: 'shorturl',
format: 'json', format: 'json',
url url
}) })
try { try {
const res = await axios.get(`${domain}/yourls-api.php?${params.toString()}`) const res = await axios.get(`${domain}/yourls-api.php?${params.toString()}`)
if (res.data?.shorturl) { if (res.data?.shorturl) {
return res.data.shorturl return res.data.shorturl
} }
} catch (e: any) { } catch (e: any) {
if (e.response?.data?.message?.includes('already exists in database')) { if (e.response?.data?.message?.includes('already exists in database')) {
return e.response.data.shorturl return e.response.data.shorturl
} }
logger.error(e) logger.error(e)
} }
return url return url
} }
const createShortUrlForCFWorker = async (url: string) => { const createShortUrlForCFWorker = async (url: string) => {
let cfWorkerHost = db.get(configPaths.settings.cfWorkerHost) || '' let cfWorkerHost = db.get(configPaths.settings.cfWorkerHost) || ''
cfWorkerHost = cfWorkerHost.replace(/\/$/, '') cfWorkerHost = cfWorkerHost.replace(/\/$/, '')
if (!cfWorkerHost) { if (!cfWorkerHost) {
logger.warn('CF Worker host is not set') logger.warn('CF Worker host is not set')
return url return url
} }
try { try {
const res = await axios.post(cfWorkerHost, { url }) const res = await axios.post(cfWorkerHost, { url })
if (res.data?.status === 200 && res.data?.key?.startsWith('/')) { if (res.data?.status === 200 && res.data?.key?.startsWith('/')) {
return `${cfWorkerHost}${res.data.key}` return `${cfWorkerHost}${res.data.key}`
} }
} catch (e: any) { } catch (e: any) {
logger.error(e) logger.error(e)
} }
return url return url
} }
const createShortUrlFromSink = async (url: string) => { const createShortUrlFromSink = async (url: string) => {
let sinkDomain = db.get(configPaths.settings.sinkDomain) || '' let sinkDomain = db.get(configPaths.settings.sinkDomain) || ''
const sinkToken = db.get(configPaths.settings.sinkToken) || '' const sinkToken = db.get(configPaths.settings.sinkToken) || ''
if (!sinkDomain || !sinkToken) { if (!sinkDomain || !sinkToken) {
logger.warn('Sink domain or token is not set') logger.warn('Sink domain or token is not set')
return url return url
} }
if (!/^https?:\/\//.test(sinkDomain)) { if (!/^https?:\/\//.test(sinkDomain)) {
sinkDomain = `http://${sinkDomain}` sinkDomain = `http://${sinkDomain}`
} }
if (sinkDomain.endsWith('/')) { if (sinkDomain.endsWith('/')) {
sinkDomain = sinkDomain.slice(0, -1) sinkDomain = sinkDomain.slice(0, -1)
} }
try { try {
const res = await axios.post( const res = await axios.post(
`${sinkDomain}/api/link/create`, `${sinkDomain}/api/link/create`,
{ url }, { url },
{ headers: { Authorization: `Bearer ${sinkToken}` } } { headers: { Authorization: `Bearer ${sinkToken}` } }
) )
if (res.data?.link?.slug) { if (res.data?.link?.slug) {
return `${sinkDomain}/${res.data.link.slug}` return `${sinkDomain}/${res.data.link.slug}`
} }
} catch (e: any) { } catch (e: any) {
logger.error(e) logger.error(e)
} }
return url return url
} }
export const generateShortUrl = async (url: string) => { export const generateShortUrl = async (url: string) => {
const server = db.get(configPaths.settings.shortUrlServer) || IShortUrlServer.C1N const server = db.get(configPaths.settings.shortUrlServer) || IShortUrlServer.C1N
switch (server) { switch (server) {
case IShortUrlServer.C1N: case IShortUrlServer.C1N:
return createC1NShortUrl(url) return createC1NShortUrl(url)
case IShortUrlServer.YOURLS: case IShortUrlServer.YOURLS:
return createYOURLSShortLink(url) return createYOURLSShortLink(url)
case IShortUrlServer.CFWORKER: case IShortUrlServer.CFWORKER:
return createShortUrlForCFWorker(url) return createShortUrlForCFWorker(url)
case IShortUrlServer.SINK: case IShortUrlServer.SINK:
return createShortUrlFromSink(url) return createShortUrlFromSink(url)
default: default:
return url return url
} }
} }
export const isUrl = (url: string): boolean => { export const isUrl = (url: string): boolean => {
try { try {
return Boolean(new URL(url)) return Boolean(new URL(url))
} catch { } catch {
return false return false
} }
} }
export const isUrlEncode = (url: string): boolean => { export const isUrlEncode = (url: string): boolean => {
url = url || '' url = url || ''
try { try {
return url !== decodeURI(url) return url !== decodeURI(url)
} catch { } catch {
return false return false
} }
} }
export const handleUrlEncode = (url: string): string => (isUrlEncode(url) ? url : encodeURI(url)) export const handleUrlEncode = (url: string): string => (isUrlEncode(url) ? url : encodeURI(url))
export const handleUrlEncodeWithSetting = (url: string) => export const handleUrlEncodeWithSetting = (url: string) =>
db.get(configPaths.settings.encodeOutputURL) ? handleUrlEncode(url) : url db.get(configPaths.settings.encodeOutputURL) ? handleUrlEncode(url) : url
export const handleStreamlinePluginName = (name: string) => name.replace(/(@[^/]+\/)?picgo-plugin-/, '') export const handleStreamlinePluginName = (name: string) => name.replace(/(@[^/]+\/)?picgo-plugin-/, '')
export const simpleClone = (obj: any) => JSON.parse(JSON.stringify(obj)) export const simpleClone = (obj: any) => JSON.parse(JSON.stringify(obj))
export const enforceNumber = (num: number | string) => (isNaN(+num) ? 0 : +num) export const enforceNumber = (num: number | string) => (isNaN(+num) ? 0 : +num)
export const trimValues = <T extends IStringKeyMap>( export const trimValues = <T extends IStringKeyMap>(
obj: T obj: T
): { [K in keyof T]: T[K] extends string ? string : T[K] } => { ): { [K in keyof T]: T[K] extends string ? string : T[K] } => {
return Object.fromEntries( return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value]) Object.entries(obj).map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value])
) as { [K in keyof T]: T[K] extends string ? string : T[K] } ) as { [K in keyof T]: T[K] extends string ? string : T[K] }
} }
export const formatEndpoint = (endpoint: string, sslEnabled: boolean): string => { export const formatEndpoint = (endpoint: string, sslEnabled: boolean): string => {
const hasProtocol = /^https?:\/\//.test(endpoint) const hasProtocol = /^https?:\/\//.test(endpoint)
if (!hasProtocol) { if (!hasProtocol) {
return `${sslEnabled ? 'https' : 'http'}://${endpoint}` return `${sslEnabled ? 'https' : 'http'}://${endpoint}`
} }
return sslEnabled ? endpoint.replace(/^http:\/\//, 'https://') : endpoint.replace(/^https:\/\//, 'http://') return sslEnabled ? endpoint.replace(/^http:\/\//, 'https://') : endpoint.replace(/^https:\/\//, 'http://')
} }
export const formatHttpProxy = ( export const formatHttpProxy = (
proxy: string | undefined, proxy: string | undefined,
type: 'object' | 'string' type: 'object' | 'string'
): IHTTPProxy | undefined | string => { ): IHTTPProxy | undefined | string => {
if (!proxy) return undefined if (!proxy) return undefined
if (/^https?:\/\//.test(proxy)) { if (/^https?:\/\//.test(proxy)) {
const { protocol, hostname, port } = new URL(proxy) const { protocol, hostname, port } = new URL(proxy)
return type === 'string' return type === 'string'
? `${protocol}//${hostname}:${port}` ? `${protocol}//${hostname}:${port}`
: { : {
host: hostname, host: hostname,
port: Number(port), port: Number(port),
protocol: protocol.slice(0, -1) protocol: protocol.slice(0, -1)
} }
} }
const [host, port] = proxy.split(':') const [host, port] = proxy.split(':')
return type === 'string' return type === 'string'
? `http://${host}:${port}` ? `http://${host}:${port}`
: { : {
host, host,
port: port ? Number(port) : 80, port: port ? Number(port) : 80,
protocol: 'http' protocol: 'http'
} }
} }
export function encodeFilePath (filePath: string) { export function encodeFilePath(filePath: string) {
return filePath.replace(/\\/g, '/').split('/').map(encodeURIComponent).join('/') return filePath.replace(/\\/g, '/').split('/').map(encodeURIComponent).join('/')
} }
export const trimPath = (path: string) => path.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/') export const trimPath = (path: string) => path.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/')

View File

@@ -1,188 +1,206 @@
import type { IBuildInCompressOptions, IBuildInWaterMarkOptions } from 'piclist' import type { IBuildInCompressOptions, IBuildInWaterMarkOptions } from 'piclist'
import type { IAliYunConfig, IAwsS3PListUserConfig, IGitHubConfig, IImgurConfig, ILocalConfig, ILskyConfig, IPicBedType, IQiniuConfig, IServerConfig, ISftpPlistConfig, IShortKeyConfig, ISMMSConfig, ISyncConfig, ITcYunConfig, IUploaderConfig, IUpYunConfig, IWebdavPlistConfig } from '#/types/types' import type {
IAliYunConfig,
export type manualPageOpenType = 'window' | 'browser' IAwsS3PListUserConfig,
IGitHubConfig,
interface IPicGoPlugins { IImgurConfig,
[key: `picgo-plugin-${string}`]: boolean ILocalConfig,
} ILskyConfig,
IPicBedType,
export interface IConfigStruct { IQiniuConfig,
picBed: { IServerConfig,
uploader: string ISftpPlistConfig,
current?: string IShortKeyConfig,
smms?: ISMMSConfig ISMMSConfig,
qiniu?: IQiniuConfig ISyncConfig,
upyun?: IUpYunConfig ITcYunConfig,
tcyun?: ITcYunConfig IUploaderConfig,
github?: IGitHubConfig IUpYunConfig,
aliyun?: IAliYunConfig IWebdavPlistConfig
imgur?: IImgurConfig } from '#/types/types'
webdavplist?: IWebdavPlistConfig
local?: ILocalConfig export type manualPageOpenType = 'window' | 'browser'
sftpplist?: ISftpPlistConfig
lskyplist?: ILskyConfig interface IPicGoPlugins {
'aws-s3-plist': IAwsS3PListUserConfig [key: `picgo-plugin-${string}`]: boolean
proxy?: string }
transformer?: string
list: IPicBedType[] export interface IConfigStruct {
[others: string]: any picBed: {
} uploader: string
settings: { current?: string
shortKey: { smms?: ISMMSConfig
[key: string]: IShortKeyConfig qiniu?: IQiniuConfig
} upyun?: IUpYunConfig
logLevel: string[] tcyun?: ITcYunConfig
logPath: string github?: IGitHubConfig
logFileSizeLimit: number aliyun?: IAliYunConfig
isAutoListenClipboard: boolean imgur?: IImgurConfig
isListeningClipboard: boolean webdavplist?: IWebdavPlistConfig
showUpdateTip: boolean local?: ILocalConfig
miniWindowPosition: [number, number] sftpplist?: ISftpPlistConfig
miniWindowOntop: boolean lskyplist?: ILskyConfig
mainWindowWidth: number 'aws-s3-plist': IAwsS3PListUserConfig
mainWindowHeight: number proxy?: string
isHideDock: boolean transformer?: string
autoCloseMiniWindow: boolean list: IPicBedType[]
autoCloseMainWindow: boolean [others: string]: any
isCustomMiniIcon: boolean }
customMiniIcon: string settings: {
startMode: string shortKey: {
autoRename: boolean [key: string]: IShortKeyConfig
deleteCloudFile: boolean }
server: IServerConfig logLevel: string[]
serverKey: string logPath: string
pasteStyle: string logFileSizeLimit: number
aesPassword: string isAutoListenClipboard: boolean
rename: boolean isListeningClipboard: boolean
sync: ISyncConfig showUpdateTip: boolean
tempDirPath: string miniWindowPosition: [number, number]
language: string miniWindowOntop: boolean
customLink: string mainWindowWidth: number
manualPageOpen: manualPageOpenType mainWindowHeight: number
encodeOutputURL: boolean isHideDock: boolean
useShortUrl: boolean autoCloseMiniWindow: boolean
shortUrlServer: string autoCloseMainWindow: boolean
c1nToken: string isCustomMiniIcon: boolean
cfWorkerHost: string customMiniIcon: string
yourlsDomain: string startMode: string
yourlsSignature: string autoRename: boolean
sinkDomain: string deleteCloudFile: boolean
sinkToken: string server: IServerConfig
isSilentNotice: boolean serverKey: string
proxy: string pasteStyle: string
registry: string aesPassword: string
autoCopy: boolean rename: boolean
enableWebServer: boolean sync: ISyncConfig
webServerHost: string tempDirPath: string
webServerPort: number language: string
webServerPath: string customLink: string
deleteLocalFile: boolean manualPageOpen: manualPageOpenType
uploadResultNotification: boolean encodeOutputURL: boolean
uploadNotification: boolean useShortUrl: boolean
useBuiltinClipboard: boolean shortUrlServer: string
autoStart: boolean c1nToken: string
autoImport: boolean cfWorkerHost: string
autoImportPicBed: string[] yourlsDomain: string
} yourlsSignature: string
needReload: boolean sinkDomain: string
picgoPlugins: IPicGoPlugins sinkToken: string
uploader: IUploaderConfig isSilentNotice: boolean
buildIn: { proxy: string
compress: IBuildInCompressOptions registry: string
watermark: IBuildInWaterMarkOptions autoCopy: boolean
rename: { enableWebServer: boolean
enable: boolean webServerHost: string
format: string webServerPort: number
} webServerPath: string
skipProcess: { deleteLocalFile: boolean
skipProcessExtList: string uploadResultNotification: boolean
} uploadNotification: boolean
} useBuiltinClipboard: boolean
debug: boolean autoStart: boolean
PICGO_ENV: string autoImport: boolean
} autoImportPicBed: string[]
}
export const configPaths = { needReload: boolean
picBed: { picgoPlugins: IPicGoPlugins
current: 'picBed.current', uploader: IUploaderConfig
uploader: 'picBed.uploader', buildIn: {
secondUploader: 'picBed.secondUploader', compress: IBuildInCompressOptions
secondUploaderId: 'picBed.secondUploaderId', watermark: IBuildInWaterMarkOptions
secondUploaderConfig: 'picBed.secondUploaderConfig', rename: {
proxy: 'picBed.proxy', enable: boolean
transformer: 'picBed.transformer', format: string
list: 'picBed.list' }
}, skipProcess: {
settings: { skipProcessExtList: string
shortKey: { }
_path: 'settings.shortKey', }
'picgo:upload': 'settings.shortKey[picgo:upload]' debug: boolean
}, PICGO_ENV: string
logLevel: 'settings.logLevel', }
logPath: 'settings.logPath',
logFileSizeLimit: 'settings.logFileSizeLimit', export const configPaths = {
isAutoListenClipboard: 'settings.isAutoListenClipboard', picBed: {
isListeningClipboard: 'settings.isListeningClipboard', current: 'picBed.current',
showUpdateTip: 'settings.showUpdateTip', uploader: 'picBed.uploader',
miniWindowPosition: 'settings.miniWindowPosition', secondUploader: 'picBed.secondUploader',
miniWindowOntop: 'settings.miniWindowOntop', secondUploaderId: 'picBed.secondUploaderId',
isHideDock: 'settings.isHideDock', secondUploaderConfig: 'picBed.secondUploaderConfig',
mainWindowWidth: 'settings.mainWindowWidth', proxy: 'picBed.proxy',
mainWindowHeight: 'settings.mainWindowHeight', transformer: 'picBed.transformer',
autoCloseMiniWindow: 'settings.autoCloseMiniWindow', list: 'picBed.list'
autoCloseMainWindow: 'settings.autoCloseMainWindow', },
isCustomMiniIcon: 'settings.isCustomMiniIcon', settings: {
customMiniIcon: 'settings.customMiniIcon', shortKey: {
startMode: 'settings.startMode', _path: 'settings.shortKey',
autoRename: 'settings.autoRename', 'picgo:upload': 'settings.shortKey[picgo:upload]'
deleteCloudFile: 'settings.deleteCloudFile', },
server: 'settings.server', logLevel: 'settings.logLevel',
serverKey: 'settings.serverKey', logPath: 'settings.logPath',
pasteStyle: 'settings.pasteStyle', logFileSizeLimit: 'settings.logFileSizeLimit',
aesPassword: 'settings.aesPassword', isAutoListenClipboard: 'settings.isAutoListenClipboard',
rename: 'settings.rename', isListeningClipboard: 'settings.isListeningClipboard',
sync: 'settings.sync', showUpdateTip: 'settings.showUpdateTip',
tempDirPath: 'settings.tempDirPath', miniWindowPosition: 'settings.miniWindowPosition',
language: 'settings.language', miniWindowOntop: 'settings.miniWindowOntop',
customLink: 'settings.customLink', isHideDock: 'settings.isHideDock',
manualPageOpen: 'settings.manualPageOpen', mainWindowWidth: 'settings.mainWindowWidth',
encodeOutputURL: 'settings.encodeOutputURL', mainWindowHeight: 'settings.mainWindowHeight',
useShortUrl: 'settings.useShortUrl', autoCloseMiniWindow: 'settings.autoCloseMiniWindow',
shortUrlServer: 'settings.shortUrlServer', autoCloseMainWindow: 'settings.autoCloseMainWindow',
c1nToken: 'settings.c1nToken', isCustomMiniIcon: 'settings.isCustomMiniIcon',
cfWorkerHost: 'settings.cfWorkerHost', customMiniIcon: 'settings.customMiniIcon',
yourlsDomain: 'settings.yourlsDomain', startMode: 'settings.startMode',
yourlsSignature: 'settings.yourlsSignature', autoRename: 'settings.autoRename',
sinkDomain: 'settings.sinkDomain', deleteCloudFile: 'settings.deleteCloudFile',
sinkToken: 'settings.sinkToken', server: 'settings.server',
isSilentNotice: 'settings.isSilentNotice', serverKey: 'settings.serverKey',
proxy: 'settings.proxy', pasteStyle: 'settings.pasteStyle',
registry: 'settings.registry', aesPassword: 'settings.aesPassword',
autoCopy: 'settings.autoCopy', rename: 'settings.rename',
enableWebServer: 'settings.enableWebServer', sync: 'settings.sync',
webServerHost: 'settings.webServerHost', tempDirPath: 'settings.tempDirPath',
webServerPort: 'settings.webServerPort', language: 'settings.language',
webServerPath: 'settings.webServerPath', customLink: 'settings.customLink',
deleteLocalFile: 'settings.deleteLocalFile', manualPageOpen: 'settings.manualPageOpen',
uploadResultNotification: 'settings.uploadResultNotification', encodeOutputURL: 'settings.encodeOutputURL',
uploadNotification: 'settings.uploadNotification', useShortUrl: 'settings.useShortUrl',
useBuiltinClipboard: 'settings.useBuiltinClipboard', shortUrlServer: 'settings.shortUrlServer',
autoStart: 'settings.autoStart', c1nToken: 'settings.c1nToken',
autoImport: 'settings.autoImport', cfWorkerHost: 'settings.cfWorkerHost',
autoImportPicBed: 'settings.autoImportPicBed', yourlsDomain: 'settings.yourlsDomain',
enableSecondUploader: 'settings.enableSecondUploader' yourlsSignature: 'settings.yourlsSignature',
}, sinkDomain: 'settings.sinkDomain',
needReload: 'needReload', sinkToken: 'settings.sinkToken',
picgoPlugins: 'picgoPlugins', isSilentNotice: 'settings.isSilentNotice',
uploader: 'uploader', proxy: 'settings.proxy',
buildIn: { registry: 'settings.registry',
compress: 'buildIn.compress', autoCopy: 'settings.autoCopy',
watermark: 'buildIn.watermark', enableWebServer: 'settings.enableWebServer',
rename: 'buildIn.rename', webServerHost: 'settings.webServerHost',
skipProcess: 'buildIn.skipProcess' webServerPort: 'settings.webServerPort',
}, webServerPath: 'settings.webServerPath',
debug: 'debug', deleteLocalFile: 'settings.deleteLocalFile',
PICGO_ENV: 'PICGO_ENV' uploadResultNotification: 'settings.uploadResultNotification',
} uploadNotification: 'settings.uploadNotification',
useBuiltinClipboard: 'settings.useBuiltinClipboard',
autoStart: 'settings.autoStart',
autoImport: 'settings.autoImport',
autoImportPicBed: 'settings.autoImportPicBed',
enableSecondUploader: 'settings.enableSecondUploader'
},
needReload: 'needReload',
picgoPlugins: 'picgoPlugins',
uploader: 'uploader',
buildIn: {
compress: 'buildIn.compress',
watermark: 'buildIn.watermark',
rename: 'buildIn.rename',
skipProcess: 'buildIn.skipProcess'
},
debug: 'debug',
PICGO_ENV: 'PICGO_ENV'
}

View File

@@ -1,98 +1,98 @@
import crypto from 'node:crypto' import crypto from 'node:crypto'
import http, { AgentOptions } from 'node:http' import http, { AgentOptions } from 'node:http'
import https from 'node:https' import https from 'node:https'
import path from 'node:path' import path from 'node:path'
import querystring from 'node:querystring' import querystring from 'node:querystring'
import type { S3ClientConfig } from '@aws-sdk/client-s3' import type { S3ClientConfig } from '@aws-sdk/client-s3'
import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3' import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3'
import logger from '@core/picgo/logger' import logger from '@core/picgo/logger'
import { NodeHttpHandler } from '@smithy/node-http-handler' import { NodeHttpHandler } from '@smithy/node-http-handler'
import axios from 'axios' import axios from 'axios'
import type { ISftpPlistConfig } from 'piclist' import type { ISftpPlistConfig } from 'piclist'
import type { IObj, IStringKeyMap } from '#/types/types' import type { IObj, IStringKeyMap } from '#/types/types'
import { getAgent } from '~/manage/utils/common' import { getAgent } from '~/manage/utils/common'
import SSHClient from '~/utils/sshClient' import SSHClient from '~/utils/sshClient'
interface DogecloudTokenFull { interface DogecloudTokenFull {
Credentials: { Credentials: {
accessKeyId: string accessKeyId: string
secretAccessKey: string secretAccessKey: string
sessionToken: string sessionToken: string
} }
ExpiredAt: number ExpiredAt: number
Buckets: { Buckets: {
name: string name: string
s3Bucket: string s3Bucket: string
s3Endpoint: string s3Endpoint: string
}[] }[]
} }
const dogeRegionMap: IStringKeyMap = { const dogeRegionMap: IStringKeyMap = {
'ap-shanghai': '0', 'ap-shanghai': '0',
'ap-beijing': '1', 'ap-beijing': '1',
'ap-guangzhou': '2', 'ap-guangzhou': '2',
'ap-chengdu': '3' 'ap-chengdu': '3'
} }
async function dogecloudApi ( async function dogecloudApi(
apiPath: string, apiPath: string,
data = {}, data = {},
jsonMode: boolean = false, jsonMode: boolean = false,
accessKey: string, accessKey: string,
secretKey: string secretKey: string
) { ) {
const body = jsonMode ? JSON.stringify(data) : querystring.encode(data) const body = jsonMode ? JSON.stringify(data) : querystring.encode(data)
const sign = crypto const sign = crypto
.createHmac('sha1', secretKey) .createHmac('sha1', secretKey)
.update(Buffer.from(apiPath + '\n' + body, 'utf8')) .update(Buffer.from(apiPath + '\n' + body, 'utf8'))
.digest('hex') .digest('hex')
const authorization = `TOKEN ${accessKey}:${sign}` const authorization = `TOKEN ${accessKey}:${sign}`
try { try {
const res = await axios.request({ const res = await axios.request({
url: `https://api.dogecloud.com${apiPath}`, url: `https://api.dogecloud.com${apiPath}`,
method: 'POST', method: 'POST',
data: body, data: body,
responseType: 'json', responseType: 'json',
headers: { headers: {
'Content-Type': jsonMode ? 'application/json' : 'application/x-www-form-urlencoded', 'Content-Type': jsonMode ? 'application/json' : 'application/x-www-form-urlencoded',
Authorization: authorization Authorization: authorization
} }
}) })
if (res.data.code !== 200) { if (res.data.code !== 200) {
throw new Error('API Error') throw new Error('API Error')
} }
return res.data.data return res.data.data
} catch (err: any) { } catch (err: any) {
throw new Error('API Error') throw new Error('API Error')
} }
} }
async function getDogeToken (accessKey: string, secretKey: string): Promise<IObj | DogecloudTokenFull> { async function getDogeToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudTokenFull> {
try { try {
const data = await dogecloudApi( const data = await dogecloudApi(
'/auth/tmp_token.json', '/auth/tmp_token.json',
{ {
channel: 'OSS_FULL', channel: 'OSS_FULL',
scopes: ['*'] scopes: ['*']
}, },
true, true,
accessKey, accessKey,
secretKey secretKey
) )
return data return data
} catch (err: any) { } catch (err: any) {
logger.error(err) logger.error(err)
return {} return {}
} }
} }
export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode: boolean = false) { export async function removeFileFromS3InMain(configMap: IStringKeyMap, dogeMode: boolean = false) {
try { try {
const { const {
url: rawUrl, url: rawUrl,
type, type,
config: { config: {
accessKeyID, accessKeyID,
secretAccessKey, secretAccessKey,
@@ -103,14 +103,14 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
proxy, proxy,
urlPrefix urlPrefix
} }
} = configMap } = configMap
let { let {
imgUrl, imgUrl,
config: { region } config: { region }
} = configMap } = configMap
if (type === 'aws-s3' || type === 'aws-s3-plist') { if (type === 'aws-s3' || type === 'aws-s3-plist') {
imgUrl = rawUrl || imgUrl || '' imgUrl = rawUrl || imgUrl || ''
} }
let fileKey let fileKey
if (urlPrefix && imgUrl.startsWith(urlPrefix)) { if (urlPrefix && imgUrl.startsWith(urlPrefix)) {
const urlPrefixObj = new URL(urlPrefix) const urlPrefixObj = new URL(urlPrefix)
@@ -126,159 +126,159 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
if (pathStyleAccess) { if (pathStyleAccess) {
fileKey = fileKey.replace(/^[^/]+\//, '') fileKey = fileKey.replace(/^[^/]+\//, '')
} }
} }
const endpointUrl: string | undefined = endpoint const endpointUrl: string | undefined = endpoint
? /^https?:\/\//.test(endpoint) ? /^https?:\/\//.test(endpoint)
? endpoint ? endpoint
: `http://${endpoint}` : `http://${endpoint}`
: undefined : undefined
if (endpointUrl && endpointUrl.includes('cloudflarestorage')) { if (endpointUrl && endpointUrl.includes('cloudflarestorage')) {
region = region || 'auto' region = region || 'auto'
} }
const sslEnabled = endpointUrl ? endpointUrl.startsWith('https') : true const sslEnabled = endpointUrl ? endpointUrl.startsWith('https') : true
const agent = getAgent(proxy, sslEnabled) const agent = getAgent(proxy, sslEnabled)
const commonOptions: AgentOptions = { const commonOptions: AgentOptions = {
keepAlive: true, keepAlive: true,
keepAliveMsecs: 1000, keepAliveMsecs: 1000,
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined scheduling: 'lifo' as 'lifo' | 'fifo' | undefined
} }
const extraOptions = sslEnabled ? { rejectUnauthorized: !!rejectUnauthorized } : {} const extraOptions = sslEnabled ? { rejectUnauthorized: !!rejectUnauthorized } : {}
const handler = sslEnabled const handler = sslEnabled
? new NodeHttpHandler({ ? new NodeHttpHandler({
httpsAgent: agent.https httpsAgent: agent.https
? agent.https ? agent.https
: new https.Agent({ : new https.Agent({
...commonOptions, ...commonOptions,
...extraOptions ...extraOptions
}) })
}) })
: new NodeHttpHandler({ : new NodeHttpHandler({
httpAgent: agent.http httpAgent: agent.http
? agent.http ? agent.http
: new http.Agent({ : new http.Agent({
...commonOptions, ...commonOptions,
...extraOptions ...extraOptions
}) })
}) })
const s3Options: S3ClientConfig = { const s3Options: S3ClientConfig = {
credentials: { credentials: {
accessKeyId: accessKeyID, accessKeyId: accessKeyID,
secretAccessKey secretAccessKey
}, },
endpoint: endpointUrl, endpoint: endpointUrl,
tls: sslEnabled, tls: sslEnabled,
forcePathStyle: pathStyleAccess, forcePathStyle: pathStyleAccess,
region, region,
requestHandler: handler requestHandler: handler
} }
if (dogeMode) { if (dogeMode) {
s3Options.credentials = { s3Options.credentials = {
accessKeyId: configMap.config.accessKeyID, accessKeyId: configMap.config.accessKeyID,
secretAccessKey: configMap.config.secretAccessKey, secretAccessKey: configMap.config.secretAccessKey,
sessionToken: configMap.config.sessionToken sessionToken: configMap.config.sessionToken
} }
} }
let result: any let result: any
try { try {
fileKey = decodeURIComponent(fileKey) fileKey = decodeURIComponent(fileKey)
} catch (err: any) {} } catch (err: any) {}
try { try {
const client = new S3Client(s3Options) const client = new S3Client(s3Options)
const command = new DeleteObjectCommand({ const command = new DeleteObjectCommand({
Bucket: bucketName, Bucket: bucketName,
Key: fileKey Key: fileKey
}) })
result = await client.send(command) result = await client.send(command)
} catch (err: any) { } catch (err: any) {
s3Options.region = 'us-east-1' s3Options.region = 'us-east-1'
const client = new S3Client(s3Options) const client = new S3Client(s3Options)
const command = new DeleteObjectCommand({ const command = new DeleteObjectCommand({
Bucket: bucketName, Bucket: bucketName,
Key: fileKey Key: fileKey
}) })
result = await client.send(command) result = await client.send(command)
} }
return result.$metadata.httpStatusCode === 204 return result.$metadata.httpStatusCode === 204
} catch (err: any) { } catch (err: any) {
logger.error(err) logger.error(err)
return false return false
} }
} }
export async function removeFileFromDogeInMain (configMap: IStringKeyMap) { export async function removeFileFromDogeInMain(configMap: IStringKeyMap) {
try { try {
const { const {
config: { bucketName, AccessKey, SecretKey } config: { bucketName, AccessKey, SecretKey }
} = configMap } = configMap
const token = (await getDogeToken(AccessKey, SecretKey)) as DogecloudTokenFull const token = (await getDogeToken(AccessKey, SecretKey)) as DogecloudTokenFull
const bucket = token.Buckets?.find(item => item.name === bucketName || item.s3Bucket === bucketName) const bucket = token.Buckets?.find(item => item.name === bucketName || item.s3Bucket === bucketName)
const newConfigMap = { ...configMap } const newConfigMap = { ...configMap }
newConfigMap.config = { newConfigMap.config = {
...newConfigMap.config, ...newConfigMap.config,
accessKeyID: token.Credentials?.accessKeyId, accessKeyID: token.Credentials?.accessKeyId,
secretAccessKey: token.Credentials?.secretAccessKey, secretAccessKey: token.Credentials?.secretAccessKey,
sessionToken: token.Credentials?.sessionToken, sessionToken: token.Credentials?.sessionToken,
endpoint: bucket?.s3Endpoint, endpoint: bucket?.s3Endpoint,
region: dogeRegionMap[bucket?.s3Endpoint?.split('.')[1] || 'ap-shanghai'], region: dogeRegionMap[bucket?.s3Endpoint?.split('.')[1] || 'ap-shanghai'],
bucketName: bucket?.s3Bucket bucketName: bucket?.s3Bucket
} }
return await removeFileFromS3InMain(newConfigMap, true) return await removeFileFromS3InMain(newConfigMap, true)
} catch (err: any) { } catch (err: any) {
logger.error(err) logger.error(err)
return false return false
} }
} }
function createHuaweiAuthorization ( function createHuaweiAuthorization(
bucketName: string, bucketName: string,
path: string, path: string,
fileName: string, fileName: string,
accessKey: string, accessKey: string,
secretKey: string, secretKey: string,
date: string = new Date().toUTCString() date: string = new Date().toUTCString()
) { ) {
const strToSign = `DELETE\n\n\n${date}\n/${bucketName}${path}/${fileName}` const strToSign = `DELETE\n\n\n${date}\n/${bucketName}${path}/${fileName}`
const singature = crypto.createHmac('sha1', secretKey).update(strToSign).digest('base64') const singature = crypto.createHmac('sha1', secretKey).update(strToSign).digest('base64')
return `OBS ${accessKey}:${singature}` return `OBS ${accessKey}:${singature}`
} }
export async function removeFileFromHuaweiInMain (configMap: IStringKeyMap) { export async function removeFileFromHuaweiInMain(configMap: IStringKeyMap) {
const { fileName, config } = configMap const { fileName, config } = configMap
const { accessKeyId, accessKeySecret, bucketName, endpoint } = config const { accessKeyId, accessKeySecret, bucketName, endpoint } = config
let path = config.path || '/' let path = config.path || '/'
path = `/${path.replace(/^\/+|\/+$/, '')}` path = `/${path.replace(/^\/+|\/+$/, '')}`
path = path === '/' ? '' : path path = path === '/' ? '' : path
const date = new Date().toUTCString() const date = new Date().toUTCString()
const authorization = createHuaweiAuthorization(bucketName, path, fileName, accessKeyId, accessKeySecret, date) const authorization = createHuaweiAuthorization(bucketName, path, fileName, accessKeyId, accessKeySecret, date)
try { try {
const res = await axios.request({ const res = await axios.request({
url: `https://${bucketName}.${endpoint}${encodeURI(path)}/${encodeURIComponent(fileName)}`, url: `https://${bucketName}.${endpoint}${encodeURI(path)}/${encodeURIComponent(fileName)}`,
method: 'DELETE', method: 'DELETE',
responseType: 'json', responseType: 'json',
headers: { headers: {
Host: `${bucketName}.${endpoint}`, Host: `${bucketName}.${endpoint}`,
Date: date, Date: date,
Authorization: authorization Authorization: authorization
} }
}) })
return res.status === 204 return res.status === 204
} catch (error: any) { } catch (error: any) {
logger.error(error) logger.error(error)
return false return false
} }
} }
export async function removeFileFromSFTPInMain (config: ISftpPlistConfig, fileName: string) { export async function removeFileFromSFTPInMain(config: ISftpPlistConfig, fileName: string) {
try { try {
const client = SSHClient.instance const client = SSHClient.instance
await client.connect(config) await client.connect(config)
const uploadPath = `/${config.uploadPath || ''}/`.replace(/\/+/g, '/') const uploadPath = `/${config.uploadPath || ''}/`.replace(/\/+/g, '/')
const remote = path.join(uploadPath, fileName) const remote = path.join(uploadPath, fileName)
const deleteResult = await client.deleteFileSFTP(config, remote) const deleteResult = await client.deleteFileSFTP(config, remote)
client.close() client.close()
return deleteResult return deleteResult
} catch (err: any) { } catch (err: any) {
logger.error(err) logger.error(err)
return false return false
} }
} }

View File

@@ -1,8 +1,8 @@
export const deleteLog = (fileName?: string, type?: string, isSuccess = true, msg?: string) => { export const deleteLog = (fileName?: string, type?: string, isSuccess = true, msg?: string) => {
console.log(`Delete ${fileName} on ${type} ${isSuccess ? 'success' : 'failed'}, message: ${msg || ''}`) console.log(`Delete ${fileName} on ${type} ${isSuccess ? 'success' : 'failed'}, message: ${msg || ''}`)
} }
export const deleteFailedLog = (fileName: string, type: string, error: any) => { export const deleteFailedLog = (fileName: string, type: string, error: any) => {
deleteLog(fileName, type, false) deleteLog(fileName, type, false)
console.error(error) console.error(error)
} }

View File

@@ -8,11 +8,11 @@ const AUTH_KEY_VALUE_RE = /(\w+)=["']?([^'"]{1,10000})["']?/
let NC = 0 let NC = 0
const NC_PAD = '00000000' const NC_PAD = '00000000'
function md5 (text: crypto.BinaryLike) { function md5(text: crypto.BinaryLike) {
return crypto.createHash('md5').update(text).digest('hex') return crypto.createHash('md5').update(text).digest('hex')
} }
export function digestAuthHeader ( export function digestAuthHeader(
method: string, method: string,
uri: string, uri: string,
wwwAuthenticate: string, wwwAuthenticate: string,
@@ -70,7 +70,7 @@ export function digestAuthHeader (
return authstring return authstring
} }
export async function getAuthHeader (method: string, host: string, uri: string, username: string, password: string) { export async function getAuthHeader(method: string, host: string, uri: string, username: string, password: string) {
try { try {
await axios.get(`${host}${uri}`) await axios.get(`${host}${uri}`)
} catch (error: any) { } catch (error: any) {

View File

@@ -1,242 +1,242 @@
export const ILogType = { export const ILogType = {
success: 'success', success: 'success',
info: 'info', info: 'info',
warn: 'warn', warn: 'warn',
error: 'error' error: 'error'
} }
export const ICOREBuildInEvent = { export const ICOREBuildInEvent = {
UPLOAD_PROGRESS: 'uploadProgress', UPLOAD_PROGRESS: 'uploadProgress',
FAILED: 'failed', FAILED: 'failed',
BEFORE_TRANSFORM: 'beforeTransform', BEFORE_TRANSFORM: 'beforeTransform',
BEFORE_UPLOAD: 'beforeUpload', BEFORE_UPLOAD: 'beforeUpload',
AFTER_UPLOAD: 'afterUpload', AFTER_UPLOAD: 'afterUpload',
FINISHED: 'finished', FINISHED: 'finished',
INSTALL: 'install', INSTALL: 'install',
UNINSTALL: 'uninstall', UNINSTALL: 'uninstall',
UPDATE: 'update', UPDATE: 'update',
NOTIFICATION: 'notification', NOTIFICATION: 'notification',
REMOVE: 'remove' REMOVE: 'remove'
} }
export const IPicGoHelperType = { export const IPicGoHelperType = {
afterUploadPlugins: 'afterUploadPlugins', afterUploadPlugins: 'afterUploadPlugins',
beforeTransformPlugins: 'beforeTransformPlugins', beforeTransformPlugins: 'beforeTransformPlugins',
beforeUploadPlugins: 'beforeUploadPlugins', beforeUploadPlugins: 'beforeUploadPlugins',
uploader: 'uploader', uploader: 'uploader',
transformer: 'transformer' transformer: 'transformer'
} }
export const IPasteStyle = { export const IPasteStyle = {
MARKDOWN: 'markdown', MARKDOWN: 'markdown',
HTML: 'HTML', HTML: 'HTML',
URL: 'URL', URL: 'URL',
UBB: 'UBB', UBB: 'UBB',
CUSTOM: 'Custom' CUSTOM: 'Custom'
} }
export const IWindowList = { export const IWindowList = {
SETTING_WINDOW: 'SETTING_WINDOW', SETTING_WINDOW: 'SETTING_WINDOW',
TRAY_WINDOW: 'TRAY_WINDOW', TRAY_WINDOW: 'TRAY_WINDOW',
MINI_WINDOW: 'MINI_WINDOW', MINI_WINDOW: 'MINI_WINDOW',
RENAME_WINDOW: 'RENAME_WINDOW', RENAME_WINDOW: 'RENAME_WINDOW',
TOOLBOX_WINDOW: 'TOOLBOX_WINDOW' TOOLBOX_WINDOW: 'TOOLBOX_WINDOW'
} }
export const IRemoteNoticeActionType = { export const IRemoteNoticeActionType = {
OPEN_URL: 'OPEN_URL', OPEN_URL: 'OPEN_URL',
SHOW_NOTICE: 'SHOW_NOTICE', // notification SHOW_NOTICE: 'SHOW_NOTICE', // notification
SHOW_DIALOG: 'SHOW_DIALOG', // dialog notice SHOW_DIALOG: 'SHOW_DIALOG', // dialog notice
COMMON: 'COMMON', COMMON: 'COMMON',
VOID: 'VOID', // do nothing VOID: 'VOID', // do nothing
SHOW_MESSAGE_BOX: 'SHOW_MESSAGE_BOX' SHOW_MESSAGE_BOX: 'SHOW_MESSAGE_BOX'
} }
export const IRemoteNoticeTriggerHook = { export const IRemoteNoticeTriggerHook = {
APP_START: 'APP_START', APP_START: 'APP_START',
SETTING_WINDOW_OPEN: 'SETTING_WINDOW_OPEN' SETTING_WINDOW_OPEN: 'SETTING_WINDOW_OPEN'
} }
export const IRemoteNoticeTriggerCount = { export const IRemoteNoticeTriggerCount = {
ONCE: 'ONCE', // default ONCE: 'ONCE', // default
ALWAYS: 'ALWAYS' ALWAYS: 'ALWAYS'
} }
export const IRPCType = { export const IRPCType = {
INVOKE: 'INVOKE', INVOKE: 'INVOKE',
SEND: 'SEND' SEND: 'SEND'
} }
export const IRPCActionType = { export const IRPCActionType = {
// system rpc // system rpc
RELOAD_APP: 'RELOAD_APP', RELOAD_APP: 'RELOAD_APP',
OPEN_URL: 'OPEN_URL', OPEN_URL: 'OPEN_URL',
OPEN_FILE: 'OPEN_FILE', OPEN_FILE: 'OPEN_FILE',
HIDE_DOCK: 'HIDE_DOCK', HIDE_DOCK: 'HIDE_DOCK',
SET_CURRENT_LANGUAGE: 'SET_CURRENT_LANGUAGE', SET_CURRENT_LANGUAGE: 'SET_CURRENT_LANGUAGE',
OPEN_WINDOW: 'OPEN_WINDOW', OPEN_WINDOW: 'OPEN_WINDOW',
OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW', OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW',
CLOSE_WINDOW: 'CLOSE_WINDOW', CLOSE_WINDOW: 'CLOSE_WINDOW',
MINIMIZE_WINDOW: 'MINIMIZE_WINDOW', MINIMIZE_WINDOW: 'MINIMIZE_WINDOW',
SHOW_MINI_PAGE_MENU: 'SHOW_MINI_PAGE_MENU', SHOW_MINI_PAGE_MENU: 'SHOW_MINI_PAGE_MENU',
SHOW_MAIN_PAGE_MENU: 'SHOW_MAIN_PAGE_MENU', SHOW_MAIN_PAGE_MENU: 'SHOW_MAIN_PAGE_MENU',
SHOW_UPLOAD_PAGE_MENU: 'SHOW_UPLOAD_PAGE_MENU', SHOW_UPLOAD_PAGE_MENU: 'SHOW_UPLOAD_PAGE_MENU',
SHOW_SECOND_UPLOADER_MENU: 'SHOW_SECOND_UPLOADER_MENU', SHOW_SECOND_UPLOADER_MENU: 'SHOW_SECOND_UPLOADER_MENU',
SHOW_PLUGIN_PAGE_MENU: 'SHOW_PLUGIN_PAGE_MENU', SHOW_PLUGIN_PAGE_MENU: 'SHOW_PLUGIN_PAGE_MENU',
SET_MINI_WINDOW_POS: 'SET_MINI_WINDOW_POS', SET_MINI_WINDOW_POS: 'SET_MINI_WINDOW_POS',
MINI_WINDOW_ON_TOP: 'MINI_WINDOW_ON_TOP', MINI_WINDOW_ON_TOP: 'MINI_WINDOW_ON_TOP',
MAIN_WINDOW_ON_TOP: 'MAIN_WINDOW_ON_TOP', MAIN_WINDOW_ON_TOP: 'MAIN_WINDOW_ON_TOP',
UPDATE_MINI_WINDOW_ICON: 'UPDATE_MINI_WINDOW_ICON', UPDATE_MINI_WINDOW_ICON: 'UPDATE_MINI_WINDOW_ICON',
REFRESH_SETTING_WINDOW: 'REFRESH_SETTING_WINDOW', REFRESH_SETTING_WINDOW: 'REFRESH_SETTING_WINDOW',
// picbed RPC // picbed RPC
PICBED_GET_PICBED_CONFIG: 'PICBED_GET_PICBED_CONFIG', PICBED_GET_PICBED_CONFIG: 'PICBED_GET_PICBED_CONFIG',
PICBED_GET_CONFIG_LIST: 'PICBED_GET_CONFIG_LIST', PICBED_GET_CONFIG_LIST: 'PICBED_GET_CONFIG_LIST',
PICBED_DELETE_CONFIG: 'PICBED_DELETE_CONFIG', PICBED_DELETE_CONFIG: 'PICBED_DELETE_CONFIG',
UPLOADER_CHANGE_CURRENT: 'UPLOADER_CHANGE_CURRENT', UPLOADER_CHANGE_CURRENT: 'UPLOADER_CHANGE_CURRENT',
UPLOADER_SELECT: 'UPLOADER_SELECT', UPLOADER_SELECT: 'UPLOADER_SELECT',
UPLOADER_UPDATE_CONFIG: 'UPLOADER_UPDATE_CONFIG', UPLOADER_UPDATE_CONFIG: 'UPLOADER_UPDATE_CONFIG',
UPLOADER_RESET_CONFIG: 'UPLOADER_RESET_CONFIG', UPLOADER_RESET_CONFIG: 'UPLOADER_RESET_CONFIG',
DELETE_ALL_API: 'DELETE_ALL_API', DELETE_ALL_API: 'DELETE_ALL_API',
// toolbox rpc // toolbox rpc
TOOLBOX_CHECK: 'TOOLBOX_CHECK', TOOLBOX_CHECK: 'TOOLBOX_CHECK',
TOOLBOX_CHECK_RES: 'TOOLBOX_CHECK_RES', TOOLBOX_CHECK_RES: 'TOOLBOX_CHECK_RES',
TOOLBOX_CHECK_FIX: 'TOOLBOX_CHECK_FIX', TOOLBOX_CHECK_FIX: 'TOOLBOX_CHECK_FIX',
// main app setting rpc // main app setting rpc
PICLIST_GET_CONFIG: 'PICLIST_GET_CONFIG', PICLIST_GET_CONFIG: 'PICLIST_GET_CONFIG',
PICLIST_GET_CONFIG_SYNC: 'PICLIST_GET_CONFIG_SYNC', PICLIST_GET_CONFIG_SYNC: 'PICLIST_GET_CONFIG_SYNC',
PICLIST_SAVE_CONFIG: 'PICLIST_SAVE_CONFIG', PICLIST_SAVE_CONFIG: 'PICLIST_SAVE_CONFIG',
PICLIST_OPEN_FILE: 'PICLIST_OPEN_FILE', PICLIST_OPEN_FILE: 'PICLIST_OPEN_FILE',
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY', PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
PICLIST_AUTO_START: 'PICLIST_AUTO_START', PICLIST_AUTO_START: 'PICLIST_AUTO_START',
// shortkey setting rpc // shortkey setting rpc
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE', SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',
SHORTKEY_BIND_OR_UNBIND: 'SHORTKEY_BIND_OR_UNBIND', SHORTKEY_BIND_OR_UNBIND: 'SHORTKEY_BIND_OR_UNBIND',
SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE: 'SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE', SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE: 'SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE',
// configuration setting rpc // configuration setting rpc
CONFIGURE_MIGRATE_FROM_PICGO: 'CONFIGURE_MIGRATE_FROM_PICGO', CONFIGURE_MIGRATE_FROM_PICGO: 'CONFIGURE_MIGRATE_FROM_PICGO',
CONFIGURE_UPLOAD_COMMON_CONFIG: 'CONFIGURE_UPLOAD_COMMON_CONFIG', CONFIGURE_UPLOAD_COMMON_CONFIG: 'CONFIGURE_UPLOAD_COMMON_CONFIG',
CONFIGURE_UPLOAD_MANAGE_CONFIG: 'CONFIGURE_UPLOAD_MANAGE_CONFIG', CONFIGURE_UPLOAD_MANAGE_CONFIG: 'CONFIGURE_UPLOAD_MANAGE_CONFIG',
CONFIGURE_UPLOAD_ALL_CONFIG: 'CONFIGURE_UPLOAD_ALL_CONFIG', CONFIGURE_UPLOAD_ALL_CONFIG: 'CONFIGURE_UPLOAD_ALL_CONFIG',
CONFIGURE_DOWNLOAD_COMMON_CONFIG: 'CONFIGURE_DOWNLOAD_COMMON_CONFIG', CONFIGURE_DOWNLOAD_COMMON_CONFIG: 'CONFIGURE_DOWNLOAD_COMMON_CONFIG',
CONFIGURE_DOWNLOAD_MANAGE_CONFIG: 'CONFIGURE_DOWNLOAD_MANAGE_CONFIG', CONFIGURE_DOWNLOAD_MANAGE_CONFIG: 'CONFIGURE_DOWNLOAD_MANAGE_CONFIG',
CONFIGURE_DOWNLOAD_ALL_CONFIG: 'CONFIGURE_DOWNLOAD_ALL_CONFIG', CONFIGURE_DOWNLOAD_ALL_CONFIG: 'CONFIGURE_DOWNLOAD_ALL_CONFIG',
// advanced setting rpc // advanced setting rpc
ADVANCED_UPDATE_SERVER: 'ADVANCED_UPDATE_SERVER', ADVANCED_UPDATE_SERVER: 'ADVANCED_UPDATE_SERVER',
ADVANCED_STOP_WEB_SERVER: 'ADVANCED_STOP_WEB_SERVER', ADVANCED_STOP_WEB_SERVER: 'ADVANCED_STOP_WEB_SERVER',
ADVANCED_RESTART_WEB_SERVER: 'ADVANCED_RESTART_WEB_SERVER', ADVANCED_RESTART_WEB_SERVER: 'ADVANCED_RESTART_WEB_SERVER',
// upload and main page rpc // upload and main page rpc
MAIN_GET_PICBED: 'MAIN_GET_PICBED', MAIN_GET_PICBED: 'MAIN_GET_PICBED',
UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE: 'UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE', UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE: 'UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE',
UPLOAD_CHOOSED_FILES: 'UPLOAD_CHOOSED_FILES', UPLOAD_CHOOSED_FILES: 'UPLOAD_CHOOSED_FILES',
// gallery rpc // gallery rpc
GALLERY_PASTE_TEXT: 'GALLERY_PASTE_TEXT', GALLERY_PASTE_TEXT: 'GALLERY_PASTE_TEXT',
GALLERY_REMOVE_FILES: 'GALLERY_REMOVE_FILES', GALLERY_REMOVE_FILES: 'GALLERY_REMOVE_FILES',
GALLERY_GET_DB: 'GALLERY_GET_DB', GALLERY_GET_DB: 'GALLERY_GET_DB',
GALLERY_GET_BY_ID_DB: 'GALLERY_GET_BY_ID_DB', GALLERY_GET_BY_ID_DB: 'GALLERY_GET_BY_ID_DB',
GALLERY_UPDATE_BY_ID_DB: 'GALLERY_UPDATE_BY_ID_DB', GALLERY_UPDATE_BY_ID_DB: 'GALLERY_UPDATE_BY_ID_DB',
GALLERY_REMOVE_BY_ID_DB: 'GALLERY_REMOVE_BY_ID_DB', GALLERY_REMOVE_BY_ID_DB: 'GALLERY_REMOVE_BY_ID_DB',
GALLERY_INSERT_DB: 'GALLERY_INSERT_DB', GALLERY_INSERT_DB: 'GALLERY_INSERT_DB',
GALLERY_INSERT_DB_BATCH: 'GALLERY_INSERT_DB_BATCH', GALLERY_INSERT_DB_BATCH: 'GALLERY_INSERT_DB_BATCH',
// plugin rpc // plugin rpc
PLUGIN_GET_LIST: 'PLUGIN_GET_LIST', PLUGIN_GET_LIST: 'PLUGIN_GET_LIST',
PLUGIN_INSTALL: 'PLUGIN_INSTALL', PLUGIN_INSTALL: 'PLUGIN_INSTALL',
PLUGIN_IMPORT_LOCAL: 'PLUGIN_IMPORT_LOCAL', PLUGIN_IMPORT_LOCAL: 'PLUGIN_IMPORT_LOCAL',
PLUGIN_UPDATE_ALL: 'PLUGIN_UPDATE_ALL', PLUGIN_UPDATE_ALL: 'PLUGIN_UPDATE_ALL',
// tray rpc // tray rpc
TRAY_SET_TOOL_TIP: 'TRAY_SET_TOOL_TIP', TRAY_SET_TOOL_TIP: 'TRAY_SET_TOOL_TIP',
TRAY_GET_SHORT_URL: 'TRAY_GET_SHORT_URL', TRAY_GET_SHORT_URL: 'TRAY_GET_SHORT_URL',
TRAY_UPLOAD_CLIPBOARD_FILES: 'TRAY_UPLOAD_CLIPBOARD_FILES', TRAY_UPLOAD_CLIPBOARD_FILES: 'TRAY_UPLOAD_CLIPBOARD_FILES',
// manage rpc // manage rpc
MANAGE_GET_CONFIG: 'MANAGE_GET_CONFIG', MANAGE_GET_CONFIG: 'MANAGE_GET_CONFIG',
MANAGE_SAVE_CONFIG: 'MANAGE_SAVE_CONFIG', MANAGE_SAVE_CONFIG: 'MANAGE_SAVE_CONFIG',
MANAGE_REMOVE_CONFIG: 'MANAGE_REMOVE_CONFIG', MANAGE_REMOVE_CONFIG: 'MANAGE_REMOVE_CONFIG',
MANAGE_GET_BUCKET_LIST: 'MANAGE_GET_BUCKET_LIST', MANAGE_GET_BUCKET_LIST: 'MANAGE_GET_BUCKET_LIST',
MANAGE_GET_BUCKET_LIST_BACKSTAGE: 'MANAGE_GET_BUCKET_LIST_BACKSTAGE', MANAGE_GET_BUCKET_LIST_BACKSTAGE: 'MANAGE_GET_BUCKET_LIST_BACKSTAGE',
MANAGE_GET_BUCKET_LIST_RECURSIVELY: 'MANAGE_GET_BUCKET_LIST_RECURSIVELY', MANAGE_GET_BUCKET_LIST_RECURSIVELY: 'MANAGE_GET_BUCKET_LIST_RECURSIVELY',
MANAGE_CREATE_BUCKET: 'MANAGE_CREATE_BUCKET', MANAGE_CREATE_BUCKET: 'MANAGE_CREATE_BUCKET',
MANAGE_GET_BUCKET_FILE_LIST: 'MANAGE_GET_BUCKET_FILE_LIST', MANAGE_GET_BUCKET_FILE_LIST: 'MANAGE_GET_BUCKET_FILE_LIST',
MANAGE_GET_BUCKET_DOMAIN: 'MANAGE_GET_BUCKET_DOMAIN', MANAGE_GET_BUCKET_DOMAIN: 'MANAGE_GET_BUCKET_DOMAIN',
MANAGE_SET_BUCKET_ACL_POLICY: 'MANAGE_SET_BUCKET_ACL_POLICY', MANAGE_SET_BUCKET_ACL_POLICY: 'MANAGE_SET_BUCKET_ACL_POLICY',
MANAGE_RENAME_BUCKET_FILE: 'MANAGE_RENAME_BUCKET_FILE', MANAGE_RENAME_BUCKET_FILE: 'MANAGE_RENAME_BUCKET_FILE',
MANAGE_DELETE_BUCKET_FILE: 'MANAGE_DELETE_BUCKET_FILE', MANAGE_DELETE_BUCKET_FILE: 'MANAGE_DELETE_BUCKET_FILE',
MANAGE_DELETE_BUCKET_FOLDER: 'MANAGE_DELETE_BUCKET_FOLDER', MANAGE_DELETE_BUCKET_FOLDER: 'MANAGE_DELETE_BUCKET_FOLDER',
MANAGE_GET_PRE_SIGNED_URL: 'MANAGE_GET_PRE_SIGNED_URL', MANAGE_GET_PRE_SIGNED_URL: 'MANAGE_GET_PRE_SIGNED_URL',
MANAGE_UPLOAD_BUCKET_FILE: 'MANAGE_UPLOAD_BUCKET_FILE', MANAGE_UPLOAD_BUCKET_FILE: 'MANAGE_UPLOAD_BUCKET_FILE',
MANAGE_DOWNLOAD_BUCKET_FILE: 'MANAGE_DOWNLOAD_BUCKET_FILE', MANAGE_DOWNLOAD_BUCKET_FILE: 'MANAGE_DOWNLOAD_BUCKET_FILE',
MANAGE_CREATE_BUCKET_FOLDER: 'MANAGE_CREATE_BUCKET_FOLDER', MANAGE_CREATE_BUCKET_FOLDER: 'MANAGE_CREATE_BUCKET_FOLDER',
MANAGE_OPEN_FILE_SELECT_DIALOG: 'MANAGE_OPEN_FILE_SELECT_DIALOG', MANAGE_OPEN_FILE_SELECT_DIALOG: 'MANAGE_OPEN_FILE_SELECT_DIALOG',
MANAGE_GET_UPLOAD_TASK_LIST: 'MANAGE_GET_UPLOAD_TASK_LIST', MANAGE_GET_UPLOAD_TASK_LIST: 'MANAGE_GET_UPLOAD_TASK_LIST',
MANAGE_GET_DOWNLOAD_TASK_LIST: 'MANAGE_GET_DOWNLOAD_TASK_LIST', MANAGE_GET_DOWNLOAD_TASK_LIST: 'MANAGE_GET_DOWNLOAD_TASK_LIST',
MANAGE_DELETE_UPLOADED_TASK: 'MANAGE_DELETE_UPLOADED_TASK', MANAGE_DELETE_UPLOADED_TASK: 'MANAGE_DELETE_UPLOADED_TASK',
MANAGE_DELETE_ALL_UPLOADED_TASK: 'MANAGE_DELETE_ALL_UPLOADED_TASK', MANAGE_DELETE_ALL_UPLOADED_TASK: 'MANAGE_DELETE_ALL_UPLOADED_TASK',
MANAGE_DELETE_DOWNLOADED_TASK: 'MANAGE_DELETE_DOWNLOADED_TASK', MANAGE_DELETE_DOWNLOADED_TASK: 'MANAGE_DELETE_DOWNLOADED_TASK',
MANAGE_DELETE_ALL_DOWNLOADED_TASK: 'MANAGE_DELETE_ALL_DOWNLOADED_TASK', MANAGE_DELETE_ALL_DOWNLOADED_TASK: 'MANAGE_DELETE_ALL_DOWNLOADED_TASK',
MANAGE_SELECT_DOWNLOAD_FOLDER: 'MANAGE_SELECT_DOWNLOAD_FOLDER', MANAGE_SELECT_DOWNLOAD_FOLDER: 'MANAGE_SELECT_DOWNLOAD_FOLDER',
MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER: 'MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER', MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER: 'MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER',
MANAGE_OPEN_DOWNLOADED_FOLDER: 'MANAGE_OPEN_DOWNLOADED_FOLDER', MANAGE_OPEN_DOWNLOADED_FOLDER: 'MANAGE_OPEN_DOWNLOADED_FOLDER',
MANAGE_OPEN_LOCAL_FILE: 'MANAGE_OPEN_LOCAL_FILE', MANAGE_OPEN_LOCAL_FILE: 'MANAGE_OPEN_LOCAL_FILE',
MANAGE_DOWNLOAD_FILE_FROM_URL: 'MANAGE_DOWNLOAD_FILE_FROM_URL', MANAGE_DOWNLOAD_FILE_FROM_URL: 'MANAGE_DOWNLOAD_FILE_FROM_URL',
MANAGE_CONVERT_PATH_TO_BASE64: 'MANAGE_CONVERT_PATH_TO_BASE64' MANAGE_CONVERT_PATH_TO_BASE64: 'MANAGE_CONVERT_PATH_TO_BASE64'
} }
export const IToolboxItemType = { export const IToolboxItemType = {
IS_CONFIG_FILE_BROKEN: 'IS_CONFIG_FILE_BROKEN', IS_CONFIG_FILE_BROKEN: 'IS_CONFIG_FILE_BROKEN',
IS_GALLERY_FILE_BROKEN: 'IS_GALLERY_FILE_BROKEN', IS_GALLERY_FILE_BROKEN: 'IS_GALLERY_FILE_BROKEN',
HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD: 'HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD', HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD: 'HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD',
HAS_PROBLEM_WITH_PROXY: 'HAS_PROBLEM_WITH_PROXY' HAS_PROBLEM_WITH_PROXY: 'HAS_PROBLEM_WITH_PROXY'
} }
export const IToolboxItemCheckStatus = { export const IToolboxItemCheckStatus = {
INIT: 'init', INIT: 'init',
LOADING: 'loading', LOADING: 'loading',
SUCCESS: 'success', SUCCESS: 'success',
ERROR: 'error' ERROR: 'error'
} }
export const ISartMode = { export const ISartMode = {
QUIET: 'quiet', QUIET: 'quiet',
MINI: 'mini', MINI: 'mini',
MAIN: 'main', MAIN: 'main',
NO_TRAY: 'no-tray' NO_TRAY: 'no-tray'
} }
export const II18nLanguage = { export const II18nLanguage = {
ZH_CN: 'zh-CN', ZH_CN: 'zh-CN',
ZH_TW: 'zh-TW', ZH_TW: 'zh-TW',
EN: 'en' EN: 'en'
} }
export const IShortUrlServer = { export const IShortUrlServer = {
C1N: 'c1n', C1N: 'c1n',
YOURLS: 'yourls', YOURLS: 'yourls',
CFWORKER: 'cf_worker', CFWORKER: 'cf_worker',
SINK: 'sink' SINK: 'sink'
} }
export const commonTaskStatus = { export const commonTaskStatus = {
queuing: 'queuing', queuing: 'queuing',
failed: 'failed', failed: 'failed',
canceled: 'canceled', canceled: 'canceled',
paused: 'paused' paused: 'paused'
} }
// manage task status // manage task status
export const uploadTaskSpecialStatus = { export const uploadTaskSpecialStatus = {
uploading: 'uploading', uploading: 'uploading',
uploaded: 'uploaded' uploaded: 'uploaded'
} }
export const downloadTaskSpecialStatus = { export const downloadTaskSpecialStatus = {
downloading: 'downloading', downloading: 'downloading',
downloaded: 'downloaded' downloaded: 'downloaded'
} }

View File

@@ -22,7 +22,7 @@ const parseVersion = (plist: string) => {
return matches[1].replace('10.16', '11') return matches[1].replace('10.16', '11')
} }
export function macOSVersion (): string { export function macOSVersion(): string {
if (!isMacOS) return '' if (!isMacOS) return ''
if (!version) { if (!version) {
@@ -43,7 +43,7 @@ if (process.env.NODE_ENV === 'test') {
macOSVersion._parseVersion = parseVersion macOSVersion._parseVersion = parseVersion
} }
export function isMacOSVersion (semverRange: string) { export function isMacOSVersion(semverRange: string) {
if (!isMacOS) { if (!isMacOS) {
return false return false
} }
@@ -53,7 +53,7 @@ export function isMacOSVersion (semverRange: string) {
return semver.satisfies(macOSVersion(), clean(semverRange)) return semver.satisfies(macOSVersion(), clean(semverRange))
} }
export function isMacOSVersionGreaterThanOrEqualTo (version: string) { export function isMacOSVersionGreaterThanOrEqualTo(version: string) {
if (!isMacOS) { if (!isMacOS) {
return false return false
} }
@@ -63,7 +63,7 @@ export function isMacOSVersionGreaterThanOrEqualTo (version: string) {
return semver.gte(macOSVersion(), clean(version)) return semver.gte(macOSVersion(), clean(version))
} }
export function assertMacOSVersion (semverRange: string) { export function assertMacOSVersion(semverRange: string) {
semverRange = semverRange.replace('10.16', '11') semverRange = semverRange.replace('10.16', '11')
if (!isMacOSVersion(semverRange)) { if (!isMacOSVersion(semverRange)) {
@@ -71,7 +71,7 @@ export function assertMacOSVersion (semverRange: string) {
} }
} }
export function assertMacOSVersionGreaterThanOrEqualTo (version: string) { export function assertMacOSVersionGreaterThanOrEqualTo(version: string) {
version = version.replace('10.16', '11') version = version.replace('10.16', '11')
if (!isMacOSVersionGreaterThanOrEqualTo(version)) { if (!isMacOSVersionGreaterThanOrEqualTo(version)) {
@@ -79,7 +79,7 @@ export function assertMacOSVersionGreaterThanOrEqualTo (version: string) {
} }
} }
export function assertMacOS () { export function assertMacOS() {
if (!isMacOS) { if (!isMacOS) {
throw new Error('Requires macOS') throw new Error('Requires macOS')
} }

View File

@@ -1,7 +1,13 @@
import picgo from '@core/picgo' import picgo from '@core/picgo'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import type { IPicGoPluginConfig, IPicGoPluginOriginConfig, IStringKeyMap, IUploaderConfigItem, IUploaderConfigListItem } from '#/types/types' import type {
IPicGoPluginConfig,
IPicGoPluginOriginConfig,
IStringKeyMap,
IUploaderConfigItem,
IUploaderConfigListItem
} from '#/types/types'
import { setTrayToolTip, trimValues } from '~/utils/common' import { setTrayToolTip, trimValues } from '~/utils/common'
import { configPaths } from '~/utils/configPaths' import { configPaths } from '~/utils/configPaths'

View File

@@ -1,3 +1,3 @@
import type { IAppNotification } from '#/types/types' import type { IAppNotification } from '#/types/types'
export const notificationList: IAppNotification[] = [] export const notificationList: IAppNotification[] = []

View File

@@ -1,31 +1,33 @@
export class MemoryMonitor { export class MemoryMonitor {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
private static interval: NodeJS.Timeout | null = null private static interval: NodeJS.Timeout | null = null
static start (intervalMs: number = 30000) { static start(intervalMs: number = 30000) {
if (this.interval) return if (this.interval) return
this.interval = setInterval(() => { this.interval = setInterval(() => {
const memUsage = process.memoryUsage() const memUsage = process.memoryUsage()
const mbUsage = { const mbUsage = {
rss: Math.round(memUsage.rss / 1024 / 1024), rss: Math.round(memUsage.rss / 1024 / 1024),
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024), heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024), heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
external: Math.round(memUsage.external / 1024 / 1024) external: Math.round(memUsage.external / 1024 / 1024)
} }
console.log(`[Memory] RSS: ${mbUsage.rss}MB, Heap: ${mbUsage.heapUsed}/${mbUsage.heapTotal}MB, External: ${mbUsage.external}MB`) console.log(
`[Memory] RSS: ${mbUsage.rss}MB, Heap: ${mbUsage.heapUsed}/${mbUsage.heapTotal}MB, External: ${mbUsage.external}MB`
if (mbUsage.heapUsed / mbUsage.heapTotal > 0.8 && global.gc) { )
console.log('[Memory] Triggering garbage collection')
global.gc() if (mbUsage.heapUsed / mbUsage.heapTotal > 0.8 && global.gc) {
} console.log('[Memory] Triggering garbage collection')
}, intervalMs) global.gc()
} }
}, intervalMs)
static stop () { }
if (this.interval) {
clearInterval(this.interval) static stop() {
this.interval = null if (this.interval) {
} clearInterval(this.interval)
} this.interval = null
} }
}
}

View File

@@ -1,194 +1,194 @@
import path from 'node:path' import path from 'node:path'
import logger from '@core/picgo/logger' import logger from '@core/picgo/logger'
import fs from 'fs-extra' import fs from 'fs-extra'
import { Config, NodeSSH, SSHExecCommandResponse } from 'node-ssh-no-cpu-features' import { Config, NodeSSH, SSHExecCommandResponse } from 'node-ssh-no-cpu-features'
import { ISftpPlistConfig } from 'piclist/dist/types' import { ISftpPlistConfig } from 'piclist/dist/types'
import { Client } from 'ssh2-no-cpu-features' import { Client } from 'ssh2-no-cpu-features'
class SSHClient { class SSHClient {
private static _instance: SSHClient private static _instance: SSHClient
private static _client: NodeSSH private static _client: NodeSSH
private _isConnected = false private _isConnected = false
static get instance (): SSHClient { static get instance(): SSHClient {
return this._instance || (this._instance = new this()) return this._instance || (this._instance = new this())
} }
static get client (): NodeSSH { static get client(): NodeSSH {
return this._client || (this._client = new NodeSSH()) return this._client || (this._client = new NodeSSH())
} }
private changeWinStylePathToUnix (path: string): string { private changeWinStylePathToUnix(path: string): string {
return path.replace(/\\/g, '/') return path.replace(/\\/g, '/')
} }
async connect (config: ISftpPlistConfig): Promise<boolean> { async connect(config: ISftpPlistConfig): Promise<boolean> {
const { username, password, privateKey, passphrase } = config const { username, password, privateKey, passphrase } = config
const loginInfo: Config = privateKey const loginInfo: Config = privateKey
? { ? {
username, username,
privateKeyPath: privateKey, privateKeyPath: privateKey,
passphrase: passphrase || undefined passphrase: passphrase || undefined
} }
: { username, password } : { username, password }
try { try {
await SSHClient.client.connect({ await SSHClient.client.connect({
host: config.host, host: config.host,
port: Number(config.port) || 22, port: Number(config.port) || 22,
...loginInfo ...loginInfo
}) })
this._isConnected = true this._isConnected = true
return true return true
} catch (err: any) { } catch (err: any) {
throw new Error(err) throw new Error(err)
} }
} }
async deleteFileSFTP (config: ISftpPlistConfig, remote: string): Promise<boolean> { async deleteFileSFTP(config: ISftpPlistConfig, remote: string): Promise<boolean> {
try { try {
const client = new Client() const client = new Client()
const { username, password, privateKey, passphrase } = config const { username, password, privateKey, passphrase } = config
const loginInfo: Config = privateKey const loginInfo: Config = privateKey
? { ? {
username, username,
privateKey: fs.readFileSync(privateKey), privateKey: fs.readFileSync(privateKey),
passphrase: passphrase || undefined passphrase: passphrase || undefined
} }
: { username, password } : { username, password }
remote = this.changeWinStylePathToUnix(remote) remote = this.changeWinStylePathToUnix(remote)
if (remote === '/' || remote.includes('*')) return false if (remote === '/' || remote.includes('*')) return false
const promise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
client client
.on('ready', () => { .on('ready', () => {
client.sftp( client.sftp(
( (
err: any, err: any,
sftp: { sftp: {
unlink: (arg0: string, arg1: (err: any) => void) => void unlink: (arg0: string, arg1: (err: any) => void) => void
} }
) => { ) => {
// eslint-disable-next-line prefer-promise-reject-errors // eslint-disable-next-line prefer-promise-reject-errors
if (err) reject(false) if (err) reject(false)
sftp.unlink(remote, (err: any) => { sftp.unlink(remote, (err: any) => {
// eslint-disable-next-line prefer-promise-reject-errors // eslint-disable-next-line prefer-promise-reject-errors
if (err) reject(false) if (err) reject(false)
client.end() client.end()
resolve(true) resolve(true)
}) })
} }
) )
}) })
.connect({ .connect({
host: config.host, host: config.host,
port: Number(config.port) || 22, port: Number(config.port) || 22,
...loginInfo ...loginInfo
}) })
}) })
return (await promise) as boolean return (await promise) as boolean
} catch (err: any) { } catch (err: any) {
logger.error(err) logger.error(err)
return false return false
} }
} }
private async exec (script: string): Promise<boolean> { private async exec(script: string): Promise<boolean> {
const execResult = await SSHClient.client.execCommand(script) const execResult = await SSHClient.client.execCommand(script)
return execResult.code === 0 return execResult.code === 0
} }
async execCommand (script: string): Promise<SSHExecCommandResponse> { async execCommand(script: string): Promise<SSHExecCommandResponse> {
const execResult = await SSHClient.client.execCommand(script) const execResult = await SSHClient.client.execCommand(script)
return execResult || { code: 1, stdout: '', stderr: '' } 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) { if (!this._isConnected) {
throw new Error('SSH 未连接') throw new Error('SSH 未连接')
} }
try { try {
remote = this.changeWinStylePathToUnix(remote) remote = this.changeWinStylePathToUnix(remote)
local = this.changeWinStylePathToUnix(local) local = this.changeWinStylePathToUnix(local)
await SSHClient.client.getFile(local, remote, undefined, { await SSHClient.client.getFile(local, remote, undefined, {
concurrency: 1 concurrency: 1
}) })
return true return true
} catch (err: any) { } catch (err: any) {
logger.error(err) logger.error(err)
return false return false
} }
} }
async putFile ( async putFile(
local: string, local: string,
remote: string, remote: string,
config: { config: {
fileMode?: string fileMode?: string
dirMode?: string dirMode?: string
} = {} } = {}
): Promise<boolean> { ): Promise<boolean> {
if (!this._isConnected) { if (!this._isConnected) {
throw new Error('SSH 未连接') throw new Error('SSH 未连接')
} }
try { try {
remote = this.changeWinStylePathToUnix(remote) remote = this.changeWinStylePathToUnix(remote)
await this.mkdir(path.dirname(remote).replace(/^\/+|\/+$/g, ''), config) await this.mkdir(path.dirname(remote).replace(/^\/+|\/+$/g, ''), config)
await SSHClient.client.putFile(local, remote) await SSHClient.client.putFile(local, remote)
const fileMode = config.fileMode || '0644' const fileMode = config.fileMode || '0644'
if (fileMode !== '0644') { if (fileMode !== '0644') {
const script = `chmod ${fileMode} "${remote}"` const script = `chmod ${fileMode} "${remote}"`
return await this.exec(script) return await this.exec(script)
} }
return true return true
} catch (err: any) { } catch (err: any) {
logger.error(err) logger.error(err)
return false return false
} }
} }
async mkdir ( async mkdir(
dirPath: string, dirPath: string,
config: { config: {
dirMode?: string dirMode?: string
} = {} } = {}
): Promise<boolean> { ): Promise<boolean> {
if (!this._isConnected) { if (!this._isConnected) {
throw new Error('SSH 未连接') throw new Error('SSH 未连接')
} }
try { try {
const directoryMode = config.dirMode || '0755' const directoryMode = config.dirMode || '0755'
if (directoryMode === '0755') { if (directoryMode === '0755') {
const script = `mkdir -p "${dirPath}"` const script = `mkdir -p "${dirPath}"`
return await this.exec(script) return await this.exec(script)
} else { } else {
const dirs = dirPath.split('/') const dirs = dirPath.split('/')
let currentPath = '' let currentPath = ''
for (const dir of dirs) { for (const dir of dirs) {
if (dir) { if (dir) {
currentPath += `/${dir}` currentPath += `/${dir}`
const script = `mkdir "${currentPath}" && chmod ${directoryMode} "${currentPath}"` const script = `mkdir "${currentPath}" && chmod ${directoryMode} "${currentPath}"`
const result = await this.exec(script) const result = await this.exec(script)
if (!result) { if (!result) {
return false return false
} }
} }
} }
return true return true
} }
} catch (err: any) { } catch (err: any) {
logger.error(err) logger.error(err)
return false return false
} }
} }
get isConnected (): boolean { get isConnected(): boolean {
return SSHClient.client.isConnected() return SSHClient.client.isConnected()
} }
close (): void { close(): void {
SSHClient.client.dispose() SSHClient.client.dispose()
this._isConnected = false this._isConnected = false
} }
} }
export default SSHClient export default SSHClient

View File

@@ -1,25 +1,25 @@
export const CLIPBOARD_IMAGE_FOLDER = 'piclist-clipboard-images' export const CLIPBOARD_IMAGE_FOLDER = 'piclist-clipboard-images'
export const cancelDownloadLoadingFileList = 'cancelDownloadLoadingFileList' export const cancelDownloadLoadingFileList = 'cancelDownloadLoadingFileList'
export const refreshDownloadFileTransferList = 'refreshDownloadFileTransferList' export const refreshDownloadFileTransferList = 'refreshDownloadFileTransferList'
export const picBedsCanbeDeleted = [ export const picBedsCanbeDeleted = [
'aliyun', 'aliyun',
'alist', 'alist',
'alistplist', 'alistplist',
'aws-s3', 'aws-s3',
'aws-s3-plist', 'aws-s3-plist',
'dogecloud', 'dogecloud',
'github', 'github',
'huaweicloud-uploader', 'huaweicloud-uploader',
'imgur', 'imgur',
'local', 'local',
'lskyplist', 'lskyplist',
'piclist', 'piclist',
'qiniu', 'qiniu',
'sftpplist', 'sftpplist',
'smms', 'smms',
'tcyun', 'tcyun',
'upyun', 'upyun',
'webdavplist' 'webdavplist'
] ]

View File

@@ -37,16 +37,16 @@ const getSyncConfig = () => {
const getProxyagent = (proxy: string | undefined) => { const getProxyagent = (proxy: string | undefined) => {
return proxy return proxy
? new HttpsProxyAgent({ ? new HttpsProxyAgent({
keepAlive: true, keepAlive: true,
keepAliveMsecs: 1000, keepAliveMsecs: 1000,
rejectUnauthorized: false, rejectUnauthorized: false,
proxy: proxy.replace('127.0.0.1', 'localhost'), proxy: proxy.replace('127.0.0.1', 'localhost'),
scheduling: 'lifo' scheduling: 'lifo'
}) })
: undefined : undefined
} }
function getOctokit (syncConfig: ISyncConfig) { function getOctokit(syncConfig: ISyncConfig) {
const { token, proxy } = syncConfig const { token, proxy } = syncConfig
return new Octokit({ return new Octokit({
auth: token, auth: token,
@@ -83,7 +83,7 @@ const isSyncConfigValidate = ({
return type && username && repo && branch && token 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) const localFilePath = path.join(STORE_PATH, fileName)
if (!fs.existsSync(localFilePath)) { if (!fs.existsSync(localFilePath)) {
return false return false
@@ -161,7 +161,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) const localFilePath = path.join(STORE_PATH, fileName)
if (!fs.existsSync(localFilePath)) { if (!fs.existsSync(localFilePath)) {
return false return false
@@ -277,7 +277,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() const syncConfig = getSyncConfig()
if (!isSyncConfigValidate(syncConfig)) { if (!isSyncConfigValidate(syncConfig)) {
logger.error('sync config is invalid') logger.error('sync config is invalid')
@@ -302,7 +302,7 @@ async function uploadFile (fileName: string[]): Promise<number> {
return count 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) const res = await axios.get(url, config)
if (isHttpResSuccess(res)) { if (isHttpResSuccess(res)) {
await fs.writeFile( await fs.writeFile(
@@ -314,7 +314,7 @@ async function downloadAndWriteFile (url: string, localFilePath: string, config:
return false return false
} }
async function downloadRemoteToLocal (syncConfig: ISyncConfig, fileName: string) { async function downloadRemoteToLocal(syncConfig: ISyncConfig, fileName: string) {
const localFilePath = path.join(STORE_PATH, fileName) const localFilePath = path.join(STORE_PATH, fileName)
const { username, repo, branch, token, proxy, type } = syncConfig const { username, repo, branch, token, proxy, type } = syncConfig
try { try {
@@ -394,7 +394,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() const syncConfig = getSyncConfig()
if (!isSyncConfigValidate(syncConfig)) { if (!isSyncConfigValidate(syncConfig)) {
logger.error('sync config is invalid') logger.error('sync config is invalid')

View File

@@ -1,66 +1,72 @@
import db from '@core/datastore' import db from '@core/datastore'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import { screen } from 'electron' import { screen } from 'electron'
import { configPaths } from '~/utils/configPaths' import { configPaths } from '~/utils/configPaths'
import { IWindowList } from '~/utils/enum' import { IWindowList } from '~/utils/enum'
export function openMiniWindow (hideSettingWindow: boolean = true) { export function openMiniWindow(hideSettingWindow: boolean = true) {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
miniWindow.removeAllListeners('close') miniWindow.removeAllListeners('close')
miniWindow.removeAllListeners('move') miniWindow.removeAllListeners('move')
if (db.get(configPaths.settings.miniWindowOntop)) { if (db.get(configPaths.settings.miniWindowOntop)) {
miniWindow.setAlwaysOnTop(true) miniWindow.setAlwaysOnTop(true)
} }
const { width, height } = screen.getPrimaryDisplay().workAreaSize const { width, height } = screen.getPrimaryDisplay().workAreaSize
const lastPosition = db.get(configPaths.settings.miniWindowPosition) const lastPosition = db.get(configPaths.settings.miniWindowPosition)
const setPositionFunc = () => { const setPositionFunc = () => {
const position = miniWindow.getPosition() const position = miniWindow.getPosition()
db.set(configPaths.settings.miniWindowPosition, position) db.set(configPaths.settings.miniWindowPosition, position)
} }
if (lastPosition) { if (lastPosition) {
if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) { if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) {
miniWindow.setPosition(width - 100, height - 100) miniWindow.setPosition(width - 100, height - 100)
db.set(configPaths.settings.miniWindowPosition, [width - 100, height - 100]) db.set(configPaths.settings.miniWindowPosition, [width - 100, height - 100])
} else if (lastPosition[0] + miniWindow.getSize()[0] > width || lastPosition[1] + miniWindow.getSize()[1] > height) { } else if (
miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]) lastPosition[0] + miniWindow.getSize()[0] > width ||
db.set(configPaths.settings.miniWindowPosition, [width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]]) lastPosition[1] + miniWindow.getSize()[1] > height
} else { ) {
miniWindow.setPosition(lastPosition[0], lastPosition[1]) miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1])
} db.set(configPaths.settings.miniWindowPosition, [
} else { width - miniWindow.getSize()[0],
miniWindow.setPosition(width - 100, height - 100) height - miniWindow.getSize()[1]
} ])
} else {
miniWindow.on('close', setPositionFunc) miniWindow.setPosition(lastPosition[0], lastPosition[1])
miniWindow.on('move', setPositionFunc) }
miniWindow.show() } else {
miniWindow.focus() miniWindow.setPosition(width - 100, height - 100)
if (hideSettingWindow) { }
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
settingWindow.hide() miniWindow.on('close', setPositionFunc)
} else { miniWindow.on('move', setPositionFunc)
const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false miniWindow.show()
if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) { miniWindow.focus()
windowManager.get(IWindowList.SETTING_WINDOW)!.hide() if (hideSettingWindow) {
} const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
} settingWindow.hide()
} } else {
const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false
export const openMainWindow = () => { if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) windowManager.get(IWindowList.SETTING_WINDOW)!.hide()
const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false }
settingWindow!.show() }
settingWindow!.focus() }
if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide() export const openMainWindow = () => {
} const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
} const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false
settingWindow!.show()
export const hideMiniWindow = () => { settingWindow!.focus()
if (windowManager.has(IWindowList.MINI_WINDOW)) { if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide() windowManager.get(IWindowList.MINI_WINDOW)!.hide()
} }
} }
export const hideMiniWindow = () => {
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}

View File

@@ -1,101 +1,101 @@
import crypto from 'node:crypto' import crypto from 'node:crypto'
import path from 'node:path' import path from 'node:path'
import { clipboard, contextBridge, ipcRenderer, IpcRendererEvent, webFrame, webUtils } from 'electron' import { clipboard, contextBridge, ipcRenderer, IpcRendererEvent, webFrame, webUtils } from 'electron'
import fs from 'fs-extra' import fs from 'fs-extra'
import yaml from 'js-yaml' import yaml from 'js-yaml'
import mime from 'mime-types' import mime from 'mime-types'
import { isReactive, isRef, toRaw, unref } from 'vue' import { isReactive, isRef, toRaw, unref } from 'vue'
export const getRawData = (args: any): any => { export const getRawData = (args: any): any => {
if (isRef(args)) return unref(args) if (isRef(args)) return unref(args)
if (isReactive(args)) return toRaw(args) if (isReactive(args)) return toRaw(args)
if (Array.isArray(args)) return args.map(getRawData) if (Array.isArray(args)) return args.map(getRawData)
if (typeof args === 'object' && args !== null) { if (typeof args === 'object' && args !== null) {
const data = {} as Record<string, any> const data = {} as Record<string, any>
for (const key in args) { for (const key in args) {
data[key] = getRawData(args[key]) data[key] = getRawData(args[key])
} }
return data return data
} }
return args return args
} }
function sendToMain (channel: string, ...args: any[]) { function sendToMain(channel: string, ...args: any[]) {
ipcRenderer.send(channel, ...getRawData(args)) ipcRenderer.send(channel, ...getRawData(args))
} }
function sendRPC (action: string, ...args: any[]): void { function sendRPC(action: string, ...args: any[]): void {
ipcRenderer.send('RPC_ACTIONS', action, getRawData(args)) ipcRenderer.send('RPC_ACTIONS', action, getRawData(args))
} }
async function triggerRPC<T> (action: string, ...args: any[]): Promise<T | undefined> { async function triggerRPC<T>(action: string, ...args: any[]): Promise<T | undefined> {
return await ipcRenderer.invoke('RPC_ACTIONS_INVOKE', action, getRawData(args)) return await ipcRenderer.invoke('RPC_ACTIONS_INVOKE', action, getRawData(args))
} }
function sendRpcSync (action: string, ...args: any[]): any { function sendRpcSync(action: string, ...args: any[]): any {
return ipcRenderer.sendSync('RPC_ACTIONS', action, getRawData(args)) return ipcRenderer.sendSync('RPC_ACTIONS', action, getRawData(args))
} }
try { try {
contextBridge.exposeInMainWorld('electron', { contextBridge.exposeInMainWorld('electron', {
setVisualZoomLevelLimits: (min: number, max: number) => { setVisualZoomLevelLimits: (min: number, max: number) => {
webFrame.setVisualZoomLevelLimits(min, max) webFrame.setVisualZoomLevelLimits(min, max)
}, },
clipboard: { clipboard: {
writeText: clipboard.writeText writeText: clipboard.writeText
}, },
platform: process.platform, platform: process.platform,
sendRpcSync, sendRpcSync,
triggerRPC, triggerRPC,
sendToMain, sendToMain,
sendRPC, sendRPC,
ipcRendererOn: (channel: string, listener: (...args: any[]) => void) => { ipcRendererOn: (channel: string, listener: (...args: any[]) => void) => {
const subscription = (_: IpcRendererEvent, ...args: any[]) => listener(...args) const subscription = (_: IpcRendererEvent, ...args: any[]) => listener(...args)
ipcRenderer.on(channel, subscription) ipcRenderer.on(channel, subscription)
return () => { return () => {
ipcRenderer.removeListener(channel, subscription) ipcRenderer.removeListener(channel, subscription)
} }
}, },
ipcRendererCountListeners: (channel: string): number => { ipcRendererCountListeners: (channel: string): number => {
return ipcRenderer.listenerCount(channel) return ipcRenderer.listenerCount(channel)
}, },
ipcRendererRemoveAllListeners: (channel: string) => { ipcRendererRemoveAllListeners: (channel: string) => {
ipcRenderer.removeAllListeners(channel) ipcRenderer.removeAllListeners(channel)
}, },
showFilePath (file: File) { showFilePath(file: File) {
return webUtils.getPathForFile(file) return webUtils.getPathForFile(file)
} }
}) })
contextBridge.exposeInMainWorld('node', { contextBridge.exposeInMainWorld('node', {
path: { path: {
join: path.join, join: path.join,
dirname: path.dirname, dirname: path.dirname,
basename: path.basename, basename: path.basename,
normalize: path.normalize, normalize: path.normalize,
extname: path.extname, extname: path.extname,
sep: path.sep, sep: path.sep,
posix: { posix: {
sep: path.posix.sep sep: path.posix.sep
} }
}, },
fs: { fs: {
remove: fs.remove, remove: fs.remove,
readFile: fs.readFile, readFile: fs.readFile,
statSync: fs.statSync statSync: fs.statSync
}, },
crypto: { crypto: {
randomBytes: crypto.randomBytes, randomBytes: crypto.randomBytes,
createHash: crypto.createHash createHash: crypto.createHash
}, },
yaml: { yaml: {
load: yaml.load load: yaml.load
}, },
mime: { mime: {
lookup: mime.lookup lookup: mime.lookup
} }
}) })
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }

View File

@@ -1,60 +1,56 @@
<template> <template>
<div <div id="app" :key="pageReloadCount">
id="app" <router-view />
:key="pageReloadCount" <UIServiceProvider />
> </div>
<router-view /> </template>
<UIServiceProvider />
</div> <script lang="ts" setup>
</template> import type { IConfig } from 'piclist'
import { onBeforeMount, onMounted } from 'vue'
<script lang="ts" setup>
import type { IConfig } from 'piclist' import UIServiceProvider from '@/components/ui/UIServiceProvider.vue'
import { onBeforeMount, onMounted } from 'vue' import { useATagClick } from '@/hooks/useATagClick'
import { useStore } from '@/hooks/useStore'
import UIServiceProvider from '@/components/ui/UIServiceProvider.vue' import { getConfig } from '@/utils/dataSender'
import { useATagClick } from '@/hooks/useATagClick' import { pageReloadCount } from '@/utils/global'
import { useStore } from '@/hooks/useStore'
import { getConfig } from '@/utils/dataSender' import { useAppStore } from './hooks/useAppStore'
import { pageReloadCount } from '@/utils/global'
useATagClick()
import { useAppStore } from './hooks/useAppStore'
const store = useStore()
useATagClick() const appStore = useAppStore()
const store = useStore() onBeforeMount(async () => {
const appStore = useAppStore() const config = await getConfig<IConfig>()
if (config) {
onBeforeMount(async () => { store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
const config = await getConfig<IConfig>() }
if (config) { })
store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
} onMounted(async () => {
}) try {
appStore.init()
onMounted(async () => { } catch (error) {
try { console.error('Failed to load settings:', error)
appStore.init() }
} catch (error) { })
console.error('Failed to load settings:', error) </script>
}
}) <script lang="ts">
export default {
</script> name: 'PicGoApp'
}
<script lang="ts"> </script>
export default {
name: 'PicGoApp' <style lang="stylus">
} body,
</script> html
padding 0
<style lang="stylus"> margin 0
body, height 100%
html #app
padding 0 height 100%
margin 0 user-select none
height 100% </style>
#app
height 100%
user-select none
</style>

View File

@@ -3,7 +3,7 @@ import { IRPCActionType } from '@/utils/enum'
import type { IStringKeyMap } from '#/types/types' import type { IStringKeyMap } from '#/types/types'
export default class ALLApi { export default class ALLApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> { static async delete(configMap: IStringKeyMap): Promise<boolean> {
return (await window.electron.triggerRPC(IRPCActionType.DELETE_ALL_API, getRawData(configMap))) || false return (await window.electron.triggerRPC(IRPCActionType.DELETE_ALL_API, getRawData(configMap))) || false
} }
} }

View File

@@ -1,115 +1,107 @@
<template> <template>
<div class="image-container"> <div class="image-container">
<div <div v-if="isLoading" class="loading-placeholder">
v-if="isLoading" <div class="loading-spinner" />
class="loading-placeholder" </div>
> <img
<div class="loading-spinner" /> v-else-if="!hasError"
</div> :src="isShowThumbnail && item.isImage ? base64Image : `./assets/icons/${getFileIconPath(item.fileName ?? '')}`"
<img alt=""
v-else-if="!hasError" class="image"
:src=" @load="handleImageLoad"
isShowThumbnail && item.isImage @error="handleImageError"
? base64Image />
: `./assets/icons/${getFileIconPath(item.fileName ?? '')}` <img v-else :src="`./assets/icons/${getFileIconPath(item.fileName ?? '')}`" alt="" class="image" />
" </div>
alt="" </template>
class="image"
@load="handleImageLoad" <script lang="ts" setup>
@error="handleImageError" import { onBeforeMount, ref } from 'vue'
>
<img import { getFileIconPath } from '@/manage/utils/common'
v-else
:src="`./assets/icons/${getFileIconPath(item.fileName ?? '')}`" const base64Image = ref('')
alt="" const isLoading = ref(true)
class="image" const hasError = ref(false)
>
</div> const props = defineProps<{
</template> isShowThumbnail: boolean
item: {
<script lang="ts" setup> isImage: boolean
import { onBeforeMount, ref } from 'vue' fileName: string
}
import { getFileIconPath } from '@/manage/utils/common' localPath: string
}>()
const base64Image = ref('')
const isLoading = ref(true) const createBase64Image = async () => {
const hasError = ref(false) try {
const filePath = window.node.path.normalize(props.localPath)
const props = defineProps<{ const base64 = await window.node.fs.readFile(filePath, 'base64')
isShowThumbnail: boolean base64Image.value = `data:${window.node.mime.lookup(filePath) || 'image/png'};base64,${base64}`
item: { isLoading.value = false
isImage: boolean } catch (e) {
fileName: string console.log(e)
} hasError.value = true
localPath: string isLoading.value = false
}>() }
}
const createBase64Image = async () => {
try { const handleImageLoad = () => {
const filePath = window.node.path.normalize(props.localPath) isLoading.value = false
const base64 = await window.node.fs.readFile(filePath, 'base64') hasError.value = false
base64Image.value = `data:${window.node.mime.lookup(filePath) || 'image/png'};base64,${base64}` }
isLoading.value = false
} catch (e) { const handleImageError = () => {
console.log(e) isLoading.value = false
hasError.value = true hasError.value = true
isLoading.value = false }
}
} onBeforeMount(async () => {
await createBase64Image()
const handleImageLoad = () => { })
isLoading.value = false </script>
hasError.value = false
} <style scoped>
.image-container {
const handleImageError = () => { height: 100px;
isLoading.value = false width: 100%;
hasError.value = true margin: 0 auto;
} display: flex;
align-items: center;
onBeforeMount(async () => { justify-content: center;
await createBase64Image() position: relative;
}) }
</script>
.image {
<style scoped> max-height: 100%;
.image-container { max-width: 100%;
height: 100px; object-fit: contain;
width: 100%; display: block;
margin: 0 auto; }
display: flex;
align-items: center; .loading-placeholder {
justify-content: center; display: flex;
position: relative; align-items: center;
} justify-content: center;
width: 100%;
.image { height: 100%;
max-height: 100%; }
max-width: 100%;
object-fit: contain; .loading-spinner {
display: block; width: 24px;
} height: 24px;
border: 2px solid #e4e7ed;
.loading-placeholder { border-top: 2px solid #409eff;
display: flex; border-radius: 50%;
align-items: center; animation: spin 1s linear infinite;
justify-content: center; }
width: 100%;
height: 100%; @keyframes spin {
} 0% {
transform: rotate(0deg);
.loading-spinner { }
width: 24px; 100% {
height: 24px; transform: rotate(360deg);
border: 2px solid #e4e7ed; }
border-top: 2px solid #409eff; }
border-radius: 50%; </style>
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>

View File

@@ -1,123 +1,123 @@
<template> <template>
<div class="image-container"> <div class="image-container">
<div <div v-if="isLoading" class="loading-placeholder">
v-if="isLoading" <div class="loading-spinner" />
class="loading-placeholder" </div>
> <img
<div class="loading-spinner" /> v-else-if="!hasError"
</div> :src="imageSource"
<img alt=""
v-else-if="!hasError" class="image"
:src="imageSource" @load="handleImageLoad"
alt="" @error="handleImageError"
class="image" />
@load="handleImageLoad" <img v-else :src="iconPath" alt="" class="image" />
@error="handleImageError" </div>
> </template>
<img
v-else <script lang="ts" setup>
:src="iconPath" import { computed, onMounted, ref, watch } from 'vue'
alt=""
class="image" import { getFileIconPath } from '@/manage/utils/common'
> import { IRPCActionType } from '@/utils/enum'
</div>
</template> const preSignedUrl = ref('')
const isLoading = ref(true)
<script lang="ts" setup> const hasError = ref(false)
import { computed, onMounted, ref, watch } from 'vue'
const props = defineProps<{
import { getFileIconPath } from '@/manage/utils/common' item: {
import { IRPCActionType } from '@/utils/enum' key: string
isImage: boolean
const preSignedUrl = ref('') fileName: string | null | undefined
const isLoading = ref(true) }
const hasError = ref(false) alias: string
url: string
const props = defineProps<{ config: any
item: { isShowThumbnail: boolean
key: string }>()
isImage: boolean
fileName: string | null | undefined const imageSource = computed(() => {
} return props.isShowThumbnail && props.item.isImage
alias: string ? preSignedUrl.value
url: string : `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`
config: any })
isShowThumbnail: boolean
}>() const iconPath = computed(() => `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)
const imageSource = computed(() => { async function getUrl() {
return props.isShowThumbnail && props.item.isImage try {
? preSignedUrl.value isLoading.value = true
: `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}` hasError.value = false
}) preSignedUrl.value = await window.electron.triggerRPC<any>(
IRPCActionType.MANAGE_GET_PRE_SIGNED_URL,
const iconPath = computed(() => `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`) props.alias,
props.config
async function getUrl () { )
try { isLoading.value = false
isLoading.value = true } catch (error) {
hasError.value = false console.error('Failed to get pre-signed URL:', error)
preSignedUrl.value = await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_GET_PRE_SIGNED_URL, props.alias, props.config) hasError.value = true
isLoading.value = false isLoading.value = false
} catch (error) { }
console.error('Failed to get pre-signed URL:', error) }
hasError.value = true
isLoading.value = false const handleImageLoad = () => {
} isLoading.value = false
} hasError.value = false
}
const handleImageLoad = () => {
isLoading.value = false const handleImageError = () => {
hasError.value = false isLoading.value = false
} hasError.value = true
}
const handleImageError = () => {
isLoading.value = false watch(() => [props.url, props.item], getUrl, { deep: true })
hasError.value = true
} onMounted(getUrl)
</script>
watch(() => [props.url, props.item], getUrl, { deep: true })
<style scoped>
onMounted(getUrl) .image-container {
</script> height: 100px;
width: 100%;
<style scoped> margin: 0 auto;
.image-container { display: flex;
height: 100px; align-items: center;
width: 100%; justify-content: center;
margin: 0 auto; position: relative;
display: flex; }
align-items: center;
justify-content: center; .image {
position: relative; max-height: 100%;
} max-width: 100%;
object-fit: contain;
.image { display: block;
max-height: 100%; }
max-width: 100%;
object-fit: contain; .loading-placeholder {
display: block; display: flex;
} align-items: center;
justify-content: center;
.loading-placeholder { width: 100%;
display: flex; height: 100%;
align-items: center; }
justify-content: center;
width: 100%; .loading-spinner {
height: 100%; width: 24px;
} height: 24px;
border: 2px solid #e4e7ed;
.loading-spinner { border-top: 2px solid #409eff;
width: 24px; border-radius: 50%;
height: 24px; animation: spin 1s linear infinite;
border: 2px solid #e4e7ed; }
border-top: 2px solid #409eff;
border-radius: 50%; @keyframes spin {
animation: spin 1s linear infinite; 0% {
} transform: rotate(0deg);
}
@keyframes spin { 100% {
0% { transform: rotate(0deg); } transform: rotate(360deg);
100% { transform: rotate(360deg); } }
} }
</style> </style>

File diff suppressed because it is too large Load Diff

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