mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
🎨 Style(custom): lint code
This commit is contained in:
@@ -1,57 +1,56 @@
|
||||
/// <reference types="vitest/config" />
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [
|
||||
externalizeDepsPlugin()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('src/renderer'),
|
||||
'~': resolve('src/main'),
|
||||
root: resolve('./'),
|
||||
'#': resolve('src/universal'),
|
||||
apis: resolve('src/main/apis'),
|
||||
'@core': resolve('src/main/apis/core')
|
||||
}
|
||||
}
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin(),
|
||||
VueI18nPlugin({
|
||||
/* options */
|
||||
// locale messages resource pre-compile option
|
||||
include: resolve(dirname(fileURLToPath(import.meta.url)), './src/renderer/i18n/locales/**')
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('src/renderer'),
|
||||
'~': resolve('src/main'),
|
||||
root: resolve('./'),
|
||||
'#': resolve('src/universal')
|
||||
}
|
||||
}
|
||||
},
|
||||
renderer: {
|
||||
root: resolve('src/renderer'),
|
||||
base: './',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('src/renderer'),
|
||||
'~': resolve('src/main'),
|
||||
root: resolve('./'),
|
||||
'#': resolve('src/universal')
|
||||
}
|
||||
},
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
port: 3000
|
||||
}
|
||||
}
|
||||
})
|
||||
/// <reference types="vitest/config" />
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('src/renderer'),
|
||||
'~': resolve('src/main'),
|
||||
root: resolve('./'),
|
||||
'#': resolve('src/universal'),
|
||||
apis: resolve('src/main/apis'),
|
||||
'@core': resolve('src/main/apis/core')
|
||||
}
|
||||
}
|
||||
},
|
||||
preload: {
|
||||
plugins: [
|
||||
externalizeDepsPlugin(),
|
||||
VueI18nPlugin({
|
||||
/* options */
|
||||
// locale messages resource pre-compile option
|
||||
include: resolve(dirname(fileURLToPath(import.meta.url)), './src/renderer/i18n/locales/**')
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('src/renderer'),
|
||||
'~': resolve('src/main'),
|
||||
root: resolve('./'),
|
||||
'#': resolve('src/universal')
|
||||
}
|
||||
}
|
||||
},
|
||||
renderer: {
|
||||
root: resolve('src/renderer'),
|
||||
base: './',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('src/renderer'),
|
||||
'~': resolve('src/main'),
|
||||
root: resolve('./'),
|
||||
'#': resolve('src/universal')
|
||||
}
|
||||
},
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
port: 3000
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import eslint from '@eslint/js'
|
||||
import standard from '@vue/eslint-config-standard'
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||
import simpleImportSort from 'eslint-plugin-simple-import-sort'
|
||||
import eslintPluginUnicorn from 'eslint-plugin-unicorn'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
@@ -18,6 +19,7 @@ export default tseslint.config(
|
||||
...tseslint.configs.stylistic,
|
||||
...pluginVue.configs['flat/recommended'],
|
||||
...standard,
|
||||
eslintPluginPrettierRecommended,
|
||||
{
|
||||
plugins: {
|
||||
'simple-import-sort': simpleImportSort,
|
||||
|
||||
15
package.json
15
package.json
@@ -25,10 +25,10 @@
|
||||
"dev": "electron-vite dev --watch",
|
||||
"i18n": "node ./scripts/gen-i18n-types.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: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",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postuninstall": "electron-builder install-app-deps",
|
||||
@@ -44,7 +44,6 @@
|
||||
"@aws-sdk/client-s3": "^3.864.0",
|
||||
"@aws-sdk/lib-storage": "^3.864.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.864.0",
|
||||
"@electron-toolkit/preload": "^3.0.2",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@highlightjs/vue-plugin": "^2.1.2",
|
||||
"@nodelib/fs.walk": "^3.0.1",
|
||||
@@ -53,11 +52,12 @@
|
||||
"@piclist/store": "^3.0.0",
|
||||
"@smithy/node-http-handler": "^4.1.1",
|
||||
"@videojs-player/vue": "^1.0.0",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"ali-oss": "^6.23.0",
|
||||
"axios": "^1.11.0",
|
||||
"chalk": "^5.5.0",
|
||||
"compare-versions": "^6.1.1",
|
||||
"cos-nodejs-sdk-v5": "^2.15.4",
|
||||
"dayjs": "^1.11.13",
|
||||
"dexie": "^3.2.4",
|
||||
"electron-updater": "^6.6.2",
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
@@ -78,13 +78,12 @@
|
||||
"piclist": "^2.0.0",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.5.0",
|
||||
"proxy-agent": "^6.5.0",
|
||||
"qiniu": "7.14.0",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"querystring": "^0.2.1",
|
||||
"semver": "^7.7.2",
|
||||
"shell-path": "3.0.0",
|
||||
"ssh2-no-cpu-features": "^2.0.0",
|
||||
"tunnel": "^0.0.6",
|
||||
"upyun": "^3.4.6",
|
||||
"uuid": "^11.1.0",
|
||||
"video.js": "^8.23.4",
|
||||
@@ -112,9 +111,7 @@
|
||||
"@types/video.js": "^7.3.58",
|
||||
"@types/write-file-atomic": "^4.0.3",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-standard": "^9.0.1",
|
||||
"@vue/eslint-config-typescript": "^14.6.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"dpdm": "^3.14.0",
|
||||
"electron": "^36.7.4",
|
||||
@@ -122,10 +119,12 @@
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"electron-vite": "^4.0.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unicorn": "^60.0.0",
|
||||
"eslint-plugin-vue": "^10.4.0",
|
||||
"globals": "^16.3.0",
|
||||
"husky": "^9.1.7",
|
||||
"node-bump-version": "^2.0.0",
|
||||
"npm-check-updates": "^18.0.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios'
|
||||
import { run } from 'npm-check-updates'
|
||||
|
||||
async function getRepositoryInfo (packageName) {
|
||||
async function getRepositoryInfo(packageName) {
|
||||
try {
|
||||
const { data } = await axios.get(`https://registry.npmjs.org/${packageName}`)
|
||||
const repository = data.repository
|
||||
@@ -17,7 +17,7 @@ async function getRepositoryInfo (packageName) {
|
||||
return null
|
||||
}
|
||||
|
||||
async function checkUpdates () {
|
||||
async function checkUpdates() {
|
||||
const updated = await run({
|
||||
packageFile: './package.json',
|
||||
upgrade: false
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
// different platform has different format
|
||||
|
||||
// macos
|
||||
const darwin = [
|
||||
{
|
||||
appNameWithPrefix: 'PicList-',
|
||||
ext: '.dmg',
|
||||
arch: '-arm64',
|
||||
'version-file': 'latest-mac.yml'
|
||||
},
|
||||
{
|
||||
appNameWithPrefix: 'PicList-',
|
||||
ext: '.dmg',
|
||||
arch: '-x64',
|
||||
'version-file': 'latest-mac.yml'
|
||||
}
|
||||
]
|
||||
|
||||
const linux = [
|
||||
{
|
||||
appNameWithPrefix: 'PicList-',
|
||||
ext: '.AppImage',
|
||||
arch: '',
|
||||
'version-file': 'latest-linux.yml'
|
||||
},
|
||||
{
|
||||
appNameWithPrefix: 'piclist_',
|
||||
ext: '.snap',
|
||||
arch: '_amd64',
|
||||
'version-file': 'latest-linux.yml'
|
||||
}
|
||||
]
|
||||
|
||||
// windows
|
||||
const win32 = [
|
||||
{
|
||||
appNameWithPrefix: 'PicList-Setup-',
|
||||
ext: '.exe',
|
||||
arch: '-ia32',
|
||||
'version-file': 'latest.yml'
|
||||
},
|
||||
{
|
||||
appNameWithPrefix: 'PicList-Setup-',
|
||||
ext: '.exe',
|
||||
arch: '-x64',
|
||||
'version-file': 'latest.yml'
|
||||
},
|
||||
{
|
||||
appNameWithPrefix: 'PicList-Setup-',
|
||||
ext: '.exe',
|
||||
arch: '', // 32 & 64
|
||||
'version-file': 'latest.yml'
|
||||
},
|
||||
{
|
||||
appNameWithPrefix: 'PicList-Setup-',
|
||||
ext: '.exe',
|
||||
arch: '-arm64',
|
||||
'version-file': 'latest.yml'
|
||||
}
|
||||
]
|
||||
|
||||
export default {
|
||||
darwin,
|
||||
linux,
|
||||
win32
|
||||
}
|
||||
// different platform has different format
|
||||
|
||||
// macos
|
||||
const darwin = [
|
||||
{
|
||||
appNameWithPrefix: 'PicList-',
|
||||
ext: '.dmg',
|
||||
arch: '-arm64',
|
||||
'version-file': 'latest-mac.yml'
|
||||
},
|
||||
{
|
||||
appNameWithPrefix: 'PicList-',
|
||||
ext: '.dmg',
|
||||
arch: '-x64',
|
||||
'version-file': 'latest-mac.yml'
|
||||
}
|
||||
]
|
||||
|
||||
const linux = [
|
||||
{
|
||||
appNameWithPrefix: 'PicList-',
|
||||
ext: '.AppImage',
|
||||
arch: '',
|
||||
'version-file': 'latest-linux.yml'
|
||||
},
|
||||
{
|
||||
appNameWithPrefix: 'piclist_',
|
||||
ext: '.snap',
|
||||
arch: '_amd64',
|
||||
'version-file': 'latest-linux.yml'
|
||||
}
|
||||
]
|
||||
|
||||
// windows
|
||||
const win32 = [
|
||||
{
|
||||
appNameWithPrefix: 'PicList-Setup-',
|
||||
ext: '.exe',
|
||||
arch: '-ia32',
|
||||
'version-file': 'latest.yml'
|
||||
},
|
||||
{
|
||||
appNameWithPrefix: 'PicList-Setup-',
|
||||
ext: '.exe',
|
||||
arch: '-x64',
|
||||
'version-file': 'latest.yml'
|
||||
},
|
||||
{
|
||||
appNameWithPrefix: 'PicList-Setup-',
|
||||
ext: '.exe',
|
||||
arch: '', // 32 & 64
|
||||
'version-file': 'latest.yml'
|
||||
},
|
||||
{
|
||||
appNameWithPrefix: 'PicList-Setup-',
|
||||
ext: '.exe',
|
||||
arch: '-arm64',
|
||||
'version-file': 'latest.yml'
|
||||
}
|
||||
]
|
||||
|
||||
export default {
|
||||
darwin,
|
||||
linux,
|
||||
win32
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import path from 'node:path'
|
||||
import axios from 'axios'
|
||||
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
|
||||
|
||||
// Configuration
|
||||
@@ -26,7 +26,7 @@ const files = [
|
||||
/**
|
||||
* Create progress bar string
|
||||
*/
|
||||
function getProgressBar (current, total, length = 20) {
|
||||
function getProgressBar(current, total, length = 20) {
|
||||
const progress = Math.round((current / total) * length)
|
||||
const percentage = Math.round((current / total) * 100)
|
||||
const bar = '█'.repeat(progress) + '░'.repeat(length - progress)
|
||||
@@ -36,7 +36,7 @@ function getProgressBar (current, total, length = 20) {
|
||||
/**
|
||||
* Format bytes to human-readable format
|
||||
*/
|
||||
function formatBytes (bytes, decimals = 2) {
|
||||
function formatBytes(bytes, decimals = 2) {
|
||||
if (bytes === 0) return '0 Bytes'
|
||||
const k = 1024
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
||||
@@ -47,7 +47,7 @@ function formatBytes (bytes, decimals = 2) {
|
||||
/**
|
||||
* Download file and calculate SHA256 hash
|
||||
*/
|
||||
async function downloadAndHash (fileInfo) {
|
||||
async function downloadAndHash(fileInfo) {
|
||||
const { url, name } = fileInfo
|
||||
const filePath = path.join(DOWNLOAD_DIR, name)
|
||||
|
||||
@@ -103,7 +103,7 @@ async function downloadAndHash (fileInfo) {
|
||||
/**
|
||||
* Main function
|
||||
*/
|
||||
async function main () {
|
||||
async function main() {
|
||||
console.log(`Generating SHA256 hashes for PicList v${version}`)
|
||||
console.log(`Download directory: ${DOWNLOAD_DIR}`)
|
||||
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import pkg from '../package.json' with { type: 'json' }
|
||||
const version = pkg.version
|
||||
// TODO: use the same name format
|
||||
const generateURL = (platform, ext, prefix = 'PicList-') => {
|
||||
return `https://release.piclist.cn/latest/${prefix}${version}${platform}${ext}`
|
||||
}
|
||||
|
||||
const template = `
|
||||
### 加速下载地址
|
||||
|
||||
#### MacOS
|
||||
[PicList-${version}-arm64.dmg](${generateURL('-arm64', '.dmg', 'PicList-')})
|
||||
[PicList-${version}-x64.dmg](${generateURL('-x64', '.dmg', 'PicList-')})
|
||||
[PicList-${version}-universal.dmg](${generateURL('-universal', '.dmg', 'PicList-')})
|
||||
|
||||
#### Windows
|
||||
[PicList-Setup-${version}-ia32.exe](${generateURL('-ia32', '.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}.exe](${generateURL('', '.exe', 'PicList-Setup-')})
|
||||
|
||||
#### Linux
|
||||
[PicList-${version}.AppImage](${generateURL('', '.AppImage', 'PicList-')})
|
||||
[piclist_${version}_amd64.snap](${generateURL('_amd64', '.snap', 'piclist_')})`
|
||||
|
||||
console.log(template)
|
||||
import pkg from '../package.json' with { type: 'json' }
|
||||
const version = pkg.version
|
||||
// TODO: use the same name format
|
||||
const generateURL = (platform, ext, prefix = 'PicList-') => {
|
||||
return `https://release.piclist.cn/latest/${prefix}${version}${platform}${ext}`
|
||||
}
|
||||
|
||||
const template = `
|
||||
### 加速下载地址
|
||||
|
||||
#### MacOS
|
||||
[PicList-${version}-arm64.dmg](${generateURL('-arm64', '.dmg', 'PicList-')})
|
||||
[PicList-${version}-x64.dmg](${generateURL('-x64', '.dmg', 'PicList-')})
|
||||
[PicList-${version}-universal.dmg](${generateURL('-universal', '.dmg', 'PicList-')})
|
||||
|
||||
#### Windows
|
||||
[PicList-Setup-${version}-ia32.exe](${generateURL('-ia32', '.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}.exe](${generateURL('', '.exe', 'PicList-Setup-')})
|
||||
|
||||
#### Linux
|
||||
[PicList-${version}.AppImage](${generateURL('', '.AppImage', 'PicList-')})
|
||||
[piclist_${version}_amd64.snap](${generateURL('_amd64', '.snap', 'piclist_')})`
|
||||
|
||||
console.log(template)
|
||||
|
||||
@@ -5,7 +5,7 @@ require('dotenv').config()
|
||||
const { notarize } = require('@electron/notarize')
|
||||
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
|
||||
|
||||
if (
|
||||
|
||||
@@ -23,12 +23,12 @@ class RemoteNoticeHandler {
|
||||
private remoteNotice: IRemoteNotice | null = null
|
||||
private remoteNoticeLocalCountStorage: IRemoteNoticeLocalCountStorage | null = null
|
||||
|
||||
async init () {
|
||||
async init() {
|
||||
this.remoteNotice = await this.getRemoteNoticeInfo()
|
||||
this.initLocalCountStorage()
|
||||
}
|
||||
|
||||
private initLocalCountStorage () {
|
||||
private initLocalCountStorage() {
|
||||
const localCountStorage = {}
|
||||
if (!fs.existsSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH)) {
|
||||
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({}))
|
||||
@@ -44,14 +44,14 @@ class RemoteNoticeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private saveLocalCountStorage (newData?: IRemoteNoticeLocalCountStorage) {
|
||||
private saveLocalCountStorage(newData?: IRemoteNoticeLocalCountStorage) {
|
||||
if (newData) {
|
||||
this.remoteNoticeLocalCountStorage = newData
|
||||
}
|
||||
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify(this.remoteNoticeLocalCountStorage))
|
||||
}
|
||||
|
||||
private async getRemoteNoticeInfo (): Promise<IRemoteNotice | null> {
|
||||
private async getRemoteNoticeInfo(): Promise<IRemoteNotice | null> {
|
||||
try {
|
||||
const noticeInfo = (await axios({
|
||||
method: 'get',
|
||||
@@ -68,7 +68,7 @@ class RemoteNoticeHandler {
|
||||
* if the notice is not shown or is always shown, then show the notice
|
||||
* @param action
|
||||
*/
|
||||
private checkActionCount (action: IRemoteNoticeAction) {
|
||||
private checkActionCount(action: IRemoteNoticeAction) {
|
||||
try {
|
||||
if (!this.remoteNoticeLocalCountStorage) {
|
||||
return true
|
||||
@@ -102,7 +102,7 @@ class RemoteNoticeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private async doActions (actions: IRemoteNoticeAction[]) {
|
||||
private async doActions(actions: IRemoteNoticeAction[]) {
|
||||
for (const action of actions) {
|
||||
if (this.checkActionCount(action)) {
|
||||
switch (action.type) {
|
||||
@@ -117,7 +117,7 @@ class RemoteNoticeHandler {
|
||||
body: action.data?.content || '',
|
||||
clickToCopy: !!action.data?.copyToClipboard,
|
||||
copyContent: action.data?.copyToClipboard || '',
|
||||
clickFn () {
|
||||
clickFn() {
|
||||
if (action.data?.url) {
|
||||
shell.openExternal(action.data.url)
|
||||
}
|
||||
@@ -163,7 +163,7 @@ class RemoteNoticeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
triggerHook (hook: string) {
|
||||
triggerHook(hook: string) {
|
||||
if (!this.remoteNotice || !this.remoteNotice.list) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,24 +6,30 @@ import shortKeyService from 'apis/app/shortKey/shortKeyService'
|
||||
import GuiApi from 'apis/gui'
|
||||
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 { configPaths } from '~/utils/configPaths'
|
||||
|
||||
class ShortKeyHandler {
|
||||
private isInModifiedMode: boolean = false
|
||||
constructor () {
|
||||
constructor() {
|
||||
bus.on(TOGGLE_SHORTKEY_MODIFIED_MODE, flag => {
|
||||
this.isInModifiedMode = flag
|
||||
})
|
||||
}
|
||||
|
||||
async init () {
|
||||
async init() {
|
||||
this.initBuiltInShortKey()
|
||||
await this.initPluginsShortKey()
|
||||
}
|
||||
|
||||
private initBuiltInShortKey () {
|
||||
private initBuiltInShortKey() {
|
||||
const commands = db.get(configPaths.settings.shortKey._path) as IShortKeyConfigs
|
||||
Object.keys(commands)
|
||||
.filter(item => item.includes('picgo:'))
|
||||
@@ -38,7 +44,7 @@ class ShortKeyHandler {
|
||||
})
|
||||
}
|
||||
|
||||
private async initPluginsShortKey () {
|
||||
private async initPluginsShortKey() {
|
||||
// get enabled plugin
|
||||
const pluginList = picgo.pluginLoader.getList()
|
||||
for (const item of pluginList) {
|
||||
@@ -68,7 +74,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private registerShortKey (
|
||||
private registerShortKey(
|
||||
config: IShortKeyConfig | IPluginShortKeyConfig,
|
||||
command: string,
|
||||
handler: IShortKeyHandler,
|
||||
@@ -97,7 +103,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
|
||||
// enable or disable shortKey
|
||||
bindOrUnbindShortKey (item: IShortKeyConfig, from: string): boolean {
|
||||
bindOrUnbindShortKey(item: IShortKeyConfig, from: string): boolean {
|
||||
const command = `${from}:${item.name}`
|
||||
if (item.enable === false) {
|
||||
globalShortcut.unregister(item.key)
|
||||
@@ -121,7 +127,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
|
||||
// update shortKey bindings
|
||||
updateShortKey (item: IShortKeyConfig, oldKey: string, from: string): boolean {
|
||||
updateShortKey(item: IShortKeyConfig, oldKey: string, from: string): boolean {
|
||||
const command = `${from}:${item.name}`
|
||||
if (globalShortcut.isRegistered(item.key)) return false
|
||||
globalShortcut.unregister(oldKey)
|
||||
@@ -134,7 +140,7 @@ class ShortKeyHandler {
|
||||
return true
|
||||
}
|
||||
|
||||
private async handler (command: string) {
|
||||
private async handler(command: string) {
|
||||
if (this.isInModifiedMode) {
|
||||
return
|
||||
}
|
||||
@@ -150,7 +156,7 @@ class ShortKeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async registerPluginShortKey (pluginName: string) {
|
||||
async registerPluginShortKey(pluginName: string) {
|
||||
const plugin = await picgo.pluginLoader.getPlugin(pluginName)
|
||||
if (plugin && plugin.commands) {
|
||||
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 keyList = Object.keys(commands)
|
||||
.filter(command => command.includes(pluginName))
|
||||
|
||||
@@ -4,22 +4,22 @@ import type { IShortKeyHandler } from '#/types/types'
|
||||
|
||||
class ShortKeyService {
|
||||
private commandList: Map<string, IShortKeyHandler> = new Map()
|
||||
registerCommand (command: string, handler: IShortKeyHandler) {
|
||||
registerCommand(command: string, handler: IShortKeyHandler) {
|
||||
this.commandList.set(command, handler)
|
||||
}
|
||||
|
||||
unregisterCommand (command: string) {
|
||||
unregisterCommand(command: string) {
|
||||
this.commandList.delete(command)
|
||||
}
|
||||
|
||||
getShortKeyHandler (command: string): IShortKeyHandler | null {
|
||||
getShortKeyHandler(command: string): IShortKeyHandler | null {
|
||||
const handler = this.commandList.get(command)
|
||||
if (handler) return handler
|
||||
logger.warn(`cannot find command: ${command}`)
|
||||
return null
|
||||
}
|
||||
|
||||
getCommandList () {
|
||||
getCommandList() {
|
||||
return [...this.commandList.keys()]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import uploadPng from '../../../../../resources/upload.png?asset&asarUnpack'
|
||||
import uploadDarkPng from '../../../../../resources/upload-dark.png?asset&asarUnpack'
|
||||
let contextMenu: Menu | null
|
||||
|
||||
export function setDockMenu () {
|
||||
export function setDockMenu() {
|
||||
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
|
||||
const dockMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
@@ -45,7 +45,7 @@ export function setDockMenu () {
|
||||
},
|
||||
{
|
||||
label: $t('START_WATCH_CLIPBOARD'),
|
||||
click () {
|
||||
click() {
|
||||
db.set(configPaths.settings.isListeningClipboard, true)
|
||||
clipboardPoll.startListening()
|
||||
clipboardPoll.on('change', () => {
|
||||
@@ -58,7 +58,7 @@ export function setDockMenu () {
|
||||
},
|
||||
{
|
||||
label: $t('STOP_WATCH_CLIPBOARD'),
|
||||
click () {
|
||||
click() {
|
||||
db.set(configPaths.settings.isListeningClipboard, false)
|
||||
clipboardPoll.stopListening()
|
||||
clipboardPoll.removeAllListeners()
|
||||
@@ -70,7 +70,7 @@ export function setDockMenu () {
|
||||
app.dock?.setMenu(dockMenu)
|
||||
}
|
||||
|
||||
export function createMenu () {
|
||||
export function createMenu() {
|
||||
const submenu = buildPicBedListMenu()
|
||||
const appMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
@@ -79,7 +79,7 @@ export function createMenu () {
|
||||
{ label: $t('OPEN_MAIN_WINDOW'), click: openMainWindow },
|
||||
{
|
||||
label: $t('RELOAD_APP'),
|
||||
click () {
|
||||
click() {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export function createMenu () {
|
||||
Menu.setApplicationMenu(appMenu)
|
||||
}
|
||||
|
||||
export function createContextMenu () {
|
||||
export function createContextMenu() {
|
||||
const ClipboardWatcher = clipboardPoll
|
||||
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
|
||||
const isMiniWindowVisible =
|
||||
@@ -147,7 +147,7 @@ export function createContextMenu () {
|
||||
},
|
||||
{
|
||||
label: $t('RELOAD_APP'),
|
||||
click () {
|
||||
click() {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
}
|
||||
@@ -160,7 +160,7 @@ export function createContextMenu () {
|
||||
0,
|
||||
{
|
||||
label: $t('OPEN_MINI_WINDOW'),
|
||||
click () {
|
||||
click() {
|
||||
openMiniWindow(false)
|
||||
},
|
||||
visible: !isMiniWindowVisible
|
||||
@@ -185,7 +185,7 @@ export function createContextMenu () {
|
||||
{ label: $t('OPEN_MAIN_WINDOW'), click: openMainWindow },
|
||||
{
|
||||
label: $t('OPEN_MINI_WINDOW'),
|
||||
click () {
|
||||
click() {
|
||||
openMiniWindow(false)
|
||||
},
|
||||
visible: !isMiniWindowVisible
|
||||
@@ -207,7 +207,7 @@ export function createContextMenu () {
|
||||
},
|
||||
{
|
||||
label: $t('ABOUT'),
|
||||
click () {
|
||||
click() {
|
||||
dialog.showMessageBox({
|
||||
title: 'PicList',
|
||||
message: 'PicList',
|
||||
@@ -230,7 +230,7 @@ const getTrayIcon = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export function createTray (tooltip: string) {
|
||||
export function createTray(tooltip: string) {
|
||||
const menubarPic = getTrayIcon()
|
||||
setTray(new Tray(menubarPic))
|
||||
tray.setToolTip(tooltip)
|
||||
@@ -309,7 +309,7 @@ export function createTray (tooltip: string) {
|
||||
// drop-files only be supported in macOS
|
||||
// so the tray window must be available
|
||||
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 rawInput = cloneDeep(files)
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
|
||||
@@ -338,14 +338,14 @@ export function createTray (tooltip: string) {
|
||||
imgs[i].shortUrl = shortUrl
|
||||
pasteText.push(pasteTextItem)
|
||||
const isShowResultNotification =
|
||||
db.get(configPaths.settings.uploadResultNotification) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.uploadResultNotification)
|
||||
db.get(configPaths.settings.uploadResultNotification) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.uploadResultNotification)
|
||||
if (isShowResultNotification) {
|
||||
const notification = new Notification({
|
||||
title: $t('UPLOAD_SUCCEED'),
|
||||
body: shortUrl || imgs[i].imgUrl!
|
||||
// icon: files[i]
|
||||
// icon: files[i]
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
|
||||
@@ -1,236 +1,236 @@
|
||||
import db, { GalleryDB } from '@core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { Notification, WebContents } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type { IPicGo } from 'piclist'
|
||||
|
||||
import type { IFileWithPath, ImgInfo, IStringKeyMap, IUploadOption } from '#/types/types'
|
||||
import { T as $t } from '~/i18n/index'
|
||||
import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IPasteStyle, IWindowList } from '~/utils/enum'
|
||||
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
|
||||
import pasteTemplate from '~/utils/pasteTemplate'
|
||||
|
||||
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
const useBuiltinClipboard =
|
||||
db.get(configPaths.settings.useBuiltinClipboard) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.useBuiltinClipboard)
|
||||
const win = windowManager.getAvailableWindow()
|
||||
if (useBuiltinClipboard) {
|
||||
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard()
|
||||
}
|
||||
return await uploader.setWebContents(win!.webContents).upload()
|
||||
}
|
||||
|
||||
const handleClipboardUploadingReturnCtx = async (img?: IUploadOption, skipProcess = false): Promise<false | IPicGo> => {
|
||||
const useBuiltinClipboard =
|
||||
db.get(configPaths.settings.useBuiltinClipboard) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.useBuiltinClipboard)
|
||||
const win = windowManager.getAvailableWindow()
|
||||
if (useBuiltinClipboard) {
|
||||
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboardReturnCtx(img, skipProcess)
|
||||
}
|
||||
return await uploader.setWebContents(win!.webContents).uploadReturnCtx(img, skipProcess)
|
||||
}
|
||||
|
||||
export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
|
||||
const { needRestore, ctx } = await handleSecondaryUpload(undefined, undefined, 'clipboard')
|
||||
let img: ImgInfo[] | false = false
|
||||
if (needRestore) {
|
||||
const res = await handleClipboardUploadingReturnCtx(ctx ? ctx.processedInput : undefined, true)
|
||||
img = res ? res.output : false
|
||||
} else {
|
||||
img = await handleClipboardUploading()
|
||||
}
|
||||
if (img !== false) {
|
||||
if (img.length > 0) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
|
||||
const [pastedText, shortUrl] = await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink))
|
||||
img[0].shortUrl = shortUrl
|
||||
handleCopyUrl(pastedText)
|
||||
const isShowResultNotification =
|
||||
db.get(configPaths.settings.uploadResultNotification) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.uploadResultNotification)
|
||||
if (isShowResultNotification) {
|
||||
const notification = new Notification({
|
||||
title: $t('UPLOAD_SUCCEED'),
|
||||
body: shortUrl || img[0].imgUrl!
|
||||
// icon: img[0].imgUrl
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, 100)
|
||||
}
|
||||
const inserted = await GalleryDB.getInstance().insert(img[0])
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', img)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
return {
|
||||
url: handleUrlEncodeWithSetting(inserted.imgUrl as string),
|
||||
fullResult: inserted
|
||||
}
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
title: $t('UPLOAD_FAILED'),
|
||||
body: $t('TIPS_UPLOAD_NOT_PICTURES')
|
||||
})
|
||||
notification.show()
|
||||
return {
|
||||
url: '',
|
||||
fullResult: {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
url: '',
|
||||
fullResult: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadChoosedFiles = async (
|
||||
webContents: WebContents,
|
||||
files: IFileWithPath[]
|
||||
): Promise<IStringKeyMap[]> => {
|
||||
const input = files.map(item => item.path)
|
||||
const rawInput = cloneDeep(input)
|
||||
const { needRestore, ctx } = await handleSecondaryUpload(webContents, input)
|
||||
let imgs: ImgInfo[] | false = false
|
||||
if (needRestore) {
|
||||
const res = await uploader.setWebContents(webContents).uploadReturnCtx(ctx ? ctx.processedInput : input, true)
|
||||
imgs = res ? res.output : false
|
||||
} else {
|
||||
imgs = await uploader.setWebContents(webContents).upload(input)
|
||||
}
|
||||
const result = []
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
|
||||
const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
if (deleteLocalFile) {
|
||||
fs.remove(rawInput[i])
|
||||
.then(() => {
|
||||
picgo.log.info(`delete local file: ${rawInput[i]}`)
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
picgo.log.error(err)
|
||||
})
|
||||
}
|
||||
const [pasteTextItem, shortUrl] = await pasteTemplate(
|
||||
pasteStyle,
|
||||
imgs[i],
|
||||
db.get(configPaths.settings.customLink)
|
||||
)
|
||||
imgs[i].shortUrl = shortUrl
|
||||
pasteText.push(pasteTextItem)
|
||||
const isShowResultNotification =
|
||||
db.get(configPaths.settings.uploadResultNotification) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.uploadResultNotification)
|
||||
if (isShowResultNotification) {
|
||||
const notification = new Notification({
|
||||
title: $t('UPLOAD_SUCCEED'),
|
||||
body: shortUrl || imgs[i].imgUrl!
|
||||
// icon: files[i].path
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, i * 100)
|
||||
}
|
||||
const inserted = await GalleryDB.getInstance().insert(imgs[i])
|
||||
result.push({
|
||||
url: handleUrlEncodeWithSetting(inserted.imgUrl!),
|
||||
fullResult: inserted
|
||||
})
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)?.webContents?.send('uploadFiles', imgs)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export const handleSecondaryUpload = async (
|
||||
webContents?: WebContents,
|
||||
input?: string[],
|
||||
uploadType: 'clipboard' | 'file' | 'tray' = 'file'
|
||||
): Promise<{ needRestore: boolean; ctx: IPicGo | false }> => {
|
||||
const enableSecondUploader = db.get(configPaths.settings.enableSecondUploader) || false
|
||||
let currentPicBedType = ''
|
||||
let currentPicBedConfig = {} as IStringKeyMap
|
||||
let currentPicBedConfigId = ''
|
||||
let needRestore = false
|
||||
let ctx: IPicGo | false = false
|
||||
if (enableSecondUploader) {
|
||||
const secondUploader = db.get(configPaths.picBed.secondUploader)
|
||||
const secondUploaderConfig = db.get(configPaths.picBed.secondUploaderConfig)
|
||||
const secondUploaderId = db.get(configPaths.picBed.secondUploaderId)
|
||||
const currentPicBed = db.get('picBed') || ({} as IStringKeyMap)
|
||||
currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms'
|
||||
currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
|
||||
currentPicBedConfigId = currentPicBedConfig._id
|
||||
if (
|
||||
secondUploader === currentPicBedType &&
|
||||
secondUploaderConfig._configName === currentPicBedConfig._configName &&
|
||||
secondUploaderId === currentPicBedConfigId
|
||||
) {
|
||||
picgo.log.info('second uploader is the same as current uploader')
|
||||
} else {
|
||||
needRestore = true
|
||||
let secondImgs: ImgInfo[] | false = false
|
||||
changeCurrentUploader(secondUploader, secondUploaderConfig, secondUploaderId)
|
||||
if (uploadType === 'clipboard') {
|
||||
ctx = await handleClipboardUploadingReturnCtx(undefined)
|
||||
} else {
|
||||
ctx = await uploader.setWebContents(webContents!).uploadReturnCtx(input)
|
||||
}
|
||||
secondImgs = ctx ? ctx.output : false
|
||||
if (secondImgs !== false) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
if (uploadType === 'clipboard') {
|
||||
if (secondImgs.length > 0) {
|
||||
await GalleryDB.getInstance().insert(secondImgs[0])
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', secondImgs)
|
||||
}
|
||||
} else {
|
||||
for (const secondImgsItem of secondImgs) {
|
||||
await GalleryDB.getInstance().insert(secondImgsItem)
|
||||
}
|
||||
if (uploadType === 'tray') {
|
||||
trayWindow?.webContents?.send('dragFiles', secondImgs)
|
||||
} else {
|
||||
trayWindow?.webContents?.send('uploadFiles', secondImgs)
|
||||
}
|
||||
}
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW) && uploadType !== 'tray') {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needRestore) {
|
||||
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
|
||||
}
|
||||
return {
|
||||
needRestore,
|
||||
ctx
|
||||
}
|
||||
}
|
||||
import db, { GalleryDB } from '@core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { Notification, WebContents } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type { IPicGo } from 'piclist'
|
||||
|
||||
import type { IFileWithPath, ImgInfo, IStringKeyMap, IUploadOption } from '#/types/types'
|
||||
import { T as $t } from '~/i18n/index'
|
||||
import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IPasteStyle, IWindowList } from '~/utils/enum'
|
||||
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
|
||||
import pasteTemplate from '~/utils/pasteTemplate'
|
||||
|
||||
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
const useBuiltinClipboard =
|
||||
db.get(configPaths.settings.useBuiltinClipboard) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.useBuiltinClipboard)
|
||||
const win = windowManager.getAvailableWindow()
|
||||
if (useBuiltinClipboard) {
|
||||
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard()
|
||||
}
|
||||
return await uploader.setWebContents(win!.webContents).upload()
|
||||
}
|
||||
|
||||
const handleClipboardUploadingReturnCtx = async (img?: IUploadOption, skipProcess = false): Promise<false | IPicGo> => {
|
||||
const useBuiltinClipboard =
|
||||
db.get(configPaths.settings.useBuiltinClipboard) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.useBuiltinClipboard)
|
||||
const win = windowManager.getAvailableWindow()
|
||||
if (useBuiltinClipboard) {
|
||||
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboardReturnCtx(img, skipProcess)
|
||||
}
|
||||
return await uploader.setWebContents(win!.webContents).uploadReturnCtx(img, skipProcess)
|
||||
}
|
||||
|
||||
export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
|
||||
const { needRestore, ctx } = await handleSecondaryUpload(undefined, undefined, 'clipboard')
|
||||
let img: ImgInfo[] | false = false
|
||||
if (needRestore) {
|
||||
const res = await handleClipboardUploadingReturnCtx(ctx ? ctx.processedInput : undefined, true)
|
||||
img = res ? res.output : false
|
||||
} else {
|
||||
img = await handleClipboardUploading()
|
||||
}
|
||||
if (img !== false) {
|
||||
if (img.length > 0) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
|
||||
const [pastedText, shortUrl] = await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink))
|
||||
img[0].shortUrl = shortUrl
|
||||
handleCopyUrl(pastedText)
|
||||
const isShowResultNotification =
|
||||
db.get(configPaths.settings.uploadResultNotification) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.uploadResultNotification)
|
||||
if (isShowResultNotification) {
|
||||
const notification = new Notification({
|
||||
title: $t('UPLOAD_SUCCEED'),
|
||||
body: shortUrl || img[0].imgUrl!
|
||||
// icon: img[0].imgUrl
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, 100)
|
||||
}
|
||||
const inserted = await GalleryDB.getInstance().insert(img[0])
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', img)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
return {
|
||||
url: handleUrlEncodeWithSetting(inserted.imgUrl as string),
|
||||
fullResult: inserted
|
||||
}
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
title: $t('UPLOAD_FAILED'),
|
||||
body: $t('TIPS_UPLOAD_NOT_PICTURES')
|
||||
})
|
||||
notification.show()
|
||||
return {
|
||||
url: '',
|
||||
fullResult: {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
url: '',
|
||||
fullResult: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadChoosedFiles = async (
|
||||
webContents: WebContents,
|
||||
files: IFileWithPath[]
|
||||
): Promise<IStringKeyMap[]> => {
|
||||
const input = files.map(item => item.path)
|
||||
const rawInput = cloneDeep(input)
|
||||
const { needRestore, ctx } = await handleSecondaryUpload(webContents, input)
|
||||
let imgs: ImgInfo[] | false = false
|
||||
if (needRestore) {
|
||||
const res = await uploader.setWebContents(webContents).uploadReturnCtx(ctx ? ctx.processedInput : input, true)
|
||||
imgs = res ? res.output : false
|
||||
} else {
|
||||
imgs = await uploader.setWebContents(webContents).upload(input)
|
||||
}
|
||||
const result = []
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
|
||||
const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
if (deleteLocalFile) {
|
||||
fs.remove(rawInput[i])
|
||||
.then(() => {
|
||||
picgo.log.info(`delete local file: ${rawInput[i]}`)
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
picgo.log.error(err)
|
||||
})
|
||||
}
|
||||
const [pasteTextItem, shortUrl] = await pasteTemplate(
|
||||
pasteStyle,
|
||||
imgs[i],
|
||||
db.get(configPaths.settings.customLink)
|
||||
)
|
||||
imgs[i].shortUrl = shortUrl
|
||||
pasteText.push(pasteTextItem)
|
||||
const isShowResultNotification =
|
||||
db.get(configPaths.settings.uploadResultNotification) === undefined
|
||||
? true
|
||||
: !!db.get(configPaths.settings.uploadResultNotification)
|
||||
if (isShowResultNotification) {
|
||||
const notification = new Notification({
|
||||
title: $t('UPLOAD_SUCCEED'),
|
||||
body: shortUrl || imgs[i].imgUrl!
|
||||
// icon: files[i].path
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
}, i * 100)
|
||||
}
|
||||
const inserted = await GalleryDB.getInstance().insert(imgs[i])
|
||||
result.push({
|
||||
url: handleUrlEncodeWithSetting(inserted.imgUrl!),
|
||||
fullResult: inserted
|
||||
})
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)?.webContents?.send('uploadFiles', imgs)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export const handleSecondaryUpload = async (
|
||||
webContents?: WebContents,
|
||||
input?: string[],
|
||||
uploadType: 'clipboard' | 'file' | 'tray' = 'file'
|
||||
): Promise<{ needRestore: boolean; ctx: IPicGo | false }> => {
|
||||
const enableSecondUploader = db.get(configPaths.settings.enableSecondUploader) || false
|
||||
let currentPicBedType = ''
|
||||
let currentPicBedConfig = {} as IStringKeyMap
|
||||
let currentPicBedConfigId = ''
|
||||
let needRestore = false
|
||||
let ctx: IPicGo | false = false
|
||||
if (enableSecondUploader) {
|
||||
const secondUploader = db.get(configPaths.picBed.secondUploader)
|
||||
const secondUploaderConfig = db.get(configPaths.picBed.secondUploaderConfig)
|
||||
const secondUploaderId = db.get(configPaths.picBed.secondUploaderId)
|
||||
const currentPicBed = db.get('picBed') || ({} as IStringKeyMap)
|
||||
currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms'
|
||||
currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
|
||||
currentPicBedConfigId = currentPicBedConfig._id
|
||||
if (
|
||||
secondUploader === currentPicBedType &&
|
||||
secondUploaderConfig._configName === currentPicBedConfig._configName &&
|
||||
secondUploaderId === currentPicBedConfigId
|
||||
) {
|
||||
picgo.log.info('second uploader is the same as current uploader')
|
||||
} else {
|
||||
needRestore = true
|
||||
let secondImgs: ImgInfo[] | false = false
|
||||
changeCurrentUploader(secondUploader, secondUploaderConfig, secondUploaderId)
|
||||
if (uploadType === 'clipboard') {
|
||||
ctx = await handleClipboardUploadingReturnCtx(undefined)
|
||||
} else {
|
||||
ctx = await uploader.setWebContents(webContents!).uploadReturnCtx(input)
|
||||
}
|
||||
secondImgs = ctx ? ctx.output : false
|
||||
if (secondImgs !== false) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
if (uploadType === 'clipboard') {
|
||||
if (secondImgs.length > 0) {
|
||||
await GalleryDB.getInstance().insert(secondImgs[0])
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', secondImgs)
|
||||
}
|
||||
} else {
|
||||
for (const secondImgsItem of secondImgs) {
|
||||
await GalleryDB.getInstance().insert(secondImgsItem)
|
||||
}
|
||||
if (uploadType === 'tray') {
|
||||
trayWindow?.webContents?.send('dragFiles', secondImgs)
|
||||
} else {
|
||||
trayWindow?.webContents?.send('uploadFiles', secondImgs)
|
||||
}
|
||||
}
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW) && uploadType !== 'tray') {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needRestore) {
|
||||
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
|
||||
}
|
||||
return {
|
||||
needRestore,
|
||||
ctx
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,11 @@ const waitForRename = (window: BrowserWindow, id: number): Promise<string | null
|
||||
class Uploader {
|
||||
private webContents: WebContents | null = null
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
init() {
|
||||
picgo.on(ICOREBuildInEvent.NOTIFICATION, (message: any) => {
|
||||
new Notification(message).show()
|
||||
})
|
||||
@@ -92,12 +92,12 @@ class Uploader {
|
||||
})
|
||||
}
|
||||
|
||||
setWebContents (webContents: WebContents) {
|
||||
setWebContents(webContents: WebContents) {
|
||||
this.webContents = webContents
|
||||
return this
|
||||
}
|
||||
|
||||
private async getClipboardImagePath (): Promise<string | false> {
|
||||
private async getClipboardImagePath(): Promise<string | false> {
|
||||
const imgPath = getClipboardFilePath()
|
||||
if (imgPath) return imgPath
|
||||
|
||||
@@ -115,7 +115,7 @@ class Uploader {
|
||||
/**
|
||||
* use electron's clipboard image to upload
|
||||
*/
|
||||
async uploadWithBuildInClipboard (): Promise<ImgInfo[] | false> {
|
||||
async uploadWithBuildInClipboard(): Promise<ImgInfo[] | false> {
|
||||
let imgPath: string | false = false
|
||||
try {
|
||||
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
|
||||
try {
|
||||
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 {
|
||||
const ctx = await picgo.uploadReturnCtx(img, skipProcess)
|
||||
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 {
|
||||
const output = await picgo.upload(img)
|
||||
if (!Array.isArray(output) || !output.some((item: ImgInfo) => item.imgUrl)) return false
|
||||
|
||||
@@ -1,277 +1,277 @@
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import bus from '@core/bus'
|
||||
import { CREATE_APP_MENU } from '@core/bus/constants'
|
||||
import db from '@core/datastore'
|
||||
import { app, BrowserWindow, Rectangle } from 'electron'
|
||||
|
||||
import type { IWindowListItem } from '#/types/electron'
|
||||
import type { IBrowserWindowOptions } from '#/types/types'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '~/events/constant'
|
||||
import { T as $t } from '~/i18n'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
import logo from '../../../../../resources/logo.png?asset&asarUnpack'
|
||||
|
||||
const windowList = new Map<string, IWindowListItem>()
|
||||
|
||||
const getDefaultWindowSizes = (): { width: number; height: number } => {
|
||||
const [mainWindowWidth, mainWindowHeight] = db.get([
|
||||
configPaths.settings.mainWindowWidth,
|
||||
configPaths.settings.mainWindowHeight
|
||||
])
|
||||
return {
|
||||
width: mainWindowWidth || 1200,
|
||||
height: mainWindowHeight || 800
|
||||
}
|
||||
}
|
||||
|
||||
function setMiniWindowShape (win: BrowserWindow) {
|
||||
const radius = 32
|
||||
const shape: Rectangle[] = []
|
||||
|
||||
for (let y = -radius; y <= radius; y++) {
|
||||
for (let x = -radius; x <= radius; x++) {
|
||||
if (x * x + y * y <= radius * radius) {
|
||||
shape.push({ x: radius + x, y: radius + y, width: 1, height: 1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
win.setShape(shape)
|
||||
}
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const preloadPath = fileURLToPath(new URL('../preload/index.mjs', import.meta.url))
|
||||
|
||||
const { width: defaultWindowWidth, height: defaultWindowHeight } = getDefaultWindowSizes()
|
||||
|
||||
const trayWindowOptions = {
|
||||
height: 350,
|
||||
width: 196,
|
||||
show: false,
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
transparent: true,
|
||||
vibrancy: 'ultra-dark',
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
backgroundThrottling: true,
|
||||
webSecurity: false
|
||||
}
|
||||
}
|
||||
|
||||
const settingWindowOptions = {
|
||||
height: defaultWindowHeight,
|
||||
width: defaultWindowWidth,
|
||||
show: false,
|
||||
frame: true,
|
||||
center: true,
|
||||
fullscreenable: true,
|
||||
resizable: true,
|
||||
title: 'PicList',
|
||||
transparent: false,
|
||||
backgroundColor: '#ebeef5',
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
webviewTag: true,
|
||||
backgroundThrottling: true,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
webSecurity: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
settingWindowOptions.frame = false
|
||||
settingWindowOptions.icon = logo
|
||||
}
|
||||
|
||||
const miniWindowOptions = {
|
||||
height: 64,
|
||||
width: 64,
|
||||
show: process.platform === 'linux',
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
skipTaskbar: true,
|
||||
resizable: false,
|
||||
transparent: process.platform !== 'linux',
|
||||
icon: logo,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
backgroundThrottling: true,
|
||||
nodeIntegrationInWorker: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (db.get(configPaths.settings.miniWindowOntop)) {
|
||||
miniWindowOptions.alwaysOnTop = true
|
||||
}
|
||||
|
||||
const renameWindowOptions = {
|
||||
height: 270,
|
||||
width: 350,
|
||||
show: true,
|
||||
fullscreenable: false,
|
||||
icon: logo,
|
||||
resizable: true,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
backgroundThrottling: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
renameWindowOptions.show = true
|
||||
renameWindowOptions.backgroundColor = '#3f3c37'
|
||||
renameWindowOptions.autoHideMenuBar = true
|
||||
renameWindowOptions.transparent = false
|
||||
}
|
||||
|
||||
const toolboxWindowOptions = {
|
||||
height: 450,
|
||||
width: 800,
|
||||
show: false,
|
||||
frame: true,
|
||||
center: true,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
title: `PicList ${$t('TOOLBOX')}`,
|
||||
backgroundColor: '#ebeef5',
|
||||
icon: logo,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
backgroundThrottling: true,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
webSecurity: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
toolboxWindowOptions.backgroundColor = '#3f3c37'
|
||||
toolboxWindowOptions.autoHideMenuBar = true
|
||||
toolboxWindowOptions.transparent = false
|
||||
}
|
||||
|
||||
windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
isValid: process.platform !== 'linux',
|
||||
multiple: false,
|
||||
options: () => trayWindowOptions,
|
||||
callback (window) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(process.env.ELECTRON_RENDERER_URL)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
window.on('blur', () => {
|
||||
window.hide()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options: () => settingWindowOptions,
|
||||
callback (window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#main-page/upload`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'main-page/upload'
|
||||
})
|
||||
}
|
||||
window.on('closed', () => {
|
||||
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
||||
if (process.platform === 'linux') {
|
||||
process.nextTick(() => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
})
|
||||
bus.emit(CREATE_APP_MENU)
|
||||
windowManager.create(IWindowList.MINI_WINDOW)
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.MINI_WINDOW, {
|
||||
isValid: process.platform !== 'darwin',
|
||||
multiple: false,
|
||||
options: () => miniWindowOptions,
|
||||
callback (window) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#mini-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'mini-page'
|
||||
})
|
||||
}
|
||||
setMiniWindowShape(window)
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.RENAME_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: true,
|
||||
options: () => renameWindowOptions,
|
||||
async callback (window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#rename-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'rename-page'
|
||||
})
|
||||
}
|
||||
const currentWindow = windowManager.getAvailableWindow(true)
|
||||
if (currentWindow && currentWindow.isVisible()) {
|
||||
const { x, y, width, height } = currentWindow.getBounds()
|
||||
const positionX = Math.floor(x + width / 2 - 150)
|
||||
const positionY = Math.floor(y + height / 2 - (height > 400 ? 88 : 0))
|
||||
window.setPosition(positionX, positionY, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.TOOLBOX_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options: () => toolboxWindowOptions,
|
||||
async callback (window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#toolbox-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'toolbox-page'
|
||||
})
|
||||
}
|
||||
const currentWindow = windowManager.getAvailableWindow(true)
|
||||
if (currentWindow && currentWindow.isVisible()) {
|
||||
const { x, y, width, height } = currentWindow.getBounds()
|
||||
const positionX = Math.floor(x + width / 2 - 400)
|
||||
const positionY = Math.floor(y + height / 2 - (height > 400 ? 225 : 0))
|
||||
window.setPosition(positionX, positionY, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default windowList
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import bus from '@core/bus'
|
||||
import { CREATE_APP_MENU } from '@core/bus/constants'
|
||||
import db from '@core/datastore'
|
||||
import { app, BrowserWindow, Rectangle } from 'electron'
|
||||
|
||||
import type { IWindowListItem } from '#/types/electron'
|
||||
import type { IBrowserWindowOptions } from '#/types/types'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '~/events/constant'
|
||||
import { T as $t } from '~/i18n'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
import logo from '../../../../../resources/logo.png?asset&asarUnpack'
|
||||
|
||||
const windowList = new Map<string, IWindowListItem>()
|
||||
|
||||
const getDefaultWindowSizes = (): { width: number; height: number } => {
|
||||
const [mainWindowWidth, mainWindowHeight] = db.get([
|
||||
configPaths.settings.mainWindowWidth,
|
||||
configPaths.settings.mainWindowHeight
|
||||
])
|
||||
return {
|
||||
width: mainWindowWidth || 1200,
|
||||
height: mainWindowHeight || 800
|
||||
}
|
||||
}
|
||||
|
||||
function setMiniWindowShape(win: BrowserWindow) {
|
||||
const radius = 32
|
||||
const shape: Rectangle[] = []
|
||||
|
||||
for (let y = -radius; y <= radius; y++) {
|
||||
for (let x = -radius; x <= radius; x++) {
|
||||
if (x * x + y * y <= radius * radius) {
|
||||
shape.push({ x: radius + x, y: radius + y, width: 1, height: 1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
win.setShape(shape)
|
||||
}
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const preloadPath = fileURLToPath(new URL('../preload/index.mjs', import.meta.url))
|
||||
|
||||
const { width: defaultWindowWidth, height: defaultWindowHeight } = getDefaultWindowSizes()
|
||||
|
||||
const trayWindowOptions = {
|
||||
height: 350,
|
||||
width: 196,
|
||||
show: false,
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
transparent: true,
|
||||
vibrancy: 'ultra-dark',
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
backgroundThrottling: true,
|
||||
webSecurity: false
|
||||
}
|
||||
}
|
||||
|
||||
const settingWindowOptions = {
|
||||
height: defaultWindowHeight,
|
||||
width: defaultWindowWidth,
|
||||
show: false,
|
||||
frame: true,
|
||||
center: true,
|
||||
fullscreenable: true,
|
||||
resizable: true,
|
||||
title: 'PicList',
|
||||
transparent: false,
|
||||
backgroundColor: '#ebeef5',
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
webviewTag: true,
|
||||
backgroundThrottling: true,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
webSecurity: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
settingWindowOptions.frame = false
|
||||
settingWindowOptions.icon = logo
|
||||
}
|
||||
|
||||
const miniWindowOptions = {
|
||||
height: 64,
|
||||
width: 64,
|
||||
show: process.platform === 'linux',
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
skipTaskbar: true,
|
||||
resizable: false,
|
||||
transparent: process.platform !== 'linux',
|
||||
icon: logo,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
backgroundThrottling: true,
|
||||
nodeIntegrationInWorker: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (db.get(configPaths.settings.miniWindowOntop)) {
|
||||
miniWindowOptions.alwaysOnTop = true
|
||||
}
|
||||
|
||||
const renameWindowOptions = {
|
||||
height: 270,
|
||||
width: 350,
|
||||
show: true,
|
||||
fullscreenable: false,
|
||||
icon: logo,
|
||||
resizable: true,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
backgroundThrottling: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
renameWindowOptions.show = true
|
||||
renameWindowOptions.backgroundColor = '#3f3c37'
|
||||
renameWindowOptions.autoHideMenuBar = true
|
||||
renameWindowOptions.transparent = false
|
||||
}
|
||||
|
||||
const toolboxWindowOptions = {
|
||||
height: 450,
|
||||
width: 800,
|
||||
show: false,
|
||||
frame: true,
|
||||
center: true,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
title: `PicList ${$t('TOOLBOX')}`,
|
||||
backgroundColor: '#ebeef5',
|
||||
icon: logo,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
backgroundThrottling: true,
|
||||
preload: preloadPath,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegrationInWorker: false,
|
||||
webSecurity: false
|
||||
}
|
||||
} as IBrowserWindowOptions
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
toolboxWindowOptions.backgroundColor = '#3f3c37'
|
||||
toolboxWindowOptions.autoHideMenuBar = true
|
||||
toolboxWindowOptions.transparent = false
|
||||
}
|
||||
|
||||
windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
isValid: process.platform !== 'linux',
|
||||
multiple: false,
|
||||
options: () => trayWindowOptions,
|
||||
callback(window) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(process.env.ELECTRON_RENDERER_URL)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
window.on('blur', () => {
|
||||
window.hide()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options: () => settingWindowOptions,
|
||||
callback(window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#main-page/upload`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'main-page/upload'
|
||||
})
|
||||
}
|
||||
window.on('closed', () => {
|
||||
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
||||
if (process.platform === 'linux') {
|
||||
process.nextTick(() => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
})
|
||||
bus.emit(CREATE_APP_MENU)
|
||||
windowManager.create(IWindowList.MINI_WINDOW)
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.MINI_WINDOW, {
|
||||
isValid: process.platform !== 'darwin',
|
||||
multiple: false,
|
||||
options: () => miniWindowOptions,
|
||||
callback(window) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#mini-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'mini-page'
|
||||
})
|
||||
}
|
||||
setMiniWindowShape(window)
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.RENAME_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: true,
|
||||
options: () => renameWindowOptions,
|
||||
async callback(window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#rename-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'rename-page'
|
||||
})
|
||||
}
|
||||
const currentWindow = windowManager.getAvailableWindow(true)
|
||||
if (currentWindow && currentWindow.isVisible()) {
|
||||
const { x, y, width, height } = currentWindow.getBounds()
|
||||
const positionX = Math.floor(x + width / 2 - 150)
|
||||
const positionY = Math.floor(y + height / 2 - (height > 400 ? 88 : 0))
|
||||
window.setPosition(positionX, positionY, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.TOOLBOX_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options: () => toolboxWindowOptions,
|
||||
async callback(window, windowManager) {
|
||||
if (!app.isPackaged && process.env.ELECTRON_RENDERER_URL) {
|
||||
window.loadURL(`${process.env.ELECTRON_RENDERER_URL}#toolbox-page`)
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||||
hash: 'toolbox-page'
|
||||
})
|
||||
}
|
||||
const currentWindow = windowManager.getAvailableWindow(true)
|
||||
if (currentWindow && currentWindow.isVisible()) {
|
||||
const { x, y, width, height } = currentWindow.getBounds()
|
||||
const positionX = Math.floor(x + width / 2 - 400)
|
||||
const positionY = Math.floor(y + height / 2 - (height > 400 ? 225 : 0))
|
||||
window.setPosition(positionX, positionY, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default windowList
|
||||
|
||||
@@ -5,10 +5,10 @@ import type { IWindowListItem, IWindowManager } from '#/types/electron'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
class WindowManager implements IWindowManager {
|
||||
#windowMap: Map< string, BrowserWindow> = new Map()
|
||||
#windowMap: Map<string, BrowserWindow> = new Map()
|
||||
#windowIdMap: Map<number, string> = new Map()
|
||||
|
||||
create (name: string) {
|
||||
create(name: string) {
|
||||
const windowConfig: IWindowListItem = windowList.get(name)!
|
||||
if (!windowConfig.isValid) return null
|
||||
|
||||
@@ -30,14 +30,14 @@ class WindowManager implements IWindowManager {
|
||||
return window
|
||||
}
|
||||
|
||||
get (name: string) {
|
||||
get(name: string) {
|
||||
if (this.has(name)) {
|
||||
return this.#windowMap.get(name)!
|
||||
}
|
||||
return this.create(name)
|
||||
}
|
||||
|
||||
has (name: string) {
|
||||
has(name: string) {
|
||||
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)
|
||||
if (miniWindow && miniWindow.isVisible() && !isSkipMiniWindow) {
|
||||
return miniWindow
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { EventEmitter } from 'node:events'
|
||||
|
||||
class OptimizedBus extends EventEmitter {
|
||||
constructor () {
|
||||
super()
|
||||
this.setMaxListeners(50)
|
||||
}
|
||||
|
||||
once (event: string | symbol, listener: (...args: any[]) => void): this {
|
||||
const wrappedListener = (...args: any[]) => {
|
||||
try {
|
||||
listener(...args)
|
||||
} finally {
|
||||
this.removeListener(event, wrappedListener)
|
||||
}
|
||||
}
|
||||
return super.once(event, wrappedListener)
|
||||
}
|
||||
|
||||
cleanupListeners () {
|
||||
const events = this.eventNames()
|
||||
events.forEach(event => {
|
||||
const listenerCount = this.listenerCount(event)
|
||||
console.log(` listener count (${listenerCount}) for event: ${String(event)}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const bus = new OptimizedBus()
|
||||
|
||||
export default bus
|
||||
import { EventEmitter } from 'node:events'
|
||||
|
||||
class OptimizedBus extends EventEmitter {
|
||||
constructor() {
|
||||
super()
|
||||
this.setMaxListeners(50)
|
||||
}
|
||||
|
||||
once(event: string | symbol, listener: (...args: any[]) => void): this {
|
||||
const wrappedListener = (...args: any[]) => {
|
||||
try {
|
||||
listener(...args)
|
||||
} finally {
|
||||
this.removeListener(event, wrappedListener)
|
||||
}
|
||||
}
|
||||
return super.once(event, wrappedListener)
|
||||
}
|
||||
|
||||
cleanupListeners() {
|
||||
const events = this.eventNames()
|
||||
events.forEach(event => {
|
||||
const listenerCount = this.listenerCount(event)
|
||||
console.log(` listener count (${listenerCount}) for event: ${String(event)}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const bus = new OptimizedBus()
|
||||
|
||||
export default bus
|
||||
|
||||
@@ -23,7 +23,7 @@ const errorMsg = {
|
||||
brokenButBackup: $t('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP')
|
||||
}
|
||||
|
||||
function dbChecker () {
|
||||
function dbChecker() {
|
||||
if (process.type !== 'renderer') {
|
||||
// db save bak
|
||||
try {
|
||||
@@ -80,7 +80,7 @@ function dbChecker () {
|
||||
/**
|
||||
* Get config path
|
||||
*/
|
||||
function dbPathChecker (): string {
|
||||
function dbPathChecker(): string {
|
||||
if (_configFilePath) {
|
||||
return _configFilePath
|
||||
}
|
||||
@@ -120,11 +120,11 @@ function dbPathChecker (): string {
|
||||
}
|
||||
}
|
||||
|
||||
function dbPathDir () {
|
||||
function dbPathDir() {
|
||||
return path.dirname(dbPathChecker())
|
||||
}
|
||||
|
||||
function getGalleryDBPath (): {
|
||||
function getGalleryDBPath(): {
|
||||
dbPath: string
|
||||
dbBackupPath: string
|
||||
} {
|
||||
|
||||
@@ -19,7 +19,7 @@ export const DB_PATH: string = getGalleryDBPath().dbPath
|
||||
class ConfigStore {
|
||||
#db: JSONStore
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.#db = new JSONStore(CONFIG_PATH)
|
||||
|
||||
if (!this.#db.has('picBed')) {
|
||||
@@ -43,11 +43,11 @@ class ConfigStore {
|
||||
this.read()
|
||||
}
|
||||
|
||||
read (flush?: boolean): IJSON {
|
||||
read(flush?: boolean): IJSON {
|
||||
return this.#db.read(flush)
|
||||
}
|
||||
|
||||
getSingle (key = ''): any {
|
||||
getSingle(key = ''): any {
|
||||
if (key === '') {
|
||||
return this.#db.read(true)
|
||||
}
|
||||
@@ -55,43 +55,43 @@ class ConfigStore {
|
||||
return this.#db.get(key)
|
||||
}
|
||||
|
||||
get (key: string): any
|
||||
get (key: string[]): any[]
|
||||
get (key: string | string[] = ''): any {
|
||||
get(key: string): any
|
||||
get(key: string[]): any[]
|
||||
get(key: string | string[] = ''): any {
|
||||
if (Array.isArray(key)) {
|
||||
return key.map(k => this.getSingle(k))
|
||||
}
|
||||
return this.getSingle(key)
|
||||
}
|
||||
|
||||
set (key: string, value: any): void {
|
||||
set(key: string, value: any): void {
|
||||
this.read(true)
|
||||
return this.#db.set(key, value)
|
||||
}
|
||||
|
||||
has (key: string) {
|
||||
has(key: string) {
|
||||
this.read(true)
|
||||
return this.#db.has(key)
|
||||
}
|
||||
|
||||
unset (key: string, value: any): boolean {
|
||||
unset(key: string, value: any): boolean {
|
||||
this.read(true)
|
||||
return this.#db.unset(key, value)
|
||||
}
|
||||
|
||||
saveConfig (config: Partial<IConfig>): void {
|
||||
saveConfig(config: Partial<IConfig>): void {
|
||||
Object.keys(config).forEach((name: string) => {
|
||||
this.set(name, config[name])
|
||||
})
|
||||
}
|
||||
|
||||
removeConfig (config: IConfig): void {
|
||||
removeConfig(config: IConfig): void {
|
||||
Object.keys(config).forEach((name: string) => {
|
||||
this.unset(name, config[name])
|
||||
})
|
||||
}
|
||||
|
||||
getConfigPath () {
|
||||
getConfigPath() {
|
||||
return CONFIG_PATH
|
||||
}
|
||||
}
|
||||
@@ -103,11 +103,11 @@ export default db
|
||||
// v2.3.0 add gallery db
|
||||
class GalleryDB {
|
||||
static #instance: DBStore
|
||||
private constructor () {
|
||||
private constructor() {
|
||||
console.log('init gallery db')
|
||||
}
|
||||
|
||||
static getInstance (): DBStore {
|
||||
static getInstance(): DBStore {
|
||||
if (!GalleryDB.#instance) {
|
||||
GalleryDB.#instance = new DBStore(DB_PATH, 'gallery')
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ picgo.GUI_VERSION = pkg.version
|
||||
|
||||
const originPicGoSaveConfig = picgo.saveConfig.bind(picgo)
|
||||
|
||||
function flushDB () {
|
||||
function flushDB() {
|
||||
db.read(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class AlistApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const { fileName, config } = configMap
|
||||
try {
|
||||
const { version, url, uploadPath, token } = config
|
||||
|
||||
@@ -26,7 +26,7 @@ const getAListToken = async (url: string, username: string, password: string) =>
|
||||
}
|
||||
|
||||
export default class AListplistApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const { fileName, config } = configMap
|
||||
try {
|
||||
const { url, username, password, uploadPath } = config
|
||||
|
||||
@@ -9,11 +9,11 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const { fileName, config } = configMap
|
||||
try {
|
||||
const client = new OSS({ ...config, region: config.area })
|
||||
|
||||
@@ -39,7 +39,7 @@ const apiMap: IStringKeyMap = {
|
||||
}
|
||||
|
||||
export default class ALLApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const api = apiMap[configMap.type]
|
||||
return api ? await api.delete(configMap) : false
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { removeFileFromS3InMain } from '~/utils/deleteFunc'
|
||||
import { deleteFailedLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class AwsS3Api {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
try {
|
||||
return await removeFileFromS3InMain(getRawData(configMap))
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { removeFileFromDogeInMain } from '~/utils/deleteFunc'
|
||||
import { deleteFailedLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class AwsS3Api {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
try {
|
||||
return await removeFileFromDogeInMain(getRawData(configMap))
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -10,18 +10,18 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class GithubApi {
|
||||
static #createOctokit (token: string) {
|
||||
static #createOctokit(token: string) {
|
||||
return new Octokit({
|
||||
auth: token
|
||||
})
|
||||
}
|
||||
|
||||
static #createKey (path: string | undefined, fileName: string): string {
|
||||
static #createKey(path: string | undefined, fileName: string): string {
|
||||
const formatedFileName = fileName.replace(/%2F/g, '/')
|
||||
return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${formatedFileName}` : formatedFileName
|
||||
}
|
||||
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const {
|
||||
fileName,
|
||||
hash,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { removeFileFromHuaweiInMain } from '~/utils/deleteFunc'
|
||||
import { deleteFailedLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class HuaweicloudApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
try {
|
||||
return await removeFileFromHuaweiInMain(getRawData(configMap))
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -11,7 +11,7 @@ interface IConfigMap {
|
||||
export default class ImgurApi {
|
||||
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
|
||||
let Authorization: string, apiUrl: string
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class LocalApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const { hash } = configMap
|
||||
if (!hash) {
|
||||
deleteLog(hash, 'Local', false, 'Local.delete: invalid params')
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { IStringKeyMap } from '#/types/types'
|
||||
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class LskyplistApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { hash, config } = configMap
|
||||
if (!hash || !config || !config.token) {
|
||||
deleteLog(hash, 'Lskyplist', false, 'LskyplistApi.delete: invalid params')
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { IStringKeyMap } from '#/types/types'
|
||||
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class PiclistApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { config, fullResult } = configMap
|
||||
const { host, port } = config
|
||||
if (!fullResult) return true
|
||||
|
||||
@@ -8,7 +8,7 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class QiniuApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const {
|
||||
fileName,
|
||||
config: { accessKey, secretKey, bucket, path }
|
||||
|
||||
@@ -4,7 +4,7 @@ import { removeFileFromSFTPInMain } from '~/utils/deleteFunc'
|
||||
import { deleteFailedLog } from '~/utils/deleteLog'
|
||||
|
||||
export default class SftpPlistApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
static async delete(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileName, config } = configMap
|
||||
try {
|
||||
return await removeFileFromSFTPInMain(getRawData(config), fileName)
|
||||
|
||||
@@ -11,7 +11,7 @@ interface IConfigMap {
|
||||
export default class SmmsApi {
|
||||
static readonly #baseUrl = 'https://smms.app/api/v2'
|
||||
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const { hash, config } = configMap
|
||||
if (!hash || !config || !config.token) {
|
||||
deleteLog(hash, 'Smms', false, 'SmmsApi.delete: invalid params')
|
||||
|
||||
@@ -7,14 +7,14 @@ interface IConfigMap {
|
||||
config: PartialKeys<ITcYunConfig, 'path'>
|
||||
}
|
||||
export default class TcyunApi {
|
||||
static #createCOS (SecretId: string, SecretKey: string): COS {
|
||||
static #createCOS(SecretId: string, SecretKey: string): COS {
|
||||
return new COS({
|
||||
SecretId,
|
||||
SecretKey
|
||||
})
|
||||
}
|
||||
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const {
|
||||
fileName,
|
||||
config: { secretId, secretKey, bucket, area, path }
|
||||
|
||||
@@ -9,7 +9,7 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class UpyunApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const {
|
||||
fileName,
|
||||
config: { bucket, operator, password, path }
|
||||
|
||||
@@ -10,7 +10,7 @@ interface IConfigMap {
|
||||
}
|
||||
|
||||
export default class WebdavApi {
|
||||
static async delete (configMap: IConfigMap): Promise<boolean> {
|
||||
static async delete(configMap: IConfigMap): Promise<boolean> {
|
||||
const {
|
||||
fileName,
|
||||
config: { host, username, password, path, sslEnabled, authType }
|
||||
|
||||
@@ -8,7 +8,16 @@ import { BrowserWindow, dialog, ipcMain, IpcMainEvent, MessageBoxOptions, Notifi
|
||||
import fs from 'fs-extra'
|
||||
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 { T as $t } from '~/i18n'
|
||||
import { handleCopyUrl } from '~/utils/common'
|
||||
@@ -21,18 +30,18 @@ class GuiApi implements IGuiApi {
|
||||
private static instance: GuiApi
|
||||
private windowId: number = -1
|
||||
private settingWindowId: number = -1
|
||||
private constructor () {
|
||||
private constructor() {
|
||||
console.log('init guiapi')
|
||||
}
|
||||
|
||||
static getInstance (): GuiApi {
|
||||
static getInstance(): GuiApi {
|
||||
if (!GuiApi.instance) {
|
||||
GuiApi.instance = new GuiApi()
|
||||
}
|
||||
return GuiApi.instance
|
||||
}
|
||||
|
||||
private async showSettingWindow () {
|
||||
private async showSettingWindow() {
|
||||
this.settingWindowId = await getSettingWindowId()
|
||||
const settingWindow = BrowserWindow.fromId(this.settingWindowId)
|
||||
if (settingWindow?.isVisible()) {
|
||||
@@ -46,11 +55,11 @@ class GuiApi implements IGuiApi {
|
||||
})
|
||||
}
|
||||
|
||||
private getWebcontentsByWindowId (id: number) {
|
||||
private getWebcontentsByWindowId(id: number) {
|
||||
return BrowserWindow.fromId(id)?.webContents
|
||||
}
|
||||
|
||||
async showInputBox (
|
||||
async showInputBox(
|
||||
options: IShowInputBoxOption = {
|
||||
title: '',
|
||||
placeholder: ''
|
||||
@@ -65,13 +74,13 @@ class GuiApi implements IGuiApi {
|
||||
})
|
||||
}
|
||||
|
||||
async showFileExplorer (options: IShowFileExplorerOption = {}) {
|
||||
async showFileExplorer(options: IShowFileExplorerOption = {}) {
|
||||
this.windowId = await getWindowId()
|
||||
const res = await dialog.showOpenDialog(BrowserWindow.fromId(this.windowId)!, options)
|
||||
return res.filePaths || []
|
||||
}
|
||||
|
||||
async upload (input: IUploadOption) {
|
||||
async upload(input: IUploadOption) {
|
||||
this.windowId = await getWindowId()
|
||||
const webContents = this.getWebcontentsByWindowId(this.windowId)
|
||||
const rawInput = cloneDeep(input)
|
||||
@@ -122,7 +131,7 @@ class GuiApi implements IGuiApi {
|
||||
return []
|
||||
}
|
||||
|
||||
showNotification (
|
||||
showNotification(
|
||||
options: IShowNotificationOption = {
|
||||
title: '',
|
||||
body: ''
|
||||
@@ -135,7 +144,7 @@ class GuiApi implements IGuiApi {
|
||||
notification.show()
|
||||
}
|
||||
|
||||
showMessageBox (
|
||||
showMessageBox(
|
||||
options: IShowMessageBoxOption = {
|
||||
title: '',
|
||||
message: '',
|
||||
@@ -159,7 +168,7 @@ class GuiApi implements IGuiApi {
|
||||
/**
|
||||
* get picgo config/data path
|
||||
*/
|
||||
async getConfigPath () {
|
||||
async getConfigPath() {
|
||||
const currentConfigPath = dbPathChecker()
|
||||
const galleryDBPath = getGalleryDBPath().dbPath
|
||||
return {
|
||||
@@ -169,12 +178,12 @@ class GuiApi implements IGuiApi {
|
||||
}
|
||||
}
|
||||
|
||||
get galleryDB (): DBStore {
|
||||
get galleryDB(): DBStore {
|
||||
return new Proxy<DBStore>(GalleryDB.getInstance(), {
|
||||
get (target, prop: keyof DBStore) {
|
||||
get(target, prop: keyof DBStore) {
|
||||
if (prop === 'overwrite') {
|
||||
return new Proxy(GalleryDB.getInstance().overwrite, {
|
||||
apply (target, ctx, args) {
|
||||
apply(target, ctx, args) {
|
||||
return new Promise(resolve => {
|
||||
const guiApi = GuiApi.getInstance()
|
||||
guiApi
|
||||
@@ -197,7 +206,7 @@ class GuiApi implements IGuiApi {
|
||||
}
|
||||
if (prop === 'removeById') {
|
||||
return new Proxy(GalleryDB.getInstance().removeById, {
|
||||
apply (target, ctx, args) {
|
||||
apply(target, ctx, args) {
|
||||
return new Promise(resolve => {
|
||||
const guiApi = GuiApi.getInstance()
|
||||
guiApi
|
||||
|
||||
@@ -17,7 +17,7 @@ import windowManager from 'apis/app/window/windowManager'
|
||||
import type { IFileWithPath } from '#/types/types'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
function initEventCenter () {
|
||||
function initEventCenter() {
|
||||
const eventList: any = {
|
||||
'picgo:upload': uploadClipboardFiles,
|
||||
[UPLOAD_WITH_CLIPBOARD_FILES]: busCallUploadClipboardFiles,
|
||||
@@ -31,31 +31,31 @@ function initEventCenter () {
|
||||
}
|
||||
}
|
||||
|
||||
async function busCallUploadClipboardFiles () {
|
||||
async function busCallUploadClipboardFiles() {
|
||||
const result = await uploadClipboardFiles()
|
||||
const imgUrl = result.url
|
||||
bus.emit(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, imgUrl)
|
||||
}
|
||||
|
||||
async function busCallUploadFiles (pathList: IFileWithPath[]) {
|
||||
async function busCallUploadFiles(pathList: IFileWithPath[]) {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
const result = await uploadChoosedFiles(win.webContents, pathList)
|
||||
const urls = result.map((item: any) => item.url)
|
||||
bus.emit(UPLOAD_WITH_FILES_RESPONSE, urls)
|
||||
}
|
||||
|
||||
function busCallGetWindowId () {
|
||||
function busCallGetWindowId() {
|
||||
const win = windowManager.getAvailableWindow()
|
||||
bus.emit(GET_WINDOW_ID_REPONSE, win.id)
|
||||
}
|
||||
|
||||
function busCallGetSettingWindowId () {
|
||||
function busCallGetSettingWindowId() {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
bus.emit(GET_SETTING_WINDOW_ID_RESPONSE, settingWindow.id)
|
||||
}
|
||||
|
||||
export default {
|
||||
listen () {
|
||||
listen() {
|
||||
initEventCenter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
export const SHOW_INPUT_BOX = 'SHOW_INPUT_BOX'
|
||||
export const SHOW_INPUT_BOX_RESPONSE = 'SHOW_INPUT_BOX_RESPONSE'
|
||||
export const TOGGLE_SHORTKEY_MODIFIED_MODE = 'TOGGLE_SHORTKEY_MODIFIED_MODE'
|
||||
// picgo plugin
|
||||
export const PICGO_CONFIG_PLUGIN = 'PICGO_CONFIG_PLUGIN'
|
||||
export const PICGO_HANDLE_PLUGIN_ING = 'PICGO_HANDLE_PLUGIN_ING'
|
||||
export const PICGO_HANDLE_PLUGIN_DONE = 'PICGO_HANDLE_PLUGIN_DONE'
|
||||
export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN'
|
||||
// picgo uploader
|
||||
export const RENAME_FILE_NAME = 'RENAME_FILE_NAME'
|
||||
export const GET_RENAME_FILE_NAME = 'GET_RENAME_FILE_NAME'
|
||||
export const SHOW_MAIN_PAGE_QRCODE = 'SHOW_MAIN_PAGE_QRCODE'
|
||||
// rpc
|
||||
export const RPC_ACTIONS = 'RPC_ACTIONS'
|
||||
export const RPC_ACTIONS_INVOKE = 'RPC_ACTIONS_INVOKE'
|
||||
export const SHOW_INPUT_BOX = 'SHOW_INPUT_BOX'
|
||||
export const SHOW_INPUT_BOX_RESPONSE = 'SHOW_INPUT_BOX_RESPONSE'
|
||||
export const TOGGLE_SHORTKEY_MODIFIED_MODE = 'TOGGLE_SHORTKEY_MODIFIED_MODE'
|
||||
// picgo plugin
|
||||
export const PICGO_CONFIG_PLUGIN = 'PICGO_CONFIG_PLUGIN'
|
||||
export const PICGO_HANDLE_PLUGIN_ING = 'PICGO_HANDLE_PLUGIN_ING'
|
||||
export const PICGO_HANDLE_PLUGIN_DONE = 'PICGO_HANDLE_PLUGIN_DONE'
|
||||
export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN'
|
||||
// picgo uploader
|
||||
export const RENAME_FILE_NAME = 'RENAME_FILE_NAME'
|
||||
export const GET_RENAME_FILE_NAME = 'GET_RENAME_FILE_NAME'
|
||||
export const SHOW_MAIN_PAGE_QRCODE = 'SHOW_MAIN_PAGE_QRCODE'
|
||||
// rpc
|
||||
export const RPC_ACTIONS = 'RPC_ACTIONS'
|
||||
export const RPC_ACTIONS_INVOKE = 'RPC_ACTIONS_INVOKE'
|
||||
|
||||
@@ -46,19 +46,19 @@ const buildMiniPageMenu = () => {
|
||||
},
|
||||
{
|
||||
label: $t('UPLOAD_BY_CLIPBOARD'),
|
||||
click () {
|
||||
click() {
|
||||
uploadClipboardFiles()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('HIDE_MINI_WINDOW'),
|
||||
click () {
|
||||
click() {
|
||||
BrowserWindow.getFocusedWindow()!.hide()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('START_WATCH_CLIPBOARD'),
|
||||
click () {
|
||||
click() {
|
||||
db.set(configPaths.settings.isListeningClipboard, true)
|
||||
ClipboardWatcher.startListening()
|
||||
ClipboardWatcher.on('change', () => {
|
||||
@@ -71,7 +71,7 @@ const buildMiniPageMenu = () => {
|
||||
},
|
||||
{
|
||||
label: $t('STOP_WATCH_CLIPBOARD'),
|
||||
click () {
|
||||
click() {
|
||||
db.set(configPaths.settings.isListeningClipboard, false)
|
||||
ClipboardWatcher.stopListening()
|
||||
ClipboardWatcher.removeAllListeners()
|
||||
@@ -81,7 +81,7 @@ const buildMiniPageMenu = () => {
|
||||
},
|
||||
{
|
||||
label: $t('RELOAD_APP'),
|
||||
click () {
|
||||
click() {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
}
|
||||
@@ -98,7 +98,7 @@ const buildMainPageMenu = (win: BrowserWindow) => {
|
||||
const template = [
|
||||
{
|
||||
label: $t('ABOUT'),
|
||||
click () {
|
||||
click() {
|
||||
dialog.showMessageBox({
|
||||
title: 'PicList',
|
||||
message: 'PicList',
|
||||
@@ -108,26 +108,26 @@ const buildMainPageMenu = (win: BrowserWindow) => {
|
||||
},
|
||||
{
|
||||
label: $t('SHOW_PICBED_QRCODE'),
|
||||
click () {
|
||||
click() {
|
||||
win?.webContents?.send(SHOW_MAIN_PAGE_QRCODE)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('OPEN_TOOLBOX'),
|
||||
click () {
|
||||
click() {
|
||||
const window = windowManager.create(IWindowList.TOOLBOX_WINDOW)
|
||||
window?.show()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('SHOW_DEVTOOLS'),
|
||||
click () {
|
||||
click() {
|
||||
win?.webContents?.openDevTools({ mode: 'detach' })
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('FEEDBACK'),
|
||||
click () {
|
||||
click() {
|
||||
const url = 'https://github.com/Kuingsmile/PicList/issues'
|
||||
shell.openExternal(url)
|
||||
}
|
||||
@@ -176,10 +176,10 @@ const buildSecondPicBedMenu = () => {
|
||||
: undefined,
|
||||
click: !hasSubmenu
|
||||
? function () {
|
||||
picgo.saveConfig({
|
||||
[configPaths.picBed.secondUploader]: item.type
|
||||
})
|
||||
}
|
||||
picgo.saveConfig({
|
||||
[configPaths.picBed.secondUploader]: item.type
|
||||
})
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
@@ -233,15 +233,15 @@ const buildPicBedListMenu = () => {
|
||||
: undefined,
|
||||
click: !hasSubmenu
|
||||
? function () {
|
||||
picgo.saveConfig({
|
||||
[configPaths.picBed.current]: item.type,
|
||||
[configPaths.picBed.uploader]: item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
picgo.saveConfig({
|
||||
[configPaths.picBed.current]: item.type,
|
||||
[configPaths.picBed.uploader]: item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
setTrayToolTip(item.type)
|
||||
}
|
||||
setTrayToolTip(item.type)
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
@@ -278,7 +278,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
{
|
||||
label: $t('ENABLE_PLUGIN'),
|
||||
enabled: !plugin.enabled,
|
||||
click () {
|
||||
click() {
|
||||
picgo.saveConfig({
|
||||
[`picgoPlugins.${plugin.fullName}`]: true
|
||||
})
|
||||
@@ -289,7 +289,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
{
|
||||
label: $t('DISABLE_PLUGIN'),
|
||||
enabled: plugin.enabled,
|
||||
click () {
|
||||
click() {
|
||||
picgo.saveConfig({
|
||||
[`picgoPlugins.${plugin.fullName}`]: false
|
||||
})
|
||||
@@ -307,7 +307,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
},
|
||||
{
|
||||
label: $t('UNINSTALL_PLUGIN'),
|
||||
click () {
|
||||
click() {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
|
||||
handlePluginUninstall(plugin.fullName)
|
||||
@@ -315,7 +315,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
},
|
||||
{
|
||||
label: $t('UPDATE_PLUGIN'),
|
||||
click () {
|
||||
click() {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
|
||||
handlePluginUpdate(plugin.fullName)
|
||||
@@ -328,7 +328,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
label: $t('CONFIG_THING', {
|
||||
c: `${i} - ${plugin.config[i].fullName || plugin.config[i].name}`
|
||||
}),
|
||||
click () {
|
||||
click() {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const currentType = i
|
||||
const configName = plugin.config[i].fullName || plugin.config[i].name
|
||||
@@ -346,7 +346,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
const pluginTransformer = plugin.config.transformer.name
|
||||
const obj = {
|
||||
label: `${currentTransformer === pluginTransformer ? $t('DISABLE') : $t('ENABLE')}transformer - ${plugin.config.transformer.name}`,
|
||||
click () {
|
||||
click() {
|
||||
const transformer = plugin.config.transformer.name
|
||||
const currentTransformer = picgo.getConfig<string>(configPaths.picBed.transformer) || 'path'
|
||||
if (currentTransformer === transformer) {
|
||||
@@ -371,7 +371,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
for (const i of plugin.guiMenu) {
|
||||
menu.push({
|
||||
label: i.label,
|
||||
async click () {
|
||||
async click() {
|
||||
const picgPlugin = await picgo.pluginLoader.getPlugin(plugin.fullName)
|
||||
if (picgPlugin?.guiMenu?.(picgo)?.length) {
|
||||
const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo)
|
||||
|
||||
@@ -37,12 +37,12 @@ class RPCServer implements IRPCServer {
|
||||
}
|
||||
}
|
||||
|
||||
start () {
|
||||
start() {
|
||||
ipcMain.on(RPC_ACTIONS, this.rpcEventHandler)
|
||||
ipcMain.handle(RPC_ACTIONS_INVOKE, this.rpcEventHandlerWithResponse)
|
||||
}
|
||||
|
||||
use (routes: IRPCRoutes) {
|
||||
use(routes: IRPCRoutes) {
|
||||
for (const [action, route] of routes) {
|
||||
if (route.type === IRPCType.SEND) {
|
||||
this.routes.set(action, route)
|
||||
@@ -52,7 +52,7 @@ class RPCServer implements IRPCServer {
|
||||
}
|
||||
}
|
||||
|
||||
stop () {
|
||||
stop() {
|
||||
ipcMain.off(RPC_ACTIONS, this.rpcEventHandler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export class RPCRouter implements IRPCRouter {
|
||||
return this
|
||||
}
|
||||
|
||||
routes () {
|
||||
routes() {
|
||||
return this.routeMap
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import ALLApi from 'apis/delete/allApi'
|
||||
|
||||
import type { IIPCEvent } from '#/types/rpc'
|
||||
import type { ImgInfo } from '#/types/types'
|
||||
import { IRPCActionType, IRPCType } from '~/utils/enum'
|
||||
|
||||
export default [
|
||||
{
|
||||
action: IRPCActionType.DELETE_ALL_API,
|
||||
handler: async (_: IIPCEvent, args:[item: ImgInfo]) => {
|
||||
return await ALLApi.delete(args[0])
|
||||
},
|
||||
type: IRPCType.INVOKE
|
||||
}
|
||||
]
|
||||
import ALLApi from 'apis/delete/allApi'
|
||||
|
||||
import type { IIPCEvent } from '#/types/rpc'
|
||||
import type { ImgInfo } from '#/types/types'
|
||||
import { IRPCActionType, IRPCType } from '~/utils/enum'
|
||||
|
||||
export default [
|
||||
{
|
||||
action: IRPCActionType.DELETE_ALL_API,
|
||||
handler: async (_: IIPCEvent, args: [item: ImgInfo]) => {
|
||||
return await ALLApi.delete(args[0])
|
||||
},
|
||||
type: IRPCType.INVOKE
|
||||
}
|
||||
]
|
||||
|
||||
@@ -85,11 +85,15 @@ const getPluginList = async (): Promise<IPicGoPlugin[]> => {
|
||||
},
|
||||
uploader: {
|
||||
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: {
|
||||
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]}`),
|
||||
|
||||
@@ -10,9 +10,7 @@ import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
|
||||
import { T as $t } from '~/i18n'
|
||||
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/utils/enum'
|
||||
|
||||
export const checkFileMap: IToolboxCheckerMap<
|
||||
string
|
||||
> = {
|
||||
export const checkFileMap: IToolboxCheckerMap<string> = {
|
||||
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async (event: IpcMainEvent) => {
|
||||
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_CONFIG_FILE_BROKEN)
|
||||
sendToolboxRes(event, {
|
||||
@@ -62,9 +60,7 @@ export const checkFileMap: IToolboxCheckerMap<
|
||||
}
|
||||
}
|
||||
|
||||
export const fixFileMap: IToolboxFixMap<
|
||||
string
|
||||
> = {
|
||||
export const fixFileMap: IToolboxFixMap<string> = {
|
||||
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async () => {
|
||||
try {
|
||||
fs.unlinkSync(dbPathChecker())
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
import { dbPathChecker } from '@core/datastore/dbChecker'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import fs from 'fs-extra'
|
||||
import { IConfig } from 'piclist'
|
||||
import tunnel from 'tunnel'
|
||||
|
||||
import type { IToolboxCheckerMap } from '#/types/rpc'
|
||||
import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
|
||||
import { T as $t } from '~/i18n'
|
||||
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/utils/enum'
|
||||
|
||||
function getProxy (proxyStr: string): AxiosRequestConfig['proxy'] | null {
|
||||
if (proxyStr) {
|
||||
try {
|
||||
const proxyOptions = new URL(proxyStr)
|
||||
return {
|
||||
host: proxyOptions.hostname,
|
||||
port: parseInt(proxyOptions.port || '0', 10),
|
||||
protocol: proxyOptions.protocol
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
|
||||
|
||||
export const checkProxyMap: IToolboxCheckerMap<string> = {
|
||||
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async event => {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.LOADING
|
||||
})
|
||||
const configFilePath = dbPathChecker()
|
||||
if (fs.existsSync(configFilePath)) {
|
||||
let config: IConfig | undefined
|
||||
try {
|
||||
config = (await fs.readJSON(configFilePath)) as IConfig
|
||||
} catch (e) {}
|
||||
if (!config) {
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
|
||||
})
|
||||
}
|
||||
|
||||
const proxy = config.picBed?.proxy
|
||||
if (!proxy) {
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
|
||||
})
|
||||
} else {
|
||||
const proxyOptions = getProxy(proxy)
|
||||
if (!proxyOptions) {
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT')
|
||||
})
|
||||
} else {
|
||||
const httpsAgent = tunnel.httpsOverHttp({
|
||||
proxy: {
|
||||
host: proxyOptions.host,
|
||||
port: proxyOptions.port
|
||||
}
|
||||
})
|
||||
try {
|
||||
await axios.get('https://www.google.com', {
|
||||
httpsAgent
|
||||
})
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_SUCCESS_TIPS')
|
||||
})
|
||||
} catch (e) {
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
|
||||
})
|
||||
}
|
||||
}
|
||||
import { dbPathChecker } from '@core/datastore/dbChecker'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import fs from 'fs-extra'
|
||||
import { IConfig } from 'piclist'
|
||||
import tunnel from 'tunnel'
|
||||
|
||||
import type { IToolboxCheckerMap } from '#/types/rpc'
|
||||
import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
|
||||
import { T as $t } from '~/i18n'
|
||||
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/utils/enum'
|
||||
|
||||
function getProxy(proxyStr: string): AxiosRequestConfig['proxy'] | null {
|
||||
if (proxyStr) {
|
||||
try {
|
||||
const proxyOptions = new URL(proxyStr)
|
||||
return {
|
||||
host: proxyOptions.hostname,
|
||||
port: parseInt(proxyOptions.port || '0', 10),
|
||||
protocol: proxyOptions.protocol
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
|
||||
|
||||
export const checkProxyMap: IToolboxCheckerMap<string> = {
|
||||
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async event => {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.LOADING
|
||||
})
|
||||
const configFilePath = dbPathChecker()
|
||||
if (fs.existsSync(configFilePath)) {
|
||||
let config: IConfig | undefined
|
||||
try {
|
||||
config = (await fs.readJSON(configFilePath)) as IConfig
|
||||
} catch (e) {}
|
||||
if (!config) {
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
|
||||
})
|
||||
}
|
||||
|
||||
const proxy = config.picBed?.proxy
|
||||
if (!proxy) {
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
|
||||
})
|
||||
} else {
|
||||
const proxyOptions = getProxy(proxy)
|
||||
if (!proxyOptions) {
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT')
|
||||
})
|
||||
} else {
|
||||
const httpsAgent = tunnel.httpsOverHttp({
|
||||
proxy: {
|
||||
host: proxyOptions.host,
|
||||
port: proxyOptions.port
|
||||
}
|
||||
})
|
||||
try {
|
||||
await axios.get('https://www.google.com', {
|
||||
httpsAgent
|
||||
})
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_SUCCESS_TIPS')
|
||||
})
|
||||
} catch (e) {
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: $t('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { IpcMainEvent } from 'electron'
|
||||
import type { IToolboxCheckRes } from '#/types/rpc'
|
||||
import { IRPCActionType } from '~/utils/enum'
|
||||
|
||||
export function sendToolboxResWithType (type: string) {
|
||||
export function sendToolboxResWithType(type: string) {
|
||||
return (event: IpcMainEvent, res?: Omit<IToolboxCheckRes, 'type'>) => {
|
||||
return event.sender.send(IRPCActionType.TOOLBOX_CHECK_RES, {
|
||||
...res,
|
||||
|
||||
@@ -12,7 +12,7 @@ const serverPort = 36699
|
||||
|
||||
let server: http.Server
|
||||
|
||||
export function startFileServer () {
|
||||
export function startFileServer() {
|
||||
server = http.createServer((req, res) => {
|
||||
const requestPath = req.url?.split('?')[0]
|
||||
const filePath = path.join(imgFilePath, decodeURIComponent(requestPath as string))
|
||||
@@ -36,7 +36,7 @@ export function startFileServer () {
|
||||
})
|
||||
}
|
||||
|
||||
export function stopFileServer () {
|
||||
export function stopFileServer() {
|
||||
server.close(() => {
|
||||
logger.info('File server is stopped')
|
||||
})
|
||||
|
||||
@@ -33,18 +33,18 @@ class I18nManager {
|
||||
readonly defaultLanguage: string = 'zh-CN'
|
||||
private i18nFileList: II18nItem[] = builtinI18nList
|
||||
|
||||
setOutterI18nFolder (folder: string) {
|
||||
setOutterI18nFolder(folder: string) {
|
||||
this.outterI18nFolder = folder
|
||||
}
|
||||
|
||||
addI18nFile (file: string, label: string) {
|
||||
addI18nFile(file: string, label: string) {
|
||||
this.i18nFileList.push({
|
||||
label,
|
||||
value: file
|
||||
})
|
||||
}
|
||||
|
||||
private getLocales (lang: string): ILocales {
|
||||
private getLocales(lang: string): ILocales {
|
||||
if (this.localesMap.has(lang)) {
|
||||
return this.localesMap.get(lang)!
|
||||
}
|
||||
@@ -71,13 +71,13 @@ class I18nManager {
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentLanguage (lang: string) {
|
||||
setCurrentLanguage(lang: string) {
|
||||
const locales = this.getLocales(lang)
|
||||
this.currentLanguage = lang
|
||||
this.initI18n(lang, locales)
|
||||
}
|
||||
|
||||
private initI18n (lang: string = this.defaultLanguage, locales: ILocales) {
|
||||
private initI18n(lang: string = this.defaultLanguage, locales: ILocales) {
|
||||
const objectAdapter = new ObjectAdapter({
|
||||
[lang]: locales
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
|
||||
get languageList () {
|
||||
get languageList() {
|
||||
return this.i18nFileList
|
||||
}
|
||||
|
||||
getCurrentLocales () {
|
||||
getCurrentLocales() {
|
||||
return {
|
||||
lang: this.currentLanguage,
|
||||
locales: this.getLocales(this.currentLanguage)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { lifeCycle } from '~/lifeCycle'
|
||||
|
||||
lifeCycle.launchApp()
|
||||
import { lifeCycle } from '~/lifeCycle'
|
||||
|
||||
lifeCycle.launchApp()
|
||||
|
||||
@@ -24,9 +24,9 @@ process.on('unhandledRejection', (error: any) => {
|
||||
})
|
||||
|
||||
// acconrding to https://github.com/Molunerfinn/PicGo/commit/7363be798cfef11e980934e542817ff1d6c04389#diff-896d0db4fbd446798fbffec14d456b4cd98d4c72c46856c770a585fa7ab0926f
|
||||
function bootstrapEPIPESuppression () {
|
||||
function bootstrapEPIPESuppression() {
|
||||
let suppressing = false
|
||||
function logEPIPEErrorOnce () {
|
||||
function logEPIPEErrorOnce() {
|
||||
if (suppressing) {
|
||||
return
|
||||
}
|
||||
@@ -41,11 +41,11 @@ function bootstrapEPIPESuppression () {
|
||||
|
||||
bootstrapEPIPESuppression()
|
||||
|
||||
function epipeBomb (stream: any, callback: any) {
|
||||
function epipeBomb(stream: any, callback: any) {
|
||||
if (stream == null) stream = process.stdout
|
||||
if (callback == null) callback = process.exit
|
||||
|
||||
function epipeFilter (err: any) {
|
||||
function epipeFilter(err: any) {
|
||||
if (err.code === 'EPIPE') return callback()
|
||||
|
||||
// If there's more than one error handler (ie, us),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { shellPathSync } from 'shell-path'
|
||||
|
||||
export default function fixPath () {
|
||||
export default function fixPath() {
|
||||
if (process.platform === 'win32') {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,354 +1,360 @@
|
||||
import '~/lifeCycle/errorHandler'
|
||||
|
||||
import path from 'node:path'
|
||||
|
||||
import bus from '@core/bus'
|
||||
import db from '@core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
import { createTray, setDockMenu } from 'apis/app/system'
|
||||
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import axios from 'axios'
|
||||
import { app, dialog, globalShortcut, Notification, protocol, screen, shell } from 'electron'
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
import updater from 'electron-updater'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
import busEventList from '~/events/busEventList'
|
||||
import { rpcServer } from '~/events/rpc'
|
||||
import { startFileServer, stopFileServer } from '~/fileServer'
|
||||
import { T as $t } from '~/i18n'
|
||||
import fixPath from '~/lifeCycle/fixPath'
|
||||
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
|
||||
import getManageApi from '~/manage/Main'
|
||||
import { clearTempFolder } from '~/manage/utils/common'
|
||||
import server from '~/server/index'
|
||||
import webServer from '~/server/webServer'
|
||||
import beforeOpen from '~/utils/beforeOpen'
|
||||
import clipboardPoll from '~/utils/clipboardPoll'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '~/utils/enum'
|
||||
import { getUploadFiles } from '~/utils/handleArgv'
|
||||
import { initI18n } from '~/utils/handleI18n'
|
||||
import { notificationList } from '~/utils/notification'
|
||||
import { MemoryMonitor } from '~/utils/performanceOptimizer'
|
||||
import { CLIPBOARD_IMAGE_FOLDER } from '~/utils/static'
|
||||
import updateChecker from '~/utils/updateChecker'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
const handleStartUpFiles = (argv: string[], cwd: string) => {
|
||||
const files = getUploadFiles(argv, cwd, logger)
|
||||
|
||||
if (files === null) {
|
||||
logger.info('cli -> uploading file from clipboard')
|
||||
uploadClipboardFiles()
|
||||
return true
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
logger.info('cli -> uploading files from cli', ...files.map(file => file.path))
|
||||
const win = windowManager.getAvailableWindow()
|
||||
uploadChoosedFiles(win.webContents, files)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
updater.autoUpdater.setFeedURL({
|
||||
provider: 'generic',
|
||||
url: 'https://release.piclist.cn/latest',
|
||||
channel: 'latest'
|
||||
})
|
||||
|
||||
updater.autoUpdater.autoDownload = false
|
||||
|
||||
updater.autoUpdater.on('update-available', async (info: updater.UpdateInfo) => {
|
||||
const lang = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN
|
||||
let updateLog = ''
|
||||
try {
|
||||
const url =
|
||||
lang === II18nLanguage.ZH_CN
|
||||
? 'https://release.piclist.cn/currentVersion.md'
|
||||
: 'https://release.piclist.cn/currentVersion_en.md'
|
||||
const res = await axios.get(url)
|
||||
updateLog = res.data
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
const maxLogLength = 800
|
||||
let displayLog = updateLog
|
||||
let truncatedNote = ''
|
||||
|
||||
if (updateLog.length > maxLogLength) {
|
||||
const truncatePoint = updateLog.lastIndexOf('\n', maxLogLength)
|
||||
displayLog = updateLog.substring(0, truncatePoint > 0 ? truncatePoint : maxLogLength)
|
||||
truncatedNote =
|
||||
lang === II18nLanguage.ZH_CN
|
||||
? '\n\n... (更多详情请查看完整更新日志)'
|
||||
: '\n\n... (See full changelog for more details)'
|
||||
}
|
||||
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: $t('FIND_NEW_VERSION'),
|
||||
buttons: ['Yes', 'Go to download page'],
|
||||
message:
|
||||
$t('TIPS_FIND_NEW_VERSION', {
|
||||
v: info.version
|
||||
}) +
|
||||
'\n\n' +
|
||||
displayLog +
|
||||
truncatedNote,
|
||||
checkboxLabel: $t('NO_MORE_NOTICE'),
|
||||
checkboxChecked: false
|
||||
})
|
||||
.then(result => {
|
||||
if (result.response === 0) {
|
||||
updater.autoUpdater.downloadUpdate()
|
||||
} else {
|
||||
shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest')
|
||||
}
|
||||
db.set(configPaths.settings.showUpdateTip, !result.checkboxChecked)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('download-progress', progressObj => {
|
||||
const percent = {
|
||||
progress: progressObj.percent
|
||||
}
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send('updateProgress', percent)
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('update-downloaded', () => {
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: $t('UPDATE_DOWNLOADED'),
|
||||
buttons: ['Yes', 'No'],
|
||||
message: $t('TIPS_UPDATE_DOWNLOADED')
|
||||
})
|
||||
.then(result => {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send('updateProgress', { progress: 100 })
|
||||
if (result.response === 0) {
|
||||
updater.autoUpdater.quitAndInstall()
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('error', err => {
|
||||
logger.error(err)
|
||||
})
|
||||
|
||||
class LifeCycle {
|
||||
async #beforeReady () {
|
||||
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
|
||||
// fix the $PATH in macOS & linux
|
||||
fixPath()
|
||||
beforeOpen()
|
||||
getManageApi()
|
||||
UpDownTaskQueue.getInstance()
|
||||
initI18n()
|
||||
rpcServer.start()
|
||||
busEventList.listen()
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
MemoryMonitor.start(30000)
|
||||
}
|
||||
}
|
||||
|
||||
#onReady () {
|
||||
const readyFunction = async () => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
installExtension(VUEJS_DEVTOOLS).catch(err => {
|
||||
logger.error('An error occurred: ', err)
|
||||
})
|
||||
}
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
const isAutoListenClipboard = db.get(configPaths.settings.isAutoListenClipboard) || false
|
||||
const ClipboardWatcher = clipboardPoll
|
||||
if (isAutoListenClipboard) {
|
||||
db.set(configPaths.settings.isListeningClipboard, true)
|
||||
ClipboardWatcher.startListening()
|
||||
ClipboardWatcher.on('change', () => {
|
||||
picgo.log.info('clipboard changed')
|
||||
uploadClipboardFiles()
|
||||
})
|
||||
} else {
|
||||
db.set(configPaths.settings.isListeningClipboard, false)
|
||||
}
|
||||
const isHideDock = db.get(configPaths.settings.isHideDock) || false
|
||||
let startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET
|
||||
if (process.platform === 'darwin' && startMode === ISartMode.MINI) {
|
||||
startMode = ISartMode.QUIET
|
||||
}
|
||||
const currentPicBed = db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms'
|
||||
const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default'
|
||||
const tooltip = `${currentPicBed} ${currentPicBedConfig}`
|
||||
if (process.platform === 'darwin') {
|
||||
isHideDock ? app.dock?.hide() : setDockMenu()
|
||||
startMode !== ISartMode.NO_TRAY && createTray(tooltip)
|
||||
} else {
|
||||
createTray(tooltip)
|
||||
}
|
||||
db.set(configPaths.needReload, false)
|
||||
updateChecker()
|
||||
// 不需要阻塞
|
||||
process.nextTick(() => {
|
||||
shortKeyHandler.init()
|
||||
})
|
||||
server.startup()
|
||||
webServer.start()
|
||||
startFileServer()
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
handleStartUpFiles(process.argv, process.cwd())
|
||||
}
|
||||
|
||||
if (notificationList && notificationList.length > 0) {
|
||||
while (notificationList.length) {
|
||||
const option = notificationList.pop()
|
||||
const notice = new Notification(option!)
|
||||
notice.show()
|
||||
}
|
||||
}
|
||||
await remoteNoticeHandler.init()
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
|
||||
if (startMode === ISartMode.MINI && process.platform !== 'darwin') {
|
||||
windowManager.create(IWindowList.MINI_WINDOW)
|
||||
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
|
||||
miniWindow.removeAllListeners()
|
||||
if (db.get(configPaths.settings.miniWindowOntop)) {
|
||||
miniWindow.setAlwaysOnTop(true)
|
||||
}
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||
const lastPosition = db.get(configPaths.settings.miniWindowPosition)
|
||||
if (lastPosition) {
|
||||
if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) {
|
||||
miniWindow.setPosition(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) {
|
||||
miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1])
|
||||
db.set(configPaths.settings.miniWindowPosition, [width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]])
|
||||
} else {
|
||||
miniWindow.setPosition(lastPosition[0], lastPosition[1])
|
||||
}
|
||||
} else {
|
||||
miniWindow.setPosition(width - 100, height - 100)
|
||||
}
|
||||
const setPositionFunc = () => {
|
||||
const position = miniWindow.getPosition()
|
||||
db.set(configPaths.settings.miniWindowPosition, position)
|
||||
}
|
||||
miniWindow.on('close', setPositionFunc)
|
||||
miniWindow.on('move', setPositionFunc)
|
||||
miniWindow.show()
|
||||
miniWindow.focus()
|
||||
} else if (startMode === ISartMode.MAIN) {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
settingWindow.show()
|
||||
settingWindow.focus()
|
||||
}
|
||||
const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER)
|
||||
fs.emptyDir(clipboardDir)
|
||||
}
|
||||
app.whenReady().then(readyFunction)
|
||||
}
|
||||
|
||||
#onRunning () {
|
||||
app.on('second-instance', (_, commandLine, workingDirectory) => {
|
||||
logger.info('detect second instance')
|
||||
const result = handleStartUpFiles(commandLine, workingDirectory)
|
||||
if (!result) {
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
if (settingWindow.isMinimized()) {
|
||||
settingWindow.restore()
|
||||
}
|
||||
settingWindow.focus()
|
||||
}
|
||||
}
|
||||
})
|
||||
app.on('activate', () => {
|
||||
if (!windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
}
|
||||
if (!windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
}
|
||||
})
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: db.get(configPaths.settings.autoStart) || false
|
||||
})
|
||||
if (process.platform === 'win32') {
|
||||
app.setAppUserModelId('com.kuingsmile.piclist')
|
||||
}
|
||||
|
||||
if (process.env.XDG_CURRENT_DESKTOP && process.env.XDG_CURRENT_DESKTOP.includes('Unity')) {
|
||||
process.env.XDG_CURRENT_DESKTOP = 'Unity'
|
||||
}
|
||||
}
|
||||
|
||||
#onQuit () {
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('will-quit', () => {
|
||||
UpDownTaskQueue.getInstance().persist()
|
||||
clearTempFolder()
|
||||
globalShortcut.unregisterAll()
|
||||
bus.removeAllListeners()
|
||||
server.shutdown()
|
||||
webServer.stop()
|
||||
stopFileServer()
|
||||
MemoryMonitor.stop()
|
||||
})
|
||||
// Exit cleanly on request from parent process in development mode.
|
||||
if (isDevelopment) {
|
||||
if (process.platform === 'win32') {
|
||||
process.on('message', data => {
|
||||
if (data === 'graceful-exit') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
process.on('SIGTERM', () => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async launchApp () {
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
await this.#beforeReady()
|
||||
this.#onReady()
|
||||
this.#onRunning()
|
||||
this.#onQuit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lifeCycle = new LifeCycle()
|
||||
|
||||
export { lifeCycle }
|
||||
import '~/lifeCycle/errorHandler'
|
||||
|
||||
import path from 'node:path'
|
||||
|
||||
import bus from '@core/bus'
|
||||
import db from '@core/datastore'
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
|
||||
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
|
||||
import { createTray, setDockMenu } from 'apis/app/system'
|
||||
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import axios from 'axios'
|
||||
import { app, dialog, globalShortcut, Notification, protocol, screen, shell } from 'electron'
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
import updater from 'electron-updater'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
import busEventList from '~/events/busEventList'
|
||||
import { rpcServer } from '~/events/rpc'
|
||||
import { startFileServer, stopFileServer } from '~/fileServer'
|
||||
import { T as $t } from '~/i18n'
|
||||
import fixPath from '~/lifeCycle/fixPath'
|
||||
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
|
||||
import getManageApi from '~/manage/Main'
|
||||
import { clearTempFolder } from '~/manage/utils/common'
|
||||
import server from '~/server/index'
|
||||
import webServer from '~/server/webServer'
|
||||
import beforeOpen from '~/utils/beforeOpen'
|
||||
import clipboardPoll from '~/utils/clipboardPoll'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '~/utils/enum'
|
||||
import { getUploadFiles } from '~/utils/handleArgv'
|
||||
import { initI18n } from '~/utils/handleI18n'
|
||||
import { notificationList } from '~/utils/notification'
|
||||
import { MemoryMonitor } from '~/utils/performanceOptimizer'
|
||||
import { CLIPBOARD_IMAGE_FOLDER } from '~/utils/static'
|
||||
import updateChecker from '~/utils/updateChecker'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
const handleStartUpFiles = (argv: string[], cwd: string) => {
|
||||
const files = getUploadFiles(argv, cwd, logger)
|
||||
|
||||
if (files === null) {
|
||||
logger.info('cli -> uploading file from clipboard')
|
||||
uploadClipboardFiles()
|
||||
return true
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
logger.info('cli -> uploading files from cli', ...files.map(file => file.path))
|
||||
const win = windowManager.getAvailableWindow()
|
||||
uploadChoosedFiles(win.webContents, files)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
updater.autoUpdater.setFeedURL({
|
||||
provider: 'generic',
|
||||
url: 'https://release.piclist.cn/latest',
|
||||
channel: 'latest'
|
||||
})
|
||||
|
||||
updater.autoUpdater.autoDownload = false
|
||||
|
||||
updater.autoUpdater.on('update-available', async (info: updater.UpdateInfo) => {
|
||||
const lang = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN
|
||||
let updateLog = ''
|
||||
try {
|
||||
const url =
|
||||
lang === II18nLanguage.ZH_CN
|
||||
? 'https://release.piclist.cn/currentVersion.md'
|
||||
: 'https://release.piclist.cn/currentVersion_en.md'
|
||||
const res = await axios.get(url)
|
||||
updateLog = res.data
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
const maxLogLength = 800
|
||||
let displayLog = updateLog
|
||||
let truncatedNote = ''
|
||||
|
||||
if (updateLog.length > maxLogLength) {
|
||||
const truncatePoint = updateLog.lastIndexOf('\n', maxLogLength)
|
||||
displayLog = updateLog.substring(0, truncatePoint > 0 ? truncatePoint : maxLogLength)
|
||||
truncatedNote =
|
||||
lang === II18nLanguage.ZH_CN
|
||||
? '\n\n... (更多详情请查看完整更新日志)'
|
||||
: '\n\n... (See full changelog for more details)'
|
||||
}
|
||||
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: $t('FIND_NEW_VERSION'),
|
||||
buttons: ['Yes', 'Go to download page'],
|
||||
message:
|
||||
$t('TIPS_FIND_NEW_VERSION', {
|
||||
v: info.version
|
||||
}) +
|
||||
'\n\n' +
|
||||
displayLog +
|
||||
truncatedNote,
|
||||
checkboxLabel: $t('NO_MORE_NOTICE'),
|
||||
checkboxChecked: false
|
||||
})
|
||||
.then(result => {
|
||||
if (result.response === 0) {
|
||||
updater.autoUpdater.downloadUpdate()
|
||||
} else {
|
||||
shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest')
|
||||
}
|
||||
db.set(configPaths.settings.showUpdateTip, !result.checkboxChecked)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('download-progress', progressObj => {
|
||||
const percent = {
|
||||
progress: progressObj.percent
|
||||
}
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send('updateProgress', percent)
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('update-downloaded', () => {
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: $t('UPDATE_DOWNLOADED'),
|
||||
buttons: ['Yes', 'No'],
|
||||
message: $t('TIPS_UPDATE_DOWNLOADED')
|
||||
})
|
||||
.then(result => {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send('updateProgress', { progress: 100 })
|
||||
if (result.response === 0) {
|
||||
updater.autoUpdater.quitAndInstall()
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
updater.autoUpdater.on('error', err => {
|
||||
logger.error(err)
|
||||
})
|
||||
|
||||
class LifeCycle {
|
||||
async #beforeReady() {
|
||||
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
|
||||
// fix the $PATH in macOS & linux
|
||||
fixPath()
|
||||
beforeOpen()
|
||||
getManageApi()
|
||||
UpDownTaskQueue.getInstance()
|
||||
initI18n()
|
||||
rpcServer.start()
|
||||
busEventList.listen()
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
MemoryMonitor.start(30000)
|
||||
}
|
||||
}
|
||||
|
||||
#onReady() {
|
||||
const readyFunction = async () => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
installExtension(VUEJS_DEVTOOLS).catch(err => {
|
||||
logger.error('An error occurred: ', err)
|
||||
})
|
||||
}
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
const isAutoListenClipboard = db.get(configPaths.settings.isAutoListenClipboard) || false
|
||||
const ClipboardWatcher = clipboardPoll
|
||||
if (isAutoListenClipboard) {
|
||||
db.set(configPaths.settings.isListeningClipboard, true)
|
||||
ClipboardWatcher.startListening()
|
||||
ClipboardWatcher.on('change', () => {
|
||||
picgo.log.info('clipboard changed')
|
||||
uploadClipboardFiles()
|
||||
})
|
||||
} else {
|
||||
db.set(configPaths.settings.isListeningClipboard, false)
|
||||
}
|
||||
const isHideDock = db.get(configPaths.settings.isHideDock) || false
|
||||
let startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET
|
||||
if (process.platform === 'darwin' && startMode === ISartMode.MINI) {
|
||||
startMode = ISartMode.QUIET
|
||||
}
|
||||
const currentPicBed = db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms'
|
||||
const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default'
|
||||
const tooltip = `${currentPicBed} ${currentPicBedConfig}`
|
||||
if (process.platform === 'darwin') {
|
||||
isHideDock ? app.dock?.hide() : setDockMenu()
|
||||
startMode !== ISartMode.NO_TRAY && createTray(tooltip)
|
||||
} else {
|
||||
createTray(tooltip)
|
||||
}
|
||||
db.set(configPaths.needReload, false)
|
||||
updateChecker()
|
||||
// 不需要阻塞
|
||||
process.nextTick(() => {
|
||||
shortKeyHandler.init()
|
||||
})
|
||||
server.startup()
|
||||
webServer.start()
|
||||
startFileServer()
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
handleStartUpFiles(process.argv, process.cwd())
|
||||
}
|
||||
|
||||
if (notificationList && notificationList.length > 0) {
|
||||
while (notificationList.length) {
|
||||
const option = notificationList.pop()
|
||||
const notice = new Notification(option!)
|
||||
notice.show()
|
||||
}
|
||||
}
|
||||
await remoteNoticeHandler.init()
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
|
||||
if (startMode === ISartMode.MINI && process.platform !== 'darwin') {
|
||||
windowManager.create(IWindowList.MINI_WINDOW)
|
||||
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
|
||||
miniWindow.removeAllListeners()
|
||||
if (db.get(configPaths.settings.miniWindowOntop)) {
|
||||
miniWindow.setAlwaysOnTop(true)
|
||||
}
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||
const lastPosition = db.get(configPaths.settings.miniWindowPosition)
|
||||
if (lastPosition) {
|
||||
if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) {
|
||||
miniWindow.setPosition(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
|
||||
) {
|
||||
miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1])
|
||||
db.set(configPaths.settings.miniWindowPosition, [
|
||||
width - miniWindow.getSize()[0],
|
||||
height - miniWindow.getSize()[1]
|
||||
])
|
||||
} else {
|
||||
miniWindow.setPosition(lastPosition[0], lastPosition[1])
|
||||
}
|
||||
} else {
|
||||
miniWindow.setPosition(width - 100, height - 100)
|
||||
}
|
||||
const setPositionFunc = () => {
|
||||
const position = miniWindow.getPosition()
|
||||
db.set(configPaths.settings.miniWindowPosition, position)
|
||||
}
|
||||
miniWindow.on('close', setPositionFunc)
|
||||
miniWindow.on('move', setPositionFunc)
|
||||
miniWindow.show()
|
||||
miniWindow.focus()
|
||||
} else if (startMode === ISartMode.MAIN) {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
settingWindow.show()
|
||||
settingWindow.focus()
|
||||
}
|
||||
const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER)
|
||||
fs.emptyDir(clipboardDir)
|
||||
}
|
||||
app.whenReady().then(readyFunction)
|
||||
}
|
||||
|
||||
#onRunning() {
|
||||
app.on('second-instance', (_, commandLine, workingDirectory) => {
|
||||
logger.info('detect second instance')
|
||||
const result = handleStartUpFiles(commandLine, workingDirectory)
|
||||
if (!result) {
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
if (settingWindow.isMinimized()) {
|
||||
settingWindow.restore()
|
||||
}
|
||||
settingWindow.focus()
|
||||
}
|
||||
}
|
||||
})
|
||||
app.on('activate', () => {
|
||||
if (!windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
}
|
||||
if (!windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
}
|
||||
})
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: db.get(configPaths.settings.autoStart) || false
|
||||
})
|
||||
if (process.platform === 'win32') {
|
||||
app.setAppUserModelId('com.kuingsmile.piclist')
|
||||
}
|
||||
|
||||
if (process.env.XDG_CURRENT_DESKTOP && process.env.XDG_CURRENT_DESKTOP.includes('Unity')) {
|
||||
process.env.XDG_CURRENT_DESKTOP = 'Unity'
|
||||
}
|
||||
}
|
||||
|
||||
#onQuit() {
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('will-quit', () => {
|
||||
UpDownTaskQueue.getInstance().persist()
|
||||
clearTempFolder()
|
||||
globalShortcut.unregisterAll()
|
||||
bus.removeAllListeners()
|
||||
server.shutdown()
|
||||
webServer.stop()
|
||||
stopFileServer()
|
||||
MemoryMonitor.stop()
|
||||
})
|
||||
// Exit cleanly on request from parent process in development mode.
|
||||
if (isDevelopment) {
|
||||
if (process.platform === 'win32') {
|
||||
process.on('message', data => {
|
||||
if (data === 'graceful-exit') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
process.on('SIGTERM', () => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async launchApp() {
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
await this.#beforeReady()
|
||||
this.#onReady()
|
||||
this.#onRunning()
|
||||
this.#onQuit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lifeCycle = new LifeCycle()
|
||||
|
||||
export { lifeCycle }
|
||||
|
||||
@@ -28,7 +28,7 @@ class AliyunApi {
|
||||
timeOut = 30000
|
||||
logger: ManageLogger
|
||||
|
||||
constructor (accessKeyId: string, accessKeySecret: string, logger: ManageLogger) {
|
||||
constructor(accessKeyId: string, accessKeySecret: string, logger: ManageLogger) {
|
||||
this.ctx = new OSS({
|
||||
accessKeyId,
|
||||
accessKeySecret,
|
||||
@@ -39,7 +39,7 @@ class AliyunApi {
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
formatFolder (item: string, slicedPrefix: string, urlPrefix: string): any {
|
||||
formatFolder(item: string, slicedPrefix: string, urlPrefix: string): any {
|
||||
return {
|
||||
key: item,
|
||||
url: `${urlPrefix}/${item}`,
|
||||
@@ -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, '')
|
||||
return {
|
||||
...item,
|
||||
@@ -71,7 +71,7 @@ class AliyunApi {
|
||||
}
|
||||
}
|
||||
|
||||
getCanonicalizedOSSHeaders (headers: IStringKeyMap) {
|
||||
getCanonicalizedOSSHeaders(headers: IStringKeyMap) {
|
||||
const lowerCaseHeaders = Object.keys(headers).reduce((acc, key) => {
|
||||
acc[key.toLowerCase()] = headers[key]
|
||||
return acc
|
||||
@@ -84,7 +84,7 @@ class AliyunApi {
|
||||
return canonicalizedOSSHeaders
|
||||
}
|
||||
|
||||
authorization (
|
||||
authorization(
|
||||
method: string,
|
||||
canonicalizedResource: string,
|
||||
headers: IStringKeyMap,
|
||||
@@ -96,7 +96,7 @@ class AliyunApi {
|
||||
return `OSS ${this.accessKeyId}:${hmacSha1Base64(this.accessKeySecret, stringToSign)}`
|
||||
}
|
||||
|
||||
getNewCtx (region: string, bucket: string) {
|
||||
getNewCtx(region: string, bucket: string) {
|
||||
return new OSS({
|
||||
accessKeyId: this.accessKeyId,
|
||||
accessKeySecret: this.accessKeySecret,
|
||||
@@ -109,7 +109,7 @@ class AliyunApi {
|
||||
/**
|
||||
* 获取存储桶列表
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
const getBuckets = async (marker?: string) => {
|
||||
const res = (await this.ctx.listBuckets({
|
||||
marker,
|
||||
@@ -143,7 +143,7 @@ class AliyunApi {
|
||||
/**
|
||||
* 获取自定义域名
|
||||
*/
|
||||
async getBucketDomain (param: IStringKeyMap): Promise<any> {
|
||||
async getBucketDomain(param: IStringKeyMap): Promise<any> {
|
||||
const headers = {
|
||||
Date: new Date().toUTCString()
|
||||
}
|
||||
@@ -186,7 +186,7 @@ class AliyunApi {
|
||||
* @description
|
||||
* acl: private | publicRead | publicReadWrite
|
||||
*/
|
||||
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const client = new OSS({
|
||||
accessKeyId: this.accessKeyId,
|
||||
accessKeySecret: this.accessKeySecret,
|
||||
@@ -207,7 +207,7 @@ class AliyunApi {
|
||||
return res?.res?.status === 200
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -262,7 +262,7 @@ class AliyunApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -336,7 +336,7 @@ class AliyunApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
|
||||
const {
|
||||
bucketName: bucket,
|
||||
bucketConfig: { Location: region },
|
||||
@@ -393,7 +393,7 @@ class AliyunApi {
|
||||
* newKey: string
|
||||
* }
|
||||
*/
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, oldKey, newKey } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
const copyRes = (await client.copy(newKey, oldKey)) as any
|
||||
@@ -413,7 +413,7 @@ class AliyunApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
const res = (await client.delete(key)) as any
|
||||
@@ -424,7 +424,7 @@ class AliyunApi {
|
||||
* 删除文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
let marker
|
||||
@@ -486,7 +486,7 @@ class AliyunApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { bucketName, region, key, expires, customUrl } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
const res = client.signatureUrl(key, {
|
||||
@@ -499,7 +499,7 @@ class AliyunApi {
|
||||
* 上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
// fileArray = [{
|
||||
// bucketName: string,
|
||||
@@ -586,7 +586,7 @@ class AliyunApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
const res = (await client.put(key, Buffer.from(''))) as any
|
||||
@@ -597,7 +597,7 @@ class AliyunApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -29,7 +29,7 @@ class GithubApi {
|
||||
baseUrl = 'https://api.github.com'
|
||||
commonHeaders: IStringKeyMap
|
||||
|
||||
constructor (token: string, username: string, proxy: string | undefined, logger: ManageLogger) {
|
||||
constructor(token: string, username: string, proxy: string | undefined, logger: ManageLogger) {
|
||||
this.logger = logger
|
||||
this.token = token.startsWith('Bearer ') ? token : `Bearer ${token}`.trim()
|
||||
this.username = username
|
||||
@@ -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}/`
|
||||
let rawUrl = ''
|
||||
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 = ''
|
||||
const placeholders = ['{username}', '{repo}', '{branch}', '{path}']
|
||||
const key = slicedPrefix === '' ? item.path : `${slicedPrefix}/${item.path}`
|
||||
@@ -119,7 +119,7 @@ class GithubApi {
|
||||
/**
|
||||
* get repo list
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
let initPage = 1
|
||||
let res
|
||||
const result = [] as any[]
|
||||
@@ -156,7 +156,7 @@ class GithubApi {
|
||||
/**
|
||||
* 获取branch列表
|
||||
*/
|
||||
async getBucketDomain (param: IStringKeyMap): Promise<any> {
|
||||
async getBucketDomain(param: IStringKeyMap): Promise<any> {
|
||||
const { bucketName: repo } = param
|
||||
let initPage = 1
|
||||
let res
|
||||
@@ -184,7 +184,7 @@ class GithubApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap
|
||||
const slicedPrefix = prefix.replace(/(^\/+|\/+$)/g, '')
|
||||
@@ -235,7 +235,7 @@ class GithubApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap
|
||||
const slicedPrefix = prefix.replace(/(^\/+|\/+$)/g, '')
|
||||
@@ -285,7 +285,7 @@ class GithubApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName: repo, githubBranch: branch, key, DeleteHash: sha } = configMap
|
||||
const body = {
|
||||
message: 'deleted by PicList',
|
||||
@@ -303,7 +303,7 @@ class GithubApi {
|
||||
* create a new tree to delete a folder
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName: repo, githubBranch: branch, key } = configMap
|
||||
// get sha of the branch
|
||||
const refRes = (await got(
|
||||
@@ -412,7 +412,7 @@ class GithubApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { bucketName: repo, customUrl: branch, key, rawUrl, githubPrivate: isPrivate } = configMap
|
||||
if (!isPrivate) return rawUrl
|
||||
const res = (await got(
|
||||
@@ -436,7 +436,7 @@ class GithubApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName: repo, githubBranch: branch, key } = configMap
|
||||
const newFileKey = `${trimPath(key)}/.gitkeep`
|
||||
const base64Content = Buffer.from('created by PicList').toString('base64')
|
||||
@@ -456,7 +456,7 @@ class GithubApi {
|
||||
* 上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
fileArray.forEach((item: any) => {
|
||||
@@ -505,7 +505,7 @@ class GithubApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -31,7 +31,7 @@ class ImgurApi {
|
||||
idHeaders: any
|
||||
baseUrl = 'https://api.imgur.com/3'
|
||||
|
||||
constructor (userName: string, accessToken: string, proxy: any, logger: ManageLogger) {
|
||||
constructor(userName: string, accessToken: string, proxy: any, logger: ManageLogger) {
|
||||
this.userName = userName
|
||||
this.accessToken = accessToken.startsWith('Bearer ') ? accessToken : `Bearer ${accessToken}`
|
||||
this.proxy = proxy
|
||||
@@ -42,7 +42,7 @@ class ImgurApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: any) {
|
||||
formatFile(item: any) {
|
||||
const fileName = path.basename(item.link)
|
||||
const isImg = isImage(fileName)
|
||||
return {
|
||||
@@ -64,7 +64,7 @@ class ImgurApi {
|
||||
/**
|
||||
* get repo list
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
let initPage = 0
|
||||
let res
|
||||
const result = [] as any[]
|
||||
@@ -93,7 +93,7 @@ class ImgurApi {
|
||||
return finalResult
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketConfig: { Location: albumHash },
|
||||
@@ -153,7 +153,7 @@ class ImgurApi {
|
||||
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||
}
|
||||
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { DeleteHash: deleteHash } = configMap
|
||||
const res = (await got(
|
||||
`${this.baseUrl}/account/${this.userName}/image/${deleteHash}`,
|
||||
@@ -166,7 +166,7 @@ class ImgurApi {
|
||||
* 上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
fileArray.forEach((item: any) => {
|
||||
@@ -226,7 +226,7 @@ class ImgurApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -17,7 +17,7 @@ class LocalApi {
|
||||
logger: ManageLogger
|
||||
isWindows: boolean
|
||||
|
||||
constructor (logger: ManageLogger) {
|
||||
constructor(logger: ManageLogger) {
|
||||
this.logger = logger
|
||||
this.isWindows = process.platform === 'win32'
|
||||
}
|
||||
@@ -25,12 +25,12 @@ class LocalApi {
|
||||
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'LocalApi', method }))
|
||||
|
||||
// windows 系统下将路径转换为 unix 风格
|
||||
transPathToUnix (filePath: string | undefined) {
|
||||
transPathToUnix(filePath: string | undefined) {
|
||||
if (!filePath) return ''
|
||||
return this.isWindows ? filePath.split(path.sep).join(path.posix.sep) : filePath.replace(/^\/+/, '')
|
||||
}
|
||||
|
||||
transBack (filePath: string | undefined) {
|
||||
transBack(filePath: string | undefined) {
|
||||
if (!filePath) return ''
|
||||
return this.isWindows
|
||||
? filePath
|
||||
@@ -40,7 +40,7 @@ class LocalApi {
|
||||
: `/${filePath.replace(/^\/+|\/+$/g, '')}`
|
||||
}
|
||||
|
||||
formatFolder (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) {
|
||||
formatFolder(item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) {
|
||||
const key = `${this.transPathToUnix(filePath)}/`.replace(/\/+$/, '/')
|
||||
return {
|
||||
...item,
|
||||
@@ -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)
|
||||
return {
|
||||
...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 { prefix, customUrl = '', cancelToken } = configMap
|
||||
const urlPrefix = customUrl.replace(/\/+$/, '')
|
||||
@@ -114,7 +114,7 @@ class LocalApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { customUrl = '', cancelToken, baseDir } = configMap
|
||||
let prefix = configMap.prefix
|
||||
@@ -170,7 +170,7 @@ class LocalApi {
|
||||
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||
}
|
||||
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { oldKey, newKey } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -182,7 +182,7 @@ class LocalApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -194,7 +194,7 @@ class LocalApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -208,7 +208,7 @@ class LocalApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
@@ -250,7 +250,7 @@ class LocalApi {
|
||||
return true
|
||||
}
|
||||
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -264,7 +264,7 @@ class LocalApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
|
||||
@@ -33,14 +33,14 @@ class QiniuApi {
|
||||
getBucketDomain: 'https://uc.qiniuapi.com/v2/domains'
|
||||
}
|
||||
|
||||
constructor (accessKey: string, secretKey: string, logger: ManageLogger) {
|
||||
constructor(accessKey: string, secretKey: string, logger: ManageLogger) {
|
||||
this.mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
|
||||
this.accessKey = accessKey
|
||||
this.secretKey = secretKey
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
formatFolder (item: string, slicedPrefix: string, urlPrefix: string) {
|
||||
formatFolder(item: string, slicedPrefix: string, urlPrefix: string) {
|
||||
return {
|
||||
Key: item,
|
||||
key: item,
|
||||
@@ -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, '')
|
||||
return {
|
||||
...item,
|
||||
@@ -69,7 +69,7 @@ class QiniuApi {
|
||||
}
|
||||
}
|
||||
|
||||
authorization (
|
||||
authorization(
|
||||
method: string,
|
||||
urlPath: string,
|
||||
host: string,
|
||||
@@ -98,7 +98,7 @@ class QiniuApi {
|
||||
/**
|
||||
* 获取存储桶列表
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
const host = this.hostList.getBucketList
|
||||
const authorization = qiniu.util.generateAccessToken(this.mac, host, undefined)
|
||||
const res = await axios.get(host, {
|
||||
@@ -128,7 +128,7 @@ class QiniuApi {
|
||||
/**
|
||||
* 获取存储桶详细信息
|
||||
*/
|
||||
async getBucketInfo (param: IStringKeyMap): Promise<any> {
|
||||
async getBucketInfo(param: IStringKeyMap): Promise<any> {
|
||||
const { bucketName } = param
|
||||
const urlPath = `/v2/bucketInfo?bucket=${bucketName}&fs=true`
|
||||
const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
|
||||
@@ -160,7 +160,7 @@ class QiniuApi {
|
||||
/**
|
||||
* 获取自定义域名
|
||||
*/
|
||||
async getBucketDomain (param: IStringKeyMap): Promise<any> {
|
||||
async getBucketDomain(param: IStringKeyMap): Promise<any> {
|
||||
const { bucketName } = param
|
||||
const host = this.hostList.getBucketDomain
|
||||
const authorization = qiniu.util.generateAccessToken(this.mac, `${host}?tbl=${bucketName}`, undefined)
|
||||
@@ -180,7 +180,7 @@ class QiniuApi {
|
||||
/**
|
||||
* 修改存储桶权限
|
||||
*/
|
||||
async setBucketAclPolicy (param: IStringKeyMap): Promise<boolean> {
|
||||
async setBucketAclPolicy(param: IStringKeyMap): Promise<boolean> {
|
||||
// 0: 公开访问 1: 私有访问
|
||||
const { bucketName } = param
|
||||
let { isPrivate } = param
|
||||
@@ -213,7 +213,7 @@ class QiniuApi {
|
||||
* acl: boolean // 是否公开访问
|
||||
* }
|
||||
*/
|
||||
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { BucketName, region, acl } = configMap
|
||||
const urlPath = `/mkbucketv3/${BucketName}/region/${region}`
|
||||
const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
|
||||
@@ -235,7 +235,7 @@ class QiniuApi {
|
||||
: false
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap
|
||||
let marker = undefined as any
|
||||
@@ -297,7 +297,7 @@ class QiniuApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap
|
||||
let marker = undefined as any
|
||||
@@ -380,7 +380,7 @@ class QiniuApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
|
||||
const { bucketName: bucket, prefix, marker, itemsPerPage, customUrl: urlPrefix } = configMap
|
||||
const slicedPrefix = prefix.slice(1)
|
||||
const config = new qiniu.conf.Config()
|
||||
@@ -440,7 +440,7 @@ class QiniuApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, key } = configMap
|
||||
const config = new qiniu.conf.Config()
|
||||
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
|
||||
@@ -463,7 +463,7 @@ class QiniuApi {
|
||||
* 删除文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, key } = configMap
|
||||
const config = new qiniu.conf.Config()
|
||||
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
|
||||
@@ -535,7 +535,7 @@ class QiniuApi {
|
||||
* newKey: string
|
||||
* }
|
||||
*/
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, oldKey, newKey } = configMap
|
||||
const config = new qiniu.conf.Config()
|
||||
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
|
||||
@@ -574,7 +574,7 @@ class QiniuApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { key, expires, customUrl } = configMap
|
||||
const config = new qiniu.conf.Config()
|
||||
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
|
||||
@@ -588,7 +588,7 @@ class QiniuApi {
|
||||
* 上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
fileArray.forEach((item: any) => {
|
||||
@@ -667,7 +667,7 @@ class QiniuApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, key } = configMap
|
||||
const putPolicy = new qiniu.rs.PutPolicy({
|
||||
scope: `${bucketName}:${key}`
|
||||
@@ -694,7 +694,7 @@ class QiniuApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -46,7 +46,7 @@ class S3plistApi {
|
||||
secretAccessKey: string
|
||||
bucketName: string
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
accessKeyId: string,
|
||||
secretAccessKey: string,
|
||||
endpoint: string | undefined,
|
||||
@@ -75,7 +75,7 @@ class S3plistApi {
|
||||
this.proxy = formatHttpProxy(proxy, 'string') as string | undefined
|
||||
}
|
||||
|
||||
async getDogeCloudToken () {
|
||||
async getDogeCloudToken() {
|
||||
if (!this.dogeCloudSupport) return
|
||||
const token = (await getTempToken(this.accessKeyId, this.secretAccessKey)) as DogecloudToken
|
||||
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 commonOptions: AgentOptions = {
|
||||
keepAlive: true,
|
||||
@@ -98,26 +98,26 @@ class S3plistApi {
|
||||
const extraOptions = sslEnabled ? { rejectUnauthorized: false } : {}
|
||||
return sslEnabled
|
||||
? new NodeHttpHandler({
|
||||
httpsAgent: agent.https
|
||||
? agent.https
|
||||
: new https.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
httpsAgent: agent.https
|
||||
? agent.https
|
||||
: new https.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
: new NodeHttpHandler({
|
||||
httpAgent: agent.http
|
||||
? agent.http
|
||||
: new http.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
httpAgent: agent.http
|
||||
? agent.http
|
||||
: new http.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'S3plistApi', method }))
|
||||
|
||||
formatFolder (item: CommonPrefix, slicedPrefix: string, urlPrefix: string): any {
|
||||
formatFolder(item: CommonPrefix, slicedPrefix: string, urlPrefix: string): any {
|
||||
return {
|
||||
Key: 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, '')
|
||||
return {
|
||||
...item,
|
||||
@@ -148,7 +148,7 @@ class S3plistApi {
|
||||
}
|
||||
}
|
||||
|
||||
async putPublicAccess (bucketName: string, client: S3Client) {
|
||||
async putPublicAccess(bucketName: string, client: S3Client) {
|
||||
const input = {
|
||||
Bucket: bucketName,
|
||||
PublicAccessBlockConfiguration: {
|
||||
@@ -175,11 +175,11 @@ class S3plistApi {
|
||||
* acl: string
|
||||
* }
|
||||
*/
|
||||
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { BucketName, region, acl, endpoint } = configMap
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new ListBucketsCommand({})
|
||||
@@ -234,7 +234,7 @@ class S3plistApi {
|
||||
/**
|
||||
* 获取存储桶列表
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
if (this.dogeCloudSupport) {
|
||||
try {
|
||||
const res = await dogecloudApi('/oss/bucket/list.json', {}, false, this.accessKeyId, this.secretAccessKey)
|
||||
@@ -255,7 +255,7 @@ class S3plistApi {
|
||||
}
|
||||
return []
|
||||
}
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
const result: IStringKeyMap[] = []
|
||||
const endpoint = (options.endpoint as string) || ''
|
||||
options.region = endpoint.includes('cloudflarestorage') ? 'auto' : 'us-east-1'
|
||||
@@ -305,7 +305,7 @@ class S3plistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -331,7 +331,7 @@ class S3plistApi {
|
||||
}
|
||||
try {
|
||||
do {
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new ListObjectsV2Command({
|
||||
@@ -369,7 +369,7 @@ class S3plistApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -396,7 +396,7 @@ class S3plistApi {
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
do {
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new ListObjectsV2Command({
|
||||
@@ -439,7 +439,7 @@ class S3plistApi {
|
||||
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||
}
|
||||
|
||||
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
|
||||
const {
|
||||
bucketName: bucket,
|
||||
bucketConfig: { Location: region },
|
||||
@@ -457,10 +457,10 @@ class S3plistApi {
|
||||
}
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({
|
||||
|
||||
...this.baseOptions, region: String(region) || 'us-east-1'
|
||||
}) as S3ClientConfig
|
||||
const options = {
|
||||
...this.baseOptions,
|
||||
region: String(region) || 'us-east-1'
|
||||
} as S3ClientConfig
|
||||
const client = new S3Client(options)
|
||||
const command = new ListObjectsV2Command({
|
||||
Bucket: bucket,
|
||||
@@ -495,15 +495,15 @@ class S3plistApi {
|
||||
* newKey: string
|
||||
* }
|
||||
*/
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, oldKey, newKey } = configMap
|
||||
let result = false
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({
|
||||
|
||||
...this.baseOptions, region: String(region) || 'us-east-1'
|
||||
}) as S3ClientConfig
|
||||
const options = {
|
||||
...this.baseOptions,
|
||||
region: String(region) || 'us-east-1'
|
||||
} as S3ClientConfig
|
||||
const client = new S3Client(options)
|
||||
const command = new CopyObjectCommand({
|
||||
Bucket: bucketName,
|
||||
@@ -540,12 +540,12 @@ class S3plistApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new DeleteObjectCommand({
|
||||
@@ -568,7 +568,7 @@ class S3plistApi {
|
||||
* 删除文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
let marker
|
||||
let result = false
|
||||
@@ -581,7 +581,7 @@ class S3plistApi {
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
do {
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new ListObjectsV2Command({
|
||||
@@ -616,7 +616,7 @@ class S3plistApi {
|
||||
}
|
||||
if (allFileList.Contents.length > 0) {
|
||||
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'
|
||||
const client = new S3Client(options)
|
||||
for (let i = 0; i < cycle; i++) {
|
||||
@@ -657,11 +657,11 @@ class S3plistApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { bucketName, region, key, expires } = configMap
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const signedUrl = await getSignedUrl(
|
||||
@@ -685,12 +685,12 @@ class S3plistApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
await this.getDogeCloudToken()
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const command = new PutObjectCommand({
|
||||
@@ -713,7 +713,7 @@ class S3plistApi {
|
||||
* upload file
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
// fileArray = [{
|
||||
// bucketName: string,
|
||||
@@ -764,7 +764,7 @@ class S3plistApi {
|
||||
})
|
||||
continue
|
||||
}
|
||||
const options = ({ ...this.baseOptions }) as S3ClientConfig
|
||||
const options = { ...this.baseOptions } as S3ClientConfig
|
||||
options.region = String(region) || 'us-east-1'
|
||||
const client = new S3Client(options)
|
||||
const fileStream = fs.createReadStream(filePath)
|
||||
@@ -825,7 +825,7 @@ class S3plistApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -44,7 +44,7 @@ class SftpApi {
|
||||
passphrase: string
|
||||
}
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
host: string,
|
||||
port: Undefinable<number>,
|
||||
username: Undefinable<string>,
|
||||
@@ -94,7 +94,7 @@ class SftpApi {
|
||||
return `0${result}`
|
||||
}
|
||||
|
||||
formatFolder (item: listDirResult, urlPrefix: string, isWebPath = false) {
|
||||
formatFolder(item: listDirResult, urlPrefix: string, isWebPath = false) {
|
||||
const key = item.key
|
||||
let url: string
|
||||
if (isWebPath) {
|
||||
@@ -121,7 +121,7 @@ class SftpApi {
|
||||
}
|
||||
}
|
||||
|
||||
formatFile (item: listDirResult, urlPrefix: string, isWebPath = false) {
|
||||
formatFile(item: listDirResult, urlPrefix: string, isWebPath = false) {
|
||||
const key = item.key
|
||||
return {
|
||||
...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 { prefix, customUrl, cancelToken } = configMap
|
||||
const urlPrefix = customUrl || `${this.host}:${this.port}`
|
||||
@@ -191,7 +191,7 @@ class SftpApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
formatLSResult (res: string, cwd: string): listDirResult[] {
|
||||
formatLSResult(res: string, cwd: string): listDirResult[] {
|
||||
const result = [] as listDirResult[]
|
||||
const resArray = res.trim().split('\n')
|
||||
resArray.slice(resArray[0].startsWith('total') ? 1 : 0).forEach((item: string) => {
|
||||
@@ -217,7 +217,7 @@ class SftpApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { prefix, customUrl, cancelToken, baseDir } = configMap
|
||||
let urlPrefix = customUrl || `${this.host}:${this.port}`
|
||||
@@ -276,7 +276,7 @@ class SftpApi {
|
||||
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||
}
|
||||
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { oldKey, newKey } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -290,7 +290,7 @@ class SftpApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -304,7 +304,7 @@ class SftpApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -321,7 +321,7 @@ class SftpApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
@@ -376,7 +376,7 @@ class SftpApi {
|
||||
return true
|
||||
}
|
||||
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -390,7 +390,7 @@ class SftpApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
|
||||
@@ -21,7 +21,7 @@ class SmmsApi {
|
||||
logger: ManageLogger
|
||||
timeout = 30000
|
||||
|
||||
constructor (token: string, logger: ManageLogger) {
|
||||
constructor(token: string, logger: ManageLogger) {
|
||||
this.token = token
|
||||
this.axiosInstance = axios.create({
|
||||
baseURL: this.baseUrl,
|
||||
@@ -37,7 +37,7 @@ class SmmsApi {
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
formatFile (item: any) {
|
||||
formatFile(item: any) {
|
||||
return {
|
||||
...item,
|
||||
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 { cancelToken } = configMap
|
||||
let marker = 1
|
||||
@@ -123,7 +123,7 @@ class SmmsApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getBucketFileList ({ currentPage }: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList({ currentPage }: IStringKeyMap): Promise<any> {
|
||||
const result = {
|
||||
fullList: [] as any,
|
||||
isTruncated: false,
|
||||
@@ -162,7 +162,7 @@ class SmmsApi {
|
||||
* DeleteHash: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile ({ DeleteHash }: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile({ DeleteHash }: IStringKeyMap): Promise<boolean> {
|
||||
const res = await this.axiosInstance(`/delete/${DeleteHash}`, {
|
||||
method: 'GET',
|
||||
params: {
|
||||
@@ -177,7 +177,7 @@ class SmmsApi {
|
||||
* 上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
@@ -214,7 +214,7 @@ class SmmsApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -17,7 +17,7 @@ class TcyunApi {
|
||||
ctx: COS
|
||||
logger: ManageLogger
|
||||
|
||||
constructor (secretId: string, secretKey: string, logger: ManageLogger) {
|
||||
constructor(secretId: string, secretKey: string, logger: ManageLogger) {
|
||||
this.ctx = new COS({
|
||||
SecretId: secretId,
|
||||
SecretKey: secretKey
|
||||
@@ -25,7 +25,7 @@ class TcyunApi {
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
formatFolder (item: { Prefix: string }, slicedPrefix: string, urlPrefix: string) {
|
||||
formatFolder(item: { Prefix: string }, slicedPrefix: string, urlPrefix: string) {
|
||||
return {
|
||||
...item,
|
||||
key: item.Prefix,
|
||||
@@ -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 {
|
||||
...item,
|
||||
key: item.Key,
|
||||
@@ -58,7 +58,7 @@ class TcyunApi {
|
||||
/**
|
||||
* 获取存储桶列表
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
async getBucketList(): Promise<any> {
|
||||
const res = await this.ctx.getService({})
|
||||
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 res = await this.ctx.getBucketDomain({
|
||||
Bucket: bucketName,
|
||||
@@ -87,7 +87,7 @@ class TcyunApi {
|
||||
* @description
|
||||
* acl: private | publicRead | publicReadWrite
|
||||
*/
|
||||
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucket(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const res = await this.ctx.putBucket({
|
||||
ACL: configMap.acl,
|
||||
Bucket: configMap.BucketName,
|
||||
@@ -96,7 +96,7 @@ class TcyunApi {
|
||||
return res?.statusCode === 200
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -150,7 +150,7 @@ class TcyunApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const {
|
||||
bucketName: bucket,
|
||||
@@ -221,7 +221,7 @@ class TcyunApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
|
||||
const {
|
||||
bucketName: bucket,
|
||||
bucketConfig: { Location: region },
|
||||
@@ -272,7 +272,7 @@ class TcyunApi {
|
||||
* newKey: string
|
||||
* }
|
||||
*/
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, oldKey, newKey } = configMap
|
||||
const copyRes = await this.ctx.putObjectCopy({
|
||||
Bucket: bucketName,
|
||||
@@ -301,7 +301,7 @@ class TcyunApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
const res = await this.ctx.deleteObject({
|
||||
Bucket: bucketName,
|
||||
@@ -315,7 +315,7 @@ class TcyunApi {
|
||||
* 删除文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
let marker
|
||||
let res: any
|
||||
@@ -346,7 +346,9 @@ class TcyunApi {
|
||||
region,
|
||||
key: item.Prefix
|
||||
}))
|
||||
) { return false }
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
const cycles = Math.ceil(allFileList.Contents.length / 1000)
|
||||
for (let i = 0; i < cycles; i++) {
|
||||
@@ -371,7 +373,7 @@ class TcyunApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { bucketName, region, key, expires, customUrl } = configMap
|
||||
const res = this.ctx.getObjectUrl(
|
||||
{
|
||||
@@ -390,7 +392,7 @@ class TcyunApi {
|
||||
* 高级上传文件
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
// fileArray = [{
|
||||
// bucketName: string,
|
||||
@@ -470,7 +472,7 @@ class TcyunApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, key } = configMap
|
||||
const res = await this.ctx.putObject({
|
||||
Bucket: bucketName,
|
||||
@@ -485,7 +487,7 @@ class TcyunApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray } = configMap
|
||||
// fileArray = [{
|
||||
// bucketName: string,
|
||||
|
||||
@@ -34,7 +34,7 @@ class UpyunApi {
|
||||
stopMarker = 'g2gCZAAEbmV4dGQAA2VvZg'
|
||||
logger: ManageLogger
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
bucket: string,
|
||||
operator: string,
|
||||
password: string,
|
||||
@@ -52,7 +52,7 @@ class UpyunApi {
|
||||
this.expireTime = expireTime || 24 * 60 * 60
|
||||
}
|
||||
|
||||
getAntiLeechParam (key: string): string {
|
||||
getAntiLeechParam(key: string): string {
|
||||
const uri = `/${key}`.replace(/%2F/g, '/').replace(/^\/+/g, '/')
|
||||
const now = Math.round(new Date().getTime() / 1000)
|
||||
const expire = this.expireTime ? now + parseInt(this.expireTime.toString(), 10) : now + 1800
|
||||
@@ -61,7 +61,7 @@ class UpyunApi {
|
||||
return `_upt=${upt}`
|
||||
}
|
||||
|
||||
formatFolder (item: any, slicedPrefix: string, urlPrefix: string) {
|
||||
formatFolder(item: any, slicedPrefix: string, urlPrefix: string) {
|
||||
const key = `${slicedPrefix}${item.name}/`
|
||||
let url = `${urlPrefix}/${key}`
|
||||
if (this.antiLeechToken) {
|
||||
@@ -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}`
|
||||
let url = `${urlPrefix}/${key}`
|
||||
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(
|
||||
md5(password, 'hex'),
|
||||
`${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
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: bucket, prefix, cancelToken } = configMap
|
||||
const slicedPrefix = prefix.slice(1)
|
||||
@@ -168,7 +168,7 @@ class UpyunApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { bucketName: bucket, prefix, cancelToken } = configMap
|
||||
const slicedPrefix = prefix.slice(1)
|
||||
@@ -227,7 +227,7 @@ class UpyunApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
|
||||
const { bucketName: bucket, prefix, marker, itemsPerPage } = configMap
|
||||
const slicedPrefix = prefix.slice(1)
|
||||
const urlPrefix = configMap.customUrl || `http://${bucket}.test.upcdn.net`
|
||||
@@ -264,7 +264,7 @@ class UpyunApi {
|
||||
* newKey: string
|
||||
* }
|
||||
*/
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const oldKey = configMap.oldKey
|
||||
let newKey = configMap.newKey
|
||||
const method = 'PUT'
|
||||
@@ -297,7 +297,7 @@ class UpyunApi {
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
const res = await this.cli.deleteFile(key)
|
||||
return res
|
||||
@@ -307,7 +307,7 @@ class UpyunApi {
|
||||
* delete bucket folder
|
||||
* @param configMap
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let marker = ''
|
||||
let isTruncated
|
||||
@@ -370,7 +370,7 @@ class UpyunApi {
|
||||
* axiso:onUploadProgress not work in nodejs , use got instead
|
||||
* @param configMap
|
||||
*/
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
fileArray.forEach((item: any) => {
|
||||
@@ -426,7 +426,7 @@ class UpyunApi {
|
||||
* 新建文件夹
|
||||
* @param configMap
|
||||
*/
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
const res = await this.cli.makeDir(`/${key}`)
|
||||
return res
|
||||
@@ -436,7 +436,7 @@ class UpyunApi {
|
||||
* 下载文件
|
||||
* @param configMap
|
||||
*/
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -28,7 +28,7 @@ class WebdavplistApi {
|
||||
agent: https.Agent | http.Agent
|
||||
ctx: WebDAVClient
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
endpoint: string,
|
||||
username: string,
|
||||
password: string,
|
||||
@@ -62,7 +62,7 @@ class WebdavplistApi {
|
||||
|
||||
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'WebdavplistApi', method }))
|
||||
|
||||
formatFolder (item: FileStat, urlPrefix: string, isWebPath = false) {
|
||||
formatFolder(item: FileStat, urlPrefix: string, isWebPath = false) {
|
||||
const key = item.filename.replace(/^\/+/, '')
|
||||
return {
|
||||
...item,
|
||||
@@ -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(/^\/+/, '')
|
||||
return {
|
||||
...item,
|
||||
@@ -98,7 +98,7 @@ class WebdavplistApi {
|
||||
|
||||
isRequestSuccess = (code: number) => code >= 200 && code < 300
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { prefix, customUrl, cancelToken } = configMap
|
||||
const urlPrefix = customUrl || this.endpoint
|
||||
@@ -138,7 +138,7 @@ class WebdavplistApi {
|
||||
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||
}
|
||||
|
||||
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const { prefix, customUrl, cancelToken, baseDir } = configMap
|
||||
let urlPrefix = customUrl || this.endpoint
|
||||
@@ -197,7 +197,7 @@ class WebdavplistApi {
|
||||
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||
}
|
||||
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { oldKey, newKey } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -209,7 +209,7 @@ class WebdavplistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -221,7 +221,7 @@ class WebdavplistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -233,7 +233,7 @@ class WebdavplistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
|
||||
const { key } = configMap
|
||||
let result = ''
|
||||
try {
|
||||
@@ -245,7 +245,7 @@ class WebdavplistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileArray } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
for (const item of fileArray) {
|
||||
@@ -306,7 +306,7 @@ class WebdavplistApi {
|
||||
return true
|
||||
}
|
||||
|
||||
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { key } = configMap
|
||||
let result = false
|
||||
try {
|
||||
@@ -320,7 +320,7 @@ class WebdavplistApi {
|
||||
return result
|
||||
}
|
||||
|
||||
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||
const instance = UpDownTaskQueue.getInstance()
|
||||
const promises = [] as any
|
||||
|
||||
@@ -9,7 +9,7 @@ interface IJSON {
|
||||
class ManageDB {
|
||||
readonly #ctx: IManageApiType
|
||||
readonly #db: JSONStore
|
||||
constructor (ctx: IManageApiType) {
|
||||
constructor(ctx: IManageApiType) {
|
||||
this.#ctx = ctx
|
||||
this.#db = new JSONStore(this.#ctx.configPath)
|
||||
const initParams: IStringKeyMap = {
|
||||
@@ -28,37 +28,37 @@ class ManageDB {
|
||||
}
|
||||
}
|
||||
|
||||
read (flush?: boolean): IJSON {
|
||||
read(flush?: boolean): IJSON {
|
||||
return this.#db.read(flush)
|
||||
}
|
||||
|
||||
get (key: string = ''): any {
|
||||
get(key: string = ''): any {
|
||||
this.read(true)
|
||||
return this.#db.get(key)
|
||||
}
|
||||
|
||||
set (key: string, value: any): void {
|
||||
set(key: string, value: any): void {
|
||||
this.read(true)
|
||||
return this.#db.set(key, value)
|
||||
}
|
||||
|
||||
has (key: string): boolean {
|
||||
has(key: string): boolean {
|
||||
this.read(true)
|
||||
return this.#db.has(key)
|
||||
}
|
||||
|
||||
unset (key: string, value: any): boolean {
|
||||
unset(key: string, value: any): boolean {
|
||||
this.read(true)
|
||||
return this.#db.unset(key, value)
|
||||
}
|
||||
|
||||
saveConfig (config: Partial<IManageConfigType>): void {
|
||||
saveConfig(config: Partial<IManageConfigType>): void {
|
||||
Object.keys(config).forEach((name: string) => {
|
||||
this.set(name, config[name])
|
||||
})
|
||||
}
|
||||
|
||||
removeConfig (config: IManageConfigType): void {
|
||||
removeConfig(config: IManageConfigType): void {
|
||||
Object.keys(config).forEach((name: string) => {
|
||||
this.unset(name, config[name])
|
||||
})
|
||||
|
||||
@@ -21,7 +21,7 @@ const errorMsg = {
|
||||
brokenButBackup: $t('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP')
|
||||
}
|
||||
|
||||
function manageDbChecker () {
|
||||
function manageDbChecker() {
|
||||
if (process.type !== 'renderer') {
|
||||
const manageConfigFilePath = managePathChecker()
|
||||
if (!fs.existsSync(manageConfigFilePath)) {
|
||||
@@ -72,7 +72,7 @@ function manageDbChecker () {
|
||||
/**
|
||||
* Get manage config path
|
||||
*/
|
||||
function managePathChecker (): string {
|
||||
function managePathChecker(): string {
|
||||
if (_configFilePath) {
|
||||
return _configFilePath
|
||||
}
|
||||
@@ -113,7 +113,7 @@ function managePathChecker (): string {
|
||||
}
|
||||
}
|
||||
|
||||
function managePathDir () {
|
||||
function managePathDir() {
|
||||
return path.dirname(managePathChecker())
|
||||
}
|
||||
|
||||
|
||||
@@ -18,38 +18,38 @@ class UpDownTaskQueue {
|
||||
|
||||
private persistPath = path.join(app.getPath('userData'), 'UpDownTaskQueue.json')
|
||||
|
||||
private constructor () {
|
||||
private constructor() {
|
||||
this.restore()
|
||||
}
|
||||
|
||||
static getInstance () {
|
||||
static getInstance() {
|
||||
if (!UpDownTaskQueue.instance) {
|
||||
UpDownTaskQueue.instance = new UpDownTaskQueue()
|
||||
}
|
||||
return UpDownTaskQueue.instance
|
||||
}
|
||||
|
||||
getUploadTaskQueue () {
|
||||
getUploadTaskQueue() {
|
||||
return UpDownTaskQueue.getInstance().uploadTaskQueue
|
||||
}
|
||||
|
||||
getDownloadTaskQueue () {
|
||||
getDownloadTaskQueue() {
|
||||
return UpDownTaskQueue.getInstance().downloadTaskQueue
|
||||
}
|
||||
|
||||
getUploadTask (taskId: string) {
|
||||
getUploadTask(taskId: string) {
|
||||
return UpDownTaskQueue.getInstance().uploadTaskQueue.find(item => item.id === taskId)
|
||||
}
|
||||
|
||||
getAllUploadTask () {
|
||||
getAllUploadTask() {
|
||||
return UpDownTaskQueue.getInstance().uploadTaskQueue
|
||||
}
|
||||
|
||||
addUploadTask (task: IUploadTask) {
|
||||
addUploadTask(task: IUploadTask) {
|
||||
UpDownTaskQueue.getInstance().uploadTaskQueue.push(task)
|
||||
}
|
||||
|
||||
updateUploadTask (task: Partial<IUploadTask>) {
|
||||
updateUploadTask(task: Partial<IUploadTask>) {
|
||||
const taskIndex = UpDownTaskQueue.getInstance().uploadTaskQueue.findIndex(item => item.id === task.id)
|
||||
if (taskIndex !== -1) {
|
||||
const taskKeys = Object.keys(task)
|
||||
@@ -61,33 +61,33 @@ class UpDownTaskQueue {
|
||||
}
|
||||
}
|
||||
|
||||
removeUploadTask (taskId: string) {
|
||||
removeUploadTask(taskId: string) {
|
||||
const taskIndex = UpDownTaskQueue.getInstance().uploadTaskQueue.findIndex(item => item.id === taskId)
|
||||
if (taskIndex !== -1) {
|
||||
UpDownTaskQueue.getInstance().uploadTaskQueue.splice(taskIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
removeDownloadTask (taskId: string) {
|
||||
removeDownloadTask(taskId: string) {
|
||||
const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === taskId)
|
||||
if (taskIndex !== -1) {
|
||||
UpDownTaskQueue.getInstance().downloadTaskQueue.splice(taskIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
getDownloadTask (taskId: string) {
|
||||
getDownloadTask(taskId: string) {
|
||||
return UpDownTaskQueue.getInstance().downloadTaskQueue.find(item => item.id === taskId)
|
||||
}
|
||||
|
||||
getAllDownloadTask () {
|
||||
getAllDownloadTask() {
|
||||
return UpDownTaskQueue.getInstance().downloadTaskQueue
|
||||
}
|
||||
|
||||
addDownloadTask (task: IDownloadTask) {
|
||||
addDownloadTask(task: IDownloadTask) {
|
||||
UpDownTaskQueue.getInstance().downloadTaskQueue.push(task)
|
||||
}
|
||||
|
||||
updateDownloadTask (task: Partial<IDownloadTask>) {
|
||||
updateDownloadTask(task: Partial<IDownloadTask>) {
|
||||
const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === task.id)
|
||||
if (taskIndex !== -1) {
|
||||
const taskKeys = Object.keys(task)
|
||||
@@ -99,11 +99,11 @@ class UpDownTaskQueue {
|
||||
}
|
||||
}
|
||||
|
||||
clearUploadTaskQueue () {
|
||||
clearUploadTaskQueue() {
|
||||
UpDownTaskQueue.getInstance().uploadTaskQueue = []
|
||||
}
|
||||
|
||||
removeUploadedTask () {
|
||||
removeUploadedTask() {
|
||||
UpDownTaskQueue.getInstance().uploadTaskQueue = UpDownTaskQueue.getInstance().uploadTaskQueue.filter(
|
||||
item =>
|
||||
item.status !== uploadTaskSpecialStatus.uploaded &&
|
||||
@@ -112,7 +112,7 @@ class UpDownTaskQueue {
|
||||
)
|
||||
}
|
||||
|
||||
removeDownloadedTask () {
|
||||
removeDownloadedTask() {
|
||||
UpDownTaskQueue.getInstance().downloadTaskQueue = UpDownTaskQueue.getInstance().downloadTaskQueue.filter(
|
||||
item =>
|
||||
item.status !== downloadTaskSpecialStatus.downloaded &&
|
||||
@@ -121,16 +121,16 @@ class UpDownTaskQueue {
|
||||
)
|
||||
}
|
||||
|
||||
clearDownloadTaskQueue () {
|
||||
clearDownloadTaskQueue() {
|
||||
UpDownTaskQueue.getInstance().downloadTaskQueue = []
|
||||
}
|
||||
|
||||
clearAllTaskQueue () {
|
||||
clearAllTaskQueue() {
|
||||
this.clearUploadTaskQueue()
|
||||
this.clearDownloadTaskQueue()
|
||||
}
|
||||
|
||||
persist () {
|
||||
persist() {
|
||||
try {
|
||||
this.checkPersistPath()
|
||||
fs.writeFileSync(
|
||||
@@ -145,7 +145,7 @@ class UpDownTaskQueue {
|
||||
}
|
||||
}
|
||||
|
||||
private restore () {
|
||||
private restore() {
|
||||
try {
|
||||
this.checkPersistPath()
|
||||
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)) {
|
||||
fs.writeFileSync(
|
||||
this.persistPath,
|
||||
|
||||
@@ -26,7 +26,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
logger: ManageLogger
|
||||
currentPicBedConfig: IPicBedMangeConfig
|
||||
|
||||
constructor (currentPicBed: string = '') {
|
||||
constructor(currentPicBed: string = '') {
|
||||
super()
|
||||
this.currentPicBed = currentPicBed || 'placeholder'
|
||||
this.configPath = managePathChecker()
|
||||
@@ -36,7 +36,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
this.currentPicBedConfig = this.getPicBedConfig(this.currentPicBed)
|
||||
}
|
||||
|
||||
getMsgParam (method: string) {
|
||||
getMsgParam(method: string) {
|
||||
return {
|
||||
class: 'ManageApi',
|
||||
method,
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
createClient () {
|
||||
createClient() {
|
||||
const name = this.currentPicBedConfig.picBedName
|
||||
switch (name) {
|
||||
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}`)
|
||||
}
|
||||
|
||||
private initConfigPath (): void {
|
||||
private initConfigPath(): void {
|
||||
if (this.configPath === '') {
|
||||
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._config = this.db.read(true) as IManageConfigType
|
||||
}
|
||||
@@ -158,7 +158,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
return get(this._config, name)
|
||||
}
|
||||
|
||||
saveConfig (config: IStringKeyMap): void {
|
||||
saveConfig(config: IStringKeyMap): void {
|
||||
if (!isInputConfigValid(config)) {
|
||||
this.logger.warn('the format of config is invalid, please provide object')
|
||||
return
|
||||
@@ -167,7 +167,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
this.db.saveConfig(config)
|
||||
}
|
||||
|
||||
removeConfig (key: string, propName: string): void {
|
||||
removeConfig(key: string, propName: string): void {
|
||||
if (!key || !propName) {
|
||||
return
|
||||
}
|
||||
@@ -175,7 +175,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
this.db.unset(key, propName)
|
||||
}
|
||||
|
||||
setConfig (config: IStringKeyMap): void {
|
||||
setConfig(config: IStringKeyMap): void {
|
||||
if (!isInputConfigValid(config)) {
|
||||
this.logger.warn('the format of config is invalid, please provide object')
|
||||
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
|
||||
unset(this.getConfig(key), propName)
|
||||
}
|
||||
|
||||
async getBucketList (param?: IStringKeyMap | undefined): Promise<any> {
|
||||
async getBucketList(param?: IStringKeyMap | undefined): Promise<any> {
|
||||
let client
|
||||
const name = this.currentPicBedConfig.picBedName.replace('plist', '')
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
@@ -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)
|
||||
return {}
|
||||
}
|
||||
|
||||
async getBucketDomain (param: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
async getBucketDomain(param: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
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
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
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)
|
||||
return false
|
||||
}
|
||||
|
||||
async getOperatorList (param?: IStringKeyMap): Promise<string[] | IManageError> {
|
||||
async getOperatorList(param?: IStringKeyMap): Promise<string[] | IManageError> {
|
||||
console.log(param)
|
||||
return []
|
||||
}
|
||||
|
||||
async addOperator (param?: IStringKeyMap): Promise<boolean> {
|
||||
async addOperator(param?: IStringKeyMap): Promise<boolean> {
|
||||
console.log(param)
|
||||
return false
|
||||
}
|
||||
|
||||
async deleteOperator (param?: IStringKeyMap): Promise<boolean> {
|
||||
async deleteOperator(param?: IStringKeyMap): Promise<boolean> {
|
||||
console.log(param)
|
||||
return false
|
||||
}
|
||||
|
||||
async getBucketAclPolicy (param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
async getBucketAclPolicy(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
console.log(param)
|
||||
return {}
|
||||
}
|
||||
|
||||
async setBucketAclPolicy (param?: IStringKeyMap): Promise<boolean> {
|
||||
async setBucketAclPolicy(param?: IStringKeyMap): Promise<boolean> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'qiniu':
|
||||
@@ -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 window
|
||||
const defaultResult = {
|
||||
@@ -365,7 +365,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
* @param param
|
||||
* @returns
|
||||
*/
|
||||
async getBucketListBackstage (param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
async getBucketListBackstage(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
let client
|
||||
let window
|
||||
const defaultResult = {
|
||||
@@ -412,7 +412,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
|
||||
* isDir: 是否是文件夹
|
||||
* fileSize: 文件大小
|
||||
**/
|
||||
async getBucketFileList (param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
async getBucketFileList(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
|
||||
const defaultResponse = {
|
||||
fullList: [] as any,
|
||||
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
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
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
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
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
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
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
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
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)
|
||||
return false
|
||||
}
|
||||
|
||||
async createBucketFolder (param?: IStringKeyMap): Promise<boolean> {
|
||||
async createBucketFolder(param?: IStringKeyMap): Promise<boolean> {
|
||||
let client
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
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
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
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
|
||||
switch (this.currentPicBedConfig.picBedName) {
|
||||
case 'tcyun':
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ export const getInnerAgent = (proxy: any, sslEnabled: boolean = true) => {
|
||||
}
|
||||
}
|
||||
|
||||
export function getOptions (
|
||||
export function getOptions(
|
||||
method?: string,
|
||||
headers?: IStringKeyMap,
|
||||
searchParams?: IStringKeyMap,
|
||||
@@ -309,14 +309,14 @@ export class ConcurrencyPromisePool {
|
||||
runningNum: number
|
||||
results: any[]
|
||||
|
||||
constructor (limit: number) {
|
||||
constructor(limit: number) {
|
||||
this.limit = limit
|
||||
this.queue = []
|
||||
this.runningNum = 0
|
||||
this.results = []
|
||||
}
|
||||
|
||||
all (promises: any[] = []) {
|
||||
all(promises: any[] = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
for (const promise of promises) {
|
||||
this._run(promise, resolve, reject)
|
||||
@@ -324,7 +324,7 @@ export class ConcurrencyPromisePool {
|
||||
})
|
||||
}
|
||||
|
||||
_run (promise: any, resolve: any, reject: any) {
|
||||
_run(promise: any, resolve: any, reject: any) {
|
||||
if (this.runningNum >= this.limit) {
|
||||
this.queue.push(promise)
|
||||
return
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface DogecloudToken {
|
||||
sessionToken: string
|
||||
}
|
||||
|
||||
export async function dogecloudApi (
|
||||
export async function dogecloudApi(
|
||||
apiPath: string,
|
||||
data = {},
|
||||
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)
|
||||
if (dogeTempToken.token && dogeTempToken.expires > Date.now() + 7200000) {
|
||||
return dogeTempToken.token
|
||||
|
||||
@@ -24,11 +24,11 @@ export class ManageLogger implements ILogger {
|
||||
#logLevel!: string
|
||||
#logPath!: string
|
||||
|
||||
constructor (ctx: IManageApiType) {
|
||||
constructor(ctx: IManageApiType) {
|
||||
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()}]`)
|
||||
console.log(logHeader, ...msg)
|
||||
this.#logLevel = this.#ctx.getConfig(configPaths.settings.logLevel)
|
||||
@@ -53,7 +53,7 @@ export class ManageLogger implements ILogger {
|
||||
}, 0)
|
||||
}
|
||||
|
||||
#checkLogFileIsLarge (logPath: string): {
|
||||
#checkLogFileIsLarge(logPath: string): {
|
||||
isLarge: boolean
|
||||
logFileSize?: number
|
||||
logFileSizeLimit?: number
|
||||
@@ -76,14 +76,14 @@ export class ManageLogger implements ILogger {
|
||||
}
|
||||
}
|
||||
|
||||
#recreateLogFile (logPath: string): void {
|
||||
#recreateLogFile(logPath: string): void {
|
||||
if (fs.existsSync(logPath)) {
|
||||
fs.unlinkSync(logPath)
|
||||
fs.createFileSync(logPath)
|
||||
}
|
||||
}
|
||||
|
||||
#handleWriteLog (logPath: string, type: string, ...msg: ILogArgvTypeWithError[]): void {
|
||||
#handleWriteLog(logPath: string, type: string, ...msg: ILogArgvTypeWithError[]): void {
|
||||
try {
|
||||
if (this.#checkLogLevel(type, this.#logLevel)) {
|
||||
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicList ${type.toUpperCase()}] `
|
||||
@@ -98,7 +98,7 @@ export class ManageLogger implements ILogger {
|
||||
}
|
||||
}
|
||||
|
||||
#formatLogItem (item: ILogArgvTypeWithError, type: string): string {
|
||||
#formatLogItem(item: ILogArgvTypeWithError, type: string): string {
|
||||
let result = ''
|
||||
if (item instanceof Error && type === 'error') {
|
||||
result += `\n------Error Stack Begin------\n${util.format(item?.stack)}\n-------Error Stack End------- `
|
||||
@@ -114,7 +114,7 @@ export class ManageLogger implements ILogger {
|
||||
return result
|
||||
}
|
||||
|
||||
#checkLogLevel (type: string, level: undefined | string | string[]): boolean {
|
||||
#checkLogLevel(type: string, level: undefined | string | string[]): boolean {
|
||||
if (level === undefined || level === 'all') {
|
||||
return true
|
||||
}
|
||||
@@ -124,23 +124,23 @@ export class ManageLogger implements ILogger {
|
||||
return type === level
|
||||
}
|
||||
|
||||
success (...msq: ILogArgvType[]): void {
|
||||
success(...msq: ILogArgvType[]): void {
|
||||
return this.#handleLog(ILogType.success, ...msq)
|
||||
}
|
||||
|
||||
info (...msq: ILogArgvType[]): void {
|
||||
info(...msq: ILogArgvType[]): void {
|
||||
return this.#handleLog(ILogType.info, ...msq)
|
||||
}
|
||||
|
||||
error (...msq: ILogArgvTypeWithError[]): void {
|
||||
error(...msq: ILogArgvTypeWithError[]): void {
|
||||
return this.#handleLog(ILogType.error, ...msq)
|
||||
}
|
||||
|
||||
warn (...msq: ILogArgvType[]): void {
|
||||
warn(...msq: ILogArgvType[]): void {
|
||||
return this.#handleLog(ILogType.warn, ...msq)
|
||||
}
|
||||
|
||||
debug (...msq: ILogArgvType[]): void {
|
||||
debug(...msq: ILogArgvType[]): void {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this.#handleLog(ILogType.info, ...msq)
|
||||
}
|
||||
|
||||
@@ -41,12 +41,12 @@ class Server {
|
||||
#httpServer: http.Server
|
||||
#config: IServerConfig
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.#config = this.getConfigWithDefaults()
|
||||
this.#httpServer = http.createServer(this.#handleRequest)
|
||||
}
|
||||
|
||||
getConfigWithDefaults () {
|
||||
getConfigWithDefaults() {
|
||||
let config = picgo.getConfig<IServerConfig>(configPaths.settings.server)
|
||||
if (!this.#isValidConfig(config)) {
|
||||
config = { port: DEFAULT_PORT, host: DEFAULT_HOST, enable: true }
|
||||
@@ -55,7 +55,7 @@ class Server {
|
||||
return config
|
||||
}
|
||||
|
||||
#isValidConfig (config: IObj | undefined) {
|
||||
#isValidConfig(config: IObj | undefined) {
|
||||
return config && config.port && config.host && config.enable !== undefined
|
||||
}
|
||||
|
||||
@@ -198,20 +198,20 @@ class Server {
|
||||
})
|
||||
}
|
||||
|
||||
startup () {
|
||||
startup() {
|
||||
if (this.#config.enable) {
|
||||
this.#listen(this.#config.port)
|
||||
}
|
||||
}
|
||||
|
||||
shutdown (hasStarted?: boolean) {
|
||||
shutdown(hasStarted?: boolean) {
|
||||
this.#httpServer.close()
|
||||
if (!hasStarted) {
|
||||
logger.info('[PicList Server] shutdown')
|
||||
}
|
||||
}
|
||||
|
||||
restart () {
|
||||
restart() {
|
||||
this.shutdown()
|
||||
this.#config = this.getConfigWithDefaults()
|
||||
this.startup()
|
||||
|
||||
@@ -5,27 +5,27 @@ type HttpMethod = 'GET' | 'POST'
|
||||
class Router {
|
||||
#router = new Map<string, Map<HttpMethod, { handler: routeHandler; urlparams?: URLSearchParams }>>()
|
||||
|
||||
#addRoute (method: HttpMethod, url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
#addRoute(method: HttpMethod, url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
if (!this.#router.has(url)) {
|
||||
this.#router.set(url, new Map())
|
||||
}
|
||||
this.#router.get(url)!.set(method, { handler: callback, urlparams })
|
||||
}
|
||||
|
||||
get (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
get(url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
this.#addRoute('GET', url, callback, urlparams)
|
||||
}
|
||||
|
||||
post (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
post(url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
this.#addRoute('POST', url, callback, urlparams)
|
||||
}
|
||||
|
||||
any (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
any(url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
|
||||
this.#addRoute('GET', url, callback, urlparams)
|
||||
this.#addRoute('POST', url, callback, urlparams)
|
||||
}
|
||||
|
||||
getHandler (url: string, method: HttpMethod) {
|
||||
getHandler(url: string, method: HttpMethod) {
|
||||
if (this.#router.has(url)) {
|
||||
const methods = this.#router.get(url)!
|
||||
if (methods.has(method)) {
|
||||
|
||||
@@ -1,234 +1,234 @@
|
||||
import http from 'node:http'
|
||||
import path from 'node:path'
|
||||
|
||||
import { dbPathDir } from '@core/datastore/dbChecker'
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import { marked } from 'marked'
|
||||
|
||||
import type { IHttpResponse, IStringKeyMap } from '#/types/types'
|
||||
import { markdownContent } from '~/server/apiDoc'
|
||||
import router from '~/server/router'
|
||||
import { deleteChoosedFiles, handleResponse } from '~/server/utils'
|
||||
import { AESHelper } from '~/utils/aesHelper'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
|
||||
|
||||
const appPath = app.getPath('userData')
|
||||
const serverTempDir = path.join(appPath, 'serverTemp')
|
||||
|
||||
const STORE_PATH = dbPathDir()
|
||||
const LOG_PATH = path.join(STORE_PATH, 'piclist.log')
|
||||
|
||||
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
|
||||
const deleteErrorMessage = `delete error. see ${LOG_PATH} for more detail.`
|
||||
|
||||
async function responseForGet ({ response }: { response: http.ServerResponse }) {
|
||||
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
||||
const htmlContent = marked(markdownContent)
|
||||
response.write(htmlContent)
|
||||
response.end()
|
||||
}
|
||||
|
||||
router.get('/', responseForGet)
|
||||
router.get('/upload', responseForGet)
|
||||
|
||||
router.post(
|
||||
'/upload',
|
||||
async ({
|
||||
response,
|
||||
list = [],
|
||||
urlparams
|
||||
}: {
|
||||
response: IHttpResponse
|
||||
list?: string[]
|
||||
urlparams?: URLSearchParams
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
const picbed = urlparams?.get('picbed')
|
||||
const passedKey = urlparams?.get('key')
|
||||
const serverKey = picgo.getConfig<string>(configPaths.settings.serverKey) || ''
|
||||
const useShortUrl = picgo.getConfig<boolean>(configPaths.settings.useShortUrl)
|
||||
if (serverKey && passedKey !== serverKey) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'server key is uncorrect'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
let currentPicBedType = ''
|
||||
let currentPicBedConfig = {} as IStringKeyMap
|
||||
let currentPicBedConfigId = ''
|
||||
let needRestore = false
|
||||
if (picbed) {
|
||||
const currentPicBed = picgo.getConfig<IStringKeyMap>('picBed') || ({} as IStringKeyMap)
|
||||
currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms'
|
||||
currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
|
||||
currentPicBedConfigId = currentPicBedConfig._id
|
||||
const configName = urlparams?.get('configName') || currentPicBed[picbed]?._configName
|
||||
if (picbed === currentPicBedType && configName === currentPicBedConfig._configName) {
|
||||
// do nothing
|
||||
} else {
|
||||
needRestore = true
|
||||
const picBeds = picgo.getConfig<IStringKeyMap>('uploader')
|
||||
const currentPicBedList = picBeds?.[picbed]?.configList
|
||||
if (currentPicBedList) {
|
||||
const currentConfig = currentPicBedList?.find((item: any) => item._configName === configName)
|
||||
if (currentConfig) {
|
||||
changeCurrentUploader(picbed, currentConfig, currentConfig._id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (list.length === 0) {
|
||||
// upload with clipboard
|
||||
logger.info('[PicList Server] upload clipboard file')
|
||||
const result = await uploadClipboardFiles()
|
||||
const res = useShortUrl ? result.fullResult.shortUrl || result.url : result.url
|
||||
const fullResult = result.fullResult
|
||||
fullResult.imgUrl = useShortUrl ? fullResult.shortUrl || fullResult.imgUrl : fullResult.imgUrl
|
||||
logger.info('[PicList Server] upload result:', res)
|
||||
if (res) {
|
||||
const treatedFullResult = {
|
||||
isEncrypted: 1,
|
||||
EncryptedData: new AESHelper().encrypt(JSON.stringify(fullResult)),
|
||||
...fullResult
|
||||
}
|
||||
delete treatedFullResult.config
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: [res],
|
||||
fullResult: [treatedFullResult]
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
logger.info('[PicList Server] upload files in list')
|
||||
// upload with files
|
||||
const pathList = list.map(item => {
|
||||
return {
|
||||
path: item
|
||||
}
|
||||
})
|
||||
const win = windowManager.getAvailableWindow()
|
||||
const result = await uploadChoosedFiles(win.webContents, pathList)
|
||||
const res = result.map(item => {
|
||||
return useShortUrl ? item.fullResult.shortUrl || item.url : item.url
|
||||
})
|
||||
const fullResult = result.map((item: any) => {
|
||||
const treatedItem = {
|
||||
isEncrypted: 1,
|
||||
EncryptedData: new AESHelper().encrypt(JSON.stringify(item.fullResult)),
|
||||
...item.fullResult
|
||||
}
|
||||
delete treatedItem.config
|
||||
treatedItem.imgUrl = useShortUrl ? treatedItem.shortUrl || treatedItem.imgUrl : treatedItem.imgUrl
|
||||
return treatedItem
|
||||
})
|
||||
logger.info('[PicList Server] upload result', res.join(' ; '))
|
||||
if (res.length) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: res,
|
||||
fullResult
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
fs.emptyDirSync(serverTempDir)
|
||||
if (needRestore) {
|
||||
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/delete',
|
||||
async ({ response, list = [] }: { response: IHttpResponse; list?: IStringKeyMap[] }): Promise<void> => {
|
||||
if (list.length === 0) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'no file to delete'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
const aesHelper = new AESHelper()
|
||||
const treatList = list.map(item => {
|
||||
if (!item.isEncrypted) return item
|
||||
return JSON.parse(aesHelper.decrypt(item.EncryptedData))
|
||||
})
|
||||
const result = await deleteChoosedFiles(treatList)
|
||||
const successCount = result.filter(item => item).length
|
||||
const failCount = result.length - successCount
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: !!successCount,
|
||||
message: successCount ? `delete success: ${successCount}, fail: ${failCount}` : deleteErrorMessage
|
||||
}
|
||||
})
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: deleteErrorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.any('/heartbeat', async ({ response }: { response: IHttpResponse }) => {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: 'alive'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export default router
|
||||
import http from 'node:http'
|
||||
import path from 'node:path'
|
||||
|
||||
import { dbPathDir } from '@core/datastore/dbChecker'
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import { marked } from 'marked'
|
||||
|
||||
import type { IHttpResponse, IStringKeyMap } from '#/types/types'
|
||||
import { markdownContent } from '~/server/apiDoc'
|
||||
import router from '~/server/router'
|
||||
import { deleteChoosedFiles, handleResponse } from '~/server/utils'
|
||||
import { AESHelper } from '~/utils/aesHelper'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
|
||||
|
||||
const appPath = app.getPath('userData')
|
||||
const serverTempDir = path.join(appPath, 'serverTemp')
|
||||
|
||||
const STORE_PATH = dbPathDir()
|
||||
const LOG_PATH = path.join(STORE_PATH, 'piclist.log')
|
||||
|
||||
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
|
||||
const deleteErrorMessage = `delete error. see ${LOG_PATH} for more detail.`
|
||||
|
||||
async function responseForGet({ response }: { response: http.ServerResponse }) {
|
||||
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
||||
const htmlContent = marked(markdownContent)
|
||||
response.write(htmlContent)
|
||||
response.end()
|
||||
}
|
||||
|
||||
router.get('/', responseForGet)
|
||||
router.get('/upload', responseForGet)
|
||||
|
||||
router.post(
|
||||
'/upload',
|
||||
async ({
|
||||
response,
|
||||
list = [],
|
||||
urlparams
|
||||
}: {
|
||||
response: IHttpResponse
|
||||
list?: string[]
|
||||
urlparams?: URLSearchParams
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
const picbed = urlparams?.get('picbed')
|
||||
const passedKey = urlparams?.get('key')
|
||||
const serverKey = picgo.getConfig<string>(configPaths.settings.serverKey) || ''
|
||||
const useShortUrl = picgo.getConfig<boolean>(configPaths.settings.useShortUrl)
|
||||
if (serverKey && passedKey !== serverKey) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'server key is uncorrect'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
let currentPicBedType = ''
|
||||
let currentPicBedConfig = {} as IStringKeyMap
|
||||
let currentPicBedConfigId = ''
|
||||
let needRestore = false
|
||||
if (picbed) {
|
||||
const currentPicBed = picgo.getConfig<IStringKeyMap>('picBed') || ({} as IStringKeyMap)
|
||||
currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms'
|
||||
currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
|
||||
currentPicBedConfigId = currentPicBedConfig._id
|
||||
const configName = urlparams?.get('configName') || currentPicBed[picbed]?._configName
|
||||
if (picbed === currentPicBedType && configName === currentPicBedConfig._configName) {
|
||||
// do nothing
|
||||
} else {
|
||||
needRestore = true
|
||||
const picBeds = picgo.getConfig<IStringKeyMap>('uploader')
|
||||
const currentPicBedList = picBeds?.[picbed]?.configList
|
||||
if (currentPicBedList) {
|
||||
const currentConfig = currentPicBedList?.find((item: any) => item._configName === configName)
|
||||
if (currentConfig) {
|
||||
changeCurrentUploader(picbed, currentConfig, currentConfig._id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (list.length === 0) {
|
||||
// upload with clipboard
|
||||
logger.info('[PicList Server] upload clipboard file')
|
||||
const result = await uploadClipboardFiles()
|
||||
const res = useShortUrl ? result.fullResult.shortUrl || result.url : result.url
|
||||
const fullResult = result.fullResult
|
||||
fullResult.imgUrl = useShortUrl ? fullResult.shortUrl || fullResult.imgUrl : fullResult.imgUrl
|
||||
logger.info('[PicList Server] upload result:', res)
|
||||
if (res) {
|
||||
const treatedFullResult = {
|
||||
isEncrypted: 1,
|
||||
EncryptedData: new AESHelper().encrypt(JSON.stringify(fullResult)),
|
||||
...fullResult
|
||||
}
|
||||
delete treatedFullResult.config
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: [res],
|
||||
fullResult: [treatedFullResult]
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
logger.info('[PicList Server] upload files in list')
|
||||
// upload with files
|
||||
const pathList = list.map(item => {
|
||||
return {
|
||||
path: item
|
||||
}
|
||||
})
|
||||
const win = windowManager.getAvailableWindow()
|
||||
const result = await uploadChoosedFiles(win.webContents, pathList)
|
||||
const res = result.map(item => {
|
||||
return useShortUrl ? item.fullResult.shortUrl || item.url : item.url
|
||||
})
|
||||
const fullResult = result.map((item: any) => {
|
||||
const treatedItem = {
|
||||
isEncrypted: 1,
|
||||
EncryptedData: new AESHelper().encrypt(JSON.stringify(item.fullResult)),
|
||||
...item.fullResult
|
||||
}
|
||||
delete treatedItem.config
|
||||
treatedItem.imgUrl = useShortUrl ? treatedItem.shortUrl || treatedItem.imgUrl : treatedItem.imgUrl
|
||||
return treatedItem
|
||||
})
|
||||
logger.info('[PicList Server] upload result', res.join(' ; '))
|
||||
if (res.length) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: res,
|
||||
fullResult
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
fs.emptyDirSync(serverTempDir)
|
||||
if (needRestore) {
|
||||
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/delete',
|
||||
async ({ response, list = [] }: { response: IHttpResponse; list?: IStringKeyMap[] }): Promise<void> => {
|
||||
if (list.length === 0) {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'no file to delete'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
const aesHelper = new AESHelper()
|
||||
const treatList = list.map(item => {
|
||||
if (!item.isEncrypted) return item
|
||||
return JSON.parse(aesHelper.decrypt(item.EncryptedData))
|
||||
})
|
||||
const result = await deleteChoosedFiles(treatList)
|
||||
const successCount = result.filter(item => item).length
|
||||
const failCount = result.length - successCount
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: !!successCount,
|
||||
message: successCount ? `delete success: ${successCount}, fail: ${failCount}` : deleteErrorMessage
|
||||
}
|
||||
})
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: deleteErrorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.any('/heartbeat', async ({ response }: { response: IHttpResponse }) => {
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: true,
|
||||
result: 'alive'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -11,7 +11,7 @@ import { configPaths } from '~/utils/configPaths'
|
||||
|
||||
const defaultPath = process.platform === 'win32' ? 'C:\\Users' : '/'
|
||||
|
||||
function generateDirectoryListingHtml (files: any[], requestPath: any) {
|
||||
function generateDirectoryListingHtml(files: any[], requestPath: any) {
|
||||
let html = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><h1>Directory Listing</h1><ul>'
|
||||
files.forEach((file: string) => {
|
||||
const filePath = path.join(requestPath, file)
|
||||
@@ -21,7 +21,7 @@ function generateDirectoryListingHtml (files: any[], requestPath: any) {
|
||||
return html
|
||||
}
|
||||
|
||||
function serveDirectory (res: http.ServerResponse, filePath: fs.PathLike, requestPath: any) {
|
||||
function serveDirectory(res: http.ServerResponse, filePath: fs.PathLike, requestPath: any) {
|
||||
fs.readdir(filePath, (err, files) => {
|
||||
if (err) {
|
||||
res.writeHead(500)
|
||||
@@ -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)
|
||||
readStream.pipe(res)
|
||||
readStream.on('error', () => {
|
||||
@@ -46,12 +46,12 @@ class WebServer {
|
||||
#server!: http.Server
|
||||
#config!: IStringKeyMap
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.loadConfig()
|
||||
this.initServer()
|
||||
}
|
||||
|
||||
loadConfig (): void {
|
||||
loadConfig(): void {
|
||||
this.#config = {
|
||||
enableWebServer: picgo.getConfig<boolean>(configPaths.settings.enableWebServer) || false,
|
||||
webServerHost: picgo.getConfig<string>(configPaths.settings.webServerHost) || '0.0.0.0',
|
||||
@@ -60,7 +60,7 @@ class WebServer {
|
||||
}
|
||||
}
|
||||
|
||||
initServer (): void {
|
||||
initServer(): void {
|
||||
this.#server = http.createServer((req, res) => {
|
||||
const requestPath = req.url?.split('?')[0]
|
||||
const filePath = path.join(this.#config.webServerPath, decodeURIComponent(requestPath || ''))
|
||||
@@ -79,7 +79,7 @@ class WebServer {
|
||||
})
|
||||
}
|
||||
|
||||
start () {
|
||||
start() {
|
||||
if (this.#config.enableWebServer) {
|
||||
this.#server
|
||||
.listen(
|
||||
@@ -99,13 +99,13 @@ class WebServer {
|
||||
}
|
||||
}
|
||||
|
||||
stop () {
|
||||
stop() {
|
||||
this.#server.close(() => {
|
||||
logger.info('Web server is stopped')
|
||||
})
|
||||
}
|
||||
|
||||
restart () {
|
||||
restart() {
|
||||
this.stop()
|
||||
this.loadConfig()
|
||||
this.initServer()
|
||||
|
||||
@@ -1,75 +1,60 @@
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
import picgo from '@core/picgo'
|
||||
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
|
||||
export class AESHelper {
|
||||
static readonly #SALT = Buffer.from('a8b3c4d2e4f5098712345678feedc0de', 'hex')
|
||||
static readonly #ITERATIONS = 100_000
|
||||
static readonly #KEYLEN = 32
|
||||
static readonly #DIGEST = 'sha512' as const
|
||||
static readonly #ALGO = 'aes-256-cbc'
|
||||
static readonly #IV_LENGTH = 16
|
||||
static readonly #SEP = ':'
|
||||
|
||||
static #keyCache = new Map<string, Buffer>()
|
||||
|
||||
readonly key: Buffer
|
||||
|
||||
constructor (password?: string) {
|
||||
const pwd =
|
||||
password ??
|
||||
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
|
||||
const key = crypto.pbkdf2Sync(
|
||||
password,
|
||||
this.#SALT,
|
||||
this.#ITERATIONS,
|
||||
this.#KEYLEN,
|
||||
this.#DIGEST
|
||||
)
|
||||
this.#keyCache.set(password, key)
|
||||
return key
|
||||
}
|
||||
|
||||
encrypt (plainText: string): string {
|
||||
const iv = crypto.randomBytes(AESHelper.#IV_LENGTH)
|
||||
const cipher = crypto.createCipheriv(AESHelper.#ALGO, this.key, iv)
|
||||
const encrypted = Buffer.concat([
|
||||
cipher.update(plainText, 'utf8'),
|
||||
cipher.final()
|
||||
])
|
||||
return `${iv.toString('hex')}${AESHelper.#SEP}${encrypted.toString('hex')}`
|
||||
}
|
||||
|
||||
decrypt (encryptedData: string): string {
|
||||
if (!encryptedData) return '{}'
|
||||
|
||||
const sepIndex = encryptedData.indexOf(AESHelper.#SEP)
|
||||
if (sepIndex <= 0) 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 '{}'
|
||||
}
|
||||
}
|
||||
}
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
import picgo from '@core/picgo'
|
||||
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
|
||||
export class AESHelper {
|
||||
static readonly #SALT = Buffer.from('a8b3c4d2e4f5098712345678feedc0de', 'hex')
|
||||
static readonly #ITERATIONS = 100_000
|
||||
static readonly #KEYLEN = 32
|
||||
static readonly #DIGEST = 'sha512' as const
|
||||
static readonly #ALGO = 'aes-256-cbc'
|
||||
static readonly #IV_LENGTH = 16
|
||||
static readonly #SEP = ':'
|
||||
|
||||
static #keyCache = new Map<string, Buffer>()
|
||||
|
||||
readonly key: Buffer
|
||||
|
||||
constructor(password?: string) {
|
||||
const pwd = password ?? 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
|
||||
const key = crypto.pbkdf2Sync(password, this.#SALT, this.#ITERATIONS, this.#KEYLEN, this.#DIGEST)
|
||||
this.#keyCache.set(password, key)
|
||||
return key
|
||||
}
|
||||
|
||||
encrypt(plainText: string): string {
|
||||
const iv = crypto.randomBytes(AESHelper.#IV_LENGTH)
|
||||
const cipher = crypto.createCipheriv(AESHelper.#ALGO, this.key, iv)
|
||||
const encrypted = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()])
|
||||
return `${iv.toString('hex')}${AESHelper.#SEP}${encrypted.toString('hex')}`
|
||||
}
|
||||
|
||||
decrypt(encryptedData: string): string {
|
||||
if (!encryptedData) return '{}'
|
||||
|
||||
const sepIndex = encryptedData.indexOf(AESHelper.#SEP)
|
||||
if (sepIndex <= 0) 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 '{}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const configPath = dbPathChecker()
|
||||
const CONFIG_DIR = path.dirname(configPath)
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
function beforeOpen () {
|
||||
function beforeOpen() {
|
||||
if (process.platform === 'darwin') {
|
||||
resolveMacWorkFlow()
|
||||
}
|
||||
@@ -21,7 +21,7 @@ function beforeOpen () {
|
||||
resolveOtherI18nFiles()
|
||||
}
|
||||
|
||||
function copyFileOutsideOfElectronAsar (sourceInAsarArchive: string, destOutsideAsarArchive: string) {
|
||||
function copyFileOutsideOfElectronAsar(sourceInAsarArchive: string, destOutsideAsarArchive: string) {
|
||||
if (fs.existsSync(sourceInAsarArchive)) {
|
||||
// file will be copied
|
||||
if (fs.statSync(sourceInAsarArchive).isFile()) {
|
||||
@@ -45,16 +45,21 @@ function copyFileOutsideOfElectronAsar (sourceInAsarArchive: string, destOutside
|
||||
/**
|
||||
* macOS 右键菜单
|
||||
*/
|
||||
function resolveMacWorkFlow () {
|
||||
function resolveMacWorkFlow() {
|
||||
const dest = `${os.homedir()}/Library/Services/Upload pictures with PicList.workflow`
|
||||
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) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
|
||||
function diffFilesAndUpdate(filePath1: string, filePath2: string) {
|
||||
try {
|
||||
const file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
|
||||
const file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
|
||||
@@ -71,7 +76,7 @@ function diffFilesAndUpdate (filePath1: string, filePath2: string) {
|
||||
/**
|
||||
* 初始化剪贴板生成图片的脚本
|
||||
*/
|
||||
function resolveClipboardImageGenerator () {
|
||||
function resolveClipboardImageGenerator() {
|
||||
const clipboardFiles = getClipboardFiles()
|
||||
if (!fs.pathExistsSync(path.join(CONFIG_DIR, 'windows10.ps1'))) {
|
||||
clipboardFiles.forEach(item => {
|
||||
@@ -84,7 +89,7 @@ function resolveClipboardImageGenerator () {
|
||||
})
|
||||
}
|
||||
|
||||
function getClipboardFiles () {
|
||||
function getClipboardFiles() {
|
||||
const files = ['linux.sh', 'mac.applescript', 'windows.ps1', 'windows10.ps1', 'wsl.sh']
|
||||
|
||||
return files.map(item => {
|
||||
@@ -99,7 +104,7 @@ function resolveClipboardImageGenerator () {
|
||||
/**
|
||||
* 初始化其他语言文件
|
||||
*/
|
||||
function resolveOtherI18nFiles () {
|
||||
function resolveOtherI18nFiles() {
|
||||
const i18nFolder = path.join(CONFIG_DIR, 'i18n')
|
||||
if (!fs.pathExistsSync(i18nFolder)) {
|
||||
fs.mkdirSync(i18nFolder)
|
||||
|
||||
@@ -9,13 +9,13 @@ class ClipboardWatcher extends EventEmitter {
|
||||
timer: NodeJS.Timeout | null
|
||||
lastImageHash: string | null
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
super()
|
||||
this.lastImageHash = null
|
||||
this.timer = null
|
||||
}
|
||||
|
||||
startListening (watchDelay = 1000) {
|
||||
startListening(watchDelay = 1000) {
|
||||
this.stopListening(false)
|
||||
|
||||
this.timer = setInterval(() => {
|
||||
@@ -34,7 +34,7 @@ class ClipboardWatcher extends EventEmitter {
|
||||
logger.info('Start to watch clipboard')
|
||||
}
|
||||
|
||||
stopListening (isLog = true) {
|
||||
stopListening(isLog = true) {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
@@ -43,7 +43,7 @@ class ClipboardWatcher extends EventEmitter {
|
||||
isLog && logger.info('Stop to watch clipboard')
|
||||
}
|
||||
|
||||
getImageHash (image: NativeImage): string {
|
||||
getImageHash(image: NativeImage): string {
|
||||
const buffer = image.toBitmap()
|
||||
return crypto.createHash('md5').update(buffer).digest('hex')
|
||||
}
|
||||
|
||||
@@ -1,323 +1,323 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import db from '@core/datastore'
|
||||
import logger from '@core/picgo/logger'
|
||||
import axios from 'axios'
|
||||
import { clipboard, Notification, Tray } from 'electron'
|
||||
import FormData from 'form-data'
|
||||
import fs from 'fs-extra'
|
||||
import { isReactive, isRef, toRaw, unref } from 'vue'
|
||||
|
||||
import type { IHTTPProxy, IPrivateShowNotificationOption, IStringKeyMap } from '#/types/types'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IShortUrlServer } from '~/utils/enum'
|
||||
|
||||
/**
|
||||
* get raw data from reactive or ref
|
||||
*/
|
||||
export const getRawData = (args: any): any => {
|
||||
if (isRef(args)) return unref(args)
|
||||
if (isReactive(args)) return toRaw(args)
|
||||
if (Array.isArray(args)) return args.map(getRawData)
|
||||
if (typeof args === 'object' && args !== null) {
|
||||
const data = {} as Record<string, any>
|
||||
for (const key in args) {
|
||||
data[key] = getRawData(args[key])
|
||||
}
|
||||
return data
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
const getExtension = (fileName: string) => path.extname(fileName).slice(1)
|
||||
|
||||
export const isImage = (fileName: string) =>
|
||||
['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico', 'svg', 'avif'].includes(getExtension(fileName))
|
||||
|
||||
export let tray: Tray
|
||||
|
||||
export const setTray = (t: Tray) => {
|
||||
tray = t
|
||||
}
|
||||
|
||||
export const getTray = () => tray
|
||||
|
||||
export function setTrayToolTip (title: string): void {
|
||||
if (tray) {
|
||||
tray.setToolTip(title)
|
||||
}
|
||||
}
|
||||
|
||||
export const handleCopyUrl = (str: string): void => {
|
||||
if (db.get(configPaths.settings.autoCopy) !== false) {
|
||||
clipboard.writeText(str)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show notification
|
||||
* @param options
|
||||
*/
|
||||
export const showNotification = (
|
||||
options: IPrivateShowNotificationOption = {
|
||||
title: '',
|
||||
body: '',
|
||||
clickToCopy: false,
|
||||
copyContent: '',
|
||||
clickFn: () => {}
|
||||
}
|
||||
) => {
|
||||
const notification = new Notification({
|
||||
title: options.title,
|
||||
body: options.body
|
||||
})
|
||||
const handleClick = () => {
|
||||
if (options.clickToCopy) {
|
||||
clipboard.writeText(options.copyContent || options.body)
|
||||
}
|
||||
if (options.clickFn) {
|
||||
options.clickFn()
|
||||
}
|
||||
}
|
||||
notification.once('click', handleClick)
|
||||
notification.once('close', () => {
|
||||
notification.removeListener('click', handleClick)
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS public.file-url will get encoded file path,
|
||||
* so we need to decode it
|
||||
*/
|
||||
export const ensureFilePath = (filePath: string, prefix = 'file://'): string => {
|
||||
filePath = filePath.replace(prefix, '')
|
||||
if (fs.existsSync(filePath)) {
|
||||
return `${prefix}${filePath}`
|
||||
}
|
||||
filePath = decodeURIComponent(filePath)
|
||||
if (fs.existsSync(filePath)) {
|
||||
return `${prefix}${filePath}`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* for builtin clipboard to get image path from clipboard
|
||||
* @returns
|
||||
*/
|
||||
export const getClipboardFilePath = (): string => {
|
||||
// TODO: linux support
|
||||
const img = clipboard.readImage()
|
||||
const platform = process.platform
|
||||
|
||||
if (!img.isEmpty() && platform === 'darwin') {
|
||||
let imgPath = clipboard.read('public.file-url') // will get file://xxx/xxx
|
||||
imgPath = ensureFilePath(imgPath)
|
||||
return imgPath ? imgPath.replace('file://', '') : ''
|
||||
}
|
||||
|
||||
if (img.isEmpty() && platform === 'win32') {
|
||||
const imgPath = clipboard
|
||||
.readBuffer('FileNameW')
|
||||
?.toString('ucs2')
|
||||
?.replace(RegExp(String.fromCharCode(0), 'g'), '')
|
||||
return imgPath || ''
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
const c1nApi = 'https://c1n.cn/link/short'
|
||||
|
||||
const createC1NShortUrl = async (url: string) => {
|
||||
const c1nToken = db.get(configPaths.settings.c1nToken) || ''
|
||||
if (!c1nToken) {
|
||||
logger.warn('c1n token is not set')
|
||||
return url
|
||||
}
|
||||
try {
|
||||
const form = new FormData()
|
||||
form.append('url', url)
|
||||
const res = await axios.post(c1nApi, form, {
|
||||
headers: {
|
||||
token: c1nToken
|
||||
}
|
||||
})
|
||||
if (res.status >= 200 && res.status < 300 && res.data?.code === 0) {
|
||||
return res.data.data
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
const createYOURLSShortLink = async (url: string) => {
|
||||
let domain = db.get(configPaths.settings.yourlsDomain) || ''
|
||||
const signature = db.get(configPaths.settings.yourlsSignature) || ''
|
||||
|
||||
if (!domain || !signature) {
|
||||
logger.warn('Yourls server or signature is not set')
|
||||
return url
|
||||
}
|
||||
if (!/^https?:\/\//.test(domain)) {
|
||||
domain = `http://${domain}`
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
signature,
|
||||
action: 'shorturl',
|
||||
format: 'json',
|
||||
url
|
||||
})
|
||||
try {
|
||||
const res = await axios.get(`${domain}/yourls-api.php?${params.toString()}`)
|
||||
if (res.data?.shorturl) {
|
||||
return res.data.shorturl
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.response?.data?.message?.includes('already exists in database')) {
|
||||
return e.response.data.shorturl
|
||||
}
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
const createShortUrlForCFWorker = async (url: string) => {
|
||||
let cfWorkerHost = db.get(configPaths.settings.cfWorkerHost) || ''
|
||||
cfWorkerHost = cfWorkerHost.replace(/\/$/, '')
|
||||
if (!cfWorkerHost) {
|
||||
logger.warn('CF Worker host is not set')
|
||||
return url
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.post(cfWorkerHost, { url })
|
||||
if (res.data?.status === 200 && res.data?.key?.startsWith('/')) {
|
||||
return `${cfWorkerHost}${res.data.key}`
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
const createShortUrlFromSink = async (url: string) => {
|
||||
let sinkDomain = db.get(configPaths.settings.sinkDomain) || ''
|
||||
const sinkToken = db.get(configPaths.settings.sinkToken) || ''
|
||||
if (!sinkDomain || !sinkToken) {
|
||||
logger.warn('Sink domain or token is not set')
|
||||
return url
|
||||
}
|
||||
if (!/^https?:\/\//.test(sinkDomain)) {
|
||||
sinkDomain = `http://${sinkDomain}`
|
||||
}
|
||||
if (sinkDomain.endsWith('/')) {
|
||||
sinkDomain = sinkDomain.slice(0, -1)
|
||||
}
|
||||
try {
|
||||
const res = await axios.post(
|
||||
`${sinkDomain}/api/link/create`,
|
||||
{ url },
|
||||
{ headers: { Authorization: `Bearer ${sinkToken}` } }
|
||||
)
|
||||
if (res.data?.link?.slug) {
|
||||
return `${sinkDomain}/${res.data.link.slug}`
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
export const generateShortUrl = async (url: string) => {
|
||||
const server = db.get(configPaths.settings.shortUrlServer) || IShortUrlServer.C1N
|
||||
switch (server) {
|
||||
case IShortUrlServer.C1N:
|
||||
return createC1NShortUrl(url)
|
||||
case IShortUrlServer.YOURLS:
|
||||
return createYOURLSShortLink(url)
|
||||
case IShortUrlServer.CFWORKER:
|
||||
return createShortUrlForCFWorker(url)
|
||||
case IShortUrlServer.SINK:
|
||||
return createShortUrlFromSink(url)
|
||||
default:
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
export const isUrl = (url: string): boolean => {
|
||||
try {
|
||||
return Boolean(new URL(url))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const isUrlEncode = (url: string): boolean => {
|
||||
url = url || ''
|
||||
try {
|
||||
return url !== decodeURI(url)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const handleUrlEncode = (url: string): string => (isUrlEncode(url) ? url : encodeURI(url))
|
||||
|
||||
export const handleUrlEncodeWithSetting = (url: string) =>
|
||||
db.get(configPaths.settings.encodeOutputURL) ? handleUrlEncode(url) : url
|
||||
|
||||
export const handleStreamlinePluginName = (name: string) => name.replace(/(@[^/]+\/)?picgo-plugin-/, '')
|
||||
export const simpleClone = (obj: any) => JSON.parse(JSON.stringify(obj))
|
||||
export const enforceNumber = (num: number | string) => (isNaN(+num) ? 0 : +num)
|
||||
|
||||
export const trimValues = <T extends IStringKeyMap>(
|
||||
obj: T
|
||||
): { [K in keyof T]: T[K] extends string ? string : T[K] } => {
|
||||
return Object.fromEntries(
|
||||
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] }
|
||||
}
|
||||
|
||||
export const formatEndpoint = (endpoint: string, sslEnabled: boolean): string => {
|
||||
const hasProtocol = /^https?:\/\//.test(endpoint)
|
||||
if (!hasProtocol) {
|
||||
return `${sslEnabled ? 'https' : 'http'}://${endpoint}`
|
||||
}
|
||||
return sslEnabled ? endpoint.replace(/^http:\/\//, 'https://') : endpoint.replace(/^https:\/\//, 'http://')
|
||||
}
|
||||
|
||||
export const formatHttpProxy = (
|
||||
proxy: string | undefined,
|
||||
type: 'object' | 'string'
|
||||
): IHTTPProxy | undefined | string => {
|
||||
if (!proxy) return undefined
|
||||
if (/^https?:\/\//.test(proxy)) {
|
||||
const { protocol, hostname, port } = new URL(proxy)
|
||||
return type === 'string'
|
||||
? `${protocol}//${hostname}:${port}`
|
||||
: {
|
||||
host: hostname,
|
||||
port: Number(port),
|
||||
protocol: protocol.slice(0, -1)
|
||||
}
|
||||
}
|
||||
const [host, port] = proxy.split(':')
|
||||
return type === 'string'
|
||||
? `http://${host}:${port}`
|
||||
: {
|
||||
host,
|
||||
port: port ? Number(port) : 80,
|
||||
protocol: 'http'
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeFilePath (filePath: string) {
|
||||
return filePath.replace(/\\/g, '/').split('/').map(encodeURIComponent).join('/')
|
||||
}
|
||||
|
||||
export const trimPath = (path: string) => path.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/')
|
||||
import path from 'node:path'
|
||||
|
||||
import db from '@core/datastore'
|
||||
import logger from '@core/picgo/logger'
|
||||
import axios from 'axios'
|
||||
import { clipboard, Notification, Tray } from 'electron'
|
||||
import FormData from 'form-data'
|
||||
import fs from 'fs-extra'
|
||||
import { isReactive, isRef, toRaw, unref } from 'vue'
|
||||
|
||||
import type { IHTTPProxy, IPrivateShowNotificationOption, IStringKeyMap } from '#/types/types'
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IShortUrlServer } from '~/utils/enum'
|
||||
|
||||
/**
|
||||
* get raw data from reactive or ref
|
||||
*/
|
||||
export const getRawData = (args: any): any => {
|
||||
if (isRef(args)) return unref(args)
|
||||
if (isReactive(args)) return toRaw(args)
|
||||
if (Array.isArray(args)) return args.map(getRawData)
|
||||
if (typeof args === 'object' && args !== null) {
|
||||
const data = {} as Record<string, any>
|
||||
for (const key in args) {
|
||||
data[key] = getRawData(args[key])
|
||||
}
|
||||
return data
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
const getExtension = (fileName: string) => path.extname(fileName).slice(1)
|
||||
|
||||
export const isImage = (fileName: string) =>
|
||||
['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico', 'svg', 'avif'].includes(getExtension(fileName))
|
||||
|
||||
export let tray: Tray
|
||||
|
||||
export const setTray = (t: Tray) => {
|
||||
tray = t
|
||||
}
|
||||
|
||||
export const getTray = () => tray
|
||||
|
||||
export function setTrayToolTip(title: string): void {
|
||||
if (tray) {
|
||||
tray.setToolTip(title)
|
||||
}
|
||||
}
|
||||
|
||||
export const handleCopyUrl = (str: string): void => {
|
||||
if (db.get(configPaths.settings.autoCopy) !== false) {
|
||||
clipboard.writeText(str)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show notification
|
||||
* @param options
|
||||
*/
|
||||
export const showNotification = (
|
||||
options: IPrivateShowNotificationOption = {
|
||||
title: '',
|
||||
body: '',
|
||||
clickToCopy: false,
|
||||
copyContent: '',
|
||||
clickFn: () => {}
|
||||
}
|
||||
) => {
|
||||
const notification = new Notification({
|
||||
title: options.title,
|
||||
body: options.body
|
||||
})
|
||||
const handleClick = () => {
|
||||
if (options.clickToCopy) {
|
||||
clipboard.writeText(options.copyContent || options.body)
|
||||
}
|
||||
if (options.clickFn) {
|
||||
options.clickFn()
|
||||
}
|
||||
}
|
||||
notification.once('click', handleClick)
|
||||
notification.once('close', () => {
|
||||
notification.removeListener('click', handleClick)
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS public.file-url will get encoded file path,
|
||||
* so we need to decode it
|
||||
*/
|
||||
export const ensureFilePath = (filePath: string, prefix = 'file://'): string => {
|
||||
filePath = filePath.replace(prefix, '')
|
||||
if (fs.existsSync(filePath)) {
|
||||
return `${prefix}${filePath}`
|
||||
}
|
||||
filePath = decodeURIComponent(filePath)
|
||||
if (fs.existsSync(filePath)) {
|
||||
return `${prefix}${filePath}`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* for builtin clipboard to get image path from clipboard
|
||||
* @returns
|
||||
*/
|
||||
export const getClipboardFilePath = (): string => {
|
||||
// TODO: linux support
|
||||
const img = clipboard.readImage()
|
||||
const platform = process.platform
|
||||
|
||||
if (!img.isEmpty() && platform === 'darwin') {
|
||||
let imgPath = clipboard.read('public.file-url') // will get file://xxx/xxx
|
||||
imgPath = ensureFilePath(imgPath)
|
||||
return imgPath ? imgPath.replace('file://', '') : ''
|
||||
}
|
||||
|
||||
if (img.isEmpty() && platform === 'win32') {
|
||||
const imgPath = clipboard
|
||||
.readBuffer('FileNameW')
|
||||
?.toString('ucs2')
|
||||
?.replace(RegExp(String.fromCharCode(0), 'g'), '')
|
||||
return imgPath || ''
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
const c1nApi = 'https://c1n.cn/link/short'
|
||||
|
||||
const createC1NShortUrl = async (url: string) => {
|
||||
const c1nToken = db.get(configPaths.settings.c1nToken) || ''
|
||||
if (!c1nToken) {
|
||||
logger.warn('c1n token is not set')
|
||||
return url
|
||||
}
|
||||
try {
|
||||
const form = new FormData()
|
||||
form.append('url', url)
|
||||
const res = await axios.post(c1nApi, form, {
|
||||
headers: {
|
||||
token: c1nToken
|
||||
}
|
||||
})
|
||||
if (res.status >= 200 && res.status < 300 && res.data?.code === 0) {
|
||||
return res.data.data
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
const createYOURLSShortLink = async (url: string) => {
|
||||
let domain = db.get(configPaths.settings.yourlsDomain) || ''
|
||||
const signature = db.get(configPaths.settings.yourlsSignature) || ''
|
||||
|
||||
if (!domain || !signature) {
|
||||
logger.warn('Yourls server or signature is not set')
|
||||
return url
|
||||
}
|
||||
if (!/^https?:\/\//.test(domain)) {
|
||||
domain = `http://${domain}`
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
signature,
|
||||
action: 'shorturl',
|
||||
format: 'json',
|
||||
url
|
||||
})
|
||||
try {
|
||||
const res = await axios.get(`${domain}/yourls-api.php?${params.toString()}`)
|
||||
if (res.data?.shorturl) {
|
||||
return res.data.shorturl
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.response?.data?.message?.includes('already exists in database')) {
|
||||
return e.response.data.shorturl
|
||||
}
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
const createShortUrlForCFWorker = async (url: string) => {
|
||||
let cfWorkerHost = db.get(configPaths.settings.cfWorkerHost) || ''
|
||||
cfWorkerHost = cfWorkerHost.replace(/\/$/, '')
|
||||
if (!cfWorkerHost) {
|
||||
logger.warn('CF Worker host is not set')
|
||||
return url
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.post(cfWorkerHost, { url })
|
||||
if (res.data?.status === 200 && res.data?.key?.startsWith('/')) {
|
||||
return `${cfWorkerHost}${res.data.key}`
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
const createShortUrlFromSink = async (url: string) => {
|
||||
let sinkDomain = db.get(configPaths.settings.sinkDomain) || ''
|
||||
const sinkToken = db.get(configPaths.settings.sinkToken) || ''
|
||||
if (!sinkDomain || !sinkToken) {
|
||||
logger.warn('Sink domain or token is not set')
|
||||
return url
|
||||
}
|
||||
if (!/^https?:\/\//.test(sinkDomain)) {
|
||||
sinkDomain = `http://${sinkDomain}`
|
||||
}
|
||||
if (sinkDomain.endsWith('/')) {
|
||||
sinkDomain = sinkDomain.slice(0, -1)
|
||||
}
|
||||
try {
|
||||
const res = await axios.post(
|
||||
`${sinkDomain}/api/link/create`,
|
||||
{ url },
|
||||
{ headers: { Authorization: `Bearer ${sinkToken}` } }
|
||||
)
|
||||
if (res.data?.link?.slug) {
|
||||
return `${sinkDomain}/${res.data.link.slug}`
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
export const generateShortUrl = async (url: string) => {
|
||||
const server = db.get(configPaths.settings.shortUrlServer) || IShortUrlServer.C1N
|
||||
switch (server) {
|
||||
case IShortUrlServer.C1N:
|
||||
return createC1NShortUrl(url)
|
||||
case IShortUrlServer.YOURLS:
|
||||
return createYOURLSShortLink(url)
|
||||
case IShortUrlServer.CFWORKER:
|
||||
return createShortUrlForCFWorker(url)
|
||||
case IShortUrlServer.SINK:
|
||||
return createShortUrlFromSink(url)
|
||||
default:
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
export const isUrl = (url: string): boolean => {
|
||||
try {
|
||||
return Boolean(new URL(url))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const isUrlEncode = (url: string): boolean => {
|
||||
url = url || ''
|
||||
try {
|
||||
return url !== decodeURI(url)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const handleUrlEncode = (url: string): string => (isUrlEncode(url) ? url : encodeURI(url))
|
||||
|
||||
export const handleUrlEncodeWithSetting = (url: string) =>
|
||||
db.get(configPaths.settings.encodeOutputURL) ? handleUrlEncode(url) : url
|
||||
|
||||
export const handleStreamlinePluginName = (name: string) => name.replace(/(@[^/]+\/)?picgo-plugin-/, '')
|
||||
export const simpleClone = (obj: any) => JSON.parse(JSON.stringify(obj))
|
||||
export const enforceNumber = (num: number | string) => (isNaN(+num) ? 0 : +num)
|
||||
|
||||
export const trimValues = <T extends IStringKeyMap>(
|
||||
obj: T
|
||||
): { [K in keyof T]: T[K] extends string ? string : T[K] } => {
|
||||
return Object.fromEntries(
|
||||
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] }
|
||||
}
|
||||
|
||||
export const formatEndpoint = (endpoint: string, sslEnabled: boolean): string => {
|
||||
const hasProtocol = /^https?:\/\//.test(endpoint)
|
||||
if (!hasProtocol) {
|
||||
return `${sslEnabled ? 'https' : 'http'}://${endpoint}`
|
||||
}
|
||||
return sslEnabled ? endpoint.replace(/^http:\/\//, 'https://') : endpoint.replace(/^https:\/\//, 'http://')
|
||||
}
|
||||
|
||||
export const formatHttpProxy = (
|
||||
proxy: string | undefined,
|
||||
type: 'object' | 'string'
|
||||
): IHTTPProxy | undefined | string => {
|
||||
if (!proxy) return undefined
|
||||
if (/^https?:\/\//.test(proxy)) {
|
||||
const { protocol, hostname, port } = new URL(proxy)
|
||||
return type === 'string'
|
||||
? `${protocol}//${hostname}:${port}`
|
||||
: {
|
||||
host: hostname,
|
||||
port: Number(port),
|
||||
protocol: protocol.slice(0, -1)
|
||||
}
|
||||
}
|
||||
const [host, port] = proxy.split(':')
|
||||
return type === 'string'
|
||||
? `http://${host}:${port}`
|
||||
: {
|
||||
host,
|
||||
port: port ? Number(port) : 80,
|
||||
protocol: 'http'
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeFilePath(filePath: string) {
|
||||
return filePath.replace(/\\/g, '/').split('/').map(encodeURIComponent).join('/')
|
||||
}
|
||||
|
||||
export const trimPath = (path: string) => path.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/')
|
||||
|
||||
@@ -1,188 +1,206 @@
|
||||
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'
|
||||
|
||||
export type manualPageOpenType = 'window' | 'browser'
|
||||
|
||||
interface IPicGoPlugins {
|
||||
[key: `picgo-plugin-${string}`]: boolean
|
||||
}
|
||||
|
||||
export interface IConfigStruct {
|
||||
picBed: {
|
||||
uploader: string
|
||||
current?: string
|
||||
smms?: ISMMSConfig
|
||||
qiniu?: IQiniuConfig
|
||||
upyun?: IUpYunConfig
|
||||
tcyun?: ITcYunConfig
|
||||
github?: IGitHubConfig
|
||||
aliyun?: IAliYunConfig
|
||||
imgur?: IImgurConfig
|
||||
webdavplist?: IWebdavPlistConfig
|
||||
local?: ILocalConfig
|
||||
sftpplist?: ISftpPlistConfig
|
||||
lskyplist?: ILskyConfig
|
||||
'aws-s3-plist': IAwsS3PListUserConfig
|
||||
proxy?: string
|
||||
transformer?: string
|
||||
list: IPicBedType[]
|
||||
[others: string]: any
|
||||
}
|
||||
settings: {
|
||||
shortKey: {
|
||||
[key: string]: IShortKeyConfig
|
||||
}
|
||||
logLevel: string[]
|
||||
logPath: string
|
||||
logFileSizeLimit: number
|
||||
isAutoListenClipboard: boolean
|
||||
isListeningClipboard: boolean
|
||||
showUpdateTip: boolean
|
||||
miniWindowPosition: [number, number]
|
||||
miniWindowOntop: boolean
|
||||
mainWindowWidth: number
|
||||
mainWindowHeight: number
|
||||
isHideDock: boolean
|
||||
autoCloseMiniWindow: boolean
|
||||
autoCloseMainWindow: boolean
|
||||
isCustomMiniIcon: boolean
|
||||
customMiniIcon: string
|
||||
startMode: string
|
||||
autoRename: boolean
|
||||
deleteCloudFile: boolean
|
||||
server: IServerConfig
|
||||
serverKey: string
|
||||
pasteStyle: string
|
||||
aesPassword: string
|
||||
rename: boolean
|
||||
sync: ISyncConfig
|
||||
tempDirPath: string
|
||||
language: string
|
||||
customLink: string
|
||||
manualPageOpen: manualPageOpenType
|
||||
encodeOutputURL: boolean
|
||||
useShortUrl: boolean
|
||||
shortUrlServer: string
|
||||
c1nToken: string
|
||||
cfWorkerHost: string
|
||||
yourlsDomain: string
|
||||
yourlsSignature: string
|
||||
sinkDomain: string
|
||||
sinkToken: string
|
||||
isSilentNotice: boolean
|
||||
proxy: string
|
||||
registry: string
|
||||
autoCopy: boolean
|
||||
enableWebServer: boolean
|
||||
webServerHost: string
|
||||
webServerPort: number
|
||||
webServerPath: string
|
||||
deleteLocalFile: boolean
|
||||
uploadResultNotification: boolean
|
||||
uploadNotification: boolean
|
||||
useBuiltinClipboard: boolean
|
||||
autoStart: boolean
|
||||
autoImport: boolean
|
||||
autoImportPicBed: string[]
|
||||
}
|
||||
needReload: boolean
|
||||
picgoPlugins: IPicGoPlugins
|
||||
uploader: IUploaderConfig
|
||||
buildIn: {
|
||||
compress: IBuildInCompressOptions
|
||||
watermark: IBuildInWaterMarkOptions
|
||||
rename: {
|
||||
enable: boolean
|
||||
format: string
|
||||
}
|
||||
skipProcess: {
|
||||
skipProcessExtList: string
|
||||
}
|
||||
}
|
||||
debug: boolean
|
||||
PICGO_ENV: string
|
||||
}
|
||||
|
||||
export const configPaths = {
|
||||
picBed: {
|
||||
current: 'picBed.current',
|
||||
uploader: 'picBed.uploader',
|
||||
secondUploader: 'picBed.secondUploader',
|
||||
secondUploaderId: 'picBed.secondUploaderId',
|
||||
secondUploaderConfig: 'picBed.secondUploaderConfig',
|
||||
proxy: 'picBed.proxy',
|
||||
transformer: 'picBed.transformer',
|
||||
list: 'picBed.list'
|
||||
},
|
||||
settings: {
|
||||
shortKey: {
|
||||
_path: 'settings.shortKey',
|
||||
'picgo:upload': 'settings.shortKey[picgo:upload]'
|
||||
},
|
||||
logLevel: 'settings.logLevel',
|
||||
logPath: 'settings.logPath',
|
||||
logFileSizeLimit: 'settings.logFileSizeLimit',
|
||||
isAutoListenClipboard: 'settings.isAutoListenClipboard',
|
||||
isListeningClipboard: 'settings.isListeningClipboard',
|
||||
showUpdateTip: 'settings.showUpdateTip',
|
||||
miniWindowPosition: 'settings.miniWindowPosition',
|
||||
miniWindowOntop: 'settings.miniWindowOntop',
|
||||
isHideDock: 'settings.isHideDock',
|
||||
mainWindowWidth: 'settings.mainWindowWidth',
|
||||
mainWindowHeight: 'settings.mainWindowHeight',
|
||||
autoCloseMiniWindow: 'settings.autoCloseMiniWindow',
|
||||
autoCloseMainWindow: 'settings.autoCloseMainWindow',
|
||||
isCustomMiniIcon: 'settings.isCustomMiniIcon',
|
||||
customMiniIcon: 'settings.customMiniIcon',
|
||||
startMode: 'settings.startMode',
|
||||
autoRename: 'settings.autoRename',
|
||||
deleteCloudFile: 'settings.deleteCloudFile',
|
||||
server: 'settings.server',
|
||||
serverKey: 'settings.serverKey',
|
||||
pasteStyle: 'settings.pasteStyle',
|
||||
aesPassword: 'settings.aesPassword',
|
||||
rename: 'settings.rename',
|
||||
sync: 'settings.sync',
|
||||
tempDirPath: 'settings.tempDirPath',
|
||||
language: 'settings.language',
|
||||
customLink: 'settings.customLink',
|
||||
manualPageOpen: 'settings.manualPageOpen',
|
||||
encodeOutputURL: 'settings.encodeOutputURL',
|
||||
useShortUrl: 'settings.useShortUrl',
|
||||
shortUrlServer: 'settings.shortUrlServer',
|
||||
c1nToken: 'settings.c1nToken',
|
||||
cfWorkerHost: 'settings.cfWorkerHost',
|
||||
yourlsDomain: 'settings.yourlsDomain',
|
||||
yourlsSignature: 'settings.yourlsSignature',
|
||||
sinkDomain: 'settings.sinkDomain',
|
||||
sinkToken: 'settings.sinkToken',
|
||||
isSilentNotice: 'settings.isSilentNotice',
|
||||
proxy: 'settings.proxy',
|
||||
registry: 'settings.registry',
|
||||
autoCopy: 'settings.autoCopy',
|
||||
enableWebServer: 'settings.enableWebServer',
|
||||
webServerHost: 'settings.webServerHost',
|
||||
webServerPort: 'settings.webServerPort',
|
||||
webServerPath: 'settings.webServerPath',
|
||||
deleteLocalFile: 'settings.deleteLocalFile',
|
||||
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'
|
||||
}
|
||||
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'
|
||||
|
||||
export type manualPageOpenType = 'window' | 'browser'
|
||||
|
||||
interface IPicGoPlugins {
|
||||
[key: `picgo-plugin-${string}`]: boolean
|
||||
}
|
||||
|
||||
export interface IConfigStruct {
|
||||
picBed: {
|
||||
uploader: string
|
||||
current?: string
|
||||
smms?: ISMMSConfig
|
||||
qiniu?: IQiniuConfig
|
||||
upyun?: IUpYunConfig
|
||||
tcyun?: ITcYunConfig
|
||||
github?: IGitHubConfig
|
||||
aliyun?: IAliYunConfig
|
||||
imgur?: IImgurConfig
|
||||
webdavplist?: IWebdavPlistConfig
|
||||
local?: ILocalConfig
|
||||
sftpplist?: ISftpPlistConfig
|
||||
lskyplist?: ILskyConfig
|
||||
'aws-s3-plist': IAwsS3PListUserConfig
|
||||
proxy?: string
|
||||
transformer?: string
|
||||
list: IPicBedType[]
|
||||
[others: string]: any
|
||||
}
|
||||
settings: {
|
||||
shortKey: {
|
||||
[key: string]: IShortKeyConfig
|
||||
}
|
||||
logLevel: string[]
|
||||
logPath: string
|
||||
logFileSizeLimit: number
|
||||
isAutoListenClipboard: boolean
|
||||
isListeningClipboard: boolean
|
||||
showUpdateTip: boolean
|
||||
miniWindowPosition: [number, number]
|
||||
miniWindowOntop: boolean
|
||||
mainWindowWidth: number
|
||||
mainWindowHeight: number
|
||||
isHideDock: boolean
|
||||
autoCloseMiniWindow: boolean
|
||||
autoCloseMainWindow: boolean
|
||||
isCustomMiniIcon: boolean
|
||||
customMiniIcon: string
|
||||
startMode: string
|
||||
autoRename: boolean
|
||||
deleteCloudFile: boolean
|
||||
server: IServerConfig
|
||||
serverKey: string
|
||||
pasteStyle: string
|
||||
aesPassword: string
|
||||
rename: boolean
|
||||
sync: ISyncConfig
|
||||
tempDirPath: string
|
||||
language: string
|
||||
customLink: string
|
||||
manualPageOpen: manualPageOpenType
|
||||
encodeOutputURL: boolean
|
||||
useShortUrl: boolean
|
||||
shortUrlServer: string
|
||||
c1nToken: string
|
||||
cfWorkerHost: string
|
||||
yourlsDomain: string
|
||||
yourlsSignature: string
|
||||
sinkDomain: string
|
||||
sinkToken: string
|
||||
isSilentNotice: boolean
|
||||
proxy: string
|
||||
registry: string
|
||||
autoCopy: boolean
|
||||
enableWebServer: boolean
|
||||
webServerHost: string
|
||||
webServerPort: number
|
||||
webServerPath: string
|
||||
deleteLocalFile: boolean
|
||||
uploadResultNotification: boolean
|
||||
uploadNotification: boolean
|
||||
useBuiltinClipboard: boolean
|
||||
autoStart: boolean
|
||||
autoImport: boolean
|
||||
autoImportPicBed: string[]
|
||||
}
|
||||
needReload: boolean
|
||||
picgoPlugins: IPicGoPlugins
|
||||
uploader: IUploaderConfig
|
||||
buildIn: {
|
||||
compress: IBuildInCompressOptions
|
||||
watermark: IBuildInWaterMarkOptions
|
||||
rename: {
|
||||
enable: boolean
|
||||
format: string
|
||||
}
|
||||
skipProcess: {
|
||||
skipProcessExtList: string
|
||||
}
|
||||
}
|
||||
debug: boolean
|
||||
PICGO_ENV: string
|
||||
}
|
||||
|
||||
export const configPaths = {
|
||||
picBed: {
|
||||
current: 'picBed.current',
|
||||
uploader: 'picBed.uploader',
|
||||
secondUploader: 'picBed.secondUploader',
|
||||
secondUploaderId: 'picBed.secondUploaderId',
|
||||
secondUploaderConfig: 'picBed.secondUploaderConfig',
|
||||
proxy: 'picBed.proxy',
|
||||
transformer: 'picBed.transformer',
|
||||
list: 'picBed.list'
|
||||
},
|
||||
settings: {
|
||||
shortKey: {
|
||||
_path: 'settings.shortKey',
|
||||
'picgo:upload': 'settings.shortKey[picgo:upload]'
|
||||
},
|
||||
logLevel: 'settings.logLevel',
|
||||
logPath: 'settings.logPath',
|
||||
logFileSizeLimit: 'settings.logFileSizeLimit',
|
||||
isAutoListenClipboard: 'settings.isAutoListenClipboard',
|
||||
isListeningClipboard: 'settings.isListeningClipboard',
|
||||
showUpdateTip: 'settings.showUpdateTip',
|
||||
miniWindowPosition: 'settings.miniWindowPosition',
|
||||
miniWindowOntop: 'settings.miniWindowOntop',
|
||||
isHideDock: 'settings.isHideDock',
|
||||
mainWindowWidth: 'settings.mainWindowWidth',
|
||||
mainWindowHeight: 'settings.mainWindowHeight',
|
||||
autoCloseMiniWindow: 'settings.autoCloseMiniWindow',
|
||||
autoCloseMainWindow: 'settings.autoCloseMainWindow',
|
||||
isCustomMiniIcon: 'settings.isCustomMiniIcon',
|
||||
customMiniIcon: 'settings.customMiniIcon',
|
||||
startMode: 'settings.startMode',
|
||||
autoRename: 'settings.autoRename',
|
||||
deleteCloudFile: 'settings.deleteCloudFile',
|
||||
server: 'settings.server',
|
||||
serverKey: 'settings.serverKey',
|
||||
pasteStyle: 'settings.pasteStyle',
|
||||
aesPassword: 'settings.aesPassword',
|
||||
rename: 'settings.rename',
|
||||
sync: 'settings.sync',
|
||||
tempDirPath: 'settings.tempDirPath',
|
||||
language: 'settings.language',
|
||||
customLink: 'settings.customLink',
|
||||
manualPageOpen: 'settings.manualPageOpen',
|
||||
encodeOutputURL: 'settings.encodeOutputURL',
|
||||
useShortUrl: 'settings.useShortUrl',
|
||||
shortUrlServer: 'settings.shortUrlServer',
|
||||
c1nToken: 'settings.c1nToken',
|
||||
cfWorkerHost: 'settings.cfWorkerHost',
|
||||
yourlsDomain: 'settings.yourlsDomain',
|
||||
yourlsSignature: 'settings.yourlsSignature',
|
||||
sinkDomain: 'settings.sinkDomain',
|
||||
sinkToken: 'settings.sinkToken',
|
||||
isSilentNotice: 'settings.isSilentNotice',
|
||||
proxy: 'settings.proxy',
|
||||
registry: 'settings.registry',
|
||||
autoCopy: 'settings.autoCopy',
|
||||
enableWebServer: 'settings.enableWebServer',
|
||||
webServerHost: 'settings.webServerHost',
|
||||
webServerPort: 'settings.webServerPort',
|
||||
webServerPath: 'settings.webServerPath',
|
||||
deleteLocalFile: 'settings.deleteLocalFile',
|
||||
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'
|
||||
}
|
||||
|
||||
@@ -1,98 +1,98 @@
|
||||
import crypto from 'node:crypto'
|
||||
import http, { AgentOptions } from 'node:http'
|
||||
import https from 'node:https'
|
||||
import path from 'node:path'
|
||||
import querystring from 'node:querystring'
|
||||
|
||||
import type { S3ClientConfig } from '@aws-sdk/client-s3'
|
||||
import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { NodeHttpHandler } from '@smithy/node-http-handler'
|
||||
import axios from 'axios'
|
||||
import type { ISftpPlistConfig } from 'piclist'
|
||||
|
||||
import type { IObj, IStringKeyMap } from '#/types/types'
|
||||
import { getAgent } from '~/manage/utils/common'
|
||||
import SSHClient from '~/utils/sshClient'
|
||||
|
||||
interface DogecloudTokenFull {
|
||||
Credentials: {
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
sessionToken: string
|
||||
}
|
||||
ExpiredAt: number
|
||||
Buckets: {
|
||||
name: string
|
||||
s3Bucket: string
|
||||
s3Endpoint: string
|
||||
}[]
|
||||
}
|
||||
|
||||
const dogeRegionMap: IStringKeyMap = {
|
||||
'ap-shanghai': '0',
|
||||
'ap-beijing': '1',
|
||||
'ap-guangzhou': '2',
|
||||
'ap-chengdu': '3'
|
||||
}
|
||||
|
||||
async function dogecloudApi (
|
||||
apiPath: string,
|
||||
data = {},
|
||||
jsonMode: boolean = false,
|
||||
accessKey: string,
|
||||
secretKey: string
|
||||
) {
|
||||
const body = jsonMode ? JSON.stringify(data) : querystring.encode(data)
|
||||
const sign = crypto
|
||||
.createHmac('sha1', secretKey)
|
||||
.update(Buffer.from(apiPath + '\n' + body, 'utf8'))
|
||||
.digest('hex')
|
||||
const authorization = `TOKEN ${accessKey}:${sign}`
|
||||
try {
|
||||
const res = await axios.request({
|
||||
url: `https://api.dogecloud.com${apiPath}`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
'Content-Type': jsonMode ? 'application/json' : 'application/x-www-form-urlencoded',
|
||||
Authorization: authorization
|
||||
}
|
||||
})
|
||||
if (res.data.code !== 200) {
|
||||
throw new Error('API Error')
|
||||
}
|
||||
return res.data.data
|
||||
} catch (err: any) {
|
||||
throw new Error('API Error')
|
||||
}
|
||||
}
|
||||
|
||||
async function getDogeToken (accessKey: string, secretKey: string): Promise<IObj | DogecloudTokenFull> {
|
||||
try {
|
||||
const data = await dogecloudApi(
|
||||
'/auth/tmp_token.json',
|
||||
{
|
||||
channel: 'OSS_FULL',
|
||||
scopes: ['*']
|
||||
},
|
||||
true,
|
||||
accessKey,
|
||||
secretKey
|
||||
)
|
||||
return data
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode: boolean = false) {
|
||||
try {
|
||||
const {
|
||||
url: rawUrl,
|
||||
type,
|
||||
import crypto from 'node:crypto'
|
||||
import http, { AgentOptions } from 'node:http'
|
||||
import https from 'node:https'
|
||||
import path from 'node:path'
|
||||
import querystring from 'node:querystring'
|
||||
|
||||
import type { S3ClientConfig } from '@aws-sdk/client-s3'
|
||||
import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { NodeHttpHandler } from '@smithy/node-http-handler'
|
||||
import axios from 'axios'
|
||||
import type { ISftpPlistConfig } from 'piclist'
|
||||
|
||||
import type { IObj, IStringKeyMap } from '#/types/types'
|
||||
import { getAgent } from '~/manage/utils/common'
|
||||
import SSHClient from '~/utils/sshClient'
|
||||
|
||||
interface DogecloudTokenFull {
|
||||
Credentials: {
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
sessionToken: string
|
||||
}
|
||||
ExpiredAt: number
|
||||
Buckets: {
|
||||
name: string
|
||||
s3Bucket: string
|
||||
s3Endpoint: string
|
||||
}[]
|
||||
}
|
||||
|
||||
const dogeRegionMap: IStringKeyMap = {
|
||||
'ap-shanghai': '0',
|
||||
'ap-beijing': '1',
|
||||
'ap-guangzhou': '2',
|
||||
'ap-chengdu': '3'
|
||||
}
|
||||
|
||||
async function dogecloudApi(
|
||||
apiPath: string,
|
||||
data = {},
|
||||
jsonMode: boolean = false,
|
||||
accessKey: string,
|
||||
secretKey: string
|
||||
) {
|
||||
const body = jsonMode ? JSON.stringify(data) : querystring.encode(data)
|
||||
const sign = crypto
|
||||
.createHmac('sha1', secretKey)
|
||||
.update(Buffer.from(apiPath + '\n' + body, 'utf8'))
|
||||
.digest('hex')
|
||||
const authorization = `TOKEN ${accessKey}:${sign}`
|
||||
try {
|
||||
const res = await axios.request({
|
||||
url: `https://api.dogecloud.com${apiPath}`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
'Content-Type': jsonMode ? 'application/json' : 'application/x-www-form-urlencoded',
|
||||
Authorization: authorization
|
||||
}
|
||||
})
|
||||
if (res.data.code !== 200) {
|
||||
throw new Error('API Error')
|
||||
}
|
||||
return res.data.data
|
||||
} catch (err: any) {
|
||||
throw new Error('API Error')
|
||||
}
|
||||
}
|
||||
|
||||
async function getDogeToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudTokenFull> {
|
||||
try {
|
||||
const data = await dogecloudApi(
|
||||
'/auth/tmp_token.json',
|
||||
{
|
||||
channel: 'OSS_FULL',
|
||||
scopes: ['*']
|
||||
},
|
||||
true,
|
||||
accessKey,
|
||||
secretKey
|
||||
)
|
||||
return data
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFileFromS3InMain(configMap: IStringKeyMap, dogeMode: boolean = false) {
|
||||
try {
|
||||
const {
|
||||
url: rawUrl,
|
||||
type,
|
||||
config: {
|
||||
accessKeyID,
|
||||
secretAccessKey,
|
||||
@@ -103,14 +103,14 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
|
||||
proxy,
|
||||
urlPrefix
|
||||
}
|
||||
} = configMap
|
||||
let {
|
||||
imgUrl,
|
||||
config: { region }
|
||||
} = configMap
|
||||
if (type === 'aws-s3' || type === 'aws-s3-plist') {
|
||||
imgUrl = rawUrl || imgUrl || ''
|
||||
}
|
||||
} = configMap
|
||||
let {
|
||||
imgUrl,
|
||||
config: { region }
|
||||
} = configMap
|
||||
if (type === 'aws-s3' || type === 'aws-s3-plist') {
|
||||
imgUrl = rawUrl || imgUrl || ''
|
||||
}
|
||||
let fileKey
|
||||
if (urlPrefix && imgUrl.startsWith(urlPrefix)) {
|
||||
const urlPrefixObj = new URL(urlPrefix)
|
||||
@@ -126,159 +126,159 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
|
||||
if (pathStyleAccess) {
|
||||
fileKey = fileKey.replace(/^[^/]+\//, '')
|
||||
}
|
||||
}
|
||||
const endpointUrl: string | undefined = endpoint
|
||||
? /^https?:\/\//.test(endpoint)
|
||||
? endpoint
|
||||
: `http://${endpoint}`
|
||||
: undefined
|
||||
if (endpointUrl && endpointUrl.includes('cloudflarestorage')) {
|
||||
region = region || 'auto'
|
||||
}
|
||||
const sslEnabled = endpointUrl ? endpointUrl.startsWith('https') : true
|
||||
const agent = getAgent(proxy, sslEnabled)
|
||||
const commonOptions: AgentOptions = {
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 1000,
|
||||
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined
|
||||
}
|
||||
const extraOptions = sslEnabled ? { rejectUnauthorized: !!rejectUnauthorized } : {}
|
||||
const handler = sslEnabled
|
||||
? new NodeHttpHandler({
|
||||
httpsAgent: agent.https
|
||||
? agent.https
|
||||
: new https.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
: new NodeHttpHandler({
|
||||
httpAgent: agent.http
|
||||
? agent.http
|
||||
: new http.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
const s3Options: S3ClientConfig = {
|
||||
credentials: {
|
||||
accessKeyId: accessKeyID,
|
||||
secretAccessKey
|
||||
},
|
||||
endpoint: endpointUrl,
|
||||
tls: sslEnabled,
|
||||
forcePathStyle: pathStyleAccess,
|
||||
region,
|
||||
requestHandler: handler
|
||||
}
|
||||
if (dogeMode) {
|
||||
s3Options.credentials = {
|
||||
accessKeyId: configMap.config.accessKeyID,
|
||||
secretAccessKey: configMap.config.secretAccessKey,
|
||||
sessionToken: configMap.config.sessionToken
|
||||
}
|
||||
}
|
||||
let result: any
|
||||
try {
|
||||
fileKey = decodeURIComponent(fileKey)
|
||||
} catch (err: any) {}
|
||||
try {
|
||||
const client = new S3Client(s3Options)
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: fileKey
|
||||
})
|
||||
result = await client.send(command)
|
||||
} catch (err: any) {
|
||||
s3Options.region = 'us-east-1'
|
||||
const client = new S3Client(s3Options)
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: fileKey
|
||||
})
|
||||
result = await client.send(command)
|
||||
}
|
||||
return result.$metadata.httpStatusCode === 204
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFileFromDogeInMain (configMap: IStringKeyMap) {
|
||||
try {
|
||||
const {
|
||||
config: { bucketName, AccessKey, SecretKey }
|
||||
} = configMap
|
||||
const token = (await getDogeToken(AccessKey, SecretKey)) as DogecloudTokenFull
|
||||
const bucket = token.Buckets?.find(item => item.name === bucketName || item.s3Bucket === bucketName)
|
||||
const newConfigMap = { ...configMap }
|
||||
newConfigMap.config = {
|
||||
...newConfigMap.config,
|
||||
accessKeyID: token.Credentials?.accessKeyId,
|
||||
secretAccessKey: token.Credentials?.secretAccessKey,
|
||||
sessionToken: token.Credentials?.sessionToken,
|
||||
endpoint: bucket?.s3Endpoint,
|
||||
region: dogeRegionMap[bucket?.s3Endpoint?.split('.')[1] || 'ap-shanghai'],
|
||||
bucketName: bucket?.s3Bucket
|
||||
}
|
||||
return await removeFileFromS3InMain(newConfigMap, true)
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function createHuaweiAuthorization (
|
||||
bucketName: string,
|
||||
path: string,
|
||||
fileName: string,
|
||||
accessKey: string,
|
||||
secretKey: string,
|
||||
date: string = new Date().toUTCString()
|
||||
) {
|
||||
const strToSign = `DELETE\n\n\n${date}\n/${bucketName}${path}/${fileName}`
|
||||
const singature = crypto.createHmac('sha1', secretKey).update(strToSign).digest('base64')
|
||||
return `OBS ${accessKey}:${singature}`
|
||||
}
|
||||
|
||||
export async function removeFileFromHuaweiInMain (configMap: IStringKeyMap) {
|
||||
const { fileName, config } = configMap
|
||||
const { accessKeyId, accessKeySecret, bucketName, endpoint } = config
|
||||
let path = config.path || '/'
|
||||
path = `/${path.replace(/^\/+|\/+$/, '')}`
|
||||
path = path === '/' ? '' : path
|
||||
const date = new Date().toUTCString()
|
||||
const authorization = createHuaweiAuthorization(bucketName, path, fileName, accessKeyId, accessKeySecret, date)
|
||||
try {
|
||||
const res = await axios.request({
|
||||
url: `https://${bucketName}.${endpoint}${encodeURI(path)}/${encodeURIComponent(fileName)}`,
|
||||
method: 'DELETE',
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
Host: `${bucketName}.${endpoint}`,
|
||||
Date: date,
|
||||
Authorization: authorization
|
||||
}
|
||||
})
|
||||
return res.status === 204
|
||||
} catch (error: any) {
|
||||
logger.error(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFileFromSFTPInMain (config: ISftpPlistConfig, fileName: string) {
|
||||
try {
|
||||
const client = SSHClient.instance
|
||||
await client.connect(config)
|
||||
const uploadPath = `/${config.uploadPath || ''}/`.replace(/\/+/g, '/')
|
||||
const remote = path.join(uploadPath, fileName)
|
||||
const deleteResult = await client.deleteFileSFTP(config, remote)
|
||||
client.close()
|
||||
return deleteResult
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
const endpointUrl: string | undefined = endpoint
|
||||
? /^https?:\/\//.test(endpoint)
|
||||
? endpoint
|
||||
: `http://${endpoint}`
|
||||
: undefined
|
||||
if (endpointUrl && endpointUrl.includes('cloudflarestorage')) {
|
||||
region = region || 'auto'
|
||||
}
|
||||
const sslEnabled = endpointUrl ? endpointUrl.startsWith('https') : true
|
||||
const agent = getAgent(proxy, sslEnabled)
|
||||
const commonOptions: AgentOptions = {
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 1000,
|
||||
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined
|
||||
}
|
||||
const extraOptions = sslEnabled ? { rejectUnauthorized: !!rejectUnauthorized } : {}
|
||||
const handler = sslEnabled
|
||||
? new NodeHttpHandler({
|
||||
httpsAgent: agent.https
|
||||
? agent.https
|
||||
: new https.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
: new NodeHttpHandler({
|
||||
httpAgent: agent.http
|
||||
? agent.http
|
||||
: new http.Agent({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
})
|
||||
const s3Options: S3ClientConfig = {
|
||||
credentials: {
|
||||
accessKeyId: accessKeyID,
|
||||
secretAccessKey
|
||||
},
|
||||
endpoint: endpointUrl,
|
||||
tls: sslEnabled,
|
||||
forcePathStyle: pathStyleAccess,
|
||||
region,
|
||||
requestHandler: handler
|
||||
}
|
||||
if (dogeMode) {
|
||||
s3Options.credentials = {
|
||||
accessKeyId: configMap.config.accessKeyID,
|
||||
secretAccessKey: configMap.config.secretAccessKey,
|
||||
sessionToken: configMap.config.sessionToken
|
||||
}
|
||||
}
|
||||
let result: any
|
||||
try {
|
||||
fileKey = decodeURIComponent(fileKey)
|
||||
} catch (err: any) {}
|
||||
try {
|
||||
const client = new S3Client(s3Options)
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: fileKey
|
||||
})
|
||||
result = await client.send(command)
|
||||
} catch (err: any) {
|
||||
s3Options.region = 'us-east-1'
|
||||
const client = new S3Client(s3Options)
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: fileKey
|
||||
})
|
||||
result = await client.send(command)
|
||||
}
|
||||
return result.$metadata.httpStatusCode === 204
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFileFromDogeInMain(configMap: IStringKeyMap) {
|
||||
try {
|
||||
const {
|
||||
config: { bucketName, AccessKey, SecretKey }
|
||||
} = configMap
|
||||
const token = (await getDogeToken(AccessKey, SecretKey)) as DogecloudTokenFull
|
||||
const bucket = token.Buckets?.find(item => item.name === bucketName || item.s3Bucket === bucketName)
|
||||
const newConfigMap = { ...configMap }
|
||||
newConfigMap.config = {
|
||||
...newConfigMap.config,
|
||||
accessKeyID: token.Credentials?.accessKeyId,
|
||||
secretAccessKey: token.Credentials?.secretAccessKey,
|
||||
sessionToken: token.Credentials?.sessionToken,
|
||||
endpoint: bucket?.s3Endpoint,
|
||||
region: dogeRegionMap[bucket?.s3Endpoint?.split('.')[1] || 'ap-shanghai'],
|
||||
bucketName: bucket?.s3Bucket
|
||||
}
|
||||
return await removeFileFromS3InMain(newConfigMap, true)
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function createHuaweiAuthorization(
|
||||
bucketName: string,
|
||||
path: string,
|
||||
fileName: string,
|
||||
accessKey: string,
|
||||
secretKey: string,
|
||||
date: string = new Date().toUTCString()
|
||||
) {
|
||||
const strToSign = `DELETE\n\n\n${date}\n/${bucketName}${path}/${fileName}`
|
||||
const singature = crypto.createHmac('sha1', secretKey).update(strToSign).digest('base64')
|
||||
return `OBS ${accessKey}:${singature}`
|
||||
}
|
||||
|
||||
export async function removeFileFromHuaweiInMain(configMap: IStringKeyMap) {
|
||||
const { fileName, config } = configMap
|
||||
const { accessKeyId, accessKeySecret, bucketName, endpoint } = config
|
||||
let path = config.path || '/'
|
||||
path = `/${path.replace(/^\/+|\/+$/, '')}`
|
||||
path = path === '/' ? '' : path
|
||||
const date = new Date().toUTCString()
|
||||
const authorization = createHuaweiAuthorization(bucketName, path, fileName, accessKeyId, accessKeySecret, date)
|
||||
try {
|
||||
const res = await axios.request({
|
||||
url: `https://${bucketName}.${endpoint}${encodeURI(path)}/${encodeURIComponent(fileName)}`,
|
||||
method: 'DELETE',
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
Host: `${bucketName}.${endpoint}`,
|
||||
Date: date,
|
||||
Authorization: authorization
|
||||
}
|
||||
})
|
||||
return res.status === 204
|
||||
} catch (error: any) {
|
||||
logger.error(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFileFromSFTPInMain(config: ISftpPlistConfig, fileName: string) {
|
||||
try {
|
||||
const client = SSHClient.instance
|
||||
await client.connect(config)
|
||||
const uploadPath = `/${config.uploadPath || ''}/`.replace(/\/+/g, '/')
|
||||
const remote = path.join(uploadPath, fileName)
|
||||
const deleteResult = await client.deleteFileSFTP(config, remote)
|
||||
client.close()
|
||||
return deleteResult
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export const deleteLog = (fileName?: string, type?: string, isSuccess = true, msg?: string) => {
|
||||
console.log(`Delete ${fileName} on ${type} ${isSuccess ? 'success' : 'failed'}, message: ${msg || ''}`)
|
||||
}
|
||||
|
||||
export const deleteFailedLog = (fileName: string, type: string, error: any) => {
|
||||
deleteLog(fileName, type, false)
|
||||
console.error(error)
|
||||
}
|
||||
export const deleteLog = (fileName?: string, type?: string, isSuccess = true, msg?: string) => {
|
||||
console.log(`Delete ${fileName} on ${type} ${isSuccess ? 'success' : 'failed'}, message: ${msg || ''}`)
|
||||
}
|
||||
|
||||
export const deleteFailedLog = (fileName: string, type: string, error: any) => {
|
||||
deleteLog(fileName, type, false)
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ const AUTH_KEY_VALUE_RE = /(\w+)=["']?([^'"]{1,10000})["']?/
|
||||
let NC = 0
|
||||
const NC_PAD = '00000000'
|
||||
|
||||
function md5 (text: crypto.BinaryLike) {
|
||||
function md5(text: crypto.BinaryLike) {
|
||||
return crypto.createHash('md5').update(text).digest('hex')
|
||||
}
|
||||
|
||||
export function digestAuthHeader (
|
||||
export function digestAuthHeader(
|
||||
method: string,
|
||||
uri: string,
|
||||
wwwAuthenticate: string,
|
||||
@@ -70,7 +70,7 @@ export function digestAuthHeader (
|
||||
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 {
|
||||
await axios.get(`${host}${uri}`)
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -1,242 +1,242 @@
|
||||
export const ILogType = {
|
||||
success: 'success',
|
||||
info: 'info',
|
||||
warn: 'warn',
|
||||
error: 'error'
|
||||
}
|
||||
|
||||
export const ICOREBuildInEvent = {
|
||||
UPLOAD_PROGRESS: 'uploadProgress',
|
||||
FAILED: 'failed',
|
||||
BEFORE_TRANSFORM: 'beforeTransform',
|
||||
BEFORE_UPLOAD: 'beforeUpload',
|
||||
AFTER_UPLOAD: 'afterUpload',
|
||||
FINISHED: 'finished',
|
||||
INSTALL: 'install',
|
||||
UNINSTALL: 'uninstall',
|
||||
UPDATE: 'update',
|
||||
NOTIFICATION: 'notification',
|
||||
REMOVE: 'remove'
|
||||
}
|
||||
|
||||
export const IPicGoHelperType = {
|
||||
afterUploadPlugins: 'afterUploadPlugins',
|
||||
beforeTransformPlugins: 'beforeTransformPlugins',
|
||||
beforeUploadPlugins: 'beforeUploadPlugins',
|
||||
uploader: 'uploader',
|
||||
transformer: 'transformer'
|
||||
}
|
||||
|
||||
export const IPasteStyle = {
|
||||
MARKDOWN: 'markdown',
|
||||
HTML: 'HTML',
|
||||
URL: 'URL',
|
||||
UBB: 'UBB',
|
||||
CUSTOM: 'Custom'
|
||||
}
|
||||
|
||||
export const IWindowList = {
|
||||
SETTING_WINDOW: 'SETTING_WINDOW',
|
||||
TRAY_WINDOW: 'TRAY_WINDOW',
|
||||
MINI_WINDOW: 'MINI_WINDOW',
|
||||
RENAME_WINDOW: 'RENAME_WINDOW',
|
||||
TOOLBOX_WINDOW: 'TOOLBOX_WINDOW'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeActionType = {
|
||||
OPEN_URL: 'OPEN_URL',
|
||||
SHOW_NOTICE: 'SHOW_NOTICE', // notification
|
||||
SHOW_DIALOG: 'SHOW_DIALOG', // dialog notice
|
||||
COMMON: 'COMMON',
|
||||
VOID: 'VOID', // do nothing
|
||||
SHOW_MESSAGE_BOX: 'SHOW_MESSAGE_BOX'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeTriggerHook = {
|
||||
APP_START: 'APP_START',
|
||||
SETTING_WINDOW_OPEN: 'SETTING_WINDOW_OPEN'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeTriggerCount = {
|
||||
ONCE: 'ONCE', // default
|
||||
ALWAYS: 'ALWAYS'
|
||||
}
|
||||
|
||||
export const IRPCType = {
|
||||
INVOKE: 'INVOKE',
|
||||
SEND: 'SEND'
|
||||
}
|
||||
|
||||
export const IRPCActionType = {
|
||||
// system rpc
|
||||
RELOAD_APP: 'RELOAD_APP',
|
||||
OPEN_URL: 'OPEN_URL',
|
||||
OPEN_FILE: 'OPEN_FILE',
|
||||
HIDE_DOCK: 'HIDE_DOCK',
|
||||
SET_CURRENT_LANGUAGE: 'SET_CURRENT_LANGUAGE',
|
||||
OPEN_WINDOW: 'OPEN_WINDOW',
|
||||
OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW',
|
||||
CLOSE_WINDOW: 'CLOSE_WINDOW',
|
||||
MINIMIZE_WINDOW: 'MINIMIZE_WINDOW',
|
||||
SHOW_MINI_PAGE_MENU: 'SHOW_MINI_PAGE_MENU',
|
||||
SHOW_MAIN_PAGE_MENU: 'SHOW_MAIN_PAGE_MENU',
|
||||
SHOW_UPLOAD_PAGE_MENU: 'SHOW_UPLOAD_PAGE_MENU',
|
||||
SHOW_SECOND_UPLOADER_MENU: 'SHOW_SECOND_UPLOADER_MENU',
|
||||
SHOW_PLUGIN_PAGE_MENU: 'SHOW_PLUGIN_PAGE_MENU',
|
||||
SET_MINI_WINDOW_POS: 'SET_MINI_WINDOW_POS',
|
||||
MINI_WINDOW_ON_TOP: 'MINI_WINDOW_ON_TOP',
|
||||
MAIN_WINDOW_ON_TOP: 'MAIN_WINDOW_ON_TOP',
|
||||
UPDATE_MINI_WINDOW_ICON: 'UPDATE_MINI_WINDOW_ICON',
|
||||
REFRESH_SETTING_WINDOW: 'REFRESH_SETTING_WINDOW',
|
||||
// picbed RPC
|
||||
PICBED_GET_PICBED_CONFIG: 'PICBED_GET_PICBED_CONFIG',
|
||||
PICBED_GET_CONFIG_LIST: 'PICBED_GET_CONFIG_LIST',
|
||||
PICBED_DELETE_CONFIG: 'PICBED_DELETE_CONFIG',
|
||||
UPLOADER_CHANGE_CURRENT: 'UPLOADER_CHANGE_CURRENT',
|
||||
UPLOADER_SELECT: 'UPLOADER_SELECT',
|
||||
UPLOADER_UPDATE_CONFIG: 'UPLOADER_UPDATE_CONFIG',
|
||||
UPLOADER_RESET_CONFIG: 'UPLOADER_RESET_CONFIG',
|
||||
DELETE_ALL_API: 'DELETE_ALL_API',
|
||||
|
||||
// toolbox rpc
|
||||
TOOLBOX_CHECK: 'TOOLBOX_CHECK',
|
||||
TOOLBOX_CHECK_RES: 'TOOLBOX_CHECK_RES',
|
||||
TOOLBOX_CHECK_FIX: 'TOOLBOX_CHECK_FIX',
|
||||
|
||||
// main app setting rpc
|
||||
PICLIST_GET_CONFIG: 'PICLIST_GET_CONFIG',
|
||||
PICLIST_GET_CONFIG_SYNC: 'PICLIST_GET_CONFIG_SYNC',
|
||||
PICLIST_SAVE_CONFIG: 'PICLIST_SAVE_CONFIG',
|
||||
PICLIST_OPEN_FILE: 'PICLIST_OPEN_FILE',
|
||||
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
|
||||
PICLIST_AUTO_START: 'PICLIST_AUTO_START',
|
||||
|
||||
// shortkey setting rpc
|
||||
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',
|
||||
SHORTKEY_BIND_OR_UNBIND: 'SHORTKEY_BIND_OR_UNBIND',
|
||||
SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE: 'SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE',
|
||||
|
||||
// configuration setting rpc
|
||||
CONFIGURE_MIGRATE_FROM_PICGO: 'CONFIGURE_MIGRATE_FROM_PICGO',
|
||||
CONFIGURE_UPLOAD_COMMON_CONFIG: 'CONFIGURE_UPLOAD_COMMON_CONFIG',
|
||||
CONFIGURE_UPLOAD_MANAGE_CONFIG: 'CONFIGURE_UPLOAD_MANAGE_CONFIG',
|
||||
CONFIGURE_UPLOAD_ALL_CONFIG: 'CONFIGURE_UPLOAD_ALL_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_COMMON_CONFIG: 'CONFIGURE_DOWNLOAD_COMMON_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_MANAGE_CONFIG: 'CONFIGURE_DOWNLOAD_MANAGE_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_ALL_CONFIG: 'CONFIGURE_DOWNLOAD_ALL_CONFIG',
|
||||
|
||||
// advanced setting rpc
|
||||
ADVANCED_UPDATE_SERVER: 'ADVANCED_UPDATE_SERVER',
|
||||
ADVANCED_STOP_WEB_SERVER: 'ADVANCED_STOP_WEB_SERVER',
|
||||
ADVANCED_RESTART_WEB_SERVER: 'ADVANCED_RESTART_WEB_SERVER',
|
||||
|
||||
// upload and main page rpc
|
||||
MAIN_GET_PICBED: 'MAIN_GET_PICBED',
|
||||
UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE: 'UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE',
|
||||
UPLOAD_CHOOSED_FILES: 'UPLOAD_CHOOSED_FILES',
|
||||
|
||||
// gallery rpc
|
||||
GALLERY_PASTE_TEXT: 'GALLERY_PASTE_TEXT',
|
||||
GALLERY_REMOVE_FILES: 'GALLERY_REMOVE_FILES',
|
||||
GALLERY_GET_DB: 'GALLERY_GET_DB',
|
||||
GALLERY_GET_BY_ID_DB: 'GALLERY_GET_BY_ID_DB',
|
||||
GALLERY_UPDATE_BY_ID_DB: 'GALLERY_UPDATE_BY_ID_DB',
|
||||
GALLERY_REMOVE_BY_ID_DB: 'GALLERY_REMOVE_BY_ID_DB',
|
||||
GALLERY_INSERT_DB: 'GALLERY_INSERT_DB',
|
||||
GALLERY_INSERT_DB_BATCH: 'GALLERY_INSERT_DB_BATCH',
|
||||
// plugin rpc
|
||||
PLUGIN_GET_LIST: 'PLUGIN_GET_LIST',
|
||||
PLUGIN_INSTALL: 'PLUGIN_INSTALL',
|
||||
PLUGIN_IMPORT_LOCAL: 'PLUGIN_IMPORT_LOCAL',
|
||||
PLUGIN_UPDATE_ALL: 'PLUGIN_UPDATE_ALL',
|
||||
|
||||
// tray rpc
|
||||
TRAY_SET_TOOL_TIP: 'TRAY_SET_TOOL_TIP',
|
||||
TRAY_GET_SHORT_URL: 'TRAY_GET_SHORT_URL',
|
||||
TRAY_UPLOAD_CLIPBOARD_FILES: 'TRAY_UPLOAD_CLIPBOARD_FILES',
|
||||
|
||||
// manage rpc
|
||||
MANAGE_GET_CONFIG: 'MANAGE_GET_CONFIG',
|
||||
MANAGE_SAVE_CONFIG: 'MANAGE_SAVE_CONFIG',
|
||||
MANAGE_REMOVE_CONFIG: 'MANAGE_REMOVE_CONFIG',
|
||||
MANAGE_GET_BUCKET_LIST: 'MANAGE_GET_BUCKET_LIST',
|
||||
MANAGE_GET_BUCKET_LIST_BACKSTAGE: 'MANAGE_GET_BUCKET_LIST_BACKSTAGE',
|
||||
MANAGE_GET_BUCKET_LIST_RECURSIVELY: 'MANAGE_GET_BUCKET_LIST_RECURSIVELY',
|
||||
MANAGE_CREATE_BUCKET: 'MANAGE_CREATE_BUCKET',
|
||||
MANAGE_GET_BUCKET_FILE_LIST: 'MANAGE_GET_BUCKET_FILE_LIST',
|
||||
MANAGE_GET_BUCKET_DOMAIN: 'MANAGE_GET_BUCKET_DOMAIN',
|
||||
MANAGE_SET_BUCKET_ACL_POLICY: 'MANAGE_SET_BUCKET_ACL_POLICY',
|
||||
MANAGE_RENAME_BUCKET_FILE: 'MANAGE_RENAME_BUCKET_FILE',
|
||||
MANAGE_DELETE_BUCKET_FILE: 'MANAGE_DELETE_BUCKET_FILE',
|
||||
MANAGE_DELETE_BUCKET_FOLDER: 'MANAGE_DELETE_BUCKET_FOLDER',
|
||||
MANAGE_GET_PRE_SIGNED_URL: 'MANAGE_GET_PRE_SIGNED_URL',
|
||||
MANAGE_UPLOAD_BUCKET_FILE: 'MANAGE_UPLOAD_BUCKET_FILE',
|
||||
MANAGE_DOWNLOAD_BUCKET_FILE: 'MANAGE_DOWNLOAD_BUCKET_FILE',
|
||||
MANAGE_CREATE_BUCKET_FOLDER: 'MANAGE_CREATE_BUCKET_FOLDER',
|
||||
MANAGE_OPEN_FILE_SELECT_DIALOG: 'MANAGE_OPEN_FILE_SELECT_DIALOG',
|
||||
MANAGE_GET_UPLOAD_TASK_LIST: 'MANAGE_GET_UPLOAD_TASK_LIST',
|
||||
MANAGE_GET_DOWNLOAD_TASK_LIST: 'MANAGE_GET_DOWNLOAD_TASK_LIST',
|
||||
MANAGE_DELETE_UPLOADED_TASK: 'MANAGE_DELETE_UPLOADED_TASK',
|
||||
MANAGE_DELETE_ALL_UPLOADED_TASK: 'MANAGE_DELETE_ALL_UPLOADED_TASK',
|
||||
MANAGE_DELETE_DOWNLOADED_TASK: 'MANAGE_DELETE_DOWNLOADED_TASK',
|
||||
MANAGE_DELETE_ALL_DOWNLOADED_TASK: 'MANAGE_DELETE_ALL_DOWNLOADED_TASK',
|
||||
MANAGE_SELECT_DOWNLOAD_FOLDER: 'MANAGE_SELECT_DOWNLOAD_FOLDER',
|
||||
MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER: 'MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER',
|
||||
MANAGE_OPEN_DOWNLOADED_FOLDER: 'MANAGE_OPEN_DOWNLOADED_FOLDER',
|
||||
MANAGE_OPEN_LOCAL_FILE: 'MANAGE_OPEN_LOCAL_FILE',
|
||||
MANAGE_DOWNLOAD_FILE_FROM_URL: 'MANAGE_DOWNLOAD_FILE_FROM_URL',
|
||||
MANAGE_CONVERT_PATH_TO_BASE64: 'MANAGE_CONVERT_PATH_TO_BASE64'
|
||||
}
|
||||
|
||||
export const IToolboxItemType = {
|
||||
IS_CONFIG_FILE_BROKEN: 'IS_CONFIG_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_PROXY: 'HAS_PROBLEM_WITH_PROXY'
|
||||
}
|
||||
|
||||
export const IToolboxItemCheckStatus = {
|
||||
INIT: 'init',
|
||||
LOADING: 'loading',
|
||||
SUCCESS: 'success',
|
||||
ERROR: 'error'
|
||||
}
|
||||
|
||||
export const ISartMode = {
|
||||
QUIET: 'quiet',
|
||||
MINI: 'mini',
|
||||
MAIN: 'main',
|
||||
NO_TRAY: 'no-tray'
|
||||
}
|
||||
|
||||
export const II18nLanguage = {
|
||||
ZH_CN: 'zh-CN',
|
||||
ZH_TW: 'zh-TW',
|
||||
EN: 'en'
|
||||
}
|
||||
|
||||
export const IShortUrlServer = {
|
||||
C1N: 'c1n',
|
||||
YOURLS: 'yourls',
|
||||
CFWORKER: 'cf_worker',
|
||||
SINK: 'sink'
|
||||
}
|
||||
|
||||
export const commonTaskStatus = {
|
||||
queuing: 'queuing',
|
||||
failed: 'failed',
|
||||
canceled: 'canceled',
|
||||
paused: 'paused'
|
||||
}
|
||||
|
||||
// manage task status
|
||||
|
||||
export const uploadTaskSpecialStatus = {
|
||||
uploading: 'uploading',
|
||||
uploaded: 'uploaded'
|
||||
}
|
||||
|
||||
export const downloadTaskSpecialStatus = {
|
||||
downloading: 'downloading',
|
||||
downloaded: 'downloaded'
|
||||
}
|
||||
export const ILogType = {
|
||||
success: 'success',
|
||||
info: 'info',
|
||||
warn: 'warn',
|
||||
error: 'error'
|
||||
}
|
||||
|
||||
export const ICOREBuildInEvent = {
|
||||
UPLOAD_PROGRESS: 'uploadProgress',
|
||||
FAILED: 'failed',
|
||||
BEFORE_TRANSFORM: 'beforeTransform',
|
||||
BEFORE_UPLOAD: 'beforeUpload',
|
||||
AFTER_UPLOAD: 'afterUpload',
|
||||
FINISHED: 'finished',
|
||||
INSTALL: 'install',
|
||||
UNINSTALL: 'uninstall',
|
||||
UPDATE: 'update',
|
||||
NOTIFICATION: 'notification',
|
||||
REMOVE: 'remove'
|
||||
}
|
||||
|
||||
export const IPicGoHelperType = {
|
||||
afterUploadPlugins: 'afterUploadPlugins',
|
||||
beforeTransformPlugins: 'beforeTransformPlugins',
|
||||
beforeUploadPlugins: 'beforeUploadPlugins',
|
||||
uploader: 'uploader',
|
||||
transformer: 'transformer'
|
||||
}
|
||||
|
||||
export const IPasteStyle = {
|
||||
MARKDOWN: 'markdown',
|
||||
HTML: 'HTML',
|
||||
URL: 'URL',
|
||||
UBB: 'UBB',
|
||||
CUSTOM: 'Custom'
|
||||
}
|
||||
|
||||
export const IWindowList = {
|
||||
SETTING_WINDOW: 'SETTING_WINDOW',
|
||||
TRAY_WINDOW: 'TRAY_WINDOW',
|
||||
MINI_WINDOW: 'MINI_WINDOW',
|
||||
RENAME_WINDOW: 'RENAME_WINDOW',
|
||||
TOOLBOX_WINDOW: 'TOOLBOX_WINDOW'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeActionType = {
|
||||
OPEN_URL: 'OPEN_URL',
|
||||
SHOW_NOTICE: 'SHOW_NOTICE', // notification
|
||||
SHOW_DIALOG: 'SHOW_DIALOG', // dialog notice
|
||||
COMMON: 'COMMON',
|
||||
VOID: 'VOID', // do nothing
|
||||
SHOW_MESSAGE_BOX: 'SHOW_MESSAGE_BOX'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeTriggerHook = {
|
||||
APP_START: 'APP_START',
|
||||
SETTING_WINDOW_OPEN: 'SETTING_WINDOW_OPEN'
|
||||
}
|
||||
|
||||
export const IRemoteNoticeTriggerCount = {
|
||||
ONCE: 'ONCE', // default
|
||||
ALWAYS: 'ALWAYS'
|
||||
}
|
||||
|
||||
export const IRPCType = {
|
||||
INVOKE: 'INVOKE',
|
||||
SEND: 'SEND'
|
||||
}
|
||||
|
||||
export const IRPCActionType = {
|
||||
// system rpc
|
||||
RELOAD_APP: 'RELOAD_APP',
|
||||
OPEN_URL: 'OPEN_URL',
|
||||
OPEN_FILE: 'OPEN_FILE',
|
||||
HIDE_DOCK: 'HIDE_DOCK',
|
||||
SET_CURRENT_LANGUAGE: 'SET_CURRENT_LANGUAGE',
|
||||
OPEN_WINDOW: 'OPEN_WINDOW',
|
||||
OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW',
|
||||
CLOSE_WINDOW: 'CLOSE_WINDOW',
|
||||
MINIMIZE_WINDOW: 'MINIMIZE_WINDOW',
|
||||
SHOW_MINI_PAGE_MENU: 'SHOW_MINI_PAGE_MENU',
|
||||
SHOW_MAIN_PAGE_MENU: 'SHOW_MAIN_PAGE_MENU',
|
||||
SHOW_UPLOAD_PAGE_MENU: 'SHOW_UPLOAD_PAGE_MENU',
|
||||
SHOW_SECOND_UPLOADER_MENU: 'SHOW_SECOND_UPLOADER_MENU',
|
||||
SHOW_PLUGIN_PAGE_MENU: 'SHOW_PLUGIN_PAGE_MENU',
|
||||
SET_MINI_WINDOW_POS: 'SET_MINI_WINDOW_POS',
|
||||
MINI_WINDOW_ON_TOP: 'MINI_WINDOW_ON_TOP',
|
||||
MAIN_WINDOW_ON_TOP: 'MAIN_WINDOW_ON_TOP',
|
||||
UPDATE_MINI_WINDOW_ICON: 'UPDATE_MINI_WINDOW_ICON',
|
||||
REFRESH_SETTING_WINDOW: 'REFRESH_SETTING_WINDOW',
|
||||
// picbed RPC
|
||||
PICBED_GET_PICBED_CONFIG: 'PICBED_GET_PICBED_CONFIG',
|
||||
PICBED_GET_CONFIG_LIST: 'PICBED_GET_CONFIG_LIST',
|
||||
PICBED_DELETE_CONFIG: 'PICBED_DELETE_CONFIG',
|
||||
UPLOADER_CHANGE_CURRENT: 'UPLOADER_CHANGE_CURRENT',
|
||||
UPLOADER_SELECT: 'UPLOADER_SELECT',
|
||||
UPLOADER_UPDATE_CONFIG: 'UPLOADER_UPDATE_CONFIG',
|
||||
UPLOADER_RESET_CONFIG: 'UPLOADER_RESET_CONFIG',
|
||||
DELETE_ALL_API: 'DELETE_ALL_API',
|
||||
|
||||
// toolbox rpc
|
||||
TOOLBOX_CHECK: 'TOOLBOX_CHECK',
|
||||
TOOLBOX_CHECK_RES: 'TOOLBOX_CHECK_RES',
|
||||
TOOLBOX_CHECK_FIX: 'TOOLBOX_CHECK_FIX',
|
||||
|
||||
// main app setting rpc
|
||||
PICLIST_GET_CONFIG: 'PICLIST_GET_CONFIG',
|
||||
PICLIST_GET_CONFIG_SYNC: 'PICLIST_GET_CONFIG_SYNC',
|
||||
PICLIST_SAVE_CONFIG: 'PICLIST_SAVE_CONFIG',
|
||||
PICLIST_OPEN_FILE: 'PICLIST_OPEN_FILE',
|
||||
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
|
||||
PICLIST_AUTO_START: 'PICLIST_AUTO_START',
|
||||
|
||||
// shortkey setting rpc
|
||||
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',
|
||||
SHORTKEY_BIND_OR_UNBIND: 'SHORTKEY_BIND_OR_UNBIND',
|
||||
SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE: 'SHORTKEY_TOGGLE_SHORTKEY_MODIFIED_MODE',
|
||||
|
||||
// configuration setting rpc
|
||||
CONFIGURE_MIGRATE_FROM_PICGO: 'CONFIGURE_MIGRATE_FROM_PICGO',
|
||||
CONFIGURE_UPLOAD_COMMON_CONFIG: 'CONFIGURE_UPLOAD_COMMON_CONFIG',
|
||||
CONFIGURE_UPLOAD_MANAGE_CONFIG: 'CONFIGURE_UPLOAD_MANAGE_CONFIG',
|
||||
CONFIGURE_UPLOAD_ALL_CONFIG: 'CONFIGURE_UPLOAD_ALL_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_COMMON_CONFIG: 'CONFIGURE_DOWNLOAD_COMMON_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_MANAGE_CONFIG: 'CONFIGURE_DOWNLOAD_MANAGE_CONFIG',
|
||||
CONFIGURE_DOWNLOAD_ALL_CONFIG: 'CONFIGURE_DOWNLOAD_ALL_CONFIG',
|
||||
|
||||
// advanced setting rpc
|
||||
ADVANCED_UPDATE_SERVER: 'ADVANCED_UPDATE_SERVER',
|
||||
ADVANCED_STOP_WEB_SERVER: 'ADVANCED_STOP_WEB_SERVER',
|
||||
ADVANCED_RESTART_WEB_SERVER: 'ADVANCED_RESTART_WEB_SERVER',
|
||||
|
||||
// upload and main page rpc
|
||||
MAIN_GET_PICBED: 'MAIN_GET_PICBED',
|
||||
UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE: 'UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE',
|
||||
UPLOAD_CHOOSED_FILES: 'UPLOAD_CHOOSED_FILES',
|
||||
|
||||
// gallery rpc
|
||||
GALLERY_PASTE_TEXT: 'GALLERY_PASTE_TEXT',
|
||||
GALLERY_REMOVE_FILES: 'GALLERY_REMOVE_FILES',
|
||||
GALLERY_GET_DB: 'GALLERY_GET_DB',
|
||||
GALLERY_GET_BY_ID_DB: 'GALLERY_GET_BY_ID_DB',
|
||||
GALLERY_UPDATE_BY_ID_DB: 'GALLERY_UPDATE_BY_ID_DB',
|
||||
GALLERY_REMOVE_BY_ID_DB: 'GALLERY_REMOVE_BY_ID_DB',
|
||||
GALLERY_INSERT_DB: 'GALLERY_INSERT_DB',
|
||||
GALLERY_INSERT_DB_BATCH: 'GALLERY_INSERT_DB_BATCH',
|
||||
// plugin rpc
|
||||
PLUGIN_GET_LIST: 'PLUGIN_GET_LIST',
|
||||
PLUGIN_INSTALL: 'PLUGIN_INSTALL',
|
||||
PLUGIN_IMPORT_LOCAL: 'PLUGIN_IMPORT_LOCAL',
|
||||
PLUGIN_UPDATE_ALL: 'PLUGIN_UPDATE_ALL',
|
||||
|
||||
// tray rpc
|
||||
TRAY_SET_TOOL_TIP: 'TRAY_SET_TOOL_TIP',
|
||||
TRAY_GET_SHORT_URL: 'TRAY_GET_SHORT_URL',
|
||||
TRAY_UPLOAD_CLIPBOARD_FILES: 'TRAY_UPLOAD_CLIPBOARD_FILES',
|
||||
|
||||
// manage rpc
|
||||
MANAGE_GET_CONFIG: 'MANAGE_GET_CONFIG',
|
||||
MANAGE_SAVE_CONFIG: 'MANAGE_SAVE_CONFIG',
|
||||
MANAGE_REMOVE_CONFIG: 'MANAGE_REMOVE_CONFIG',
|
||||
MANAGE_GET_BUCKET_LIST: 'MANAGE_GET_BUCKET_LIST',
|
||||
MANAGE_GET_BUCKET_LIST_BACKSTAGE: 'MANAGE_GET_BUCKET_LIST_BACKSTAGE',
|
||||
MANAGE_GET_BUCKET_LIST_RECURSIVELY: 'MANAGE_GET_BUCKET_LIST_RECURSIVELY',
|
||||
MANAGE_CREATE_BUCKET: 'MANAGE_CREATE_BUCKET',
|
||||
MANAGE_GET_BUCKET_FILE_LIST: 'MANAGE_GET_BUCKET_FILE_LIST',
|
||||
MANAGE_GET_BUCKET_DOMAIN: 'MANAGE_GET_BUCKET_DOMAIN',
|
||||
MANAGE_SET_BUCKET_ACL_POLICY: 'MANAGE_SET_BUCKET_ACL_POLICY',
|
||||
MANAGE_RENAME_BUCKET_FILE: 'MANAGE_RENAME_BUCKET_FILE',
|
||||
MANAGE_DELETE_BUCKET_FILE: 'MANAGE_DELETE_BUCKET_FILE',
|
||||
MANAGE_DELETE_BUCKET_FOLDER: 'MANAGE_DELETE_BUCKET_FOLDER',
|
||||
MANAGE_GET_PRE_SIGNED_URL: 'MANAGE_GET_PRE_SIGNED_URL',
|
||||
MANAGE_UPLOAD_BUCKET_FILE: 'MANAGE_UPLOAD_BUCKET_FILE',
|
||||
MANAGE_DOWNLOAD_BUCKET_FILE: 'MANAGE_DOWNLOAD_BUCKET_FILE',
|
||||
MANAGE_CREATE_BUCKET_FOLDER: 'MANAGE_CREATE_BUCKET_FOLDER',
|
||||
MANAGE_OPEN_FILE_SELECT_DIALOG: 'MANAGE_OPEN_FILE_SELECT_DIALOG',
|
||||
MANAGE_GET_UPLOAD_TASK_LIST: 'MANAGE_GET_UPLOAD_TASK_LIST',
|
||||
MANAGE_GET_DOWNLOAD_TASK_LIST: 'MANAGE_GET_DOWNLOAD_TASK_LIST',
|
||||
MANAGE_DELETE_UPLOADED_TASK: 'MANAGE_DELETE_UPLOADED_TASK',
|
||||
MANAGE_DELETE_ALL_UPLOADED_TASK: 'MANAGE_DELETE_ALL_UPLOADED_TASK',
|
||||
MANAGE_DELETE_DOWNLOADED_TASK: 'MANAGE_DELETE_DOWNLOADED_TASK',
|
||||
MANAGE_DELETE_ALL_DOWNLOADED_TASK: 'MANAGE_DELETE_ALL_DOWNLOADED_TASK',
|
||||
MANAGE_SELECT_DOWNLOAD_FOLDER: 'MANAGE_SELECT_DOWNLOAD_FOLDER',
|
||||
MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER: 'MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER',
|
||||
MANAGE_OPEN_DOWNLOADED_FOLDER: 'MANAGE_OPEN_DOWNLOADED_FOLDER',
|
||||
MANAGE_OPEN_LOCAL_FILE: 'MANAGE_OPEN_LOCAL_FILE',
|
||||
MANAGE_DOWNLOAD_FILE_FROM_URL: 'MANAGE_DOWNLOAD_FILE_FROM_URL',
|
||||
MANAGE_CONVERT_PATH_TO_BASE64: 'MANAGE_CONVERT_PATH_TO_BASE64'
|
||||
}
|
||||
|
||||
export const IToolboxItemType = {
|
||||
IS_CONFIG_FILE_BROKEN: 'IS_CONFIG_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_PROXY: 'HAS_PROBLEM_WITH_PROXY'
|
||||
}
|
||||
|
||||
export const IToolboxItemCheckStatus = {
|
||||
INIT: 'init',
|
||||
LOADING: 'loading',
|
||||
SUCCESS: 'success',
|
||||
ERROR: 'error'
|
||||
}
|
||||
|
||||
export const ISartMode = {
|
||||
QUIET: 'quiet',
|
||||
MINI: 'mini',
|
||||
MAIN: 'main',
|
||||
NO_TRAY: 'no-tray'
|
||||
}
|
||||
|
||||
export const II18nLanguage = {
|
||||
ZH_CN: 'zh-CN',
|
||||
ZH_TW: 'zh-TW',
|
||||
EN: 'en'
|
||||
}
|
||||
|
||||
export const IShortUrlServer = {
|
||||
C1N: 'c1n',
|
||||
YOURLS: 'yourls',
|
||||
CFWORKER: 'cf_worker',
|
||||
SINK: 'sink'
|
||||
}
|
||||
|
||||
export const commonTaskStatus = {
|
||||
queuing: 'queuing',
|
||||
failed: 'failed',
|
||||
canceled: 'canceled',
|
||||
paused: 'paused'
|
||||
}
|
||||
|
||||
// manage task status
|
||||
|
||||
export const uploadTaskSpecialStatus = {
|
||||
uploading: 'uploading',
|
||||
uploaded: 'uploaded'
|
||||
}
|
||||
|
||||
export const downloadTaskSpecialStatus = {
|
||||
downloading: 'downloading',
|
||||
downloaded: 'downloaded'
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const parseVersion = (plist: string) => {
|
||||
return matches[1].replace('10.16', '11')
|
||||
}
|
||||
|
||||
export function macOSVersion (): string {
|
||||
export function macOSVersion(): string {
|
||||
if (!isMacOS) return ''
|
||||
|
||||
if (!version) {
|
||||
@@ -43,7 +43,7 @@ if (process.env.NODE_ENV === 'test') {
|
||||
macOSVersion._parseVersion = parseVersion
|
||||
}
|
||||
|
||||
export function isMacOSVersion (semverRange: string) {
|
||||
export function isMacOSVersion(semverRange: string) {
|
||||
if (!isMacOS) {
|
||||
return false
|
||||
}
|
||||
@@ -53,7 +53,7 @@ export function isMacOSVersion (semverRange: string) {
|
||||
return semver.satisfies(macOSVersion(), clean(semverRange))
|
||||
}
|
||||
|
||||
export function isMacOSVersionGreaterThanOrEqualTo (version: string) {
|
||||
export function isMacOSVersionGreaterThanOrEqualTo(version: string) {
|
||||
if (!isMacOS) {
|
||||
return false
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export function isMacOSVersionGreaterThanOrEqualTo (version: string) {
|
||||
return semver.gte(macOSVersion(), clean(version))
|
||||
}
|
||||
|
||||
export function assertMacOSVersion (semverRange: string) {
|
||||
export function assertMacOSVersion(semverRange: string) {
|
||||
semverRange = semverRange.replace('10.16', '11')
|
||||
|
||||
if (!isMacOSVersion(semverRange)) {
|
||||
@@ -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')
|
||||
|
||||
if (!isMacOSVersionGreaterThanOrEqualTo(version)) {
|
||||
@@ -79,7 +79,7 @@ export function assertMacOSVersionGreaterThanOrEqualTo (version: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function assertMacOS () {
|
||||
export function assertMacOS() {
|
||||
if (!isMacOS) {
|
||||
throw new Error('Requires macOS')
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import picgo from '@core/picgo'
|
||||
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 { configPaths } from '~/utils/configPaths'
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import type { IAppNotification } from '#/types/types'
|
||||
|
||||
export const notificationList: IAppNotification[] = []
|
||||
import type { IAppNotification } from '#/types/types'
|
||||
|
||||
export const notificationList: IAppNotification[] = []
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
export class MemoryMonitor {
|
||||
// eslint-disable-next-line no-undef
|
||||
private static interval: NodeJS.Timeout | null = null
|
||||
|
||||
static start (intervalMs: number = 30000) {
|
||||
if (this.interval) return
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
const memUsage = process.memoryUsage()
|
||||
const mbUsage = {
|
||||
rss: Math.round(memUsage.rss / 1024 / 1024),
|
||||
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
|
||||
heapUsed: Math.round(memUsage.heapUsed / 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`)
|
||||
|
||||
if (mbUsage.heapUsed / mbUsage.heapTotal > 0.8 && global.gc) {
|
||||
console.log('[Memory] Triggering garbage collection')
|
||||
global.gc()
|
||||
}
|
||||
}, intervalMs)
|
||||
}
|
||||
|
||||
static stop () {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
this.interval = null
|
||||
}
|
||||
}
|
||||
}
|
||||
export class MemoryMonitor {
|
||||
// eslint-disable-next-line no-undef
|
||||
private static interval: NodeJS.Timeout | null = null
|
||||
|
||||
static start(intervalMs: number = 30000) {
|
||||
if (this.interval) return
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
const memUsage = process.memoryUsage()
|
||||
const mbUsage = {
|
||||
rss: Math.round(memUsage.rss / 1024 / 1024),
|
||||
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
|
||||
heapUsed: Math.round(memUsage.heapUsed / 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`
|
||||
)
|
||||
|
||||
if (mbUsage.heapUsed / mbUsage.heapTotal > 0.8 && global.gc) {
|
||||
console.log('[Memory] Triggering garbage collection')
|
||||
global.gc()
|
||||
}
|
||||
}, intervalMs)
|
||||
}
|
||||
|
||||
static stop() {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
this.interval = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,194 +1,194 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import logger from '@core/picgo/logger'
|
||||
import fs from 'fs-extra'
|
||||
import { Config, NodeSSH, SSHExecCommandResponse } from 'node-ssh-no-cpu-features'
|
||||
import { ISftpPlistConfig } from 'piclist/dist/types'
|
||||
import { Client } from 'ssh2-no-cpu-features'
|
||||
|
||||
class SSHClient {
|
||||
private static _instance: SSHClient
|
||||
private static _client: NodeSSH
|
||||
private _isConnected = false
|
||||
|
||||
static get instance (): SSHClient {
|
||||
return this._instance || (this._instance = new this())
|
||||
}
|
||||
|
||||
static get client (): NodeSSH {
|
||||
return this._client || (this._client = new NodeSSH())
|
||||
}
|
||||
|
||||
private changeWinStylePathToUnix (path: string): string {
|
||||
return path.replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
async connect (config: ISftpPlistConfig): Promise<boolean> {
|
||||
const { username, password, privateKey, passphrase } = config
|
||||
const loginInfo: Config = privateKey
|
||||
? {
|
||||
username,
|
||||
privateKeyPath: privateKey,
|
||||
passphrase: passphrase || undefined
|
||||
}
|
||||
: { username, password }
|
||||
try {
|
||||
await SSHClient.client.connect({
|
||||
host: config.host,
|
||||
port: Number(config.port) || 22,
|
||||
...loginInfo
|
||||
})
|
||||
this._isConnected = true
|
||||
return true
|
||||
} catch (err: any) {
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFileSFTP (config: ISftpPlistConfig, remote: string): Promise<boolean> {
|
||||
try {
|
||||
const client = new Client()
|
||||
const { username, password, privateKey, passphrase } = config
|
||||
const loginInfo: Config = privateKey
|
||||
? {
|
||||
username,
|
||||
privateKey: fs.readFileSync(privateKey),
|
||||
passphrase: passphrase || undefined
|
||||
}
|
||||
: { username, password }
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
if (remote === '/' || remote.includes('*')) return false
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
client
|
||||
.on('ready', () => {
|
||||
client.sftp(
|
||||
(
|
||||
err: any,
|
||||
sftp: {
|
||||
unlink: (arg0: string, arg1: (err: any) => void) => void
|
||||
}
|
||||
) => {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
if (err) reject(false)
|
||||
sftp.unlink(remote, (err: any) => {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
if (err) reject(false)
|
||||
client.end()
|
||||
resolve(true)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
.connect({
|
||||
host: config.host,
|
||||
port: Number(config.port) || 22,
|
||||
...loginInfo
|
||||
})
|
||||
})
|
||||
return (await promise) as boolean
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private async exec (script: string): Promise<boolean> {
|
||||
const execResult = await SSHClient.client.execCommand(script)
|
||||
return execResult.code === 0
|
||||
}
|
||||
|
||||
async execCommand (script: string): Promise<SSHExecCommandResponse> {
|
||||
const execResult = await SSHClient.client.execCommand(script)
|
||||
return execResult || { code: 1, stdout: '', stderr: '' }
|
||||
}
|
||||
|
||||
async getFile (local: string, remote: string): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
local = this.changeWinStylePathToUnix(local)
|
||||
await SSHClient.client.getFile(local, remote, undefined, {
|
||||
concurrency: 1
|
||||
})
|
||||
return true
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async putFile (
|
||||
local: string,
|
||||
remote: string,
|
||||
config: {
|
||||
fileMode?: string
|
||||
dirMode?: string
|
||||
} = {}
|
||||
): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
await this.mkdir(path.dirname(remote).replace(/^\/+|\/+$/g, ''), config)
|
||||
await SSHClient.client.putFile(local, remote)
|
||||
const fileMode = config.fileMode || '0644'
|
||||
if (fileMode !== '0644') {
|
||||
const script = `chmod ${fileMode} "${remote}"`
|
||||
return await this.exec(script)
|
||||
}
|
||||
return true
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async mkdir (
|
||||
dirPath: string,
|
||||
config: {
|
||||
dirMode?: string
|
||||
} = {}
|
||||
): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
const directoryMode = config.dirMode || '0755'
|
||||
if (directoryMode === '0755') {
|
||||
const script = `mkdir -p "${dirPath}"`
|
||||
return await this.exec(script)
|
||||
} else {
|
||||
const dirs = dirPath.split('/')
|
||||
let currentPath = ''
|
||||
for (const dir of dirs) {
|
||||
if (dir) {
|
||||
currentPath += `/${dir}`
|
||||
const script = `mkdir "${currentPath}" && chmod ${directoryMode} "${currentPath}"`
|
||||
const result = await this.exec(script)
|
||||
if (!result) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
get isConnected (): boolean {
|
||||
return SSHClient.client.isConnected()
|
||||
}
|
||||
|
||||
close (): void {
|
||||
SSHClient.client.dispose()
|
||||
this._isConnected = false
|
||||
}
|
||||
}
|
||||
|
||||
export default SSHClient
|
||||
import path from 'node:path'
|
||||
|
||||
import logger from '@core/picgo/logger'
|
||||
import fs from 'fs-extra'
|
||||
import { Config, NodeSSH, SSHExecCommandResponse } from 'node-ssh-no-cpu-features'
|
||||
import { ISftpPlistConfig } from 'piclist/dist/types'
|
||||
import { Client } from 'ssh2-no-cpu-features'
|
||||
|
||||
class SSHClient {
|
||||
private static _instance: SSHClient
|
||||
private static _client: NodeSSH
|
||||
private _isConnected = false
|
||||
|
||||
static get instance(): SSHClient {
|
||||
return this._instance || (this._instance = new this())
|
||||
}
|
||||
|
||||
static get client(): NodeSSH {
|
||||
return this._client || (this._client = new NodeSSH())
|
||||
}
|
||||
|
||||
private changeWinStylePathToUnix(path: string): string {
|
||||
return path.replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
async connect(config: ISftpPlistConfig): Promise<boolean> {
|
||||
const { username, password, privateKey, passphrase } = config
|
||||
const loginInfo: Config = privateKey
|
||||
? {
|
||||
username,
|
||||
privateKeyPath: privateKey,
|
||||
passphrase: passphrase || undefined
|
||||
}
|
||||
: { username, password }
|
||||
try {
|
||||
await SSHClient.client.connect({
|
||||
host: config.host,
|
||||
port: Number(config.port) || 22,
|
||||
...loginInfo
|
||||
})
|
||||
this._isConnected = true
|
||||
return true
|
||||
} catch (err: any) {
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFileSFTP(config: ISftpPlistConfig, remote: string): Promise<boolean> {
|
||||
try {
|
||||
const client = new Client()
|
||||
const { username, password, privateKey, passphrase } = config
|
||||
const loginInfo: Config = privateKey
|
||||
? {
|
||||
username,
|
||||
privateKey: fs.readFileSync(privateKey),
|
||||
passphrase: passphrase || undefined
|
||||
}
|
||||
: { username, password }
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
if (remote === '/' || remote.includes('*')) return false
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
client
|
||||
.on('ready', () => {
|
||||
client.sftp(
|
||||
(
|
||||
err: any,
|
||||
sftp: {
|
||||
unlink: (arg0: string, arg1: (err: any) => void) => void
|
||||
}
|
||||
) => {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
if (err) reject(false)
|
||||
sftp.unlink(remote, (err: any) => {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
if (err) reject(false)
|
||||
client.end()
|
||||
resolve(true)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
.connect({
|
||||
host: config.host,
|
||||
port: Number(config.port) || 22,
|
||||
...loginInfo
|
||||
})
|
||||
})
|
||||
return (await promise) as boolean
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private async exec(script: string): Promise<boolean> {
|
||||
const execResult = await SSHClient.client.execCommand(script)
|
||||
return execResult.code === 0
|
||||
}
|
||||
|
||||
async execCommand(script: string): Promise<SSHExecCommandResponse> {
|
||||
const execResult = await SSHClient.client.execCommand(script)
|
||||
return execResult || { code: 1, stdout: '', stderr: '' }
|
||||
}
|
||||
|
||||
async getFile(local: string, remote: string): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
local = this.changeWinStylePathToUnix(local)
|
||||
await SSHClient.client.getFile(local, remote, undefined, {
|
||||
concurrency: 1
|
||||
})
|
||||
return true
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async putFile(
|
||||
local: string,
|
||||
remote: string,
|
||||
config: {
|
||||
fileMode?: string
|
||||
dirMode?: string
|
||||
} = {}
|
||||
): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
remote = this.changeWinStylePathToUnix(remote)
|
||||
await this.mkdir(path.dirname(remote).replace(/^\/+|\/+$/g, ''), config)
|
||||
await SSHClient.client.putFile(local, remote)
|
||||
const fileMode = config.fileMode || '0644'
|
||||
if (fileMode !== '0644') {
|
||||
const script = `chmod ${fileMode} "${remote}"`
|
||||
return await this.exec(script)
|
||||
}
|
||||
return true
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async mkdir(
|
||||
dirPath: string,
|
||||
config: {
|
||||
dirMode?: string
|
||||
} = {}
|
||||
): Promise<boolean> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('SSH 未连接')
|
||||
}
|
||||
try {
|
||||
const directoryMode = config.dirMode || '0755'
|
||||
if (directoryMode === '0755') {
|
||||
const script = `mkdir -p "${dirPath}"`
|
||||
return await this.exec(script)
|
||||
} else {
|
||||
const dirs = dirPath.split('/')
|
||||
let currentPath = ''
|
||||
for (const dir of dirs) {
|
||||
if (dir) {
|
||||
currentPath += `/${dir}`
|
||||
const script = `mkdir "${currentPath}" && chmod ${directoryMode} "${currentPath}"`
|
||||
const result = await this.exec(script)
|
||||
if (!result) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
get isConnected(): boolean {
|
||||
return SSHClient.client.isConnected()
|
||||
}
|
||||
|
||||
close(): void {
|
||||
SSHClient.client.dispose()
|
||||
this._isConnected = false
|
||||
}
|
||||
}
|
||||
|
||||
export default SSHClient
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
export const CLIPBOARD_IMAGE_FOLDER = 'piclist-clipboard-images'
|
||||
|
||||
export const cancelDownloadLoadingFileList = 'cancelDownloadLoadingFileList'
|
||||
export const refreshDownloadFileTransferList = 'refreshDownloadFileTransferList'
|
||||
|
||||
export const picBedsCanbeDeleted = [
|
||||
'aliyun',
|
||||
'alist',
|
||||
'alistplist',
|
||||
'aws-s3',
|
||||
'aws-s3-plist',
|
||||
'dogecloud',
|
||||
'github',
|
||||
'huaweicloud-uploader',
|
||||
'imgur',
|
||||
'local',
|
||||
'lskyplist',
|
||||
'piclist',
|
||||
'qiniu',
|
||||
'sftpplist',
|
||||
'smms',
|
||||
'tcyun',
|
||||
'upyun',
|
||||
'webdavplist'
|
||||
]
|
||||
export const CLIPBOARD_IMAGE_FOLDER = 'piclist-clipboard-images'
|
||||
|
||||
export const cancelDownloadLoadingFileList = 'cancelDownloadLoadingFileList'
|
||||
export const refreshDownloadFileTransferList = 'refreshDownloadFileTransferList'
|
||||
|
||||
export const picBedsCanbeDeleted = [
|
||||
'aliyun',
|
||||
'alist',
|
||||
'alistplist',
|
||||
'aws-s3',
|
||||
'aws-s3-plist',
|
||||
'dogecloud',
|
||||
'github',
|
||||
'huaweicloud-uploader',
|
||||
'imgur',
|
||||
'local',
|
||||
'lskyplist',
|
||||
'piclist',
|
||||
'qiniu',
|
||||
'sftpplist',
|
||||
'smms',
|
||||
'tcyun',
|
||||
'upyun',
|
||||
'webdavplist'
|
||||
]
|
||||
|
||||
@@ -37,16 +37,16 @@ const getSyncConfig = () => {
|
||||
const getProxyagent = (proxy: string | undefined) => {
|
||||
return proxy
|
||||
? new HttpsProxyAgent({
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 1000,
|
||||
rejectUnauthorized: false,
|
||||
proxy: proxy.replace('127.0.0.1', 'localhost'),
|
||||
scheduling: 'lifo'
|
||||
})
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 1000,
|
||||
rejectUnauthorized: false,
|
||||
proxy: proxy.replace('127.0.0.1', 'localhost'),
|
||||
scheduling: 'lifo'
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
|
||||
function getOctokit (syncConfig: ISyncConfig) {
|
||||
function getOctokit(syncConfig: ISyncConfig) {
|
||||
const { token, proxy } = syncConfig
|
||||
return new Octokit({
|
||||
auth: token,
|
||||
@@ -83,7 +83,7 @@ const isSyncConfigValidate = ({
|
||||
return type && username && repo && branch && token
|
||||
}
|
||||
|
||||
async function uploadLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
|
||||
async function uploadLocalToRemote(syncConfig: ISyncConfig, fileName: string) {
|
||||
const localFilePath = path.join(STORE_PATH, fileName)
|
||||
if (!fs.existsSync(localFilePath)) {
|
||||
return false
|
||||
@@ -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)
|
||||
if (!fs.existsSync(localFilePath)) {
|
||||
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()
|
||||
if (!isSyncConfigValidate(syncConfig)) {
|
||||
logger.error('sync config is invalid')
|
||||
@@ -302,7 +302,7 @@ async function uploadFile (fileName: string[]): Promise<number> {
|
||||
return count
|
||||
}
|
||||
|
||||
async function downloadAndWriteFile (url: string, localFilePath: string, config: any, isWriteJson = false) {
|
||||
async function downloadAndWriteFile(url: string, localFilePath: string, config: any, isWriteJson = false) {
|
||||
const res = await axios.get(url, config)
|
||||
if (isHttpResSuccess(res)) {
|
||||
await fs.writeFile(
|
||||
@@ -314,7 +314,7 @@ async function downloadAndWriteFile (url: string, localFilePath: string, config:
|
||||
return false
|
||||
}
|
||||
|
||||
async function downloadRemoteToLocal (syncConfig: ISyncConfig, fileName: string) {
|
||||
async function downloadRemoteToLocal(syncConfig: ISyncConfig, fileName: string) {
|
||||
const localFilePath = path.join(STORE_PATH, fileName)
|
||||
const { username, repo, branch, token, proxy, type } = syncConfig
|
||||
try {
|
||||
@@ -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()
|
||||
if (!isSyncConfigValidate(syncConfig)) {
|
||||
logger.error('sync config is invalid')
|
||||
|
||||
@@ -1,66 +1,72 @@
|
||||
import db from '@core/datastore'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { screen } from 'electron'
|
||||
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
export function openMiniWindow (hideSettingWindow: boolean = true) {
|
||||
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
|
||||
|
||||
miniWindow.removeAllListeners('close')
|
||||
miniWindow.removeAllListeners('move')
|
||||
|
||||
if (db.get(configPaths.settings.miniWindowOntop)) {
|
||||
miniWindow.setAlwaysOnTop(true)
|
||||
}
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||
const lastPosition = db.get(configPaths.settings.miniWindowPosition)
|
||||
const setPositionFunc = () => {
|
||||
const position = miniWindow.getPosition()
|
||||
db.set(configPaths.settings.miniWindowPosition, position)
|
||||
}
|
||||
if (lastPosition) {
|
||||
if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) {
|
||||
miniWindow.setPosition(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) {
|
||||
miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1])
|
||||
db.set(configPaths.settings.miniWindowPosition, [width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]])
|
||||
} else {
|
||||
miniWindow.setPosition(lastPosition[0], lastPosition[1])
|
||||
}
|
||||
} else {
|
||||
miniWindow.setPosition(width - 100, height - 100)
|
||||
}
|
||||
|
||||
miniWindow.on('close', setPositionFunc)
|
||||
miniWindow.on('move', setPositionFunc)
|
||||
miniWindow.show()
|
||||
miniWindow.focus()
|
||||
if (hideSettingWindow) {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
settingWindow.hide()
|
||||
} else {
|
||||
const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const openMainWindow = () => {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
|
||||
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 hideMiniWindow = () => {
|
||||
if (windowManager.has(IWindowList.MINI_WINDOW)) {
|
||||
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
|
||||
}
|
||||
}
|
||||
import db from '@core/datastore'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { screen } from 'electron'
|
||||
|
||||
import { configPaths } from '~/utils/configPaths'
|
||||
import { IWindowList } from '~/utils/enum'
|
||||
|
||||
export function openMiniWindow(hideSettingWindow: boolean = true) {
|
||||
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
|
||||
|
||||
miniWindow.removeAllListeners('close')
|
||||
miniWindow.removeAllListeners('move')
|
||||
|
||||
if (db.get(configPaths.settings.miniWindowOntop)) {
|
||||
miniWindow.setAlwaysOnTop(true)
|
||||
}
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||
const lastPosition = db.get(configPaths.settings.miniWindowPosition)
|
||||
const setPositionFunc = () => {
|
||||
const position = miniWindow.getPosition()
|
||||
db.set(configPaths.settings.miniWindowPosition, position)
|
||||
}
|
||||
if (lastPosition) {
|
||||
if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) {
|
||||
miniWindow.setPosition(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
|
||||
) {
|
||||
miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1])
|
||||
db.set(configPaths.settings.miniWindowPosition, [
|
||||
width - miniWindow.getSize()[0],
|
||||
height - miniWindow.getSize()[1]
|
||||
])
|
||||
} else {
|
||||
miniWindow.setPosition(lastPosition[0], lastPosition[1])
|
||||
}
|
||||
} else {
|
||||
miniWindow.setPosition(width - 100, height - 100)
|
||||
}
|
||||
|
||||
miniWindow.on('close', setPositionFunc)
|
||||
miniWindow.on('move', setPositionFunc)
|
||||
miniWindow.show()
|
||||
miniWindow.focus()
|
||||
if (hideSettingWindow) {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
settingWindow.hide()
|
||||
} else {
|
||||
const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const openMainWindow = () => {
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
|
||||
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 hideMiniWindow = () => {
|
||||
if (windowManager.has(IWindowList.MINI_WINDOW)) {
|
||||
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,101 +1,101 @@
|
||||
import crypto from 'node:crypto'
|
||||
import path from 'node:path'
|
||||
|
||||
import { clipboard, contextBridge, ipcRenderer, IpcRendererEvent, webFrame, webUtils } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import yaml from 'js-yaml'
|
||||
import mime from 'mime-types'
|
||||
import { isReactive, isRef, toRaw, unref } from 'vue'
|
||||
|
||||
export const getRawData = (args: any): any => {
|
||||
if (isRef(args)) return unref(args)
|
||||
if (isReactive(args)) return toRaw(args)
|
||||
if (Array.isArray(args)) return args.map(getRawData)
|
||||
if (typeof args === 'object' && args !== null) {
|
||||
const data = {} as Record<string, any>
|
||||
for (const key in args) {
|
||||
data[key] = getRawData(args[key])
|
||||
}
|
||||
return data
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
function sendToMain (channel: string, ...args: any[]) {
|
||||
ipcRenderer.send(channel, ...getRawData(args))
|
||||
}
|
||||
|
||||
function sendRPC (action: string, ...args: any[]): void {
|
||||
ipcRenderer.send('RPC_ACTIONS', action, getRawData(args))
|
||||
}
|
||||
|
||||
async function triggerRPC<T> (action: string, ...args: any[]): Promise<T | undefined> {
|
||||
return await ipcRenderer.invoke('RPC_ACTIONS_INVOKE', action, getRawData(args))
|
||||
}
|
||||
|
||||
function sendRpcSync (action: string, ...args: any[]): any {
|
||||
return ipcRenderer.sendSync('RPC_ACTIONS', action, getRawData(args))
|
||||
}
|
||||
|
||||
try {
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
setVisualZoomLevelLimits: (min: number, max: number) => {
|
||||
webFrame.setVisualZoomLevelLimits(min, max)
|
||||
},
|
||||
clipboard: {
|
||||
writeText: clipboard.writeText
|
||||
},
|
||||
platform: process.platform,
|
||||
sendRpcSync,
|
||||
triggerRPC,
|
||||
sendToMain,
|
||||
sendRPC,
|
||||
ipcRendererOn: (channel: string, listener: (...args: any[]) => void) => {
|
||||
const subscription = (_: IpcRendererEvent, ...args: any[]) => listener(...args)
|
||||
ipcRenderer.on(channel, subscription)
|
||||
return () => {
|
||||
ipcRenderer.removeListener(channel, subscription)
|
||||
}
|
||||
},
|
||||
ipcRendererCountListeners: (channel: string): number => {
|
||||
return ipcRenderer.listenerCount(channel)
|
||||
},
|
||||
ipcRendererRemoveAllListeners: (channel: string) => {
|
||||
ipcRenderer.removeAllListeners(channel)
|
||||
},
|
||||
showFilePath (file: File) {
|
||||
return webUtils.getPathForFile(file)
|
||||
}
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('node', {
|
||||
path: {
|
||||
join: path.join,
|
||||
dirname: path.dirname,
|
||||
basename: path.basename,
|
||||
normalize: path.normalize,
|
||||
extname: path.extname,
|
||||
sep: path.sep,
|
||||
posix: {
|
||||
sep: path.posix.sep
|
||||
}
|
||||
},
|
||||
fs: {
|
||||
remove: fs.remove,
|
||||
readFile: fs.readFile,
|
||||
statSync: fs.statSync
|
||||
},
|
||||
crypto: {
|
||||
randomBytes: crypto.randomBytes,
|
||||
createHash: crypto.createHash
|
||||
},
|
||||
yaml: {
|
||||
load: yaml.load
|
||||
},
|
||||
mime: {
|
||||
lookup: mime.lookup
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
import crypto from 'node:crypto'
|
||||
import path from 'node:path'
|
||||
|
||||
import { clipboard, contextBridge, ipcRenderer, IpcRendererEvent, webFrame, webUtils } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import yaml from 'js-yaml'
|
||||
import mime from 'mime-types'
|
||||
import { isReactive, isRef, toRaw, unref } from 'vue'
|
||||
|
||||
export const getRawData = (args: any): any => {
|
||||
if (isRef(args)) return unref(args)
|
||||
if (isReactive(args)) return toRaw(args)
|
||||
if (Array.isArray(args)) return args.map(getRawData)
|
||||
if (typeof args === 'object' && args !== null) {
|
||||
const data = {} as Record<string, any>
|
||||
for (const key in args) {
|
||||
data[key] = getRawData(args[key])
|
||||
}
|
||||
return data
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
function sendToMain(channel: string, ...args: any[]) {
|
||||
ipcRenderer.send(channel, ...getRawData(args))
|
||||
}
|
||||
|
||||
function sendRPC(action: string, ...args: any[]): void {
|
||||
ipcRenderer.send('RPC_ACTIONS', action, getRawData(args))
|
||||
}
|
||||
|
||||
async function triggerRPC<T>(action: string, ...args: any[]): Promise<T | undefined> {
|
||||
return await ipcRenderer.invoke('RPC_ACTIONS_INVOKE', action, getRawData(args))
|
||||
}
|
||||
|
||||
function sendRpcSync(action: string, ...args: any[]): any {
|
||||
return ipcRenderer.sendSync('RPC_ACTIONS', action, getRawData(args))
|
||||
}
|
||||
|
||||
try {
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
setVisualZoomLevelLimits: (min: number, max: number) => {
|
||||
webFrame.setVisualZoomLevelLimits(min, max)
|
||||
},
|
||||
clipboard: {
|
||||
writeText: clipboard.writeText
|
||||
},
|
||||
platform: process.platform,
|
||||
sendRpcSync,
|
||||
triggerRPC,
|
||||
sendToMain,
|
||||
sendRPC,
|
||||
ipcRendererOn: (channel: string, listener: (...args: any[]) => void) => {
|
||||
const subscription = (_: IpcRendererEvent, ...args: any[]) => listener(...args)
|
||||
ipcRenderer.on(channel, subscription)
|
||||
return () => {
|
||||
ipcRenderer.removeListener(channel, subscription)
|
||||
}
|
||||
},
|
||||
ipcRendererCountListeners: (channel: string): number => {
|
||||
return ipcRenderer.listenerCount(channel)
|
||||
},
|
||||
ipcRendererRemoveAllListeners: (channel: string) => {
|
||||
ipcRenderer.removeAllListeners(channel)
|
||||
},
|
||||
showFilePath(file: File) {
|
||||
return webUtils.getPathForFile(file)
|
||||
}
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('node', {
|
||||
path: {
|
||||
join: path.join,
|
||||
dirname: path.dirname,
|
||||
basename: path.basename,
|
||||
normalize: path.normalize,
|
||||
extname: path.extname,
|
||||
sep: path.sep,
|
||||
posix: {
|
||||
sep: path.posix.sep
|
||||
}
|
||||
},
|
||||
fs: {
|
||||
remove: fs.remove,
|
||||
readFile: fs.readFile,
|
||||
statSync: fs.statSync
|
||||
},
|
||||
crypto: {
|
||||
randomBytes: crypto.randomBytes,
|
||||
createHash: crypto.createHash
|
||||
},
|
||||
yaml: {
|
||||
load: yaml.load
|
||||
},
|
||||
mime: {
|
||||
lookup: mime.lookup
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
@@ -1,60 +1,56 @@
|
||||
<template>
|
||||
<div
|
||||
id="app"
|
||||
:key="pageReloadCount"
|
||||
>
|
||||
<router-view />
|
||||
<UIServiceProvider />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IConfig } from 'piclist'
|
||||
import { onBeforeMount, onMounted } from 'vue'
|
||||
|
||||
import UIServiceProvider from '@/components/ui/UIServiceProvider.vue'
|
||||
import { useATagClick } from '@/hooks/useATagClick'
|
||||
import { useStore } from '@/hooks/useStore'
|
||||
import { getConfig } from '@/utils/dataSender'
|
||||
import { pageReloadCount } from '@/utils/global'
|
||||
|
||||
import { useAppStore } from './hooks/useAppStore'
|
||||
|
||||
useATagClick()
|
||||
|
||||
const store = useStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const config = await getConfig<IConfig>()
|
||||
if (config) {
|
||||
store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
appStore.init()
|
||||
} catch (error) {
|
||||
console.error('Failed to load settings:', error)
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'PicGoApp'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
body,
|
||||
html
|
||||
padding 0
|
||||
margin 0
|
||||
height 100%
|
||||
#app
|
||||
height 100%
|
||||
user-select none
|
||||
</style>
|
||||
<template>
|
||||
<div id="app" :key="pageReloadCount">
|
||||
<router-view />
|
||||
<UIServiceProvider />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IConfig } from 'piclist'
|
||||
import { onBeforeMount, onMounted } from 'vue'
|
||||
|
||||
import UIServiceProvider from '@/components/ui/UIServiceProvider.vue'
|
||||
import { useATagClick } from '@/hooks/useATagClick'
|
||||
import { useStore } from '@/hooks/useStore'
|
||||
import { getConfig } from '@/utils/dataSender'
|
||||
import { pageReloadCount } from '@/utils/global'
|
||||
|
||||
import { useAppStore } from './hooks/useAppStore'
|
||||
|
||||
useATagClick()
|
||||
|
||||
const store = useStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const config = await getConfig<IConfig>()
|
||||
if (config) {
|
||||
store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
appStore.init()
|
||||
} catch (error) {
|
||||
console.error('Failed to load settings:', error)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'PicGoApp'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
body,
|
||||
html
|
||||
padding 0
|
||||
margin 0
|
||||
height 100%
|
||||
#app
|
||||
height 100%
|
||||
user-select none
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { IRPCActionType } from '@/utils/enum'
|
||||
import type { IStringKeyMap } from '#/types/types'
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +1,107 @@
|
||||
<template>
|
||||
<div class="image-container">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="loading-placeholder"
|
||||
>
|
||||
<div class="loading-spinner" />
|
||||
</div>
|
||||
<img
|
||||
v-else-if="!hasError"
|
||||
:src="
|
||||
isShowThumbnail && item.isImage
|
||||
? base64Image
|
||||
: `./assets/icons/${getFileIconPath(item.fileName ?? '')}`
|
||||
"
|
||||
alt=""
|
||||
class="image"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
:src="`./assets/icons/${getFileIconPath(item.fileName ?? '')}`"
|
||||
alt=""
|
||||
class="image"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
|
||||
const base64Image = ref('')
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
|
||||
const props = defineProps<{
|
||||
isShowThumbnail: boolean
|
||||
item: {
|
||||
isImage: boolean
|
||||
fileName: string
|
||||
}
|
||||
localPath: string
|
||||
}>()
|
||||
|
||||
const createBase64Image = async () => {
|
||||
try {
|
||||
const filePath = window.node.path.normalize(props.localPath)
|
||||
const base64 = await window.node.fs.readFile(filePath, 'base64')
|
||||
base64Image.value = `data:${window.node.mime.lookup(filePath) || 'image/png'};base64,${base64}`
|
||||
isLoading.value = false
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
hasError.value = true
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageLoad = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = false
|
||||
}
|
||||
|
||||
const handleImageError = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = true
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await createBase64Image()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-container {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-top: 2px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="image-container">
|
||||
<div v-if="isLoading" class="loading-placeholder">
|
||||
<div class="loading-spinner" />
|
||||
</div>
|
||||
<img
|
||||
v-else-if="!hasError"
|
||||
:src="isShowThumbnail && item.isImage ? base64Image : `./assets/icons/${getFileIconPath(item.fileName ?? '')}`"
|
||||
alt=""
|
||||
class="image"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<img v-else :src="`./assets/icons/${getFileIconPath(item.fileName ?? '')}`" alt="" class="image" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
|
||||
const base64Image = ref('')
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
|
||||
const props = defineProps<{
|
||||
isShowThumbnail: boolean
|
||||
item: {
|
||||
isImage: boolean
|
||||
fileName: string
|
||||
}
|
||||
localPath: string
|
||||
}>()
|
||||
|
||||
const createBase64Image = async () => {
|
||||
try {
|
||||
const filePath = window.node.path.normalize(props.localPath)
|
||||
const base64 = await window.node.fs.readFile(filePath, 'base64')
|
||||
base64Image.value = `data:${window.node.mime.lookup(filePath) || 'image/png'};base64,${base64}`
|
||||
isLoading.value = false
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
hasError.value = true
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageLoad = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = false
|
||||
}
|
||||
|
||||
const handleImageError = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = true
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await createBase64Image()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-container {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-top: 2px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,123 +1,123 @@
|
||||
<template>
|
||||
<div class="image-container">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="loading-placeholder"
|
||||
>
|
||||
<div class="loading-spinner" />
|
||||
</div>
|
||||
<img
|
||||
v-else-if="!hasError"
|
||||
:src="imageSource"
|
||||
alt=""
|
||||
class="image"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
:src="iconPath"
|
||||
alt=""
|
||||
class="image"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
import { IRPCActionType } from '@/utils/enum'
|
||||
|
||||
const preSignedUrl = ref('')
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
|
||||
const props = defineProps<{
|
||||
item: {
|
||||
key: string
|
||||
isImage: boolean
|
||||
fileName: string | null | undefined
|
||||
}
|
||||
alias: string
|
||||
url: string
|
||||
config: any
|
||||
isShowThumbnail: boolean
|
||||
}>()
|
||||
|
||||
const imageSource = computed(() => {
|
||||
return props.isShowThumbnail && props.item.isImage
|
||||
? preSignedUrl.value
|
||||
: `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`
|
||||
})
|
||||
|
||||
const iconPath = computed(() => `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)
|
||||
|
||||
async function getUrl () {
|
||||
try {
|
||||
isLoading.value = true
|
||||
hasError.value = false
|
||||
preSignedUrl.value = await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_GET_PRE_SIGNED_URL, props.alias, props.config)
|
||||
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 handleImageError = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = true
|
||||
}
|
||||
|
||||
watch(() => [props.url, props.item], getUrl, { deep: true })
|
||||
|
||||
onMounted(getUrl)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-container {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-top: 2px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="image-container">
|
||||
<div v-if="isLoading" class="loading-placeholder">
|
||||
<div class="loading-spinner" />
|
||||
</div>
|
||||
<img
|
||||
v-else-if="!hasError"
|
||||
:src="imageSource"
|
||||
alt=""
|
||||
class="image"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<img v-else :src="iconPath" alt="" class="image" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
import { IRPCActionType } from '@/utils/enum'
|
||||
|
||||
const preSignedUrl = ref('')
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
|
||||
const props = defineProps<{
|
||||
item: {
|
||||
key: string
|
||||
isImage: boolean
|
||||
fileName: string | null | undefined
|
||||
}
|
||||
alias: string
|
||||
url: string
|
||||
config: any
|
||||
isShowThumbnail: boolean
|
||||
}>()
|
||||
|
||||
const imageSource = computed(() => {
|
||||
return props.isShowThumbnail && props.item.isImage
|
||||
? preSignedUrl.value
|
||||
: `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`
|
||||
})
|
||||
|
||||
const iconPath = computed(() => `./assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)
|
||||
|
||||
async function getUrl() {
|
||||
try {
|
||||
isLoading.value = true
|
||||
hasError.value = false
|
||||
preSignedUrl.value = await window.electron.triggerRPC<any>(
|
||||
IRPCActionType.MANAGE_GET_PRE_SIGNED_URL,
|
||||
props.alias,
|
||||
props.config
|
||||
)
|
||||
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 handleImageError = () => {
|
||||
isLoading.value = false
|
||||
hasError.value = true
|
||||
}
|
||||
|
||||
watch(() => [props.url, props.item], getUrl, { deep: true })
|
||||
|
||||
onMounted(getUrl)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-container {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-top: 2px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</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
Reference in New Issue
Block a user