🚧 WIP(custom): v3.0.0 migrate to vite and esm

This commit is contained in:
Kuingsmile
2025-07-31 17:37:30 +08:00
parent cd76bc7c10
commit 054f4b4cff
597 changed files with 197292 additions and 13329 deletions

View File

@@ -1,12 +1,14 @@
import path from 'node:path'
import windowManager from 'apis/app/window/windowManager'
import axios from 'axios'
import { app, clipboard, dialog, shell } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import { gte, lte } from 'semver'
import windowManager from 'apis/app/window/windowManager'
import { showNotification } from '~/utils/common'
import { IRemoteNoticeActionType, IRemoteNoticeTriggerCount, IRemoteNoticeTriggerHook } from '#/types/enum'
import { IRemoteNotice, IRemoteNoticeAction, IRemoteNoticeLocalCountStorage } from '#/types/types'
import { showNotification } from '~/utils/common'
// for test
const REMOTE_NOTICE_URL = 'https://release.piclist.cn/remote-notice.json'
@@ -21,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({}))
@@ -42,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',
@@ -66,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
@@ -100,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) {
@@ -115,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)
}
@@ -161,7 +163,7 @@ class RemoteNoticeHandler {
}
}
triggerHook(hook: IRemoteNoticeTriggerHook) {
triggerHook (hook: IRemoteNoticeTriggerHook) {
if (!this.remoteNotice || !this.remoteNotice.list) {
return
}

View File

@@ -1,30 +1,29 @@
import { globalShortcut } from 'electron'
import shortKeyService from 'apis/app/shortKey/shortKeyService'
import GuiApi from 'apis/gui'
import bus from '@core/bus'
import db from '@core/datastore'
import logger from '@core/picgo/logger'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import shortKeyService from 'apis/app/shortKey/shortKeyService'
import GuiApi from 'apis/gui'
import { globalShortcut } from 'electron'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { IKeyCommandType, IPluginShortKeyConfig, IShortKeyConfig, IShortKeyConfigs, IShortKeyHandler } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
class ShortKeyHandler {
private isInModifiedMode: boolean = false
constructor() {
constructor () {
bus.on(TOGGLE_SHORTKEY_MODIFIED_MODE, flag => {
this.isInModifiedMode = flag
})
}
init() {
async init () {
this.initBuiltInShortKey()
this.initPluginsShortKey()
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:'))
@@ -39,11 +38,11 @@ class ShortKeyHandler {
})
}
private initPluginsShortKey() {
private async initPluginsShortKey () {
// get enabled plugin
const pluginList = picgo.pluginLoader.getList()
for (const item of pluginList) {
const plugin = picgo.pluginLoader.getPlugin(item)
const plugin = await picgo.pluginLoader.getPlugin(item)
// if a plugin has commands
if (plugin && plugin.commands) {
if (typeof plugin.commands !== 'function') {
@@ -69,7 +68,7 @@ class ShortKeyHandler {
}
}
private registerShortKey(
private registerShortKey (
config: IShortKeyConfig | IPluginShortKeyConfig,
command: string,
handler: IShortKeyHandler,
@@ -98,7 +97,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)
@@ -122,7 +121,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)
@@ -135,7 +134,7 @@ class ShortKeyHandler {
return true
}
private async handler(command: string) {
private async handler (command: string) {
if (this.isInModifiedMode) {
return
}
@@ -151,8 +150,8 @@ class ShortKeyHandler {
}
}
registerPluginShortKey(pluginName: string) {
const plugin = picgo.pluginLoader.getPlugin(pluginName)
async registerPluginShortKey (pluginName: string) {
const plugin = await picgo.pluginLoader.getPlugin(pluginName)
if (plugin && plugin.commands) {
if (typeof plugin.commands !== 'function') {
logger.warn(`${pluginName}'s commands is not a function`)
@@ -171,7 +170,7 @@ class ShortKeyHandler {
}
}
unregisterPluginShortKey(pluginName: string) {
unregisterPluginShortKey (pluginName: string) {
const commands = db.get(configPaths.settings.shortKey._path) as IShortKeyConfigs
const keyList = Object.keys(commands)
.filter(command => command.includes(pluginName))

View File

@@ -1,23 +1,25 @@
import logger from '@core/picgo/logger'
import { 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()]
}
}

View File

@@ -1,3 +1,8 @@
import db, { GalleryDB } from '@core/datastore'
import picgo from '@core/picgo'
import uploader from 'apis/app/uploader'
import { handleSecondaryUpload, uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import {
app,
clipboard,
@@ -10,31 +15,23 @@ import {
Tray
} from 'electron'
import fs from 'fs-extra'
import { cloneDeep } from 'lodash'
import db, { GalleryDB } from '@core/datastore'
import picgo from '@core/picgo'
import uploader from 'apis/app/uploader'
import { handleSecondaryUpload, uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import { cloneDeep } from 'lodash-es'
import pkg from 'root/package.json'
import { IPasteStyle, IWindowList } from '#/types/enum'
import { IBounds, ImgInfo } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
import { buildPicBedListMenu } from '~/events/remotes/menu'
import { T } from '~/i18n'
import clipboardPoll from '~/utils/clipboardPoll'
import { ensureFilePath, handleCopyUrl, setTray, tray } from '~/utils/common'
import { isMacOSVersionGreaterThanOrEqualTo } from '~/utils/getMacOSVersion'
import pasteTemplate from '~/utils/pasteTemplate'
import { configPaths } from '#/utils/configPaths'
import { IPasteStyle, IWindowList } from '#/types/enum'
import pkg from 'root/package.json'
import { hideMiniWindow, openMainWindow, openMiniWindow } from '~/utils/windowHelper'
let contextMenu: Menu | null
export function setDockMenu() {
export function setDockMenu () {
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const dockMenu = Menu.buildFromTemplate([
{
@@ -43,7 +40,7 @@ export function setDockMenu() {
},
{
label: T('START_WATCH_CLIPBOARD'),
click() {
click () {
db.set(configPaths.settings.isListeningClipboard, true)
clipboardPoll.startListening()
clipboardPoll.on('change', () => {
@@ -56,7 +53,7 @@ export function setDockMenu() {
},
{
label: T('STOP_WATCH_CLIPBOARD'),
click() {
click () {
db.set(configPaths.settings.isListeningClipboard, false)
clipboardPoll.stopListening()
clipboardPoll.removeAllListeners()
@@ -65,10 +62,10 @@ export function setDockMenu() {
visible: isListeningClipboard
}
])
app.dock.setMenu(dockMenu)
app.dock?.setMenu(dockMenu)
}
export function createMenu() {
export function createMenu () {
const submenu = buildPicBedListMenu()
const appMenu = Menu.buildFromTemplate([
{
@@ -77,7 +74,7 @@ export function createMenu() {
{ label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
{
label: T('RELOAD_APP'),
click() {
click () {
app.relaunch()
app.exit(0)
}
@@ -105,7 +102,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 =
@@ -130,7 +127,7 @@ export function createContextMenu() {
if (process.platform === 'darwin' || process.platform === 'win32') {
const submenu = buildPicBedListMenu()
const template: Array<MenuItemConstructorOptions | MenuItem> = [
const template: (MenuItemConstructorOptions | MenuItem)[] = [
{ label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
{ label: T('CHOOSE_DEFAULT_PICBED'), type: 'submenu', submenu },
{
@@ -145,7 +142,7 @@ export function createContextMenu() {
},
{
label: T('RELOAD_APP'),
click() {
click () {
app.relaunch()
app.exit(0)
}
@@ -158,7 +155,7 @@ export function createContextMenu() {
0,
{
label: T('OPEN_MINI_WINDOW'),
click() {
click () {
openMiniWindow(false)
},
visible: !isMiniWindowVisible
@@ -183,7 +180,7 @@ export function createContextMenu() {
{ label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
{
label: T('OPEN_MINI_WINDOW'),
click() {
click () {
openMiniWindow(false)
},
visible: !isMiniWindowVisible
@@ -205,7 +202,7 @@ export function createContextMenu() {
},
{
label: T('ABOUT'),
click() {
click () {
dialog.showMessageBox({
title: 'PicList',
message: 'PicList',
@@ -222,13 +219,13 @@ export function createContextMenu() {
const getTrayIcon = () => {
if (process.platform === 'darwin') {
const isMacOSGreaterThan11 = isMacOSVersionGreaterThanOrEqualTo('11')
return isMacOSGreaterThan11 ? `${__static}/menubar-newdarwinTemplate.png` : `${__static}/menubar.png`
return isMacOSGreaterThan11 ? './resources/menubar-newdarwinTemplate.png' : './resources/menubar.png'
} else {
return `${__static}/menubar-nodarwin.png`
return './resources/menubar-nodarwin.png'
}
}
export function createTray(tooltip: string) {
export function createTray (tooltip: string) {
const menubarPic = getTrayIcon()
setTray(new Tray(menubarPic))
tray.setToolTip(tooltip)
@@ -241,6 +238,7 @@ export function createTray(tooltip: string) {
createContextMenu()
tray!.popUpContextMenu(contextMenu!)
})
tray.on('click', (_, bounds) => {
if (process.platform === 'darwin') {
toggleWindow(bounds)
@@ -293,9 +291,9 @@ export function createTray(tooltip: string) {
tray.on('drag-enter', () => {
if (nativeTheme.shouldUseDarkColors) {
tray!.setImage(`${__static}/upload-dark.png`)
tray!.setImage('./resources/upload-dark.png')
} else {
tray!.setImage(`${__static}/upload.png`)
tray!.setImage('./resources/upload.png')
}
})
@@ -305,54 +303,56 @@ export function createTray(tooltip: string) {
// drop-files only be supported in macOS
// so the tray window must be available
tray.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)!
const { needRestore, ctx } = await handleSecondaryUpload(trayWindow.webContents, files, 'tray')
let imgs: ImgInfo[] | false = false
if (needRestore) {
const res = await uploader
.setWebContents(trayWindow.webContents)
.uploadReturnCtx(ctx ? ctx.processedInput : files, true)
imgs = res ? res.output : false
} else {
imgs = await uploader.setWebContents(trayWindow.webContents).upload(files)
}
const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false
if (imgs !== false) {
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
if (deleteLocalFile) {
await fs.remove(rawInput[i])
}
const [pasteTextItem, shortUrl] = await pasteTemplate(
pasteStyle,
imgs[i],
db.get(configPaths.settings.customLink)
)
imgs[i].shortUrl = shortUrl
pasteText.push(pasteTextItem)
const isShowResultNotification =
if (process.platform === 'darwin') {
(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)!
const { needRestore, ctx } = await handleSecondaryUpload(trayWindow.webContents, files, 'tray')
let imgs: ImgInfo[] | false = false
if (needRestore) {
const res = await uploader
.setWebContents(trayWindow.webContents)
.uploadReturnCtx(ctx ? ctx.processedInput : files, true)
imgs = res ? res.output : false
} else {
imgs = await uploader.setWebContents(trayWindow.webContents).upload(files)
}
const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false
if (imgs !== false) {
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
if (deleteLocalFile) {
await fs.remove(rawInput[i])
}
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!
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),
body: shortUrl || imgs[i].imgUrl!
// icon: files[i]
})
setTimeout(() => {
notification.show()
}, i * 100)
})
setTimeout(() => {
notification.show()
}, i * 100)
}
await GalleryDB.getInstance().insert(imgs[i])
}
await GalleryDB.getInstance().insert(imgs[i])
handleCopyUrl(pasteText.join('\n'))
trayWindow.webContents.send('dragFiles', imgs)
}
handleCopyUrl(pasteText.join('\n'))
trayWindow.webContents.send('dragFiles', imgs)
}
})
})
}
// toggleWindow()
} else if (process.platform === 'linux') {
// click事件在Ubuntu上无法触发Unity不支持在Mac和Windows上可以触发

View File

@@ -1,21 +1,19 @@
import { Notification, WebContents } from 'electron'
import fs from 'fs-extra'
import { cloneDeep } from 'lodash'
import picgo from '@core/picgo'
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 { T } from '~/i18n/index'
import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common'
import pasteTemplate from '~/utils/pasteTemplate'
import { Notification, WebContents } from 'electron'
import fs from 'fs-extra'
import { cloneDeep } from 'lodash-es'
import { IPicGo } from 'piclist'
import { IPasteStyle, IWindowList } from '#/types/enum'
import { IFileWithPath, ImgInfo, IStringKeyMap, IUploadOption } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
import { T } from '~/i18n/index'
import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common'
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
import { IPicGo } from 'piclist'
import pasteTemplate from '~/utils/pasteTemplate'
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
const useBuiltinClipboard =
@@ -213,8 +211,8 @@ export const handleSecondaryUpload = async (
trayWindow?.webContents?.send('uploadFiles', secondImgs)
}
} else {
for (let i = 0; i < secondImgs.length; i++) {
await GalleryDB.getInstance().insert(secondImgs[i])
for (const secondImgsItem of secondImgs) {
await GalleryDB.getInstance().insert(secondImgsItem)
}
if (uploadType === 'tray') {
trayWindow?.webContents?.send('dragFiles', secondImgs)

View File

@@ -1,28 +1,27 @@
import dayjs from 'dayjs'
import { BrowserWindow, clipboard, ipcMain, Notification, WebContents } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import { IPicGo } from 'piclist'
import util from 'util'
import writeFile from 'write-file-atomic'
import windowManager from 'apis/app/window/windowManager'
import path from 'node:path'
import util from 'node:util'
import db from '@core/datastore'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import { T } from '~/i18n'
import { showNotification, getClipboardFilePath, calcDurationRange } from '~/utils/common'
import windowManager from 'apis/app/window/windowManager'
import dayjs from 'dayjs'
import { BrowserWindow, clipboard, ipcMain, IpcMainEvent, Notification, WebContents } from 'electron'
import fs from 'fs-extra'
import { IPicGo } from 'piclist'
import writeFile from 'write-file-atomic'
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME, TALKING_DATA_EVENT } from '#/events/constants'
import { ICOREBuildInEvent, IWindowList } from '#/types/enum'
import { IAnalyticsData, ImgInfo, ITalkingDataOptions, IUploadOption } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static'
import { T } from '~/i18n'
import { calcDurationRange, getClipboardFilePath, showNotification } from '~/utils/common'
const waitForRename = (window: BrowserWindow, id: number): Promise<string | null> => {
return new Promise(resolve => {
ipcMain.once(`${RENAME_FILE_NAME}${id}`, (_: Event, newName: string) => {
ipcMain.once(`${RENAME_FILE_NAME}${id}`, (_: IpcMainEvent, newName: string) => {
resolve(newName)
window.close()
})
@@ -52,12 +51,12 @@ const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) =>
class Uploader {
private webContents: WebContents | null = null
constructor() {
constructor () {
this.init()
}
init() {
picgo.on(ICOREBuildInEvent.NOTIFICATION, (message: Electron.NotificationConstructorOptions | undefined) => {
init () {
picgo.on(ICOREBuildInEvent.NOTIFICATION, (message: any) => {
new Notification(message).show()
})
@@ -88,7 +87,7 @@ class Uploader {
: item.fileName
if (rename) {
const window = windowManager.create(IWindowList.RENAME_WINDOW)!
ipcMain.on(GET_RENAME_FILE_NAME, evt => {
ipcMain.on(GET_RENAME_FILE_NAME, (evt, _) => {
try {
if (evt.sender.id === window.webContents.id) {
logger.info('rename window ready, wait for rename...')
@@ -108,12 +107,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
@@ -131,7 +130,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()
@@ -147,7 +146,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()
@@ -163,7 +162,7 @@ class Uploader {
}
}
async uploadReturnCtx(img?: IUploadOption, skipProcess = false): Promise<IPicGo | false> {
async uploadReturnCtx (img?: IUploadOption, skipProcess = false): Promise<IPicGo | false> {
try {
const startTime = Date.now()
const ctx = await picgo.uploadReturnCtx(img, skipProcess)
@@ -198,7 +197,7 @@ class Uploader {
}
}
async upload(img?: IUploadOption): Promise<ImgInfo[] | false> {
async upload (img?: IUploadOption): Promise<ImgInfo[] | false> {
try {
const startTime = Date.now()
const output = await picgo.upload(img)

View File

@@ -2,25 +2,27 @@ const isDevelopment = process.env.NODE_ENV !== 'production'
export const MANUAL_WINDOW_URL =
process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#documents`
: 'picgo://./index.html#documents'
? 'http://localhost:3000#documents'
: 'index.html#documents'
export const MINI_WINDOW_URL = isDevelopment
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#mini-page`
: 'picgo://./index.html#mini-page'
? 'http://localhost:3000#mini-page'
: 'index.html#mini-page'
export const RENAME_WINDOW_URL =
process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#rename-page`
: 'picgo://./index.html#rename-page'
? 'http://localhost:3000#rename-page'
: 'index.html#rename-page'
export const SETTING_WINDOW_URL = isDevelopment
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#main-page/upload`
: 'picgo://./index.html#main-page/upload'
? 'http://localhost:3000#main-page/upload'
: 'index.html#main-page/upload'
export const TRAY_WINDOW_URL = isDevelopment ? (process.env.WEBPACK_DEV_SERVER_URL as string) : 'picgo://./index.html'
export const TRAY_WINDOW_URL = isDevelopment ? 'http://localhost:3000' : 'index.html'
console.log(TRAY_WINDOW_URL)
export const TOOLBOX_WINDOW_URL =
process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#toolbox-page`
: 'picgo://./index.html#toolbox-page'
? 'http://localhost:3000#toolbox-page'
: 'index.html#toolbox-page'

View File

@@ -1,24 +1,26 @@
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 } from 'electron'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { IWindowListItem } from '#/types/electron'
import { IWindowList } from '#/types/enum'
import { IBrowserWindowOptions } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
import { T } from '~/i18n'
import {
MANUAL_WINDOW_URL,
MINI_WINDOW_URL,
RENAME_WINDOW_URL,
SETTING_WINDOW_URL,
TRAY_WINDOW_URL,
TOOLBOX_WINDOW_URL
TOOLBOX_WINDOW_URL,
TRAY_WINDOW_URL
} from './constants'
import bus from '@core/bus'
import { CREATE_APP_MENU } from '@core/bus/constants'
import db from '@core/datastore'
import { T } from '~/i18n'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
const windowList = new Map<IWindowList, IWindowListItem>()
const handleWindowParams = (windowURL: string) => windowURL
@@ -33,6 +35,7 @@ const getDefaultWindowSizes = (): { width: number; height: number } => {
height: mainWindowHeight || 800
}
}
const preloadPath = fileURLToPath(new URL('../preload/index.mjs', import.meta.url))
const { width: defaultWindowWidth, height: defaultWindowHeight } = getDefaultWindowSizes()
@@ -46,8 +49,10 @@ const trayWindowOptions = {
transparent: true,
vibrancy: 'ultra-dark',
webPreferences: {
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
sandbox: false,
preload: preloadPath,
nodeIntegration: false,
contextIsolation: true,
nodeIntegrationInWorker: true,
backgroundThrottling: false,
webSecurity: false
@@ -66,10 +71,12 @@ const manualWindowOptions = {
vibrancy: 'ultra-dark',
transparent: false,
webPreferences: {
sandbox: false,
webviewTag: true,
backgroundThrottling: false,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
preload: preloadPath,
nodeIntegration: false,
contextIsolation: true,
nodeIntegrationInWorker: true,
webSecurity: false
}
@@ -88,10 +95,12 @@ const settingWindowOptions = {
transparent: true,
titleBarStyle: 'hidden',
webPreferences: {
sandbox: false,
webviewTag: true,
backgroundThrottling: false,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
preload: preloadPath,
nodeIntegration: false,
contextIsolation: true,
nodeIntegrationInWorker: true,
webSecurity: false
}
@@ -102,7 +111,7 @@ if (process.platform !== 'darwin') {
settingWindowOptions.frame = false
settingWindowOptions.backgroundColor = '#3f3c37'
settingWindowOptions.transparent = false
settingWindowOptions.icon = `${__static}/logo.png`
settingWindowOptions.icon = '.resources/logo.png'
}
const miniWindowOptions = {
@@ -114,11 +123,13 @@ const miniWindowOptions = {
skipTaskbar: true,
resizable: false,
transparent: process.platform !== 'linux',
icon: `${__static}/logo.png`,
icon: './resources/logo.png',
webPreferences: {
sandbox: false,
preload: preloadPath,
nodeIntegration: false,
contextIsolation: true,
backgroundThrottling: false,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true
}
} as IBrowserWindowOptions
@@ -135,8 +146,10 @@ const renameWindowOptions = {
resizable: false,
vibrancy: 'ultra-dark',
webPreferences: {
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
sandbox: false,
preload: preloadPath,
nodeIntegration: false,
contextIsolation: true,
nodeIntegrationInWorker: true,
backgroundThrottling: false
}
@@ -159,11 +172,13 @@ const toolboxWindowOptions = {
resizable: false,
title: `PicList ${T('TOOLBOX')}`,
vibrancy: 'ultra-dark',
icon: `${__static}/logo.png`,
icon: './resources/logo.png',
webPreferences: {
sandbox: false,
backgroundThrottling: false,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
preload: preloadPath,
nodeIntegration: false,
contextIsolation: true,
nodeIntegrationInWorker: true,
webSecurity: false
}
@@ -179,7 +194,7 @@ windowList.set(IWindowList.TRAY_WINDOW, {
isValid: process.platform !== 'linux',
multiple: false,
options: () => trayWindowOptions,
callback(window) {
callback (window) {
window.loadURL(handleWindowParams(TRAY_WINDOW_URL))
window.on('blur', () => {
window.hide()
@@ -191,7 +206,7 @@ windowList.set(IWindowList.MANUAL_WINDOW, {
isValid: true,
multiple: false,
options: () => manualWindowOptions,
callback(window) {
callback (window) {
window.loadURL(handleWindowParams(MANUAL_WINDOW_URL))
window.focus()
}
@@ -201,8 +216,9 @@ windowList.set(IWindowList.SETTING_WINDOW, {
isValid: true,
multiple: false,
options: () => settingWindowOptions,
callback(window, windowManager) {
callback (window, windowManager) {
window.loadURL(handleWindowParams(SETTING_WINDOW_URL))
window.webContents.openDevTools({ mode: 'detach' })
window.on('closed', () => {
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
if (process.platform === 'linux') {
@@ -220,7 +236,7 @@ windowList.set(IWindowList.MINI_WINDOW, {
isValid: process.platform !== 'darwin',
multiple: false,
options: () => miniWindowOptions,
callback(window) {
callback (window) {
window.loadURL(handleWindowParams(MINI_WINDOW_URL))
}
})
@@ -229,7 +245,7 @@ windowList.set(IWindowList.RENAME_WINDOW, {
isValid: true,
multiple: true,
options: () => renameWindowOptions,
async callback(window, windowManager) {
async callback (window, windowManager) {
window.loadURL(handleWindowParams(RENAME_WINDOW_URL))
const currentWindow = windowManager.getAvailableWindow(true)
if (currentWindow && currentWindow.isVisible()) {
@@ -245,7 +261,7 @@ windowList.set(IWindowList.TOOLBOX_WINDOW, {
isValid: true,
multiple: false,
options: () => toolboxWindowOptions,
async callback(window, windowManager) {
async callback (window, windowManager) {
window.loadURL(TOOLBOX_WINDOW_URL)
const currentWindow = windowManager.getAvailableWindow(true)
if (currentWindow && currentWindow.isVisible()) {

View File

@@ -1,13 +1,14 @@
import windowList from 'apis/app/window/windowList'
import { BrowserWindow } from 'electron'
import windowList from 'apis/app/window/windowList'
import { IWindowListItem, IWindowManager } from '#/types/electron'
import { IWindowList } from '#/types/enum'
class WindowManager implements IWindowManager {
#windowMap: Map<IWindowList | string, BrowserWindow> = new Map()
#windowIdMap: Map<number, IWindowList | string> = new Map()
create(name: IWindowList) {
create (name: IWindowList) {
const windowConfig: IWindowListItem = windowList.get(name)!
if (!windowConfig.isValid) return null
@@ -29,14 +30,14 @@ class WindowManager implements IWindowManager {
return window
}
get(name: IWindowList) {
get (name: IWindowList) {
if (this.has(name)) {
return this.#windowMap.get(name)!
}
return this.create(name)
}
has(name: IWindowList) {
has (name: IWindowList) {
return this.#windowMap.has(name)
}
@@ -48,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

View File

@@ -1,4 +1,3 @@
import bus from '@core/bus/index'
import {
GET_SETTING_WINDOW_ID,
GET_SETTING_WINDOW_ID_RESPONSE,
@@ -9,6 +8,9 @@ import {
UPLOAD_WITH_FILES,
UPLOAD_WITH_FILES_RESPONSE
} from '@core/bus/constants'
import bus from '@core/bus/index'
import { IFileWithPath } from '#/types/types'
export const uploadWithClipboardFiles = (): Promise<{
success: boolean

View File

@@ -1,4 +1,4 @@
import { EventEmitter } from 'events'
import { EventEmitter } from 'node:events'
const bus = new EventEmitter()

View File

@@ -1,11 +1,12 @@
import { app } from 'electron'
import fs from 'fs-extra'
import dayjs from 'dayjs'
import path from 'path'
import writeFile from 'write-file-atomic'
import path from 'node:path'
import { getLogger } from '@core/utils/localLogger'
import dayjs from 'dayjs'
import { app } from 'electron'
import fs from 'fs-extra'
import writeFile from 'write-file-atomic'
import { notificationList } from '#/utils/notification'
import { T } from '~/i18n'
const STORE_PATH = app.getPath('userData')
@@ -22,10 +23,7 @@ const errorMsg = {
brokenButBackup: T('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP')
}
/** ensure notification list */
if (!global.notificationList) global.notificationList = []
function dbChecker() {
function dbChecker () {
if (process.type !== 'renderer') {
// db save bak
try {
@@ -63,16 +61,16 @@ function dbChecker() {
optionsTpl.body = `${errorMsg.brokenButBackup}\n${T('TIPS_PICGO_BACKUP_FILE_VERSION', {
v: dayjs(stats.mtime).format('YYYY-MM-DD HH:mm:ss')
})}`
global.notificationList?.push(optionsTpl)
notificationList.push(optionsTpl)
return
} catch (e) {
optionsTpl.body = errorMsg.broken
global.notificationList?.push(optionsTpl)
notificationList.push(optionsTpl)
return
}
}
optionsTpl.body = errorMsg.broken
global.notificationList?.push(optionsTpl)
notificationList.push(optionsTpl)
return
}
writeFile.sync(configFileBackupPath, configFile, { encoding: 'utf-8' })
@@ -82,7 +80,7 @@ function dbChecker() {
/**
* Get config path
*/
function dbPathChecker(): string {
function dbPathChecker (): string {
if (_configFilePath) {
return _configFilePath
}
@@ -113,7 +111,7 @@ function dbPathChecker(): string {
title: T('TIPS_NOTICE'),
body: T('TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR')
}
global.notificationList?.push(optionsTpl)
notificationList.push(optionsTpl)
hasCheckPath = true
}
logger('error', e)
@@ -122,11 +120,11 @@ function dbPathChecker(): string {
}
}
function dbPathDir() {
function dbPathDir () {
return path.dirname(dbPathChecker())
}
function getGalleryDBPath(): {
function getGalleryDBPath (): {
dbPath: string
dbBackupPath: string
} {

View File

@@ -1,13 +1,13 @@
import fs from 'fs-extra'
import { DBStore, JSONStore } from '@picgo/store'
import { dbPathChecker, dbPathDir, getGalleryDBPath } from '@core/datastore/dbChecker'
import { T } from '~/i18n'
import { configPaths } from '#/utils/configPaths'
import { IJSON } from '@picgo/store/dist/types'
import { DBStore, JSONStore } from '@piclist/store'
import fs from 'fs-extra'
import { IConfig } from 'piclist'
import { configPaths } from '#/utils/configPaths'
import { T } from '~/i18n'
interface IJSON {
[propsName: string]: string | number | IJSON
}
const STORE_PATH = dbPathDir()
if (!fs.pathExistsSync(STORE_PATH)) {
@@ -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')
}

View File

@@ -1,27 +1,27 @@
import debounce from 'lodash/debounce'
import { PicGo } from 'piclist'
import db from '@core/datastore'
import { dbChecker, dbPathChecker } from '@core/datastore/dbChecker'
import { debounce } from 'lodash-es'
import { PicGo } from 'piclist'
import pkg from 'root/package.json'
import { IStringKeyMap } from '#/types/types'
const CONFIG_PATH = dbPathChecker()
dbChecker()
const picgo = new PicGo(CONFIG_PATH)
const picgo = await PicGo.create(CONFIG_PATH)
picgo.saveConfig({
debug: true,
PICGO_ENV: 'GUI'
})
global.PICGO_GUI_VERSION = pkg.version
picgo.GUI_VERSION = global.PICGO_GUI_VERSION
picgo.GUI_VERSION = pkg.version
const originPicGoSaveConfig = picgo.saveConfig.bind(picgo)
function flushDB() {
function flushDB () {
db.read(true)
}

View File

@@ -1,6 +1,9 @@
import fs from 'fs-extra'
import util from 'node:util'
import dayjs from 'dayjs'
import util from 'util'
import fs from 'fs-extra'
import { ILogArgvTypeWithError } from '#/types/types'
const MB = 1024 * 1024
const DEFAULT_LOG_FILE_SIZE_LIMIT = 10 * MB

View File

@@ -0,0 +1,49 @@
import path from 'node:path'
import axios from 'axios'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
interface IConfigMap {
fileName: string
config: {
version: string
url: string
uploadPath: string
token: string
}
}
export default class AlistApi {
static async delete (configMap: IConfigMap): Promise<boolean> {
const { fileName, config } = configMap
try {
const { version, url, uploadPath, token } = config
if (String(version) === '2') {
deleteLog(fileName, 'Alist', false, 'Alist version 2 is not supported, deletion is skipped')
return true
}
const result = await axios.request({
method: 'post',
url: `${url}/api/fs/remove`,
headers: {
'Content-Type': 'application/json',
Authorization: token
},
data: {
dir: path.join('/', uploadPath, path.dirname(fileName)),
names: [path.basename(fileName)]
}
})
if (result.data.code === 200) {
deleteLog(fileName, 'Alist')
return true
}
deleteLog(fileName, 'Alist', false)
return false
} catch (error: any) {
deleteFailedLog(fileName, 'Alist', error)
return false
}
}
}

View File

@@ -0,0 +1,64 @@
import path from 'node:path'
import axios from 'axios'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
interface IConfigMap {
fileName: string
config: {
url: string
username: string
password: string
uploadPath: string
token: string
}
}
const getAListToken = async (url: string, username: string, password: string) => {
const res = await axios.post(`${url}/api/auth/login`, {
username,
password
})
if (res.data.code === 200 && res.data.message === 'success') {
return res.data.data.token
}
}
export default class AListplistApi {
static async delete (configMap: IConfigMap): Promise<boolean> {
const { fileName, config } = configMap
try {
const { url, username, password, uploadPath } = config
let token = config.token
if (!token) {
token = await getAListToken(url, username, password)
}
if (!url || !(token || (username && password))) {
deleteFailedLog(fileName, 'Alist', 'No valid token or username/password provided')
return false
}
const result = await axios.request({
method: 'post',
url: `${url}/api/fs/remove`,
headers: {
'Content-Type': 'application/json',
Authorization: token
},
data: {
dir: path.join('/', uploadPath, path.dirname(fileName)),
names: [path.basename(fileName)]
}
})
if (result.data.code === 200) {
deleteLog(fileName, 'Alist')
return true
}
deleteLog(fileName, 'Alist', false)
return false
} catch (error: any) {
deleteFailedLog(fileName, 'Alist', error)
return false
}
}
}

View File

@@ -0,0 +1,33 @@
import OSS from 'ali-oss'
import { IAliYunConfig, PartialKeys } from '#/types/types'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
interface IConfigMap {
fileName: string
config: PartialKeys<IAliYunConfig, 'path'>
}
export default class AliyunApi {
static #getKey (fileName: string, path?: string): string {
return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${fileName}` : fileName
}
static async delete (configMap: IConfigMap): Promise<boolean> {
const { fileName, config } = configMap
try {
const client = new OSS({ ...config, region: config.area })
const key = AliyunApi.#getKey(fileName, config.path)
const result = await client.delete(key)
if (result.res.status === 204) {
deleteLog(fileName, 'Aliyun')
return true
}
deleteLog(fileName, 'Aliyun', false)
return false
} catch (error: any) {
deleteFailedLog(fileName, 'Aliyun', error)
return false
}
}
}

View File

@@ -0,0 +1,46 @@
import { IStringKeyMap } from '#/types/types'
import AlistApi from '~/apis/delete/alist'
import AlistplistApi from '~/apis/delete/alistplist'
import AliyunApi from '~/apis/delete/aliyun'
import AwsS3Api from '~/apis/delete/awss3'
import DogeCloudApi from '~/apis/delete/dogecloud'
import GithubApi from '~/apis/delete/github'
import HuaweicloudApi from '~/apis/delete/huaweiyun'
import ImgurApi from '~/apis/delete/imgur'
import LocalApi from '~/apis/delete/local'
import LskyplistApi from '~/apis/delete/lskyplist'
import PiclistApi from '~/apis/delete/piclist'
import QiniuApi from '~/apis/delete/qiniu'
import SftpPlistApi from '~/apis/delete/sftpplist'
import SmmsApi from '~/apis/delete/smms'
import TcyunApi from '~/apis/delete/tcyun'
import UpyunApi from '~/apis/delete/upyun'
import WebdavApi from '~/apis/delete/webdav'
const apiMap: IStringKeyMap = {
alist: AlistApi,
alistplist: AlistplistApi,
aliyun: AliyunApi,
'aws-s3': AwsS3Api,
'aws-s3-plist': AwsS3Api,
dogecloud: DogeCloudApi,
github: GithubApi,
'huaweicloud-uploader': HuaweicloudApi,
imgur: ImgurApi,
local: LocalApi,
lskyplist: LskyplistApi,
piclist: PiclistApi,
qiniu: QiniuApi,
sftpplist: SftpPlistApi,
smms: SmmsApi,
tcyun: TcyunApi,
upyun: UpyunApi,
webdavplist: WebdavApi
}
export default class ALLApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
const api = apiMap[configMap.type]
return api ? await api.delete(configMap) : false
}
}

View File

@@ -0,0 +1,15 @@
import { getRawData } from '@/utils/common'
import { IStringKeyMap } from '#/types/types'
import { removeFileFromS3InMain } from '~/utils/deleteFunc'
import { deleteFailedLog } from '~/utils/deleteLog'
export default class AwsS3Api {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
try {
return await removeFileFromS3InMain(getRawData(configMap))
} catch (error: any) {
deleteFailedLog(configMap.fileName, 'AWS S3', error)
return false
}
}
}

View File

@@ -0,0 +1,15 @@
import { getRawData } from '@/utils/common'
import { IStringKeyMap } from '#/types/types'
import { removeFileFromDogeInMain } from '~/utils/deleteFunc'
import { deleteFailedLog } from '~/utils/deleteLog'
export default class AwsS3Api {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
try {
return await removeFileFromDogeInMain(getRawData(configMap))
} catch (error: any) {
deleteFailedLog(configMap.fileName, 'DogeCloud', error)
return false
}
}
}

View File

@@ -0,0 +1,53 @@
import { Octokit } from '@octokit/rest'
import { IGitHubConfig, PartialKeys } from '#/types/types'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
interface IConfigMap {
fileName: string
hash: string
config: PartialKeys<IGitHubConfig, 'path'>
}
export default class GithubApi {
static #createOctokit (token: string) {
return new Octokit({
auth: token
})
}
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> {
const {
fileName,
hash,
config: { repo, token, branch, path }
} = configMap
const [owner, repoName] = repo.split('/')
const octokit = GithubApi.#createOctokit(token)
const key = GithubApi.#createKey(path, fileName)
try {
const { status } = await octokit.rest.repos.deleteFile({
owner,
repo: repoName,
path: key,
message: `delete ${fileName} by PicList`,
sha: hash,
branch
})
if (status === 200) {
deleteLog(fileName, 'GitHub')
return true
}
deleteLog(fileName, 'GitHub', false)
return false
} catch (error: any) {
deleteFailedLog(fileName, 'GitHub', error)
return false
}
}
}

View File

@@ -0,0 +1,15 @@
import { getRawData } from '@/utils/common'
import { IStringKeyMap } from '#/types/types'
import { removeFileFromHuaweiInMain } from '~/utils/deleteFunc'
import { deleteFailedLog } from '~/utils/deleteLog'
export default class HuaweicloudApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
try {
return await removeFileFromHuaweiInMain(getRawData(configMap))
} catch (error: any) {
deleteFailedLog(configMap.fileName, 'HuaweiCloud', error)
return false
}
}
}

View File

@@ -0,0 +1,44 @@
import axios, { AxiosResponse } from 'axios'
import { IImgurConfig } from '#/types/types'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
interface IConfigMap {
config?: Partial<IImgurConfig>
hash?: string
}
export default class ImgurApi {
static #baseUrl = 'https://api.imgur.com/3'
static async delete (configMap: IConfigMap): Promise<boolean> {
const { config: { clientId = '', username = '', accessToken = '' } = {}, hash = '' } = configMap
let Authorization: string, apiUrl: string
if (username && accessToken) {
Authorization = `Bearer ${accessToken}`
apiUrl = `${ImgurApi.#baseUrl}/account/${username}/image/${hash}`
} else if (clientId) {
Authorization = `Client-ID ${clientId}`
apiUrl = `${ImgurApi.#baseUrl}/image/${hash}`
} else {
deleteLog(hash, 'Imgur', false, 'No credentials found')
return false
}
try {
const response: AxiosResponse = await axios.delete(apiUrl, {
headers: { Authorization },
timeout: 30000
})
if (response.status === 200) {
deleteLog(hash, 'Imgur')
return true
}
deleteLog(hash, 'Imgur', false)
return false
} catch (error: any) {
deleteFailedLog(hash, 'Imgur', error)
return false
}
}
}

View File

@@ -0,0 +1,26 @@
import fs from 'fs-extra'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
interface IConfigMap {
hash: string
}
export default class LocalApi {
static async delete (configMap: IConfigMap): Promise<boolean> {
const { hash } = configMap
if (!hash) {
deleteLog(hash, 'Local', false, 'Local.delete: invalid params')
return false
}
try {
await fs.remove(hash)
deleteLog(hash, 'Local')
return true
} catch (error: any) {
deleteFailedLog(hash, 'Local', error)
return false
}
}
}

View File

@@ -0,0 +1,47 @@
import https from 'node:https'
import axios, { AxiosResponse } from 'axios'
import { IStringKeyMap } from '#/types/types'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
export default class LskyplistApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
const { hash, config } = configMap
if (!hash || !config || !config.token) {
deleteLog(hash, 'Lskyplist', false, 'LskyplistApi.delete: invalid params')
return false
}
const { host, token, version } = config
if (version !== 'V2') {
deleteLog(hash, 'Lskyplist', false, 'LskyplistApi.delete: invalid version')
return false
}
const v2Headers = {
Accept: 'application/json',
Authorization: token || undefined
}
const requestAgent = new https.Agent({
rejectUnauthorized: false
})
try {
const response: AxiosResponse = await axios.delete(`${host}/api/v1/images/${hash}`, {
headers: v2Headers,
timeout: 30000,
httpsAgent: requestAgent
})
if (response.status === 200 && response.data.status === true) {
deleteLog(hash, 'Lskyplist')
return true
}
deleteLog(hash, 'Lskyplist', false)
return false
} catch (error: any) {
deleteFailedLog(hash, 'Lskyplist', error)
return false
}
}
}

View File

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

View File

@@ -0,0 +1,44 @@
import { IQiniuConfig, PartialKeys } from '#/types/types'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
interface IConfigMap {
fileName: string
config: PartialKeys<IQiniuConfig, 'path'>
}
export default class QiniuApi {
static async delete (configMap: IConfigMap): Promise<boolean> {
const {
fileName,
config: { accessKey, secretKey, bucket, path }
} = configMap
const mac = new window.node.qiniu.auth.digest.Mac(accessKey, secretKey)
const qiniuConfig = new window.node.qiniu.conf.Config()
try {
const bucketManager = new window.node.qiniu.rs.BucketManager(mac, qiniuConfig)
const formattedPath = path?.replace(/^\/+|\/+$/, '') || ''
const key = path === '/' || !path ? fileName : `${formattedPath}/${fileName}`
const res = (await new Promise((resolve, reject) => {
bucketManager.delete(bucket, key, (err, respBody, respInfo) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
}
})
})) as any
if (res?.respInfo?.statusCode === 200) {
deleteLog(fileName, 'Qiniu')
return true
}
deleteLog(fileName, 'Qiniu', false)
return false
} catch (error: any) {
deleteFailedLog(fileName, 'Qiniu', error)
return false
}
}
}

View File

@@ -0,0 +1,16 @@
import { getRawData } from '@/utils/common'
import { IStringKeyMap } from '#/types/types'
import { removeFileFromSFTPInMain } from '~/utils/deleteFunc'
import { deleteFailedLog } from '~/utils/deleteLog'
export default class SftpPlistApi {
static async delete (configMap: IStringKeyMap): Promise<boolean> {
const { fileName, config } = configMap
try {
return await removeFileFromSFTPInMain(getRawData(config), fileName)
} catch (error: any) {
deleteFailedLog(fileName, 'SFTP', error)
return false
}
}
}

View File

@@ -0,0 +1,45 @@
import axios, { AxiosResponse } from 'axios'
import { ISMMSConfig } from '#/types/types'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
interface IConfigMap {
hash?: string
config?: Partial<ISMMSConfig>
}
export default class SmmsApi {
static readonly #baseUrl = 'https://smms.app/api/v2'
static async delete (configMap: IConfigMap): Promise<boolean> {
const { hash, config } = configMap
if (!hash || !config || !config.token) {
deleteLog(hash, 'Smms', false, 'SmmsApi.delete: invalid params')
return false
}
const { token } = config
try {
const response: AxiosResponse = await axios.get(`${SmmsApi.#baseUrl}/delete/${hash}`, {
headers: {
Authorization: token
},
params: {
hash,
format: 'json'
},
timeout: 30000
})
if (response.status === 200) {
deleteLog(hash, 'Smms')
return true
}
deleteLog(hash, 'Smms', false)
return false
} catch (error: any) {
deleteFailedLog(hash, 'Smms', error)
return false
}
}
}

View File

@@ -0,0 +1,46 @@
import COS from 'cos-nodejs-sdk-v5'
import { ITcYunConfig, PartialKeys } from '#/types/types'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
interface IConfigMap {
fileName: string
config: PartialKeys<ITcYunConfig, 'path'>
}
export default class TcyunApi {
static #createCOS (SecretId: string, SecretKey: string): COS {
return new COS({
SecretId,
SecretKey
})
}
static async delete (configMap: IConfigMap): Promise<boolean> {
const {
fileName,
config: { secretId, secretKey, bucket, area, path }
} = configMap
try {
const cos = TcyunApi.#createCOS(secretId, secretKey)
let key
if (path === '/' || !path) {
key = `/${fileName}`
} else {
key = `/${path.replace(/^\/+|\/+$/, '')}/${fileName}`
}
const result = await cos.deleteObject({
Bucket: bucket,
Region: area,
Key: key
})
if (result.statusCode === 204) {
deleteLog(fileName, 'Tcyun')
return true
}
deleteLog(fileName, 'Tcyun', false)
return false
} catch (error: any) {
deleteFailedLog(fileName, 'Tcyun', error)
return false
}
}
}

View File

@@ -0,0 +1,38 @@
import Upyun from 'upyun'
import { IUpYunConfig, PartialKeys } from '#/types/types'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
interface IConfigMap {
fileName: string
config: PartialKeys<IUpYunConfig, 'path'>
}
export default class UpyunApi {
static async delete (configMap: IConfigMap): Promise<boolean> {
const {
fileName,
config: { bucket, operator, password, path }
} = configMap
try {
const service = new Upyun.Service(bucket, operator, password)
const client = new Upyun.Client(service)
let key
if (path === '/' || !path) {
key = fileName
} else {
key = `${path.replace(/^\/+|\/+$/, '')}/${fileName}`
}
const result = await client.deleteFile(key)
if (result) {
deleteLog(fileName, 'Upyun')
return true
}
deleteLog(fileName, 'Upyun', false)
return false
} catch (error: any) {
deleteFailedLog(fileName, 'Upyun', error)
return false
}
}
}

View File

@@ -0,0 +1,42 @@
import { AuthType, createClient, WebDAVClientOptions } from 'webdav'
import { IWebdavPlistConfig, PartialKeys } from '#/types/types'
import { formatEndpoint } from '#/utils/common'
import { deleteFailedLog, deleteLog } from '~/utils/deleteLog'
interface IConfigMap {
fileName: string
config: PartialKeys<IWebdavPlistConfig, 'path'>
}
export default class WebdavApi {
static async delete (configMap: IConfigMap): Promise<boolean> {
const {
fileName,
config: { host, username, password, path, sslEnabled, authType }
} = configMap
const endpoint = formatEndpoint(host, sslEnabled)
const options: WebDAVClientOptions = {
username,
password
}
if (authType === 'digest') {
options.authType = AuthType.Digest
}
const ctx = createClient(endpoint, options)
let key
if (path === '/' || !path) {
key = fileName
} else {
key = `${path.replace(/^\/+|\/+$/, '')}/${fileName}`
}
try {
await ctx.deleteFile(key)
deleteLog(fileName, 'WebDAV')
return true
} catch (error: any) {
deleteFailedLog(fileName, 'WebDAV', error)
return false
}
}
}

View File

@@ -1,42 +1,38 @@
import { BrowserWindow, dialog, ipcMain, Notification } from 'electron'
import fs from 'fs-extra'
import { cloneDeep } from 'lodash'
import { DBStore } from '@picgo/store'
import { getWindowId, getSettingWindowId } from '@core/bus/apis'
import { getSettingWindowId, getWindowId } from '@core/bus/apis'
import db, { GalleryDB } from '@core/datastore'
import { dbPathChecker, defaultConfigPath, getGalleryDBPath } from '@core/datastore/dbChecker'
import { DBStore } from '@piclist/store'
import uploader from 'apis/app/uploader'
import { handleSecondaryUpload } from 'apis/app/uploader/apis'
import { BrowserWindow, dialog, ipcMain, IpcMainEvent, MessageBoxOptions, Notification } from 'electron'
import fs from 'fs-extra'
import { cloneDeep } from 'lodash-es'
import { SHOW_INPUT_BOX } from '#/events/constants'
import { IPasteStyle } from '#/types/enum'
import { IGuiApi, ImgInfo, IShowFileExplorerOption, IShowInputBoxOption, IShowMessageBoxOption, IShowMessageBoxResult, IShowNotificationOption, IUploadOption } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
import { T } from '~/i18n'
import { handleCopyUrl } from '~/utils/common'
import pasteTemplate from '~/utils/pasteTemplate'
import { SHOW_INPUT_BOX } from '#/events/constants'
import { IPasteStyle } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
import { handleSecondaryUpload } from '../app/uploader/apis'
// Cross-process support may be required in the future
class GuiApi implements IGuiApi {
// eslint-disable-next-line no-use-before-define
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()) {
@@ -50,11 +46,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: ''
@@ -63,19 +59,19 @@ class GuiApi implements IGuiApi {
await this.showSettingWindow()
this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
return new Promise<string>(resolve => {
ipcMain.once(SHOW_INPUT_BOX, (_: Event, value: string) => {
ipcMain.once(SHOW_INPUT_BOX, (_: IpcMainEvent, value: string) => {
resolve(value)
})
})
}
async showFileExplorer(options: IShowFileExplorerOption = {}) {
async showFileExplorer (options: IShowFileExplorerOption = {}) {
this.windowId = await getWindowId()
const res = await dialog.showOpenDialog(BrowserWindow.fromId(this.windowId)!, options)
return res.filePaths || []
}
async upload(input: IUploadOption) {
async upload (input: IUploadOption) {
this.windowId = await getWindowId()
const webContents = this.getWebcontentsByWindowId(this.windowId)
const rawInput = cloneDeep(input)
@@ -126,7 +122,7 @@ class GuiApi implements IGuiApi {
return []
}
showNotification(
showNotification (
options: IShowNotificationOption = {
title: '',
body: ''
@@ -139,7 +135,7 @@ class GuiApi implements IGuiApi {
notification.show()
}
showMessageBox(
showMessageBox (
options: IShowMessageBoxOption = {
title: '',
message: '',
@@ -147,12 +143,14 @@ class GuiApi implements IGuiApi {
buttons: ['Yes', 'No']
}
) {
return new Promise<IShowMessageBoxResult>(async resolve => {
this.windowId = await getWindowId()
dialog.showMessageBox(BrowserWindow.fromId(this.windowId)!, options).then(res => {
resolve({
result: res.response,
checkboxChecked: res.checkboxChecked
return new Promise<IShowMessageBoxResult>(resolve => {
getWindowId().then(id => {
this.windowId = id
dialog.showMessageBox(BrowserWindow.fromId(id)!, options as MessageBoxOptions).then(res => {
resolve({
result: res.response,
checkboxChecked: res.checkboxChecked
})
})
})
})
@@ -161,7 +159,7 @@ class GuiApi implements IGuiApi {
/**
* get picgo config/data path
*/
async getConfigPath() {
async getConfigPath () {
const currentConfigPath = dbPathChecker()
const galleryDBPath = getGalleryDBPath().dbPath
return {
@@ -171,12 +169,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
@@ -199,7 +197,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

View File

@@ -1,23 +1,23 @@
import bus from '@core/bus'
import {
CREATE_APP_MENU,
GET_WINDOW_ID,
GET_WINDOW_ID_REPONSE,
GET_SETTING_WINDOW_ID,
GET_SETTING_WINDOW_ID_RESPONSE,
UPLOAD_WITH_FILES,
UPLOAD_WITH_FILES_RESPONSE,
GET_WINDOW_ID,
GET_WINDOW_ID_REPONSE,
UPLOAD_WITH_CLIPBOARD_FILES,
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
UPLOAD_WITH_FILES,
UPLOAD_WITH_FILES_RESPONSE
} from '@core/bus/constants'
import { createMenu } from 'apis/app/system'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum'
import { IFileWithPath } from '#/types/types'
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()
}
}

View File

@@ -1,19 +1,11 @@
import { app, dialog, BrowserWindow, Menu, shell, MenuItemConstructorOptions, MenuItem } from 'electron'
import { PicGo as PicGoCore } from 'piclist'
import db from '@core/datastore'
import picgo from '@core/picgo'
import { uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import GuiApi from 'apis/gui'
import { handlePluginUninstall, handlePluginUpdate } from '~/events/rpc/routes/plugin/utils'
import { T } from '~/i18n'
import clipboardPoll from '~/utils/clipboardPoll'
import { setTrayToolTip } from '~/utils/common'
import getPicBeds from '~/utils/getPicBeds'
import { changeCurrentUploader, changeSecondUploader } from '~/utils/handleUploaderConfig'
import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, shell } from 'electron'
import { PicGo as PicGoCore } from 'piclist'
import pkg from 'root/package.json'
import {
PICGO_CONFIG_PLUGIN,
@@ -23,9 +15,14 @@ import {
SHOW_MAIN_PAGE_QRCODE
} from '#/events/constants'
import { IWindowList } from '#/types/enum'
import { IPicGoPlugin, IUploaderConfig } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
import pkg from 'root/package.json'
import { handlePluginUninstall, handlePluginUpdate } from '~/events/rpc/routes/plugin/utils'
import { T } from '~/i18n'
import clipboardPoll from '~/utils/clipboardPoll'
import { setTrayToolTip } from '~/utils/common'
import getPicBeds from '~/utils/getPicBeds'
import { changeCurrentUploader, changeSecondUploader } from '~/utils/handleUploaderConfig'
import { openMainWindow } from '~/utils/windowHelper'
interface GuiMenuItem {
@@ -37,7 +34,7 @@ const buildMiniPageMenu = () => {
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const ClipboardWatcher = clipboardPoll
const submenu = buildPicBedListMenu()
const template: Array<MenuItemConstructorOptions | MenuItem> = [
const template: (MenuItemConstructorOptions | MenuItem)[] = [
{
label: T('OPEN_MAIN_WINDOW'),
click: openMainWindow
@@ -49,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', () => {
@@ -74,7 +71,7 @@ const buildMiniPageMenu = () => {
},
{
label: T('STOP_WATCH_CLIPBOARD'),
click() {
click () {
db.set(configPaths.settings.isListeningClipboard, false)
ClipboardWatcher.stopListening()
ClipboardWatcher.removeAllListeners()
@@ -84,7 +81,7 @@ const buildMiniPageMenu = () => {
},
{
label: T('RELOAD_APP'),
click() {
click () {
app.relaunch()
app.exit(0)
}
@@ -101,7 +98,7 @@ const buildMainPageMenu = (win: BrowserWindow) => {
const template = [
{
label: T('ABOUT'),
click() {
click () {
dialog.showMessageBox({
title: 'PicList',
message: 'PicList',
@@ -111,31 +108,31 @@ const buildMainPageMenu = (win: BrowserWindow) => {
},
{
label: T('SHOW_PICBED_QRCODE'),
click() {
click () {
win?.webContents?.send(SHOW_MAIN_PAGE_QRCODE)
}
},
{
label: T('OPEN_TOOLBOX'),
click() {
click () {
const window = windowManager.create(IWindowList.TOOLBOX_WINDOW)
window?.show()
}
},
{
label: T('SHOW_DEVTOOLS'),
click() {
click () {
win?.webContents?.openDevTools({ mode: 'detach' })
}
},
{
label: T('FEEDBACK'),
click() {
click () {
const url = 'https://github.com/Kuingsmile/PicList/issues'
shell.openExternal(url)
}
}
] as Array<MenuItemConstructorOptions | MenuItem>
] as (MenuItemConstructorOptions | MenuItem)[]
return Menu.buildFromTemplate(template)
}
@@ -179,10 +176,10 @@ const buildSecondPicBedMenu = () => {
: undefined,
click: !hasSubmenu
? function () {
picgo.saveConfig({
[configPaths.picBed.secondUploader]: item.type
})
}
picgo.saveConfig({
[configPaths.picBed.secondUploader]: item.type
})
}
: undefined
}
})
@@ -236,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')
}
setTrayToolTip(item.type)
picgo.saveConfig({
[configPaths.picBed.current]: item.type,
[configPaths.picBed.uploader]: item.type
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
setTrayToolTip(item.type)
}
: undefined
}
})
@@ -281,7 +278,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
{
label: T('ENABLE_PLUGIN'),
enabled: !plugin.enabled,
click() {
click () {
picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: true
})
@@ -292,7 +289,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
{
label: T('DISABLE_PLUGIN'),
enabled: plugin.enabled,
click() {
click () {
picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: false
})
@@ -310,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)
@@ -318,20 +315,20 @@ 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)
}
}
] as Array<MenuItemConstructorOptions | MenuItem>
] as (MenuItemConstructorOptions | MenuItem)[]
for (const i in plugin.config) {
if (plugin.config[i].config.length > 0) {
const obj = {
label: T('CONFIG_THING', {
c: `${i} - ${plugin.config[i].fullName || plugin.config[i].name}`
}),
click() {
click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const currentType = i
const configName = plugin.config[i].fullName || plugin.config[i].name
@@ -349,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) {
@@ -374,8 +371,8 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
for (const i of plugin.guiMenu) {
menu.push({
label: i.label,
click() {
const picgPlugin = picgo.pluginLoader.getPlugin(plugin.fullName)
async click () {
const picgPlugin = await picgo.pluginLoader.getPlugin(plugin.fullName)
if (picgPlugin?.guiMenu?.(picgo)?.length) {
const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo)
menu.forEach(item => {
@@ -392,4 +389,4 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
return Menu.buildFromTemplate(menu)
}
export { buildMiniPageMenu, buildMainPageMenu, buildPicBedListMenu, buildPluginPageMenu, buildSecondPicBedMenu }
export { buildMainPageMenu, buildMiniPageMenu, buildPicBedListMenu, buildPluginPageMenu, buildSecondPicBedMenu }

View File

@@ -1,8 +1,11 @@
import logger from '@core/picgo/logger'
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from 'electron'
import logger from '@core/picgo/logger'
import { RPC_ACTIONS, RPC_ACTIONS_INVOKE } from '#/events/constants'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { IRPCRoutes, IRPCServer } from '#/types/rpc'
import { galleryRouter } from '~/events/rpc/routes/gallery'
import { manageRouter } from '~/events/rpc/routes/manage'
import { picbedRouter } from '~/events/rpc/routes/picbed'
import { pluginRouter } from '~/events/rpc/routes/plugin'
import { settingRouter } from '~/events/rpc/routes/setting'
@@ -10,10 +13,6 @@ import { systemRouter } from '~/events/rpc/routes/system'
import { toolboxRouter } from '~/events/rpc/routes/toolbox'
import { trayRouter } from '~/events/rpc/routes/tray'
import { uploadRouter } from '~/events/rpc/routes/upload'
import { manageRouter } from '~/events/rpc/routes/manage'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { RPC_ACTIONS, RPC_ACTIONS_INVOKE } from '#/events/constants'
class RPCServer implements IRPCServer {
private routes: IRPCRoutes = new Map()
@@ -38,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)
@@ -53,7 +52,7 @@ class RPCServer implements IRPCServer {
}
}
stop() {
stop () {
ipcMain.off(RPC_ACTIONS, this.rpcEventHandler)
}
}

View File

@@ -1,4 +1,5 @@
import { IRPCType, IRPCActionType } from '#/types/enum'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { IRPCHandler, IRPCRouter, IRPCRoutes } from '#/types/rpc'
interface IBatchAddParams {
action: IRPCActionType
@@ -20,7 +21,7 @@ export class RPCRouter implements IRPCRouter {
return this
}
routes() {
routes () {
return this.routeMap
}
}

View File

@@ -1,11 +1,13 @@
import { clipboard } from 'electron'
import { GalleryDB } from '@core/datastore'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import { IFilter, IObject } from '@picgo/store/dist/types'
import GuiApi from 'apis/gui'
import { clipboard } from 'electron'
import { ICOREBuildInEvent, IPasteStyle, IRPCActionType, IRPCType } from '#/types/enum'
import { IIPCEvent } from '#/types/rpc'
import { ILogType, ImgInfo, ISftpPlistConfig, IStringKeyMap } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
import { RPCRouter } from '~/events/rpc/router'
import {
removeFileFromDogeInMain,
@@ -14,10 +16,16 @@ import {
removeFileFromSFTPInMain
} from '~/utils/deleteFunc'
import pasteTemplate from '~/utils/pasteTemplate'
interface IFilter {
orderBy?: 'asc' | 'desc'
limit?: number
offset?: number
}
import { ICOREBuildInEvent, IPasteStyle, IRPCActionType, IRPCType } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
interface IObject {
id?: string
[propName: string]: any
}
const galleryRouter = new RPCRouter()
const galleryRoutes = [

View File

@@ -1,6 +1,7 @@
import { ManageApi } from '~/manage/manageApi'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { IIPCEvent } from '#/types/rpc'
import { IStringKeyMap } from '#/types/types'
import { ManageApi } from '~/manage/manageApi'
export default [
{

View File

@@ -1,4 +1,6 @@
import { IRPCActionType, IRPCType } from '#/types/enum'
import { IIPCEvent } from '#/types/rpc'
import { IObj } from '#/types/types'
import getManageApi from '~/manage/Main'
const manageApi = getManageApi()

View File

@@ -1,7 +1,6 @@
import { RPCRouter } from '~/events/rpc/router'
import configRoutes from '~/events/rpc/routes/manage/config'
import bucketRoutes from '~/events/rpc/routes/manage/bucket'
import configRoutes from '~/events/rpc/routes/manage/config'
import upDownLoadRoutes from '~/events/rpc/routes/manage/upDownload'
const manageRouter = new RPCRouter()

View File

@@ -1,9 +1,10 @@
import path from 'node:path'
import { app, dialog, shell } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { IIPCEvent } from '#/types/rpc'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { downloadFileFromUrl } from '~/manage/utils/common'

View File

@@ -1,5 +1,8 @@
import picgo from '@core/picgo'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { IIPCEvent } from '#/types/rpc'
import { IStringKeyMap } from '#/types/types'
import { RPCRouter } from '~/events/rpc/router'
import {
deleteUploaderConfig,
@@ -8,7 +11,6 @@ import {
selectUploaderConfig,
updateUploaderConfig
} from '~/utils/handleUploaderConfig'
import { IRPCActionType, IRPCType } from '#/types/enum'
const picbedRouter = new RPCRouter()

View File

@@ -1,13 +1,12 @@
import { IRPCActionType } from '#/types/enum'
import { RPCRouter } from '~/events/rpc/router'
import {
pluginGetListFunc,
pluginImportLocalFunc,
pluginInstallFunc,
pluginGetListFunc,
pluginUpdateAllFunc
} from '~/events/rpc/routes/plugin/utils'
import { IRPCActionType } from '#/types/enum'
const pluginRouter = new RPCRouter()
const pluginRoutes = [

View File

@@ -1,24 +1,22 @@
import { dialog, shell } from 'electron'
import { IGuiMenuItem, PicGo as PicGoCore } from 'piclist'
import path from 'path'
import path from 'node:path'
import { dbPathDir } from '@core/datastore/dbChecker'
import picgo from '@core/picgo'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import windowManager from 'apis/app/window/windowManager'
import { dialog, shell } from 'electron'
import fs from 'fs-extra'
import { IGuiMenuItem, PicGo as PicGoCore } from 'piclist'
import { ICOREBuildInEvent, IPicGoHelperType, IWindowList } from '#/types/enum'
import { IIPCEvent } from '#/types/rpc'
import { IDispose, IPicGoPlugin } from '#/types/types'
import { handleStreamlinePluginName, simpleClone } from '#/utils/common'
import { T } from '~/i18n'
import { showNotification } from '~/utils/common'
import { handleStreamlinePluginName, simpleClone } from '#/utils/common'
import { ICOREBuildInEvent, IPicGoHelperType, IWindowList } from '#/types/enum'
const STORE_PATH = dbPathDir()
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
// get uploader or transformer config
const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => {
let config: any[] = []
@@ -47,13 +45,17 @@ const handleConfigWithFunction = (config: any[]) => {
return config
}
const getPluginList = (): IPicGoPlugin[] => {
const getPluginList = async (): Promise<IPicGoPlugin[]> => {
const pluginList = picgo.pluginLoader.getFullList()
const list = []
for (const i in pluginList) {
const plugin = picgo.pluginLoader.getPlugin(pluginList[i])!
const plugin = (await picgo.pluginLoader.getPlugin(pluginList[i]))!
const pluginPath = path.join(STORE_PATH, `/node_modules/${pluginList[i]}`)
const pluginPKG = requireFunc(path.join(pluginPath, 'package.json'))
const pluginPKGPath = path.join(pluginPath, 'package.json')
if (!fs.existsSync(pluginPKGPath)) {
continue
}
const pluginPKG = fs.readJSONSync(pluginPKGPath, 'utf8')
const uploaderName = plugin.uploader || ''
const transformerName = plugin.transformer || ''
let menu: Omit<IGuiMenuItem, 'handle'>[] = []
@@ -156,7 +158,7 @@ export const handlePluginUninstall = async (fullName: string) => {
export const pluginGetListFunc = async (event: IIPCEvent) => {
try {
const list = simpleClone(getPluginList())
const list = simpleClone(await getPluginList())
// here can just send JS Object not function
// or will cause [Failed to serialize arguments] error
event.sender.send('pluginList', list)
@@ -180,7 +182,7 @@ export const pluginInstallFunc = async (event: IIPCEvent, args: [fullName: strin
errMsg: res.success ? '' : res.body
})
if (res.success) {
shortKeyHandler.registerPluginShortKey(res.body[0])
await shortKeyHandler.registerPluginShortKey(res.body[0])
} else {
showNotification({
title: T('PLUGIN_INSTALL_FAILED'),
@@ -201,7 +203,7 @@ export const pluginImportLocalFunc = async (event: IIPCEvent) => {
const res = await picgo.pluginHandler.install(filePaths)
if (res.success) {
try {
const list = simpleClone(getPluginList())
const list = simpleClone(await getPluginList())
event.sender.send('pluginList', list)
} catch (e: any) {
event.sender.send('pluginList', [])

View File

@@ -1,10 +1,11 @@
import { app } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import path from 'node:path'
import logger from '@core/picgo/logger'
import { downloadFile, uploadFile } from '~/utils/syncSettings'
import { app } from 'electron'
import fs from 'fs-extra'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { downloadFile, uploadFile } from '~/utils/syncSettings'
const STORE_PATH = app.getPath('userData')

View File

@@ -1,5 +1,4 @@
import { RPCRouter } from '~/events/rpc/router'
import advancedRoutes from '~/events/rpc/routes/setting/advanced'
import configureRoutes from '~/events/rpc/routes/setting/configure'
import mainAppRoutes from '~/events/rpc/routes/setting/mainApp'

View File

@@ -1,11 +1,13 @@
import path from 'node:path'
import { dbPathDir } from '@core/datastore/dbChecker'
import picgo from '@core/picgo'
import { app, IpcMainEvent, shell } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import picgo from '@core/picgo'
import { dbPathDir } from '@core/datastore/dbChecker'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { IIPCEvent } from '#/types/rpc'
import { IObj } from '#/types/types'
const STORE_PATH = dbPathDir()

View File

@@ -1,11 +1,12 @@
import { Notification } from 'electron'
import bus from '@core/bus'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import { T } from '~/i18n'
import { Notification } from 'electron'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { IIPCEvent } from '#/types/rpc'
import { IShortKeyConfig } from '#/types/types'
import { T } from '~/i18n'
const notificationFunc = (result: boolean) => {
const notification = new Notification({

View File

@@ -1,14 +1,19 @@
import { app, shell } from 'electron'
import picgo from '@core/picgo'
import windowManager from 'apis/app/window/windowManager'
import { app, IpcMainEvent, shell } from 'electron'
import { IIPCEvent } from 'root/src/universal/types/rpc'
import { SET_CURRENT_LANGUAGE } from '#/events/constants'
import { IRPCActionType, IWindowList } from '#/types/enum'
import { i18nManager } from '~/i18n'
import { IRPCActionType, IWindowList } from '#/types/enum'
import { SET_CURRENT_LANGUAGE } from '#/events/constants'
export default [
{
action: IRPCActionType.GET_PLATFORM,
handler: async (event: IIPCEvent) => {
(event as IpcMainEvent).returnValue = process.platform
}
},
{
action: IRPCActionType.RELOAD_APP,
handler: async () => {
@@ -31,14 +36,14 @@ export default [
{
action: IRPCActionType.GET_LANGUAGE_LIST,
handler: async (event: IIPCEvent) => {
event.returnValue = i18nManager.languageList
(event as IpcMainEvent).returnValue = i18nManager.languageList
}
},
{
action: IRPCActionType.GET_CURRENT_LANGUAGE,
handler: async (event: IIPCEvent) => {
const { lang, locales } = i18nManager.getCurrentLocales()
event.returnValue = [lang, locales]
;(event as IpcMainEvent).returnValue = [lang, locales]
}
},
{

View File

@@ -1,7 +1,9 @@
import windowManager from 'apis/app/window/windowManager'
import { app, BrowserWindow } from 'electron'
import windowManager from 'apis/app/window/windowManager'
import { IRPCActionType, IWindowList } from '#/types/enum'
import { IIPCEvent } from '#/types/rpc'
import { IMiniWindowPos, IPicGoPlugin } from '#/types/types'
import {
buildMainPageMenu,
buildMiniPageMenu,
@@ -11,13 +13,11 @@ import {
} from '~/events/remotes/menu'
import { openMiniWindow } from '~/utils/windowHelper'
import { IRPCActionType, IWindowList } from '#/types/enum'
export default [
{
action: IRPCActionType.HIDE_DOCK,
handler: async (_: IIPCEvent, args: [value: boolean]) => {
args[0] ? app.dock.hide() : app.dock.show()
args[0] ? app.dock?.hide() : app.dock?.show()
}
},
{

View File

@@ -1,14 +1,13 @@
import fs from 'fs-extra'
import path from 'path'
import path from 'node:path'
import { dbPathChecker, defaultConfigPath } from '@core/datastore/dbChecker'
import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
import { T } from '~/i18n'
import fs from 'fs-extra'
import { IToolboxItemCheckStatus, IToolboxItemType } from '#/types/enum'
import { IToolboxCheckerMap, IToolboxFixMap } from '#/types/rpc'
import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static'
import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
import { T } from '~/i18n'
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD)

View File

@@ -1,14 +1,14 @@
import path from 'node:path'
import { DB_PATH, GalleryDB } from '@core/datastore'
import { dbPathChecker } from '@core/datastore/dbChecker'
import { IpcMainEvent } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import { dbPathChecker } from '@core/datastore/dbChecker'
import { GalleryDB, DB_PATH } from '@core/datastore'
import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
import { T } from '~/i18n'
import { IToolboxItemCheckStatus, IToolboxItemType } from '#/types/enum'
import { IToolboxCheckerMap, IToolboxFixMap } from '#/types/rpc'
import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
import { T } from '~/i18n'
export const checkFileMap: IToolboxCheckerMap<
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN

View File

@@ -1,16 +1,15 @@
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 { dbPathChecker } from '@core/datastore/dbChecker'
import { IToolboxItemCheckStatus, IToolboxItemType } from '#/types/enum'
import { IToolboxCheckerMap } from '#/types/rpc'
import { sendToolboxResWithType } from '~/events/rpc/routes/toolbox/utils'
import { T } from '~/i18n'
import { IToolboxItemCheckStatus, IToolboxItemType } from '#/types/enum'
function getProxy(proxyStr: string): AxiosRequestConfig['proxy'] | null {
function getProxy (proxyStr: string): AxiosRequestConfig['proxy'] | null {
if (proxyStr) {
try {
const proxyOptions = new URL(proxyStr)

View File

@@ -1,10 +1,11 @@
import { IpcMainEvent } from 'electron'
import { IRPCActionType, IRPCType, IToolboxItemType } from '#/types/enum'
import { IToolboxCheckArgs, IToolboxCheckerMap, IToolboxFixMap } from '#/types/rpc'
import { RPCRouter } from '~/events/rpc/router'
import { checkClipboardUploadMap, fixClipboardUploadMap } from '~/events/rpc/routes/toolbox/checkClipboardUpload'
import { checkFileMap, fixFileMap } from '~/events/rpc/routes/toolbox/checkFile'
import { checkProxyMap } from '~/events/rpc/routes/toolbox/checkProxy'
import { RPCRouter } from '~/events/rpc/router'
import { IRPCActionType, IRPCType, IToolboxItemType } from '#/types/enum'
import { IpcMainEvent } from 'electron'
const toolboxRouter = new RPCRouter()
@@ -22,7 +23,7 @@ const toolboxFixMap: Partial<IToolboxFixMap<IToolboxItemType>> = {
toolboxRouter
.add(
IRPCActionType.TOOLBOX_CHECK,
async (event, args) => {
async (event: any, args: IToolboxCheckArgs) => {
const [type] = args as IToolboxCheckArgs
if (type) {
const handler = toolboxCheckMap[type]
@@ -43,7 +44,7 @@ toolboxRouter
)
.add(
IRPCActionType.TOOLBOX_CHECK_FIX,
async (event, args) => {
async (event: any, args: IToolboxCheckArgs) => {
const [type] = args as IToolboxCheckArgs
const handler = toolboxFixMap[type]
if (handler) {

View File

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

View File

@@ -1,20 +1,15 @@
import { Notification } from 'electron'
import { RPCRouter } from '~/events/rpc/router'
import { generateShortUrl, setTrayToolTip, handleCopyUrl } from '~/utils/common'
import { IRPCActionType, IRPCType, IPasteStyle, IWindowList } from '#/types/enum'
import db, { GalleryDB } from '@core/datastore'
import uploader from 'apis/app/uploader'
import windowManager from 'apis/app/window/windowManager'
import { Notification } from 'electron'
import { T } from '~/i18n'
import pasteTemplate from '~/utils/pasteTemplate'
import { IPasteStyle, IRPCActionType, IRPCType, IWindowList } from '#/types/enum'
import { IIPCEvent } from '#/types/rpc'
import { configPaths } from '#/utils/configPaths'
import { RPCRouter } from '~/events/rpc/router'
import { T } from '~/i18n'
import { generateShortUrl, handleCopyUrl, setTrayToolTip } from '~/utils/common'
import pasteTemplate from '~/utils/pasteTemplate'
const trayRouter = new RPCRouter()

View File

@@ -1,8 +1,10 @@
import { RPCRouter } from '~/events/rpc/router'
import getPicBeds from '~/utils/getPicBeds'
import { uploadChoosedFiles, uploadClipboardFiles } from '~/apis/app/uploader/apis'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { IIPCEvent } from '#/types/rpc'
import { IFileWithPath } from '#/types/types'
import { RPCRouter } from '~/events/rpc/router'
import getPicBeds from '~/utils/getPicBeds'
const uploadRouter = new RPCRouter()

View File

@@ -1,9 +1,9 @@
import http from 'http'
import fs from 'fs-extra'
import path from 'path'
import http from 'node:http'
import path from 'node:path'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import fs from 'fs-extra'
export const imgFilePath = path.join(picgo.baseDir, 'imgTemp')
fs.ensureDirSync(imgFilePath)
@@ -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')
})

View File

@@ -1,32 +1,34 @@
import path from 'node:path'
import { I18n, ObjectAdapter } from '@piclist/i18n'
import fs from 'fs-extra'
import yaml from 'js-yaml'
import path from 'path'
import { ObjectAdapter, I18n } from '@picgo/i18n'
import { builtinI18nList } from '#/i18n'
import { ILocales, ILocalesKey } from '#/types/i18n'
import { II18nItem, IStringKeyMap } from '#/types/types'
class I18nManager {
private i18n: I18n | null = null
private builtinI18nFolder = path.join(__static, 'i18n')
private builtinI18nFolder = path.join('./resources', 'i18n')
private outterI18nFolder = ''
private localesMap: Map<string, ILocales> = new Map()
private currentLanguage: string = 'zh-CN'
readonly defaultLanguage: string = 'zh-CN'
private i18nFileList: II18nItem[] = builtinI18nList
setOutterI18nFolder(folder: string) {
setOutterI18nFolder (folder: string) {
this.outterI18nFolder = folder
}
addI18nFile(file: string, label: string) {
addI18nFile (file: string, label: string) {
this.i18nFileList.push({
label,
value: file
})
}
private getLocales(lang: string): ILocales {
private getLocales (lang: string): ILocales {
if (this.localesMap.has(lang)) {
return this.localesMap.get(lang)!
}
@@ -53,13 +55,13 @@ class I18nManager {
}
}
setCurrentLanguage(lang: string) {
setCurrentLanguage (lang: string) {
const locales = this.getLocales(lang)
this.currentLanguage = lang
this.initI18n(lang, locales)
}
private initI18n(lang: string = this.defaultLanguage, locales: ILocales) {
private initI18n (lang: string = this.defaultLanguage, locales: ILocales) {
const objectAdapter = new ObjectAdapter({
[lang]: locales
})
@@ -69,15 +71,15 @@ class I18nManager {
})
}
T(key: ILocalesKey, args: IStringKeyMap = {}): string {
T (key: ILocalesKey, args: IStringKeyMap = {}): string {
return this.i18n?.translate(key, args) || key
}
get languageList() {
get languageList () {
return this.i18nFileList
}
getCurrentLocales() {
getCurrentLocales () {
return {
lang: this.currentLanguage,
locales: this.getLocales(this.currentLanguage)

View File

@@ -1,7 +1,7 @@
import path from 'path'
import { app } from 'electron'
import path from 'node:path'
import { getLogger } from '@core/utils/localLogger'
import { app } from 'electron'
const STORE_PATH = app.getPath('userData')
const LOG_PATH = path.join(STORE_PATH, 'piclist-gui-local.log')
@@ -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
}
@@ -35,8 +35,27 @@ function bootstrapEPIPESuppression() {
handleProcessError('Detected EPIPE error; suppressing further EPIPE errors')
}
require('epipebomb')(process.stdout, logEPIPEErrorOnce)
require('epipebomb')(process.stderr, logEPIPEErrorOnce)
epipeBomb(process.stdout, logEPIPEErrorOnce)
epipeBomb(process.stderr, logEPIPEErrorOnce)
}
bootstrapEPIPESuppression()
function epipeBomb (stream: any, callback: any) {
if (stream == null) stream = process.stdout
if (callback == null) callback = process.exit
function epipeFilter (err: any) {
if (err.code === 'EPIPE') return callback()
// If there's more than one error handler (ie, us),
// then the error won't be bubbled up anyway
if (stream.listeners('error').length <= 1) {
stream.removeAllListeners() // Pretend we were never here
stream.emit('error', err) // Then emit as if we were never here
stream.on('error', epipeFilter) // Then reattach, ready for the next error!
}
}
stream.on('error', epipeFilter)
}

View File

@@ -1,12 +1,6 @@
// TODO: so how to import pure esm module in electron main process????? help wanted
import { shellPath } from 'shell-path'
// just copy the fix-path because I can't import pure ESM module in electron main process
// @ts-nocheck since the module is not pure ESM
// shell-path 3.0.0 not work
const shellPath = require('shell-path')
export default function fixPath() {
export default function fixPath () {
if (process.platform === 'win32') {
return
}

View File

@@ -1,25 +1,29 @@
import axios from 'axios'
import fs from 'fs-extra'
import { app, globalShortcut, protocol, Notification, dialog, screen, shell } from 'electron'
import { UpdateInfo, autoUpdater } from 'electron-updater'
import path from 'path'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import '~/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 updater from 'electron-updater'
import fs from 'fs-extra'
import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
import { notificationList } from '#/utils/notification'
import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static'
import busEventList from '~/events/busEventList'
import { rpcServer } from '~/events/rpc'
import { startFileServer, stopFileServer } from '~/fileServer'
import { T } from '~/i18n'
import '~/lifeCycle/errorHandler'
import fixPath from '~/lifeCycle/fixPath'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import getManageApi from '~/manage/Main'
@@ -32,11 +36,6 @@ import { getUploadFiles } from '~/utils/handleArgv'
import { initI18n } from '~/utils/handleI18n'
import updateChecker from '~/utils/updateChecker'
import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static'
import { rpcServer } from '~/events/rpc'
const isDevelopment = process.env.NODE_ENV !== 'production'
const handleStartUpFiles = (argv: string[], cwd: string) => {
@@ -58,15 +57,15 @@ const handleStartUpFiles = (argv: string[], cwd: string) => {
return false
}
autoUpdater.setFeedURL({
updater.autoUpdater.setFeedURL({
provider: 'generic',
url: 'https://release.piclist.cn/latest',
channel: 'latest'
})
autoUpdater.autoDownload = false
updater.autoUpdater.autoDownload = false
autoUpdater.on('update-available', async (info: UpdateInfo) => {
updater.autoUpdater.on('update-available', async (info: updater.UpdateInfo) => {
const lang = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN
let updateLog = ''
try {
@@ -110,7 +109,7 @@ autoUpdater.on('update-available', async (info: UpdateInfo) => {
})
.then(result => {
if (result.response === 0) {
autoUpdater.downloadUpdate()
updater.autoUpdater.downloadUpdate()
} else {
shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest')
}
@@ -121,7 +120,7 @@ autoUpdater.on('update-available', async (info: UpdateInfo) => {
})
})
autoUpdater.on('download-progress', progressObj => {
updater.autoUpdater.on('download-progress', progressObj => {
const percent = {
progress: progressObj.percent
}
@@ -129,7 +128,7 @@ autoUpdater.on('download-progress', progressObj => {
window.webContents.send('updateProgress', percent)
})
autoUpdater.on('update-downloaded', () => {
updater.autoUpdater.on('update-downloaded', () => {
dialog
.showMessageBox({
type: 'info',
@@ -141,7 +140,7 @@ autoUpdater.on('update-downloaded', () => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send('updateProgress', { progress: 100 })
if (result.response === 0) {
autoUpdater.quitAndInstall()
updater.autoUpdater.quitAndInstall()
}
})
.catch(err => {
@@ -149,12 +148,12 @@ autoUpdater.on('update-downloaded', () => {
})
})
autoUpdater.on('error', err => {
updater.autoUpdater.on('error', err => {
console.log(err)
})
class LifeCycle {
async #beforeReady() {
async #beforeReady () {
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
// fix the $PATH in macOS & linux
fixPath()
@@ -166,9 +165,8 @@ class LifeCycle {
busEventList.listen()
}
#onReady() {
#onReady () {
const readyFunction = async () => {
createProtocol('picgo')
windowManager.create(IWindowList.TRAY_WINDOW)
windowManager.create(IWindowList.SETTING_WINDOW)
const isAutoListenClipboard = db.get(configPaths.settings.isAutoListenClipboard) || false
@@ -192,7 +190,7 @@ class LifeCycle {
const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default'
const tooltip = `${currentPicBed} ${currentPicBedConfig}`
if (process.platform === 'darwin') {
isHideDock ? app.dock.hide() : setDockMenu()
isHideDock ? app.dock?.hide() : setDockMenu()
startMode !== ISartMode.NO_TRAY && createTray(tooltip)
} else {
createTray(tooltip)
@@ -210,9 +208,9 @@ class LifeCycle {
handleStartUpFiles(process.argv, process.cwd())
}
if (global.notificationList && global.notificationList?.length > 0) {
while (global.notificationList?.length) {
const option = global.notificationList.pop()
if (notificationList && notificationList.length > 0) {
while (notificationList.length) {
const option = notificationList.pop()
const notice = new Notification(option!)
notice.show()
}
@@ -252,7 +250,7 @@ class LifeCycle {
app.whenReady().then(readyFunction)
}
#onRunning() {
#onRunning () {
app.on('second-instance', (_, commandLine, workingDirectory) => {
logger.info('detect second instance')
const result = handleStartUpFiles(commandLine, workingDirectory)
@@ -267,7 +265,6 @@ class LifeCycle {
}
})
app.on('activate', () => {
createProtocol('picgo')
if (!windowManager.has(IWindowList.TRAY_WINDOW)) {
windowManager.create(IWindowList.TRAY_WINDOW)
}
@@ -287,7 +284,7 @@ class LifeCycle {
}
}
#onQuit() {
#onQuit () {
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
@@ -319,7 +316,7 @@ class LifeCycle {
}
}
async launchApp() {
async launchApp () {
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()

View File

@@ -1,24 +1,24 @@
import path from 'node:path'
import OSS from 'ali-oss'
import windowManager from 'apis/app/window/windowManager'
import axios from 'axios'
import { ipcMain, IpcMainEvent } from 'electron'
import { XMLParser } from 'fast-xml-parser'
import path from 'path'
import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { ManageLogger } from '~/manage/utils/logger'
import {
hmacSha1Base64,
getFileMimeType,
formatError,
NewDownloader,
ConcurrencyPromisePool
} from '~/manage/utils/common'
import * as fastxml from 'fast-xml-parser'
import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { isImage } from '#/utils/common'
import { IStringKeyMap } from '#/types/types'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import {
ConcurrencyPromisePool,
formatError,
getFileMimeType,
hmacSha1Base64,
NewDownloader
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { isImage } from '~/utils/common'
// 坑爹阿里云 返回数据类型标注和实际各种不一致
class AliyunApi {
@@ -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()
}
@@ -159,7 +159,7 @@ class AliyunApi {
})
if (res?.status === 200) {
const parser = new XMLParser()
const parser = new fastxml.XMLParser()
const result = parser.parse(res.data)
if (result.ListCnameResult?.Cname) {
@@ -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,
@@ -227,7 +227,7 @@ class AliyunApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -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,
@@ -282,7 +282,7 @@ class AliyunApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -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

View File

@@ -1,24 +1,25 @@
import path from 'node:path'
import windowManager from 'apis/app/window/windowManager'
import { ipcMain, IpcMainEvent } from 'electron'
import fs from 'fs-extra'
import got from 'got'
import path from 'path'
import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import {
gotUpload,
NewDownloader,
getAgent,
getOptions,
ConcurrencyPromisePool,
formatError
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, IWindowList } from '#/types/enum'
import { formatHttpProxy, isImage, trimPath } from '#/utils/common'
import { IStringKeyMap } from '#/types/types'
import { formatHttpProxy, trimPath } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import {
ConcurrencyPromisePool,
formatError,
getAgent,
getOptions,
gotUpload,
NewDownloader
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { isImage } from '~/utils/common'
class GithubApi {
token: string
@@ -29,7 +30,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 +42,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 +79,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 +120,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 +157,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 +185,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, '')
@@ -197,7 +198,7 @@ class GithubApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -235,7 +236,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, '')
@@ -248,7 +249,7 @@ class GithubApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -285,7 +286,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 +304,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 +413,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 +437,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 +457,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 +506,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

View File

@@ -1,25 +1,26 @@
import path from 'node:path'
import windowManager from 'apis/app/window/windowManager'
import { ipcMain, IpcMainEvent } from 'electron'
import FormData from 'form-data'
import fs from 'fs-extra'
import got from 'got'
import path from 'path'
import windowManager from 'apis/app/window/windowManager'
import { commonTaskStatus, IWindowList } from '#/types/enum'
import { IStringKeyMap } from '#/types/types'
import { formatHttpProxy } from '#/utils/common'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import {
ConcurrencyPromisePool,
formatError,
getAgent,
getFileMimeType,
getOptions,
getAgent,
gotUpload,
NewDownloader
} from '~/manage/utils/common'
import ManageLogger from '~/manage/utils/logger'
import { commonTaskStatus, IWindowList } from '#/types/enum'
import { formatHttpProxy, isImage } from '#/utils/common'
import { isImage } from '~/utils/common'
class ImgurApi {
userName: string
@@ -31,7 +32,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 +43,7 @@ class ImgurApi {
}
}
formatFile(item: any) {
formatFile (item: any) {
const fileName = path.basename(item.link)
const isImg = isImage(fileName)
return {
@@ -64,7 +65,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 +94,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 },
@@ -108,7 +109,7 @@ class ImgurApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -153,7 +154,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 +167,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 +227,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

View File

@@ -1,22 +1,23 @@
import path from 'node:path'
import * as fsWalk from '@nodelib/fs.walk'
import windowManager from 'apis/app/window/windowManager'
import { ipcMain, IpcMainEvent } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import * as fsWalk from '@nodelib/fs.walk'
import windowManager from 'apis/app/window/windowManager'
import { commonTaskStatus, downloadTaskSpecialStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { IStringKeyMap } from '#/types/types'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { formatError } from '~/manage/utils/common'
import ManageLogger from '~/manage/utils/logger'
import { commonTaskStatus, downloadTaskSpecialStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { isImage } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import { isImage } from '~/utils/common'
class LocalApi {
logger: ManageLogger
isWindows: boolean
constructor(logger: ManageLogger) {
constructor (logger: ManageLogger) {
this.logger = logger
this.isWindows = process.platform === 'win32'
}
@@ -24,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
@@ -39,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,
@@ -56,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,
@@ -73,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(/\/+$/, '')
@@ -86,7 +87,7 @@ class LocalApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -113,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
@@ -132,7 +133,7 @@ class LocalApi {
}
})
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -169,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 {
@@ -181,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 {
@@ -193,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 {
@@ -207,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) {
@@ -249,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 {
@@ -263,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) {

View File

@@ -1,23 +1,23 @@
import axios from 'axios'
import { ipcMain, IpcMainEvent } from 'electron'
import path from 'path'
import qiniu from 'qiniu/index'
import path from 'node:path'
import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import {
hmacSha1Base64,
getFileMimeType,
NewDownloader,
formatError,
ConcurrencyPromisePool
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import axios from 'axios'
import { ipcMain, IpcMainEvent } from 'electron'
import qiniu from 'qiniu'
import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { isImage } from '#/utils/common'
import { IStringKeyMap } from '#/types/types'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import {
ConcurrencyPromisePool,
formatError,
getFileMimeType,
hmacSha1Base64,
NewDownloader
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { isImage } from '~/utils/common'
class QiniuApi {
mac: qiniu.auth.digest.Mac
@@ -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, {
@@ -110,11 +110,11 @@ class QiniuApi {
})
if (res?.status === 200 && res?.data?.length) {
const result = [] as any[]
for (let i = 0; i < res.data.length; i++) {
const info = await this.getBucketInfo({ bucketName: res.data[i] })
for (const dataItem of res.data) {
const info = await this.getBucketInfo({ bucketName: dataItem })
if (!info.success) return []
result.push({
Name: res.data[i],
Name: dataItem,
Location: info.zone,
CreationDate: new Date().toISOString(),
Private: info.private
@@ -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
@@ -249,7 +249,7 @@ class QiniuApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -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
@@ -311,7 +311,7 @@ class QiniuApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -380,14 +380,14 @@ 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()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
isTruncated: false,
nextMarker: '',
success: false
@@ -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

View File

@@ -1,40 +1,41 @@
import http, { AgentOptions } from 'node:http'
import https from 'node:https'
import path from 'node:path'
import {
S3Client,
ListBucketsCommand,
ListObjectsV2Command,
GetBucketLocationCommand,
_Object,
CommonPrefix,
ListObjectsV2CommandOutput,
CopyObjectCommand,
GetObjectCommand,
CreateBucketCommand,
DeleteObjectCommand,
DeleteObjectsCommand,
GetBucketLocationCommand,
GetObjectCommand,
ListBucketsCommand,
ListObjectsV2Command,
ListObjectsV2CommandOutput,
PutBucketAclCommand,
PutObjectCommand,
S3ClientConfig,
CreateBucketCommand,
PutPublicAccessBlockCommand,
PutBucketAclCommand
S3Client,
S3ClientConfig
} from '@aws-sdk/client-s3'
import { Upload, Progress } from '@aws-sdk/lib-storage'
import { Progress, Upload } from '@aws-sdk/lib-storage'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import { NodeHttpHandler } from '@smithy/node-http-handler'
import windowManager from 'apis/app/window/windowManager'
import { ipcMain, IpcMainEvent } from 'electron'
import fs from 'fs-extra'
import http, { AgentOptions } from 'http'
import https from 'https'
import path from 'path'
import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { formatError, getAgent, getFileMimeType, NewDownloader, ConcurrencyPromisePool } from '~/manage/utils/common'
import { dogecloudApi, DogecloudToken, getTempToken } from '~/manage/utils/dogeAPI'
import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { isImage, formatEndpoint, formatHttpProxy } from '#/utils/common'
import { IStringKeyMap } from '#/types/types'
import { formatEndpoint, formatHttpProxy } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { ConcurrencyPromisePool, formatError, getAgent, getFileMimeType, NewDownloader } from '~/manage/utils/common'
import { dogecloudApi, DogecloudToken, getTempToken } from '~/manage/utils/dogeAPI'
import { ManageLogger } from '~/manage/utils/logger'
import { isImage } from '~/utils/common'
class S3plistApi {
baseOptions: S3ClientConfig
@@ -46,7 +47,7 @@ class S3plistApi {
secretAccessKey: string
bucketName: string
constructor(
constructor (
accessKeyId: string,
secretAccessKey: string,
endpoint: string | undefined,
@@ -75,7 +76,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 +89,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 +99,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 +133,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 +149,7 @@ class S3plistApi {
}
}
async putPublicAccess(bucketName: string, client: S3Client) {
async putPublicAccess (bucketName: string, client: S3Client) {
const input = {
Bucket: bucketName,
PublicAccessBlockConfiguration: {
@@ -175,11 +176,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 = Object.assign({}, 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 +235,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 +256,7 @@ class S3plistApi {
}
return []
}
const options = Object.assign({}, 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 +306,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,
@@ -325,13 +326,13 @@ class S3plistApi {
})
let res = {} as ListObjectsV2CommandOutput
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
try {
do {
const options = Object.assign({}, 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 +370,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,
@@ -389,14 +390,14 @@ class S3plistApi {
})
let res = {} as ListObjectsV2CommandOutput
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
try {
await this.getDogeCloudToken()
do {
const options = Object.assign({}, 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 +440,7 @@ class S3plistApi {
ipcMain.removeAllListeners('cancelLoadingFileList')
}
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
const {
bucketName: bucket,
bucketConfig: { Location: region },
@@ -450,17 +451,17 @@ class S3plistApi {
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
const result = {
fullList: <any>[],
fullList: [] as any,
isTruncated: false,
nextMarker: '',
success: false
}
try {
await this.getDogeCloudToken()
const options = Object.assign(
{},
{ ...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 +496,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 = Object.assign(
{},
{ ...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 +541,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 = Object.assign({}, 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 +569,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 +582,7 @@ class S3plistApi {
try {
await this.getDogeCloudToken()
do {
const options = Object.assign({}, 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 +617,7 @@ class S3plistApi {
}
if (allFileList.Contents.length > 0) {
const cycle = Math.ceil(allFileList.Contents.length / 1000)
const options = Object.assign({}, 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 +658,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 = Object.assign({}, 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 +686,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 = Object.assign({}, 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 +714,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 +765,7 @@ class S3plistApi {
})
continue
}
const options = Object.assign({}, 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 +826,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

View File

@@ -1,18 +1,17 @@
import { ipcMain, IpcMainEvent } from 'electron'
import path from 'path'
import path from 'node:path'
import windowManager from 'apis/app/window/windowManager'
import SSHClient from '~/utils/sshClient'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { formatError } from '~/manage/utils/common'
import ManageLogger from '~/manage/utils/logger'
import { ipcMain, IpcMainEvent } from 'electron'
import { commonTaskStatus, downloadTaskSpecialStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { Undefinable } from '#/types/manage'
import { isImage } from '#/utils/common'
import { IStringKeyMap } from '#/types/types'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { formatError } from '~/manage/utils/common'
import ManageLogger from '~/manage/utils/logger'
import { isImage } from '~/utils/common'
import SSHClient from '~/utils/sshClient'
interface listDirResult {
permissions: string
@@ -45,7 +44,7 @@ class SftpApi {
passphrase: string
}
constructor(
constructor (
host: string,
port: Undefinable<number>,
username: Undefinable<string>,
@@ -95,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) {
@@ -122,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,
@@ -152,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}`
@@ -165,7 +164,7 @@ class SftpApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -192,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) => {
@@ -218,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}`
@@ -236,7 +235,7 @@ class SftpApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -277,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 {
@@ -291,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 {
@@ -305,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 {
@@ -322,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) {
@@ -377,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 {
@@ -391,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) {

View File

@@ -1,17 +1,18 @@
import { Agent } from 'node:https'
import path from 'node:path'
import windowManager from 'apis/app/window/windowManager'
import axios, { AxiosInstance } from 'axios'
import { ipcMain, IpcMainEvent } from 'electron'
import FormData from 'form-data'
import fs from 'fs-extra'
import path from 'path'
import windowManager from 'apis/app/window/windowManager'
import { getFileMimeType, gotUpload, NewDownloader, ConcurrencyPromisePool, formatError } from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { commonTaskStatus, IWindowList } from '#/types/enum'
import { isImage } from '#/utils/common'
import { IStringKeyMap } from '#/types/types'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { ConcurrencyPromisePool, formatError, getFileMimeType, gotUpload, NewDownloader } from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { isImage } from '~/utils/common'
class SmmsApi {
baseUrl = 'https://smms.app/api/v2'
@@ -20,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,
@@ -28,7 +29,7 @@ class SmmsApi {
headers: {
Authorization: this.token
},
httpsAgent: new (require('https').Agent)({
httpsAgent: new Agent({
keepAlive: true,
timeout: this.timeout
})
@@ -36,7 +37,7 @@ class SmmsApi {
this.logger = logger
}
formatFile(item: any) {
formatFile (item: any) {
return {
...item,
Key: item.path,
@@ -53,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
@@ -66,7 +67,7 @@ class SmmsApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -122,9 +123,9 @@ class SmmsApi {
* customUrl: string
* }
*/
async getBucketFileList({ currentPage }: IStringKeyMap): Promise<any> {
async getBucketFileList ({ currentPage }: IStringKeyMap): Promise<any> {
const result = {
fullList: <any>[],
fullList: [] as any,
isTruncated: false,
nextMarker: '',
success: false
@@ -161,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: {
@@ -176,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) {
@@ -213,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

View File

@@ -1,23 +1,24 @@
import path from 'node:path'
import windowManager from 'apis/app/window/windowManager'
import COS from 'cos-nodejs-sdk-v5'
import { ipcMain, IpcMainEvent } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import windowManager from 'apis/app/window/windowManager'
import { commonTaskStatus, downloadTaskSpecialStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { IStringKeyMap } from '#/types/types'
import { handleUrlEncode } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { formatError, getFileMimeType } from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { handleUrlEncode, isImage } from '#/utils/common'
import { commonTaskStatus, downloadTaskSpecialStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import { isImage } from '~/utils/common'
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 +26,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 +41,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 +59,7 @@ class TcyunApi {
/**
* 获取存储桶列表
*/
async getBucketList(): Promise<any> {
async getBucketList (): Promise<any> {
const res = await this.ctx.getService({})
return res?.Buckets || []
}
@@ -66,7 +67,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 +88,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 +97,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,
@@ -117,7 +118,7 @@ class TcyunApi {
}
})
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -150,7 +151,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,
@@ -172,7 +173,7 @@ class TcyunApi {
})
let res = {} as COS.GetBucketResult
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -221,7 +222,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 +273,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 +302,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 +316,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,8 +347,7 @@ 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++) {
@@ -372,7 +372,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(
{
@@ -391,7 +391,7 @@ class TcyunApi {
* 高级上传文件
* @param configMap
*/
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
// fileArray = [{
// bucketName: string,
@@ -471,7 +471,7 @@ class TcyunApi {
* 新建文件夹
* @param configMap
*/
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
const res = await this.ctx.putObject({
Bucket: bucketName,
@@ -486,7 +486,7 @@ class TcyunApi {
* 下载文件
* @param configMap
*/
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray } = configMap
// fileArray = [{
// bucketName: string,

View File

@@ -1,27 +1,27 @@
import path from 'node:path'
import windowManager from 'apis/app/window/windowManager'
import axios from 'axios'
import { ipcMain, IpcMainEvent } from 'electron'
import FormData from 'form-data'
import fs from 'fs-extra'
import path from 'path'
import Upyun from 'upyun'
import windowManager from 'apis/app/window/windowManager'
import { commonTaskStatus, IWindowList } from '#/types/enum'
import { IStringKeyMap } from '#/types/types'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import {
md5,
hmacSha1Base64,
getFileMimeType,
NewDownloader,
gotUpload,
ConcurrencyPromisePool,
formatError
formatError,
getFileMimeType,
gotUpload,
hmacSha1Base64,
md5,
NewDownloader
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { commonTaskStatus, IWindowList } from '#/types/enum'
import { isImage } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import { isImage } from '~/utils/common'
class UpyunApi {
ser: Upyun.Service
@@ -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)
@@ -130,7 +130,7 @@ class UpyunApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -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)
@@ -183,7 +183,7 @@ class UpyunApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -227,13 +227,13 @@ 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`
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
isTruncated: false,
nextMarker: '',
success: false
@@ -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
@@ -341,9 +341,8 @@ class UpyunApi {
} while (isTruncated)
if (allFileList.Contents.length > 0) {
let success = false
for (let i = 0; i < allFileList.Contents.length; i++) {
const item = allFileList.Contents[i]
success = await this.cli.deleteFile(item.key)
for (const allFileListItem of allFileList.Contents) {
success = await this.cli.deleteFile(allFileListItem.key)
if (!success) {
return false
}
@@ -371,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) => {
@@ -427,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
@@ -437,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

View File

@@ -1,21 +1,21 @@
import { ipcMain, IpcMainEvent } from 'electron'
import fs from 'fs-extra'
import http from 'http'
import https from 'https'
import path from 'path'
import { createClient, WebDAVClient, FileStat, ProgressEvent, AuthType, WebDAVClientOptions } from 'webdav'
import http from 'node:http'
import https from 'node:https'
import path from 'node:path'
import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { formatError, getInnerAgent, NewDownloader, ConcurrencyPromisePool } from '~/manage/utils/common'
import ManageLogger from '~/manage/utils/logger'
import { getAuthHeader } from '@/manage/utils/digestAuth'
import { ipcMain, IpcMainEvent } from 'electron'
import fs from 'fs-extra'
import { AuthType, createClient, FileStat, ProgressEvent, WebDAVClient, WebDAVClientOptions } from 'webdav'
import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { isImage, formatEndpoint, formatHttpProxy } from '#/utils/common'
import { IStringKeyMap } from '#/types/types'
import { formatEndpoint, formatHttpProxy } from '#/utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { ConcurrencyPromisePool, formatError, getInnerAgent, NewDownloader } from '~/manage/utils/common'
import ManageLogger from '~/manage/utils/logger'
import { isImage } from '~/utils/common'
import { getAuthHeader } from '~/utils/digestAuth'
class WebdavplistApi {
endpoint: string
@@ -29,7 +29,7 @@ class WebdavplistApi {
agent: https.Agent | http.Agent
ctx: WebDAVClient
constructor(
constructor (
endpoint: string,
username: string,
password: string,
@@ -63,7 +63,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,
@@ -80,7 +80,7 @@ class WebdavplistApi {
}
}
formatFile(item: FileStat, urlPrefix: string, isWebPath = false) {
formatFile (item: FileStat, urlPrefix: string, isWebPath = false) {
const key = item.filename.replace(/^\/+/, '')
return {
...item,
@@ -99,7 +99,7 @@ class WebdavplistApi {
isRequestSuccess = (code: number) => code >= 200 && code < 300
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken } = configMap
const urlPrefix = customUrl || this.endpoint
@@ -112,7 +112,7 @@ class WebdavplistApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -139,7 +139,7 @@ class WebdavplistApi {
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken, baseDir } = configMap
let urlPrefix = customUrl || this.endpoint
@@ -157,7 +157,7 @@ class WebdavplistApi {
})
let res = {} as any
const result = {
fullList: <any>[],
fullList: [] as any,
success: false,
finished: false
}
@@ -198,7 +198,7 @@ class WebdavplistApi {
ipcMain.removeAllListeners('cancelLoadingFileList')
}
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
const { oldKey, newKey } = configMap
let result = false
try {
@@ -210,7 +210,7 @@ class WebdavplistApi {
return result
}
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -222,7 +222,7 @@ class WebdavplistApi {
return result
}
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -234,7 +234,7 @@ class WebdavplistApi {
return result
}
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
const { key } = configMap
let result = ''
try {
@@ -246,7 +246,7 @@ class WebdavplistApi {
return result
}
async uploadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
const { fileArray } = configMap
const instance = UpDownTaskQueue.getInstance()
for (const item of fileArray) {
@@ -307,7 +307,7 @@ class WebdavplistApi {
return true
}
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
const { key } = configMap
let result = false
try {
@@ -321,7 +321,7 @@ class WebdavplistApi {
return result
}
async downloadBucketFile(configMap: IStringKeyMap): Promise<boolean> {
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
const instance = UpDownTaskQueue.getInstance()
const promises = [] as any

View File

@@ -1,12 +1,15 @@
import { JSONStore } from '@picgo/store'
import { IJSON } from '@picgo/store/dist/types'
import { JSONStore } from '@piclist/store'
import { IManageApiType, IManageConfigType } from '#/types/manage'
import { IStringKeyMap } from '#/types/types'
interface IJSON {
[propsName: string]: string | number | 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 = {
@@ -25,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])
})

View File

@@ -1,11 +1,12 @@
import path from 'node:path'
import { getLogger } from '@core/utils/localLogger'
import dayjs from 'dayjs'
import { app } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import writeFile from 'write-file-atomic'
import { getLogger } from '@core/utils/localLogger'
import { notificationList } from '#/utils/notification'
import { T } from '~/i18n'
const STORE_PATH = app.getPath('userData')
@@ -20,10 +21,7 @@ const errorMsg = {
brokenButBackup: T('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP')
}
/** ensure notification list */
if (!global.notificationList) global.notificationList = []
function manageDbChecker() {
function manageDbChecker () {
if (process.type !== 'renderer') {
const manageConfigFilePath = managePathChecker()
if (!fs.existsSync(manageConfigFilePath)) {
@@ -53,16 +51,16 @@ function manageDbChecker() {
optionsTpl.body = `${errorMsg.brokenButBackup}\n${T('TIPS_PICGO_BACKUP_FILE_VERSION', {
v: dayjs(stats.mtime).format('YYYY-MM-DD HH:mm:ss')
})}`
global.notificationList.push(optionsTpl)
notificationList.push(optionsTpl)
return
} catch (e) {
optionsTpl.body = errorMsg.broken
global.notificationList.push(optionsTpl)
notificationList.push(optionsTpl)
return
}
}
optionsTpl.body = errorMsg.broken
global.notificationList.push(optionsTpl)
notificationList.push(optionsTpl)
return
}
writeFile.sync(manageConfigFileBackupPath, configFile, {
@@ -74,7 +72,7 @@ function manageDbChecker() {
/**
* Get manage config path
*/
function managePathChecker(): string {
function managePathChecker (): string {
if (_configFilePath) {
return _configFilePath
}
@@ -106,7 +104,7 @@ function managePathChecker(): string {
title: T('TIPS_NOTICE'),
body: T('TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR')
}
global.notificationList?.push(optionsTpl)
notificationList?.push(optionsTpl)
hasCheckPath = true
}
logger('error', e)
@@ -115,8 +113,8 @@ function managePathChecker(): string {
}
}
function managePathDir() {
function managePathDir () {
return path.dirname(managePathChecker())
}
export { managePathChecker, managePathDir, manageDbChecker }
export { manageDbChecker, managePathChecker, managePathDir }

View File

@@ -1,55 +1,55 @@
// a singleton class to manage the up/down task queue
// qiniu tcyun aliyun smms imgur github upyun
import path from 'node:path'
import { app } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import { commonTaskStatus, downloadTaskSpecialStatus, uploadTaskSpecialStatus } from '#/types/enum'
import { IDownloadTask, IUploadTask } from '#/types/manage'
class UpDownTaskQueue {
/* eslint-disable */
private static instance: UpDownTaskQueue
/* eslint-enable */
private uploadTaskQueue = <IUploadTask[]>[]
private static instance: UpDownTaskQueue
private downloadTaskQueue = <IDownloadTask[]>[]
private uploadTaskQueue = [] as IUploadTask[]
private downloadTaskQueue = [] as IDownloadTask[]
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,

View File

@@ -1,21 +1,21 @@
import { ipcMain } from 'electron'
import { EventEmitter } from 'events'
import fs from 'fs-extra'
import { get, set, unset } from 'lodash'
import { homedir } from 'os'
import path from 'path'
import { EventEmitter } from 'node:events'
import { homedir } from 'node:os'
import path from 'node:path'
import windowManager from 'apis/app/window/windowManager'
import API from '~/manage/apis/api'
import ManageDB from '~/manage/datastore/db'
import { managePathChecker } from '~/manage/datastore/dbChecker'
import { isInputConfigValid, formatError } from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { ipcMain } from 'electron'
import fs from 'fs-extra'
import { get, set, unset } from 'lodash-es'
import { IWindowList } from '#/types/enum'
import { IManageApiType, IManageConfigType, IManageError, IPicBedMangeConfig } from '#/types/manage'
import { IStringKeyMap } from '#/types/types'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
import API from '~/manage/apis/api'
import ManageDB from '~/manage/datastore/db'
import { managePathChecker } from '~/manage/datastore/dbChecker'
import { formatError, isInputConfigValid } from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
export class ManageApi extends EventEmitter implements IManageApiType {
private _config!: Partial<IManageConfigType>
@@ -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,9 +412,9 @@ 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: <any>[],
fullList: [] as any,
isTruncated: false,
nextMarker: '',
success: 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':

View File

@@ -1,22 +1,23 @@
import crypto from 'node:crypto'
import http from 'node:http'
import https from 'node:https'
import path from 'node:path'
import { Stream } from 'node:stream'
import { promisify } from 'node:util'
import axios from 'axios'
import crypto from 'crypto'
import { app } from 'electron'
import fs from 'fs-extra'
import got, { OptionsOfTextResponseBody, RequestError } from 'got'
import { HttpsProxyAgent, HttpProxyAgent } from 'hpagent'
import http from 'http'
import https from 'https'
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'
import mime from 'mime-types'
import Downloader from 'nodejs-file-downloader'
import path from 'path'
import { Stream } from 'stream'
import { promisify } from 'util'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, downloadTaskSpecialStatus, uploadTaskSpecialStatus } from '#/types/enum'
import { IHTTPProxy, IStringKeyMap } from '#/types/types'
import { formatHttpProxy } from '#/utils/common'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { ManageLogger } from '~/manage/utils/logger'
export const getFSFile = async (filePath: string, stream: boolean = false): Promise<IStringKeyMap> => {
try {
@@ -33,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
}
@@ -55,14 +56,14 @@ export const downloadFileFromUrl = async (urls: string[]) => {
const tempPath = getTempDirPath()
await checkTempFolderExist(tempPath)
const result = [] as string[]
for (let i = 0; i < urls.length; i++) {
for (const url of urls) {
const finishDownload = promisify(Stream.finished)
const fileName = path.basename(urls[i]).split('?')[0]
const fileName = path.basename(url).split('?')[0]
const filePath = path.join(tempPath, fileName)
const writer = fs.createWriteStream(filePath)
const res = await axios({
method: 'get',
url: urls[i],
url,
responseType: 'stream'
})
res.data.pipe(writer)
@@ -279,7 +280,7 @@ export const getInnerAgent = (proxy: any, sslEnabled: boolean = true) => {
}
}
export function getOptions(
export function getOptions (
method?: string,
headers?: IStringKeyMap,
searchParams?: IStringKeyMap,
@@ -308,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)
@@ -323,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

View File

@@ -1,3 +1,5 @@
import { IStringKeyMap } from '#/types/types'
const AliyunAreaCodeName: IStringKeyMap = {
'oss-cn-hangzhou': '华东1(杭州)',
'oss-cn-shanghai': '华东2(上海)',

View File

@@ -1,8 +1,10 @@
import axios from 'axios'
import crypto from 'crypto'
import querystring from 'querystring'
import crypto from 'node:crypto'
import querystring from 'node:querystring'
import picgo from '@core/picgo'
import axios from 'axios'
import { IObj } from '#/types/types'
export interface DogecloudToken {
accessKeyId: string
@@ -10,7 +12,7 @@ export interface DogecloudToken {
sessionToken: string
}
export async function dogecloudApi(
export async function dogecloudApi (
apiPath: string,
data = {},
jsonMode: boolean = false,
@@ -43,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

View File

@@ -1,13 +1,15 @@
import path from 'node:path'
import util from 'node:util'
import chalk from 'chalk'
import dayjs from 'dayjs'
import fs from 'fs-extra'
import path from 'path'
import { ILogColor, ILogger } from 'piclist/dist/types'
import util from 'util'
import { ILogType } from '#/types/enum'
import { IManageApiType, Undefinable } from '#/types/manage'
import { enforceNumber, isDev } from '#/utils/common'
import { ILogArgvType, ILogArgvTypeWithError } from '#/types/types'
import { enforceNumber } from '#/utils/common'
import { configPaths } from '#/utils/configPaths'
export class ManageLogger implements ILogger {
@@ -22,11 +24,11 @@ export class ManageLogger implements ILogger {
#logLevel!: string
#logPath!: string
constructor(ctx: IManageApiType) {
constructor (ctx: IManageApiType) {
this.#ctx = ctx
}
#handleLog(type: ILogType, ...msg: ILogArgvTypeWithError[]): void {
#handleLog (type: ILogType, ...msg: ILogArgvTypeWithError[]): void {
const logHeader = chalk[this.#level[type] as ILogColor](`[PicList ${type.toUpperCase()}]`)
console.log(logHeader, ...msg)
this.#logLevel = this.#ctx.getConfig(configPaths.settings.logLevel)
@@ -51,7 +53,7 @@ export class ManageLogger implements ILogger {
}, 0)
}
#checkLogFileIsLarge(logPath: string): {
#checkLogFileIsLarge (logPath: string): {
isLarge: boolean
logFileSize?: number
logFileSizeLimit?: number
@@ -74,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()}] `
@@ -96,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------- `
@@ -112,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
}
@@ -122,24 +124,24 @@ 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 {
if (isDev) {
debug (...msq: ILogArgvType[]): void {
if (process.env.NODE_ENV === 'development') {
this.#handleLog(ILogType.info, ...msq)
}
}

View File

@@ -1,17 +1,17 @@
import axios from 'axios'
import { app } from 'electron'
import fs from 'fs-extra'
import http from 'http'
import multer from 'multer'
import path from 'path'
import http from 'node:http'
import path from 'node:path'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import axios from 'axios'
import { app } from 'electron'
import fs from 'fs-extra'
import multer from 'multer'
import routers from '~/server/routerManager'
import { handleResponse, ensureHTTPLink } from '~/server/utils'
import { ErrnoException, IObj, IServerConfig } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
import routers from '~/server/routerManager'
import { ensureHTTPLink, handleResponse } from '~/server/utils'
const DEFAULT_PORT = 36677
const DEFAULT_HOST = '0.0.0.0'
@@ -26,7 +26,6 @@ const multerStorage = multer.diskStorage({
cb(null, serverTempDir)
},
filename: function (_req: any, file: { originalname: any }, cb: (arg0: null, arg1: any) => void) {
// eslint-disable-next-line no-control-regex
if (!/[^\u0000-\u00ff]/.test(file.originalname)) {
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
}
@@ -42,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 }
@@ -56,7 +55,7 @@ class Server {
return config
}
#isValidConfig(config: IObj | undefined) {
#isValidConfig (config: IObj | undefined) {
return config && config.port && config.host && config.enable !== undefined
}
@@ -199,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()

View File

@@ -1,29 +1,31 @@
import { routeHandler } from '#/types/types'
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)) {

View File

@@ -1,24 +1,22 @@
import { app } from 'electron'
import fs from 'fs-extra'
import http from 'http'
import { marked } from 'marked'
import path from 'path'
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 { AESHelper } from '~/utils/aesHelper'
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
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 { IHttpResponse, IStringKeyMap } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
import { markdownContent } from '~/server/apiDoc'
import router from '~/server/router'
import { deleteChoosedFiles, handleResponse } from '~/server/utils'
import { configPaths } from '#/utils/configPaths'
import { AESHelper } from '~/utils/aesHelper'
import { changeCurrentUploader } from '~/utils/handleUploaderConfig'
const appPath = app.getPath('userData')
const serverTempDir = path.join(appPath, 'serverTemp')
@@ -29,7 +27,7 @@ const LOG_PATH = path.join(STORE_PATH, 'piclist.log')
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
const deleteErrorMessage = `delete error. see ${LOG_PATH} for more detail.`
async function responseForGet({ response }: { response: http.ServerResponse }) {
async function responseForGet ({ response }: { response: http.ServerResponse }) {
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
const htmlContent = marked(markdownContent)
response.write(htmlContent)

View File

@@ -1,18 +1,16 @@
import { Notification } from 'electron'
import db, { GalleryDB } from '@core/datastore'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import db, { GalleryDB } from '@core/datastore'
import windowManager from 'apis/app/window/windowManager'
import ALLApi from 'apis/delete/allApi'
import GuiApi from 'apis/gui'
import { Notification } from 'electron'
import GuiApi from '~/apis/gui'
import { T } from '~/i18n/index'
import { ICOREBuildInEvent, IWindowList } from '#/types/enum'
import { IHttpResponse, ImgInfo, IObj } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
import { picBedsCanbeDeleted } from '#/utils/static'
import { ICOREBuildInEvent, IWindowList } from '#/types/enum'
import ALLApi from '@/apis/allApi'
import { T } from '~/i18n/index'
export const handleResponse = ({
response,

View File

@@ -1,16 +1,17 @@
import fs from 'fs-extra'
import http from 'http'
import path from 'path'
import http from 'node:http'
import path from 'node:path'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import fs from 'fs-extra'
import { IStringKeyMap } from '#/types/types'
import { encodeFilePath } from '#/utils/common'
import { configPaths } from '#/utils/configPaths'
const defaultPath = process.platform === 'win32' ? 'C:\\Users' : '/'
function generateDirectoryListingHtml(files: any[], requestPath: any) {
function generateDirectoryListingHtml (files: any[], requestPath: any) {
let html = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><h1>Directory Listing</h1><ul>'
files.forEach((file: string) => {
const filePath = path.join(requestPath, file)
@@ -20,7 +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)
@@ -32,7 +33,7 @@ function serveDirectory(res: http.ServerResponse, filePath: fs.PathLike, request
})
}
function serveFile(res: http.ServerResponse, filePath: fs.PathLike) {
function serveFile (res: http.ServerResponse, filePath: fs.PathLike) {
const readStream = fs.createReadStream(filePath)
readStream.pipe(res)
readStream.on('error', () => {
@@ -45,12 +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',
@@ -59,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 || ''))
@@ -78,7 +79,7 @@ class WebServer {
})
}
start() {
start () {
if (this.#config.enableWebServer) {
this.#server
.listen(
@@ -98,13 +99,13 @@ class WebServer {
}
}
stop() {
stop () {
this.#server.close(() => {
logger.info('Web server is stopped')
})
}
restart() {
restart () {
this.stop()
this.loadConfig()
this.initServer()

View File

@@ -1,4 +1,4 @@
import crypto from 'crypto'
import crypto from 'node:crypto'
import picgo from '@core/picgo'
@@ -14,7 +14,7 @@ export class AESHelper {
'sha512'
)
encrypt(plainText: string) {
encrypt (plainText: string) {
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv('aes-256-cbc', this.key, iv)
let encrypted = cipher.update(plainText, 'utf8', 'hex')
@@ -22,7 +22,7 @@ export class AESHelper {
return `${iv.toString('hex')}:${encrypted}`
}
decrypt(encryptedData: string) {
decrypt (encryptedData: string) {
const [ivHex, encryptedText] = encryptedData.split(':')
if (!ivHex || !encryptedText) return '{}'

View File

@@ -1,16 +1,17 @@
import fs from 'fs-extra'
import yaml from 'js-yaml'
import path from 'path'
import os from 'os'
import os from 'node:os'
import path from 'node:path'
import { dbPathChecker } from '@core/datastore/dbChecker'
import fs from 'fs-extra'
import yaml from 'js-yaml'
import { ILocales } from 'root/src/universal/types/i18n'
import { i18nManager } from '~/i18n'
const configPath = dbPathChecker()
const CONFIG_DIR = path.dirname(configPath)
function beforeOpen() {
function beforeOpen () {
if (process.platform === 'darwin') {
resolveMacWorkFlow()
}
@@ -18,7 +19,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()) {
@@ -42,16 +43,16 @@ function copyFileOutsideOfElectronAsar(sourceInAsarArchive: string, destOutsideA
/**
* macOS 右键菜单
*/
function resolveMacWorkFlow() {
function resolveMacWorkFlow () {
const dest = `${os.homedir()}/Library/Services/Upload pictures with PicList.workflow`
try {
copyFileOutsideOfElectronAsar(path.join(__static, 'Upload pictures with PicList.workflow'), dest)
copyFileOutsideOfElectronAsar(path.join('./resources', 'Upload pictures with PicList.workflow'), 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)
@@ -68,7 +69,7 @@ function diffFilesAndUpdate(filePath1: string, filePath2: string) {
/**
* 初始化剪贴板生成图片的脚本
*/
function resolveClipboardImageGenerator() {
function resolveClipboardImageGenerator () {
const clipboardFiles = getClipboardFiles()
if (!fs.pathExistsSync(path.join(CONFIG_DIR, 'windows10.ps1'))) {
clipboardFiles.forEach(item => {
@@ -80,12 +81,12 @@ function resolveClipboardImageGenerator() {
})
}
function getClipboardFiles() {
const files = ['/linux.sh', '/mac.applescript', '/windows.ps1', '/windows10.ps1', '/wsl.sh']
function getClipboardFiles () {
const files = ['linux.sh', 'mac.applescript', 'windows.ps1', 'windows10.ps1', 'wsl.sh']
return files.map(item => {
return {
origin: path.join(__static, item),
origin: path.join('./resources', item),
dest: path.join(CONFIG_DIR, item)
}
})
@@ -95,7 +96,7 @@ function resolveClipboardImageGenerator() {
/**
* 初始化其他语言文件
*/
function resolveOtherI18nFiles() {
function resolveOtherI18nFiles () {
const i18nFolder = path.join(CONFIG_DIR, 'i18n')
if (!fs.pathExistsSync(i18nFolder)) {
fs.mkdirSync(i18nFolder)

View File

@@ -1,20 +1,21 @@
import crypto from 'crypto'
import { clipboard } from 'electron'
import { EventEmitter } from 'events'
import crypto from 'node:crypto'
import { EventEmitter } from 'node:events'
import logger from '@core/picgo/logger'
import { clipboard, NativeImage } from 'electron'
class ClipboardWatcher extends EventEmitter {
// eslint-disable-next-line no-undef
timer: NodeJS.Timeout | null
lastImageHash: string | null
constructor() {
constructor () {
super()
this.lastImageHash = null
this.timer = null
}
startListening(watchDelay = 500) {
startListening (watchDelay = 500) {
this.stopListening(false)
this.timer = setInterval(() => {
@@ -33,7 +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
@@ -42,7 +43,7 @@ class ClipboardWatcher extends EventEmitter {
isLog && logger.info('Stop to watch clipboard')
}
getImageHash(image: Electron.NativeImage): string {
getImageHash (image: NativeImage): string {
const buffer = image.toBitmap()
return crypto.createHash('md5').update(buffer).digest('hex')
}

View File

@@ -1,15 +1,22 @@
import axios from 'axios'
import { clipboard, Notification, dialog, Tray } from 'electron'
import FormData from 'form-data'
import fs from 'fs-extra'
import path from 'node:path'
import db from '@core/datastore'
import logger from '@core/picgo/logger'
import axios from 'axios'
import { clipboard, dialog, Notification, Tray } from 'electron'
import FormData from 'form-data'
import fs from 'fs-extra'
import { IShortUrlServer } from '#/types/enum'
import { IPrivateShowNotificationOption, IShowMessageBoxResult } from '#/types/types'
import { handleUrlEncode } from '#/utils/common'
import { configPaths } from '#/utils/configPaths'
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) => {
@@ -18,7 +25,7 @@ export const setTray = (t: Tray) => {
export const getTray = () => tray
export function setTrayToolTip(title: string): void {
export function setTrayToolTip (title: string): void {
if (tray) {
tray.setToolTip(title)
}
@@ -64,7 +71,7 @@ export const showNotification = (
}
export const showMessageBox = (options: any) => {
return new Promise<IShowMessageBoxResult>(async resolve => {
return new Promise<IShowMessageBoxResult>(resolve => {
dialog.showMessageBox(options).then(res => {
resolve({
result: res.response,

View File

@@ -1,15 +1,17 @@
import axios from 'axios'
import crypto from 'crypto'
import http, { AgentOptions } from 'http'
import https from 'https'
import path from 'path'
import { ISftpPlistConfig } from 'piclist'
import querystring from 'querystring'
import { S3Client, DeleteObjectCommand, S3ClientConfig } from '@aws-sdk/client-s3'
import { NodeHttpHandler } from '@smithy/node-http-handler'
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 SSHClient from '~/utils/sshClient'
import { DeleteObjectCommand, S3Client, S3ClientConfig } from '@aws-sdk/client-s3'
import { NodeHttpHandler } from '@smithy/node-http-handler'
import axios from 'axios'
import { ISftpPlistConfig } from 'piclist'
import { IObj, IStringKeyMap } from '#/types/types'
import { getAgent } from '~/manage/utils/common'
import SSHClient from '~/utils/sshClient'
interface DogecloudTokenFull {
Credentials: {
@@ -32,7 +34,7 @@ const dogeRegionMap: IStringKeyMap = {
'ap-chengdu': '3'
}
async function dogecloudApi(
async function dogecloudApi (
apiPath: string,
data = {},
jsonMode: boolean = false,
@@ -65,7 +67,7 @@ async function dogecloudApi(
}
}
async function getDogeToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudTokenFull> {
async function getDogeToken (accessKey: string, secretKey: string): Promise<IObj | DogecloudTokenFull> {
try {
const data = await dogecloudApi(
'/auth/tmp_token.json',
@@ -84,7 +86,7 @@ async function getDogeToken(accessKey: string, secretKey: string): Promise<IObj
}
}
export async function removeFileFromS3InMain(configMap: IStringKeyMap, dogeMode: boolean = false) {
export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode: boolean = false) {
try {
const {
url: rawUrl,
@@ -121,21 +123,21 @@ export async function removeFileFromS3InMain(configMap: IStringKeyMap, dogeMode:
const extraOptions = sslEnabled ? { rejectUnauthorized: !!rejectUnauthorized } : {}
const handler = sslEnabled
? new NodeHttpHandler({
httpsAgent: agent.https
? agent.https
: new https.Agent({
...commonOptions,
...extraOptions
})
})
httpsAgent: agent.https
? agent.https
: new https.Agent({
...commonOptions,
...extraOptions
})
})
: new NodeHttpHandler({
httpAgent: agent.http
? agent.http
: new http.Agent({
...commonOptions,
...extraOptions
})
})
httpAgent: agent.http
? agent.http
: new http.Agent({
...commonOptions,
...extraOptions
})
})
const s3Options: S3ClientConfig = {
credentials: {
accessKeyId: accessKeyID,
@@ -181,14 +183,14 @@ export async function removeFileFromS3InMain(configMap: IStringKeyMap, dogeMode:
}
}
export async function removeFileFromDogeInMain(configMap: IStringKeyMap) {
export async function removeFileFromDogeInMain (configMap: IStringKeyMap) {
try {
const {
config: { bucketName, AccessKey, SecretKey }
} = configMap
const token = (await getDogeToken(AccessKey, SecretKey)) as DogecloudTokenFull
const bucket = token.Buckets?.find(item => item.name === bucketName || item.s3Bucket === bucketName)
const newConfigMap = Object.assign({}, configMap)
const newConfigMap = { ...configMap }
newConfigMap.config = {
...newConfigMap.config,
accessKeyID: token.Credentials?.accessKeyId,
@@ -205,7 +207,7 @@ export async function removeFileFromDogeInMain(configMap: IStringKeyMap) {
}
}
function createHuaweiAuthorization(
function createHuaweiAuthorization (
bucketName: string,
path: string,
fileName: string,
@@ -218,7 +220,7 @@ function createHuaweiAuthorization(
return `OBS ${accessKey}:${singature}`
}
export async function removeFileFromHuaweiInMain(configMap: IStringKeyMap) {
export async function removeFileFromHuaweiInMain (configMap: IStringKeyMap) {
const { fileName, config } = configMap
const { accessKeyId, accessKeySecret, bucketName, endpoint } = config
let path = config.path || '/'
@@ -244,7 +246,7 @@ export async function removeFileFromHuaweiInMain(configMap: IStringKeyMap) {
}
}
export async function removeFileFromSFTPInMain(config: ISftpPlistConfig, fileName: string) {
export async function removeFileFromSFTPInMain (config: ISftpPlistConfig, fileName: string) {
try {
const client = SSHClient.instance
await client.connect(config)

View File

@@ -0,0 +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)
}

View File

@@ -0,0 +1,80 @@
import crypto from 'node:crypto'
import axios from 'axios'
import { IStringKeyMap } from '#/types/types'
const AUTH_KEY_VALUE_RE = /(\w+)=["']?([^'"]{1,10000})["']?/
let NC = 0
const NC_PAD = '00000000'
function md5 (text: crypto.BinaryLike) {
return crypto.createHash('md5').update(text).digest('hex')
}
export function digestAuthHeader (
method: string,
uri: string,
wwwAuthenticate: string,
username: string,
password: string
) {
const parts = wwwAuthenticate.split(',')
const opts = {} as IStringKeyMap
for (const i of parts) {
const m = AUTH_KEY_VALUE_RE.exec(i)
if (m) {
opts[m[1]] = m[2].replace(/["']/g, '')
}
}
if (!opts.realm || !opts.nonce) {
return ''
}
let qop = opts.qop || ''
const userpassArray = [username, password]
let nc = String(++NC)
nc = NC_PAD.substring(nc.length) + nc
const cnonce = crypto.randomBytes(8).toString('hex')
const ha1 = md5(userpassArray[0] + ':' + opts.realm + ':' + userpassArray[1])
const ha2 = md5(method.toUpperCase() + ':' + uri)
let s = ha1 + ':' + opts.nonce
if (qop) {
qop = qop.split(',')[0]
s += ':' + nc + ':' + cnonce + ':' + qop
}
s += ':' + ha2
const response = md5(s)
let authstring =
'Digest username="' +
userpassArray[0] +
'", realm="' +
opts.realm +
'", nonce="' +
opts.nonce +
'", uri="' +
uri +
'", response="' +
response +
'"'
if (opts.opaque) {
authstring += ', opaque="' + opts.opaque + '"'
}
if (qop) {
authstring += ', qop=' + qop + ', nc=' + nc + ', cnonce="' + cnonce + '"'
}
return authstring
}
export async function getAuthHeader (method: string, host: string, uri: string, username: string, password: string) {
try {
await axios.get(`${host}${uri}`)
} catch (error: any) {
if (error.response.status === 401 && error.response.headers['www-authenticate']) {
return digestAuthHeader(method, uri, error.response.headers['www-authenticate'], username, password)
}
}
}

View File

@@ -1,8 +1,9 @@
// fork from https://github.com/sindresorhus/macos-version
// cause I can't change it to common-js module
import fs from 'fs'
import process from 'process'
import fs from 'node:fs'
import process from 'node:process'
import semver from 'semver'
export const isMacOS = process.platform === 'darwin'
@@ -21,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) {
@@ -42,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
}
@@ -52,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
}
@@ -62,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)) {
@@ -70,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)) {
@@ -78,7 +79,7 @@ export function assertMacOSVersionGreaterThanOrEqualTo(version: string) {
}
}
export function assertMacOS() {
export function assertMacOS () {
if (!isMacOS) {
throw new Error('Requires macOS')
}

View File

@@ -1,4 +1,6 @@
import picgo from '@core/picgo'
import { IPicBedType } from '#/types/types'
import { configPaths } from '#/utils/configPaths'
const getPicBeds = () => {

View File

@@ -1,5 +1,6 @@
import path from 'node:path'
import fs from 'fs-extra'
import path from 'path'
import { Logger } from 'piclist'
import { isUrl } from '#/utils/common'

View File

@@ -1,9 +1,8 @@
import db from '@core/datastore'
import { i18nManager } from '~/i18n'
import { II18nLanguage } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
import { i18nManager } from '~/i18n'
export const initI18n = () => {
const currentLanguage = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN

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