mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-26 02:30:18 +08:00
🔨 Refactor: refactored of some manage api
This commit is contained in:
@@ -47,7 +47,6 @@ class RemoteNoticeHandler {
|
||||
const localCountStorage: IRemoteNoticeLocalCountStorage = fs.readJSONSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, 'utf8')
|
||||
this.remoteNoticeLocalCountStorage = localCountStorage
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
this.remoteNoticeLocalCountStorage = localCountStorage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,37 @@
|
||||
// Axios
|
||||
import axios from 'axios'
|
||||
|
||||
// 加密函数、获取文件 MIME 类型、错误格式化函数、新的下载器、并发异步任务池
|
||||
import { hmacSha1Base64, getFileMimeType, formatError, NewDownloader, ConcurrencyPromisePool } from '../utils/common'
|
||||
|
||||
// Electron 相关
|
||||
import { ipcMain, IpcMainEvent } from 'electron'
|
||||
|
||||
// 快速 XML 解析器
|
||||
import { XMLParser } from 'fast-xml-parser'
|
||||
|
||||
// 阿里云 OSS 客户端库
|
||||
import OSS from 'ali-oss'
|
||||
|
||||
// 路径处理库
|
||||
import path from 'path'
|
||||
|
||||
// 是否为图片的判断函数
|
||||
import { isImage } from '~/renderer/manage/utils/common'
|
||||
|
||||
// 窗口管理器
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
|
||||
// 枚举类型声明
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import UpDownTaskQueue,
|
||||
{
|
||||
uploadTaskSpecialStatus,
|
||||
commonTaskStatus
|
||||
} from '../datastore/upDownTaskQueue'
|
||||
|
||||
// 上传下载任务队列
|
||||
import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus } from '../datastore/upDownTaskQueue'
|
||||
|
||||
// 日志记录器
|
||||
import { ManageLogger } from '../utils/logger'
|
||||
|
||||
// 取消下载任务的加载文件列表、刷新下载文件传输列表
|
||||
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
|
||||
|
||||
// 坑爹阿里云 返回数据类型标注和实际各种不一致
|
||||
@@ -49,22 +68,20 @@ class AliyunApi {
|
||||
}
|
||||
|
||||
formatFile (item: OSS.ObjectMeta, slicedPrefix: string, urlPrefix: string): any {
|
||||
const result = {
|
||||
const fileName = item.name.replace(slicedPrefix, '')
|
||||
return {
|
||||
...item,
|
||||
key: item.name,
|
||||
rawUrl: `${urlPrefix}/${item.name}`,
|
||||
fileName: item.name.replace(slicedPrefix, ''),
|
||||
fileName,
|
||||
fileSize: item.size,
|
||||
formatedTime: new Date(item.lastModified).toLocaleString(),
|
||||
isDir: false,
|
||||
checked: false,
|
||||
match: false,
|
||||
isImage: isImage(item.name.replace(slicedPrefix, ''))
|
||||
isImage: isImage(fileName),
|
||||
rawUrl: item.url,
|
||||
url: `${urlPrefix}/${item.name}`
|
||||
}
|
||||
const temp = result.rawUrl
|
||||
result.rawUrl = result.url
|
||||
result.url = temp
|
||||
return result
|
||||
}
|
||||
|
||||
getCanonicalizedOSSHeaders (headers: IStringKeyMap) {
|
||||
@@ -100,47 +117,30 @@ class AliyunApi {
|
||||
* 获取存储桶列表
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
const formatItem = (item: OSS.Bucket) => {
|
||||
return {
|
||||
const getBuckets = async (marker?: string) => {
|
||||
const res = await this.ctx.listBuckets({
|
||||
marker,
|
||||
'max-keys': 1000
|
||||
}) as IStringKeyMap
|
||||
if (res.res.statusCode !== 200 || !res.buckets) return { result: [], isTruncated: false }
|
||||
const formattedBuckets = res.buckets.map((item: OSS.Bucket) => ({
|
||||
Name: item.name,
|
||||
Location: item.region,
|
||||
CreationDate: item.creationDate
|
||||
}
|
||||
}
|
||||
const res = await this.ctx.listBuckets({
|
||||
'max-keys': 1000
|
||||
}) as IStringKeyMap
|
||||
const result = [] as IStringKeyMap[]
|
||||
let NextMarker = ''
|
||||
if (res.res.statusCode === 200) {
|
||||
if (res.buckets) {
|
||||
result.push(...res.buckets.map((item: OSS.Bucket) => formatItem(item)))
|
||||
let isTruncated = res.isTruncated
|
||||
NextMarker = res.nextMarker
|
||||
while (isTruncated) {
|
||||
const res = await this.ctx.listBuckets({
|
||||
marker: NextMarker,
|
||||
'max-keys': 1000
|
||||
}) as IStringKeyMap
|
||||
if (res.res.statusCode === 200) {
|
||||
if (res.buckets) {
|
||||
result.push(...res.buckets.map((item: OSS.Bucket) => formatItem(item)))
|
||||
isTruncated = res.isTruncated
|
||||
NextMarker = res.nextMarker
|
||||
} else {
|
||||
isTruncated = false
|
||||
}
|
||||
} else {
|
||||
isTruncated = false
|
||||
}
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}))
|
||||
return { result: formattedBuckets, isTruncated: res.isTruncated, nextMarker: res.nextMarker }
|
||||
}
|
||||
const result: IStringKeyMap[] = []
|
||||
let NextMarker: string | undefined
|
||||
let isTruncated: boolean
|
||||
do {
|
||||
const { result: buckets, isTruncated: truncated, nextMarker } = await getBuckets(NextMarker)
|
||||
result.push(...buckets)
|
||||
NextMarker = nextMarker
|
||||
isTruncated = truncated
|
||||
} while (isTruncated)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,6 +151,7 @@ class AliyunApi {
|
||||
Date: new Date().toUTCString()
|
||||
}
|
||||
const authorization = this.authorization('GET', `/${param.bucketName}/?cname`, headers, '', '')
|
||||
|
||||
const res = await axios({
|
||||
url: `https://${param.bucketName}.${param.region}.aliyuncs.com/?cname`,
|
||||
method: 'GET',
|
||||
@@ -159,25 +160,22 @@ class AliyunApi {
|
||||
Authorization: authorization
|
||||
}
|
||||
})
|
||||
|
||||
if (res.status === 200) {
|
||||
const parser = new XMLParser()
|
||||
const result = parser.parse(res.data)
|
||||
if (result.ListCnameResult && result.ListCnameResult.Cname) {
|
||||
if (Array.isArray(result.ListCnameResult.Cname)) {
|
||||
const cnameList = [] as string[]
|
||||
result.ListCnameResult.Cname.forEach((item: IStringKeyMap) => {
|
||||
item.Status === 'Enabled' && cnameList.push(item.Domain)
|
||||
})
|
||||
return cnameList
|
||||
} else {
|
||||
return result.ListCnameResult.Cname.Status === 'Enabled' ? [result.ListCnameResult.Cname.Domain] : []
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
|
||||
if (result.ListCnameResult?.Cname) {
|
||||
const cnames = Array.isArray(result.ListCnameResult.Cname)
|
||||
? result.ListCnameResult.Cname
|
||||
: [result.ListCnameResult.Cname]
|
||||
|
||||
return cnames
|
||||
.filter((item: IStringKeyMap) => item.Status === 'Enabled')
|
||||
.map((item: IStringKeyMap) => item.Domain)
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,16 +327,10 @@ class AliyunApi {
|
||||
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, marker, itemsPerPage } = configMap
|
||||
const slicedPrefix = prefix.slice(1)
|
||||
const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
|
||||
let res = {} as any
|
||||
const result = {
|
||||
fullList: <any>[],
|
||||
isTruncated: false,
|
||||
nextMarker: '',
|
||||
success: false
|
||||
}
|
||||
|
||||
const client = this.getNewCtx(region, bucket)
|
||||
res = await client.listV2({
|
||||
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
|
||||
const res = await client.listV2({
|
||||
prefix: slicedPrefix || undefined,
|
||||
delimiter: '/',
|
||||
'max-keys': itemsPerPage.toString(),
|
||||
'continuation-token': marker
|
||||
@@ -347,18 +339,24 @@ class AliyunApi {
|
||||
}) as any
|
||||
// prefixes can be null
|
||||
// objects will be [] when no file
|
||||
if (res && res.res.statusCode === 200) {
|
||||
res.prefixes && res.prefixes.forEach((item: string) => {
|
||||
result.fullList.push(this.formatFolder(item, slicedPrefix))
|
||||
})
|
||||
res.objects && res.objects.forEach((item: OSS.ObjectMeta) => {
|
||||
item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
|
||||
})
|
||||
result.isTruncated = res.isTruncated
|
||||
result.nextMarker = res.nextContinuationToken || ''
|
||||
result.success = true
|
||||
if (res?.res.statusCode !== 200) {
|
||||
return {
|
||||
fullList: [],
|
||||
isTruncated: false,
|
||||
nextMarker: '',
|
||||
success: false
|
||||
}
|
||||
}
|
||||
const fullList = [
|
||||
...(res.prefixes?.map((item: string) => this.formatFolder(item, slicedPrefix)) || []),
|
||||
...(res.objects?.filter((item: OSS.ObjectMeta) => item.size !== 0).map((item: OSS.ObjectMeta) => this.formatFile(item, slicedPrefix, urlPrefix)) || [])
|
||||
]
|
||||
return {
|
||||
fullList,
|
||||
isTruncated: res.isTruncated,
|
||||
nextMarker: res.nextContinuationToken || '',
|
||||
success: true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,16 +372,15 @@ class AliyunApi {
|
||||
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName, region, oldKey, newKey } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
const res = await client.copy(
|
||||
const copyRes = await client.copy(
|
||||
newKey,
|
||||
oldKey
|
||||
) as any
|
||||
if (res && res.res.statusCode === 200) {
|
||||
const res2 = await client.delete(oldKey) as any
|
||||
return res2 && res2.res.statusCode === 204
|
||||
} else {
|
||||
return false
|
||||
if (copyRes?.res.statusCode === 200) {
|
||||
const deleteRes = await client.delete(oldKey) as any
|
||||
return deleteRes?.res.statusCode === 204
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,7 +396,7 @@ class AliyunApi {
|
||||
const { bucketName, region, key } = configMap
|
||||
const client = this.getNewCtx(region, bucketName)
|
||||
const res = await client.delete(key) as any
|
||||
return res && res.res.statusCode === 204
|
||||
return res?.res.statusCode === 204
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,62 +412,39 @@ class AliyunApi {
|
||||
CommonPrefixes: [] as any[],
|
||||
Contents: [] as any[]
|
||||
}
|
||||
let res = await client.listV2({
|
||||
prefix: key,
|
||||
delimiter: '/',
|
||||
'max-keys': '1000'
|
||||
}, {
|
||||
timeout: this.timeOut
|
||||
}) as any
|
||||
if (res && res.res.statusCode === 200) {
|
||||
do {
|
||||
const res = await client.listV2({
|
||||
prefix: key,
|
||||
delimiter: '/',
|
||||
'max-keys': '1000',
|
||||
'continuation-token': marker
|
||||
}, {
|
||||
timeout: this.timeOut
|
||||
}) as any
|
||||
if (res?.res.statusCode !== 200) return false
|
||||
|
||||
res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes)
|
||||
res.objects.length > 0 && allFileList.Contents.push(...res.objects)
|
||||
res.objects?.length > 0 && allFileList.Contents.push(...res.objects)
|
||||
isTruncated = res.isTruncated
|
||||
marker = res.nextContinuationToken
|
||||
while (isTruncated) {
|
||||
res = await client.listV2({
|
||||
prefix: key,
|
||||
delimiter: '/',
|
||||
'max-keys': '1000',
|
||||
'continuation-token': marker
|
||||
}, {
|
||||
timeout: this.timeOut
|
||||
}) as any
|
||||
if (res && res.res.statusCode === 200) {
|
||||
res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes)
|
||||
res.objects.length > 0 && allFileList.Contents.push(...res.objects)
|
||||
isTruncated = res.isTruncated
|
||||
marker = res.nextContinuationToken
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} while (isTruncated)
|
||||
|
||||
if (allFileList.CommonPrefixes.length > 0) {
|
||||
for (const item of allFileList.CommonPrefixes) {
|
||||
res = await this.deleteBucketFolder({
|
||||
const successfully = await this.deleteBucketFolder({
|
||||
bucketName,
|
||||
region,
|
||||
key: item
|
||||
})
|
||||
if (!res) {
|
||||
return false
|
||||
}
|
||||
if (!successfully) return false
|
||||
}
|
||||
}
|
||||
if (allFileList.Contents.length > 0) {
|
||||
const cycle = Math.ceil(allFileList.Contents.length / 1000)
|
||||
for (let i = 0; i < cycle; i++) {
|
||||
res = await client.deleteMulti(
|
||||
allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => {
|
||||
return item.name
|
||||
})
|
||||
) as any
|
||||
if (!(res && res.res.statusCode === 200)) {
|
||||
return false
|
||||
}
|
||||
const deleteRes = await client.deleteMulti(
|
||||
allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => item.name)) as any
|
||||
if (deleteRes?.res.statusCode !== 200) return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -9,13 +9,13 @@ import UpyunApi from './upyun'
|
||||
import WebdavplistApi from './webdavplist'
|
||||
|
||||
export default {
|
||||
TcyunApi,
|
||||
AliyunApi,
|
||||
QiniuApi,
|
||||
UpyunApi,
|
||||
SmmsApi,
|
||||
GithubApi,
|
||||
ImgurApi,
|
||||
QiniuApi,
|
||||
S3plistApi,
|
||||
SmmsApi,
|
||||
TcyunApi,
|
||||
UpyunApi,
|
||||
WebdavplistApi
|
||||
}
|
||||
|
||||
@@ -1,16 +1,34 @@
|
||||
// HTTP 请求库
|
||||
import got from 'got'
|
||||
|
||||
// 日志记录器
|
||||
import { ManageLogger } from '../utils/logger'
|
||||
|
||||
// HTTP 代理格式化函数、是否为图片的判断函数
|
||||
import { formatHttpProxy, isImage } from '~/renderer/manage/utils/common'
|
||||
|
||||
// 窗口管理器
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
|
||||
// 枚举类型声明
|
||||
import { IWindowList } from '#/types/enum'
|
||||
|
||||
// Electron 相关
|
||||
import { ipcMain, IpcMainEvent } from 'electron'
|
||||
|
||||
// got 上传函数、路径处理函数、新的下载器、获取请求代理、获取请求选项、并发异步任务池、错误格式化函数
|
||||
import { gotUpload, trimPath, NewDownloader, getAgent, getOptions, ConcurrencyPromisePool, formatError } from '../utils/common'
|
||||
import UpDownTaskQueue,
|
||||
{
|
||||
commonTaskStatus
|
||||
} from '../datastore/upDownTaskQueue'
|
||||
|
||||
// 上传下载任务队列
|
||||
import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue'
|
||||
|
||||
// 文件系统库
|
||||
import fs from 'fs-extra'
|
||||
|
||||
// 路径处理库
|
||||
import path from 'path'
|
||||
|
||||
// 取消下载任务的加载文件列表、刷新下载文件传输列表
|
||||
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
|
||||
|
||||
class GithubApi {
|
||||
@@ -252,34 +270,32 @@ class GithubApi {
|
||||
*/
|
||||
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { bucketName: repo, githubBranch: branch, key } = configMap
|
||||
// get sha of the branch
|
||||
const refRes = await got(
|
||||
`${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`,
|
||||
getOptions('GET', this.commonHeaders, undefined, 'json', undefined, undefined, this.proxy)
|
||||
) as any
|
||||
if (refRes.statusCode !== 200) {
|
||||
return false
|
||||
}
|
||||
if (refRes.statusCode !== 200) return false
|
||||
const refSha = refRes.body.object.sha
|
||||
// get sha of the root tree
|
||||
const rootRes = await got(
|
||||
`${this.baseUrl}/repos/${this.username}/${repo}/branches/${branch}`,
|
||||
getOptions('GET', undefined, undefined, 'json', undefined, undefined, this.proxy)
|
||||
) as any
|
||||
if (rootRes.statusCode !== 200) {
|
||||
return false
|
||||
}
|
||||
if (rootRes.statusCode !== 200) return false
|
||||
const rootSha = rootRes.body.commit.commit.tree.sha
|
||||
// TODO: if there are more than 10000 files in the folder, it will be truncated
|
||||
// Rare cases, not considered for now
|
||||
// get sha of the folder tree
|
||||
const treeRes = await got(
|
||||
`${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${key.replace(/(^\/+|\/+$)/g, '')}`,
|
||||
getOptions('GET', this.commonHeaders, {
|
||||
recursive: true
|
||||
}, 'json', undefined, undefined, this.proxy)
|
||||
) as any
|
||||
if (treeRes.statusCode !== 200) {
|
||||
return false
|
||||
}
|
||||
if (treeRes.statusCode !== 200) return false
|
||||
const oldTree = treeRes.body.tree
|
||||
// create a new tree
|
||||
const newTree = oldTree.filter((item: any) => item.type === 'blob')
|
||||
.map((item:any) => ({
|
||||
path: `${key.replace(/(^\/+|\/+$)/g, '')}/${item.path}`,
|
||||
@@ -294,10 +310,9 @@ class GithubApi {
|
||||
tree: newTree
|
||||
}), undefined, this.proxy)
|
||||
) as any
|
||||
if (newTreeShaRes.statusCode !== 201) {
|
||||
return false
|
||||
}
|
||||
if (newTreeShaRes.statusCode !== 201) return false
|
||||
const newTreeSha = newTreeShaRes.body.sha
|
||||
// create a new commit
|
||||
const commitRes = await got(
|
||||
`${this.baseUrl}/repos/${this.username}/${repo}/git/commits`,
|
||||
getOptions('POST', this.commonHeaders, undefined, 'json', JSON.stringify({
|
||||
@@ -306,10 +321,9 @@ class GithubApi {
|
||||
parents: [refSha]
|
||||
}), undefined, this.proxy)
|
||||
) as any
|
||||
if (commitRes.statusCode !== 201) {
|
||||
return false
|
||||
}
|
||||
if (commitRes.statusCode !== 201) return false
|
||||
const commitSha = commitRes.body.sha
|
||||
// update the branch
|
||||
const updateRefRes = await got(
|
||||
`${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`,
|
||||
getOptions('PATCH', this.commonHeaders, undefined, 'json', JSON.stringify({
|
||||
|
||||
@@ -81,16 +81,12 @@ class ImgurApi {
|
||||
result.push(...res.body.data)
|
||||
initPage++
|
||||
} while (res.body.data.length > 0)
|
||||
const finalResult = [] as any[]
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const item = result[i]
|
||||
finalResult.push({
|
||||
...item,
|
||||
Name: item.title,
|
||||
Location: item.id,
|
||||
CreationDate: item.datetime
|
||||
})
|
||||
}
|
||||
const finalResult = result.map((item: any) => ({
|
||||
...item,
|
||||
Name: item.title,
|
||||
Location: item.id,
|
||||
CreationDate: item.datetime
|
||||
})) as any[]
|
||||
finalResult.push({
|
||||
Name: '全部',
|
||||
Location: 'unclassified',
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
// Axios
|
||||
import axios from 'axios'
|
||||
|
||||
// 加密函数、获取文件 MIME 类型、新的下载器、错误格式化函数、并发异步任务池
|
||||
import { hmacSha1Base64, getFileMimeType, NewDownloader, formatError, ConcurrencyPromisePool } from '../utils/common'
|
||||
|
||||
// 七牛云客户端库
|
||||
import qiniu from 'qiniu/index'
|
||||
|
||||
// 路径处理库
|
||||
import path from 'path'
|
||||
|
||||
// 是否为图片的判断函数
|
||||
import { isImage } from '~/renderer/manage/utils/common'
|
||||
|
||||
// 窗口管理器
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
|
||||
// 枚举类型声明
|
||||
import { IWindowList } from '#/types/enum'
|
||||
|
||||
// Electron 相关
|
||||
import { ipcMain, IpcMainEvent } from 'electron'
|
||||
import UpDownTaskQueue,
|
||||
{
|
||||
uploadTaskSpecialStatus,
|
||||
commonTaskStatus
|
||||
} from '../datastore/upDownTaskQueue'
|
||||
|
||||
// 上传下载任务队列
|
||||
import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus } from '../datastore/upDownTaskQueue'
|
||||
|
||||
// 日志记录器
|
||||
import { ManageLogger } from '../utils/logger'
|
||||
|
||||
// 取消下载任务的加载文件列表、刷新下载文件传输列表
|
||||
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
|
||||
|
||||
class QiniuApi {
|
||||
@@ -49,16 +66,17 @@ class QiniuApi {
|
||||
}
|
||||
|
||||
formatFile (item: any, slicedPrefix: string, urlPrefix: string) {
|
||||
const fileName = item.key.replace(slicedPrefix, '')
|
||||
return {
|
||||
...item,
|
||||
fileName: item.key.replace(slicedPrefix, ''),
|
||||
fileName,
|
||||
url: `${urlPrefix}/${item.key}`,
|
||||
fileSize: item.fsize,
|
||||
formatedTime: new Date(parseInt(item.putTime.toString().slice(0, -4), 10)).toLocaleString(),
|
||||
isDir: false,
|
||||
checked: false,
|
||||
match: false,
|
||||
isImage: isImage(item.key.replace(slicedPrefix, ''))
|
||||
isImage: isImage(fileName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,22 +89,20 @@ class QiniuApi {
|
||||
contentType: string,
|
||||
xQiniuHeaders?: IStringKeyMap
|
||||
) {
|
||||
let signStr = `${method.toUpperCase()} ${urlPath}`
|
||||
query && (signStr += `?${query}`)
|
||||
signStr += `\nHost: ${host}`
|
||||
let signStr = `${method.toUpperCase()} ${urlPath}${query ? `?${query}` : ''}\nHost: ${host}`
|
||||
|
||||
contentType && (signStr += `\nContent-Type: ${contentType}`)
|
||||
let xQiniuHeaderStr = ''
|
||||
if (xQiniuHeaders) {
|
||||
const xQiniuHeaderKeys = Object.keys(xQiniuHeaders).sort()
|
||||
xQiniuHeaderKeys.forEach((key) => {
|
||||
xQiniuHeaderStr += `\n${key}:${xQiniuHeaders[key]}`
|
||||
})
|
||||
const xQiniuHeaderStr = Object.keys(xQiniuHeaders)
|
||||
.sort()
|
||||
.map((key) => `\n${key}:${xQiniuHeaders[key]}`)
|
||||
.join('')
|
||||
signStr += xQiniuHeaderStr
|
||||
}
|
||||
|
||||
signStr += '\n\n'
|
||||
if (contentType !== 'application/octet-stream' && body) {
|
||||
signStr += body
|
||||
}
|
||||
|
||||
if (contentType !== 'application/octet-stream' && body) signStr += body
|
||||
return `Qiniu ${this.accessKey}:${hmacSha1Base64(this.secretKey, signStr).replace(/\+/g, '-').replace(/\//g, '_')}`
|
||||
}
|
||||
|
||||
@@ -103,28 +119,21 @@ class QiniuApi {
|
||||
},
|
||||
timeout: this.timeout
|
||||
})
|
||||
if (res && res.status === 200) {
|
||||
if (res.data && 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] })
|
||||
if (!info.success) {
|
||||
return []
|
||||
}
|
||||
result.push({
|
||||
Name: res.data[i],
|
||||
Location: info.zone,
|
||||
CreationDate: new Date().toISOString(),
|
||||
Private: info.private
|
||||
})
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return []
|
||||
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] })
|
||||
if (!info.success) return []
|
||||
result.push({
|
||||
Name: res.data[i],
|
||||
Location: info.zone,
|
||||
CreationDate: new Date().toISOString(),
|
||||
Private: info.private
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
return result
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,17 +157,15 @@ class QiniuApi {
|
||||
},
|
||||
timeout: this.timeout
|
||||
})
|
||||
if (res && res.status === 200) {
|
||||
return {
|
||||
return res?.status === 200
|
||||
? {
|
||||
success: true,
|
||||
private: res.data.private,
|
||||
zone: res.data.zone
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
: {
|
||||
success: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,11 +185,7 @@ class QiniuApi {
|
||||
},
|
||||
timeout: this.timeout
|
||||
})
|
||||
if (res && res.status === 200) {
|
||||
return res.data && res.data.length ? res.data : []
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
return res?.status === 200 && res?.data?.length ? res.data : []
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +212,7 @@ class QiniuApi {
|
||||
},
|
||||
timeout: this.timeout
|
||||
})
|
||||
return res && res.status === 200
|
||||
return res?.status === 200
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,8 +225,7 @@ class QiniuApi {
|
||||
* }
|
||||
*/
|
||||
async createBucket (configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { BucketName, region } = configMap
|
||||
const { acl } = configMap
|
||||
const { BucketName, region, acl } = configMap
|
||||
const urlPath = `/mkbucketv3/${BucketName}/region/${region}`
|
||||
const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
|
||||
const res = await axios({
|
||||
@@ -236,15 +238,12 @@ class QiniuApi {
|
||||
},
|
||||
timeout: this.timeout
|
||||
})
|
||||
if (res && res.status === 200) {
|
||||
const changeAclRes = await this.setBucketAclPolicy({
|
||||
return res?.status === 200
|
||||
? await this.setBucketAclPolicy({
|
||||
bucketName: BucketName,
|
||||
isPrivate: !acl
|
||||
})
|
||||
return changeAclRes
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
: false
|
||||
}
|
||||
|
||||
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||
@@ -407,19 +406,19 @@ class QiniuApi {
|
||||
}
|
||||
})
|
||||
})
|
||||
if (res && res.respInfo.statusCode === 200) {
|
||||
if (res.respBody && res.respBody.commonPrefixes) {
|
||||
if (res?.respInfo?.statusCode === 200) {
|
||||
if (res.respBody?.commonPrefixes) {
|
||||
res.respBody.commonPrefixes.forEach((item: string) => {
|
||||
result.fullList.push(this.formatFolder(item, slicedPrefix))
|
||||
})
|
||||
}
|
||||
if (res.respBody && res.respBody.items) {
|
||||
if (res.respBody?.items) {
|
||||
res.respBody.items.forEach((item: any) => {
|
||||
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
|
||||
})
|
||||
}
|
||||
result.isTruncated = !!(res.respBody && res.respBody.marker)
|
||||
result.nextMarker = res.respBody && res.respBody.marker ? res.respBody.marker : ''
|
||||
result.isTruncated = !!(res.respBody?.marker)
|
||||
result.nextMarker = res.respBody?.marker ? res.respBody.marker : ''
|
||||
result.success = true
|
||||
}
|
||||
return result
|
||||
@@ -450,11 +449,7 @@ class QiniuApi {
|
||||
}
|
||||
})
|
||||
}) as any
|
||||
if (res && res.respInfo.statusCode === 200) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return res?.respInfo?.statusCode === 200
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,12 +482,12 @@ class QiniuApi {
|
||||
}
|
||||
})
|
||||
}) as any
|
||||
if (res && res.respInfo.statusCode === 200) {
|
||||
if (res.respBody && res.respBody.items) {
|
||||
if (res?.respInfo?.statusCode === 200) {
|
||||
if (res.respBody?.items) {
|
||||
allFileList.Contents = allFileList.Contents.concat(res.respBody.items)
|
||||
}
|
||||
isTruncated = !!(res.respBody && res.respBody.marker)
|
||||
marker = res.respBody && res.respBody.marker ? res.respBody.marker : ''
|
||||
isTruncated = !!(res.respBody?.marker)
|
||||
marker = res.respBody?.marker ? res.respBody.marker : ''
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@@ -514,9 +509,7 @@ class QiniuApi {
|
||||
}
|
||||
})
|
||||
}) as any
|
||||
if (!(res && res.respInfo.statusCode === 200)) {
|
||||
return false
|
||||
}
|
||||
if (res?.respInfo?.statusCode !== 200) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -549,7 +542,7 @@ class QiniuApi {
|
||||
}
|
||||
})
|
||||
}) as any
|
||||
return res && res.respInfo.statusCode === 200
|
||||
return res?.respInfo?.statusCode === 200
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -671,11 +664,7 @@ class QiniuApi {
|
||||
}
|
||||
})
|
||||
}) as any
|
||||
if (res && res.respInfo.statusCode === 200) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return res?.respInfo?.statusCode === 200
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// AWS S3 相关
|
||||
import {
|
||||
S3Client,
|
||||
ListBucketsCommand,
|
||||
@@ -13,24 +14,48 @@ import {
|
||||
DeleteObjectsCommand,
|
||||
PutObjectCommand
|
||||
} from '@aws-sdk/client-s3'
|
||||
|
||||
// AWS S3 上传和进度
|
||||
import { Upload, Progress } from '@aws-sdk/lib-storage'
|
||||
|
||||
// AWS S3 请求签名
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
|
||||
|
||||
// HTTP 和 HTTPS 模块
|
||||
import https from 'https'
|
||||
import http from 'http'
|
||||
|
||||
// 日志记录器
|
||||
import { ManageLogger } from '../utils/logger'
|
||||
|
||||
// 端点地址格式化函数、错误格式化函数、获取请求代理、获取文件 MIME 类型、新的下载器、并发异步任务池
|
||||
import { formatEndpoint, formatError, getAgent, getFileMimeType, NewDownloader, ConcurrencyPromisePool } from '../utils/common'
|
||||
|
||||
// 是否为图片的判断函数、HTTP 代理格式化函数
|
||||
import { isImage, formatHttpProxy } from '@/manage/utils/common'
|
||||
|
||||
// HTTP 和 HTTPS 代理库
|
||||
import { HttpsProxyAgent, HttpProxyAgent } from 'hpagent'
|
||||
|
||||
// 窗口管理器
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
|
||||
// 枚举类型声明
|
||||
import { IWindowList } from '#/types/enum'
|
||||
|
||||
// Electron 相关
|
||||
import { ipcMain, IpcMainEvent } from 'electron'
|
||||
import UpDownTaskQueue,
|
||||
{
|
||||
uploadTaskSpecialStatus,
|
||||
commonTaskStatus
|
||||
} from '../datastore/upDownTaskQueue'
|
||||
|
||||
// 上传下载任务队列
|
||||
import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus } from '../datastore/upDownTaskQueue'
|
||||
|
||||
// 文件系统库
|
||||
import fs from 'fs-extra'
|
||||
|
||||
// 路径处理库
|
||||
import path from 'path'
|
||||
|
||||
// 取消下载任务的加载文件列表、刷新下载文件传输列表
|
||||
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
|
||||
|
||||
interface S3plistApiOptions {
|
||||
@@ -79,18 +104,14 @@ class S3plistApi {
|
||||
}
|
||||
|
||||
setAgent (proxy: string | undefined, sslEnabled: boolean) : HttpProxyAgent | HttpsProxyAgent | undefined {
|
||||
if (sslEnabled) {
|
||||
const agent = getAgent(proxy, true).https
|
||||
return agent ?? new https.Agent({
|
||||
keepAlive: true,
|
||||
rejectUnauthorized: false
|
||||
})
|
||||
} else {
|
||||
const agent = getAgent(proxy, false).http
|
||||
return agent ?? new http.Agent({
|
||||
keepAlive: true
|
||||
})
|
||||
}
|
||||
const protocol = sslEnabled ? 'https' : 'http'
|
||||
const agent = getAgent(proxy, sslEnabled)[protocol]
|
||||
const commonOptions = { keepAlive: true }
|
||||
const extraOptions = sslEnabled ? { rejectUnauthorized: false } : {}
|
||||
return agent ?? new (sslEnabled ? https.Agent : http.Agent)({
|
||||
...commonOptions,
|
||||
...extraOptions
|
||||
})
|
||||
}
|
||||
|
||||
logParam = (error:any, method: string) =>
|
||||
@@ -111,17 +132,18 @@ class S3plistApi {
|
||||
}
|
||||
|
||||
formatFile (item: _Object, slicedPrefix: string, urlPrefix: string): any {
|
||||
const fileName = item.Key?.replace(slicedPrefix, '')
|
||||
return {
|
||||
...item,
|
||||
key: item.Key,
|
||||
url: `${urlPrefix}/${item.Key}`,
|
||||
fileName: item.Key?.replace(slicedPrefix, ''),
|
||||
fileName,
|
||||
fileSize: item.Size,
|
||||
formatedTime: new Date(item.LastModified!).toLocaleString(),
|
||||
isDir: false,
|
||||
checked: false,
|
||||
match: false,
|
||||
isImage: isImage(item.Key?.replace(slicedPrefix, '') || '')
|
||||
isImage: isImage(fileName || '')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,50 +152,43 @@ class S3plistApi {
|
||||
*/
|
||||
async getBucketList (): Promise<any> {
|
||||
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||
const result = [] as IStringKeyMap[]
|
||||
const endpoint = options.endpoint as string || '' as string
|
||||
options.region = endpoint.indexOf('cloudflarestorage') !== -1 ? 'auto' : 'us-east-1'
|
||||
const result: IStringKeyMap[] = []
|
||||
const endpoint = options.endpoint as string || ''
|
||||
options.region = endpoint.includes('cloudflarestorage') ? 'auto' : 'us-east-1'
|
||||
try {
|
||||
const client = new S3Client(options)
|
||||
const command = new ListBucketsCommand({})
|
||||
const data = await client.send(command)
|
||||
if (data.$metadata.httpStatusCode === 200) {
|
||||
if (data.Buckets) {
|
||||
if (endpoint.indexOf('cloudflarestorage') !== -1) {
|
||||
data.Buckets.forEach((bucket) => {
|
||||
result.push({
|
||||
Name: bucket.Name,
|
||||
CreationDate: bucket.CreationDate,
|
||||
Location: 'auto'
|
||||
})
|
||||
const data = await client.send(new ListBucketsCommand({}))
|
||||
|
||||
if (data.$metadata.httpStatusCode !== 200) {
|
||||
this.logParam(data, 'getBucketList')
|
||||
return result
|
||||
}
|
||||
|
||||
if (data.Buckets) {
|
||||
if (endpoint.includes('cloudflarestorage')) {
|
||||
result.push(...data.Buckets.map(bucket => ({
|
||||
Name: bucket.Name,
|
||||
CreationDate: bucket.CreationDate,
|
||||
Location: 'auto'
|
||||
})))
|
||||
} else {
|
||||
for (const bucket of data.Buckets) {
|
||||
const bucketName = bucket.Name
|
||||
const bucketConfig = await client.send(new GetBucketLocationCommand({
|
||||
Bucket: bucketName
|
||||
}))
|
||||
result.push({
|
||||
Name: bucketName,
|
||||
CreationDate: bucket.CreationDate,
|
||||
Location: bucketConfig.$metadata.httpStatusCode === 200
|
||||
? bucketConfig.LocationConstraint?.toLowerCase() || 'us-east-1'
|
||||
: 'us-east-1'
|
||||
})
|
||||
} else {
|
||||
for (let i = 0; i < data.Buckets.length; i++) {
|
||||
const bucket = data.Buckets[i]
|
||||
const bucketName = bucket.Name
|
||||
const command = new GetBucketLocationCommand({
|
||||
Bucket: bucketName
|
||||
})
|
||||
const bucketConfig = await client.send(command)
|
||||
if (bucketConfig.$metadata.httpStatusCode === 200) {
|
||||
result.push({
|
||||
Name: bucketName,
|
||||
CreationDate: bucket.CreationDate,
|
||||
Location: bucketConfig.LocationConstraint?.toLowerCase() || 'us-east-1'
|
||||
})
|
||||
} else {
|
||||
this.logParam(bucketConfig, 'getBucketList')
|
||||
result.push({
|
||||
Name: bucketName,
|
||||
CreationDate: bucket.CreationDate,
|
||||
Location: 'us-east-1'
|
||||
})
|
||||
}
|
||||
if (bucketConfig.$metadata.httpStatusCode !== 200) {
|
||||
this.logParam(bucketConfig, 'getBucketList')
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.logParam(data, 'getBucketList')
|
||||
}
|
||||
} catch (error) {
|
||||
this.logParam(error, 'getBucketList')
|
||||
@@ -312,8 +327,7 @@ class S3plistApi {
|
||||
success: false
|
||||
}
|
||||
try {
|
||||
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||
options.region = region || 'us-east-1'
|
||||
const options = Object.assign({}, { ...this.baseOptions, region: region || 'us-east-1' }) as S3ClientConfig
|
||||
const client = new S3Client(options)
|
||||
const command = new ListObjectsV2Command({
|
||||
Bucket: bucket,
|
||||
@@ -324,12 +338,10 @@ class S3plistApi {
|
||||
})
|
||||
const data = await client.send(command)
|
||||
if (data.$metadata.httpStatusCode === 200) {
|
||||
data.CommonPrefixes && data.CommonPrefixes.forEach((item: CommonPrefix) => {
|
||||
result.fullList.push(this.formatFolder(item, slicedPrefix))
|
||||
})
|
||||
data.Contents && data.Contents.forEach((item: _Object) => {
|
||||
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
|
||||
})
|
||||
result.fullList = [
|
||||
...(data.CommonPrefixes?.map(item => this.formatFolder(item, slicedPrefix)) || []),
|
||||
...(data.Contents?.map(item => this.formatFile(item, slicedPrefix, urlPrefix)) || [])
|
||||
]
|
||||
result.isTruncated = data.IsTruncated || false
|
||||
result.nextMarker = data.NextContinuationToken || ''
|
||||
result.success = true
|
||||
@@ -354,8 +366,7 @@ class S3plistApi {
|
||||
const { bucketName, region, oldKey, newKey } = configMap
|
||||
let result = false
|
||||
try {
|
||||
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||
options.region = region || 'us-east-1'
|
||||
const options = Object.assign({}, { ...this.baseOptions, region: region || 'us-east-1' }) as S3ClientConfig
|
||||
const client = new S3Client(options)
|
||||
const command = new CopyObjectCommand({
|
||||
Bucket: bucketName,
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
// 是否为图片的判断函数
|
||||
import { isImage } from '@/manage/utils/common'
|
||||
|
||||
// Axios 和 Axios 实例类型声明
|
||||
import axios, { AxiosInstance } from 'axios'
|
||||
|
||||
// 窗口管理器
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
|
||||
// 枚举类型声明
|
||||
import { IWindowList } from '#/types/enum'
|
||||
|
||||
// Electron 相关
|
||||
import { ipcMain, IpcMainEvent } from 'electron'
|
||||
|
||||
// 表单数据库
|
||||
import FormData from 'form-data'
|
||||
|
||||
// 文件系统库
|
||||
import fs from 'fs-extra'
|
||||
|
||||
// 获取文件 MIME 类型、got 上传函数、新的下载器、并发异步任务池、错误格式化函数
|
||||
import { getFileMimeType, gotUpload, NewDownloader, ConcurrencyPromisePool, formatError } from '../utils/common'
|
||||
|
||||
// 路径处理库
|
||||
import path from 'path'
|
||||
|
||||
// 上传下载任务队列
|
||||
import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue'
|
||||
|
||||
// 日志记录器
|
||||
import { ManageLogger } from '../utils/logger'
|
||||
|
||||
class SmmsApi {
|
||||
@@ -99,7 +120,7 @@ class SmmsApi {
|
||||
return
|
||||
}
|
||||
marker++
|
||||
} while (!cancelTask[0] && res && res.status === 200 && res.data && res.data.success && res.data.CurrentPage < res.data.TotalPages)
|
||||
} while (!cancelTask[0] && res?.status === 200 && res?.data?.success && res.data.CurrentPage < res.data.TotalPages)
|
||||
result.success = !cancelTask[0]
|
||||
result.finished = true
|
||||
window.webContents.send('refreshFileTransferList', result)
|
||||
@@ -121,16 +142,14 @@ class SmmsApi {
|
||||
* customUrl: string
|
||||
* }
|
||||
*/
|
||||
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||
const { currentPage } = configMap
|
||||
let res = {} as any
|
||||
async getBucketFileList ({ currentPage }: IStringKeyMap): Promise<any> {
|
||||
const result = {
|
||||
fullList: <any>[],
|
||||
isTruncated: false,
|
||||
nextMarker: '',
|
||||
success: false
|
||||
}
|
||||
res = await this.axiosInstance(
|
||||
const res = await this.axiosInstance(
|
||||
'/upload_history',
|
||||
{
|
||||
method: 'GET',
|
||||
@@ -142,21 +161,17 @@ class SmmsApi {
|
||||
}
|
||||
}
|
||||
)
|
||||
if (res && res.status === 200 && res.data && res.data.success) {
|
||||
if (res.data.Count === 0) {
|
||||
result.success = true
|
||||
return result
|
||||
}
|
||||
res.data.data.forEach((item: any) => {
|
||||
result.fullList.push(this.formatFile(item))
|
||||
})
|
||||
result.isTruncated = res.data.CurrentPage < res.data.TotalPages
|
||||
result.nextMarker = res.data.CurrentPage + 1
|
||||
result.success = true
|
||||
return result
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
if (res?.status !== 200 || !res?.data?.success) return result
|
||||
|
||||
if (res.data.Count === 0) return { ...result, success: true }
|
||||
|
||||
res.data.data.forEach((item: any) => {
|
||||
result.fullList.push(this.formatFile(item))
|
||||
})
|
||||
result.isTruncated = res.data.CurrentPage < res.data.TotalPages
|
||||
result.nextMarker = res.data.CurrentPage + 1
|
||||
result.success = true
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,20 +184,18 @@ class SmmsApi {
|
||||
* DeleteHash: string
|
||||
* }
|
||||
*/
|
||||
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { DeleteHash } = configMap
|
||||
const params = {
|
||||
hash: DeleteHash,
|
||||
format: 'json'
|
||||
}
|
||||
async deleteBucketFile ({ DeleteHash }: IStringKeyMap): Promise<boolean> {
|
||||
const res = await this.axiosInstance(
|
||||
`/delete/${DeleteHash}`,
|
||||
{
|
||||
method: 'GET',
|
||||
params
|
||||
params: {
|
||||
hash: DeleteHash,
|
||||
format: 'json'
|
||||
}
|
||||
}
|
||||
)
|
||||
return res && res.status === 200 && res.data && res.data.success
|
||||
return res?.status === 200 && res?.data?.success
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user