Feature: downloaded file or folder can keey folder structure now

This commit is contained in:
萌萌哒赫萝
2023-02-27 23:11:43 +08:00
parent 750ea58845
commit 1e9c87dc22
17 changed files with 666 additions and 34 deletions
+1
View File
@@ -59,6 +59,7 @@ const uploadFile = async () => {
const progressBar = Math.round((progress.loaded / progress.total) * 100) const progressBar = Math.round((progress.loaded / progress.total) * 100)
process.stdout.write(`\r${progressBar}% ${fileName}`) process.stdout.write(`\r${progressBar}% ${fileName}`)
}) })
console.log('\n')
await parallelUploads3.done() await parallelUploads3.done()
console.log(`${fileName} uploaded!`) console.log(`${fileName} uploaded!`)
if (!versionFileHasUploaded) { if (!versionFileHasUploaded) {
+49 -1
View File
@@ -13,6 +13,7 @@ import UpDownTaskQueue,
commonTaskStatus commonTaskStatus
} from '../datastore/upDownTaskQueue' } from '../datastore/upDownTaskQueue'
import { ManageLogger } from '../utils/logger' import { ManageLogger } from '../utils/logger'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
// 坑爹阿里云 返回数据类型标注和实际各种不一致 // 坑爹阿里云 返回数据类型标注和实际各种不一致
class AliyunApi { class AliyunApi {
@@ -211,6 +212,53 @@ class AliyunApi {
return res && res.res.status === 200 return res && res.res.status === 200
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
let marker
const cancelTask = [false]
ipcMain.on(cancelDownloadLoadingFileList, (_evt: IpcMainEvent, token: string) => {
if (token === cancelToken) {
cancelTask[0] = true
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
})
let res = {} as any
const result = {
fullList: <any>[],
success: false,
finished: false
}
const client = this.getNewCtx(region, bucket)
do {
res = await client.listV2({
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
'max-keys': '1000',
'continuation-token': marker
}, {
timeout: this.timeOut
})
if (res && res.res.statusCode === 200) {
res.objects && res.objects.forEach((item: OSS.ObjectMeta) => {
item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
window.webContents.send(refreshDownloadFileTransferList, result)
} else {
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return
}
marker = res.nextContinuationToken
} while (res.isTruncated === true && !cancelTask[0])
result.success = !cancelTask[0]
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap
@@ -256,7 +304,7 @@ class AliyunApi {
} }
marker = res.nextContinuationToken marker = res.nextContinuationToken
} while (res.isTruncated === true && !cancelTask[0]) } while (res.isTruncated === true && !cancelTask[0])
result.success = true result.success = !cancelTask[0]
result.finished = true result.finished = true
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
+52
View File
@@ -11,6 +11,7 @@ import UpDownTaskQueue,
} from '../datastore/upDownTaskQueue' } from '../datastore/upDownTaskQueue'
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path' import path from 'path'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
class GithubApi { class GithubApi {
token: string token: string
@@ -147,6 +148,57 @@ class GithubApi {
return result return result
} }
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(/^\//, '').replace(/\/$/, '')
const cancelTask = [false]
ipcMain.on(cancelDownloadLoadingFileList, (_evt: IpcMainEvent, token: string) => {
if (token === cancelToken) {
cancelTask[0] = true
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
})
let res = {} as any
const result = {
fullList: <any>[],
success: false,
finished: false
}
const treeQueue = [slicedPrefix]
while (treeQueue.length) {
if (cancelTask[0]) {
result.finished = true
return result
}
const currentPrefix = treeQueue[0]
res = await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${treeQueue.shift()}`,
getOptions('GET', this.commonHeaders, {}, 'json', undefined, undefined, this.proxy)
) as any
if (res && res.statusCode === 200) {
const { tree } = res.body
tree.forEach((item: any) => {
if (item.type === 'tree') {
treeQueue.push(`${currentPrefix}/${item.path}`)
} else {
result.fullList.push(this.formatFile(item, currentPrefix, branch, repo, cdnUrl))
}
})
window.webContents.send(refreshDownloadFileTransferList, result)
} else {
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return
}
}
result.success = true
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap
+2 -2
View File
@@ -147,9 +147,9 @@ class ImgurApi {
return return
} }
initPage++ initPage++
} while (res.body.data.length > 0) } while (res.body.data.length > 0 && !cancelTask[0])
} }
result.success = true result.success = !cancelTask[0]
result.finished = true result.finished = true
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
+58 -1
View File
@@ -12,6 +12,7 @@ import UpDownTaskQueue,
commonTaskStatus commonTaskStatus
} from '../datastore/upDownTaskQueue' } from '../datastore/upDownTaskQueue'
import { ManageLogger } from '../utils/logger' import { ManageLogger } from '../utils/logger'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
class QiniuApi { class QiniuApi {
mac: qiniu.auth.digest.Mac mac: qiniu.auth.digest.Mac
@@ -246,6 +247,62 @@ class QiniuApi {
} }
} }
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
const slicedPrefix = prefix.slice(1)
const cancelTask = [false]
ipcMain.on(cancelDownloadLoadingFileList, (_evt: IpcMainEvent, token: string) => {
if (token === cancelToken) {
cancelTask[0] = true
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
})
let res = {} as any
const result = {
fullList: <any>[],
success: false,
finished: false
}
const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
do {
res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucket, {
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
marker,
limit: 1000
}, (err: any, respBody: any, respInfo: any) => {
if (err) {
reject(err)
} else {
resolve({
respBody,
respInfo
})
}
})
})
if (res && res.respInfo.statusCode === 200) {
res.respBody && res.respBody.items && res.respBody.items.forEach((item: any) => {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
window.webContents.send(refreshDownloadFileTransferList, result)
} else {
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return
}
marker = res.respBody.marker
} while (res.respBody && res.respBody.marker && !cancelTask[0])
result.success = !cancelTask[0]
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap
@@ -300,7 +357,7 @@ class QiniuApi {
} }
marker = res.respBody.marker marker = res.respBody.marker
} while (res.respBody && res.respBody.marker && !cancelTask[0]) } while (res.respBody && res.respBody.marker && !cancelTask[0])
result.success = true result.success = !cancelTask[0]
result.finished = true result.finished = true
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
+64 -4
View File
@@ -31,6 +31,7 @@ import UpDownTaskQueue,
} from '../datastore/upDownTaskQueue' } from '../datastore/upDownTaskQueue'
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path' import path from 'path'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
interface S3plistApiOptions { interface S3plistApiOptions {
credentials: { credentials: {
@@ -129,9 +130,9 @@ class S3plistApi {
*/ */
async getBucketList (): Promise<any> { async getBucketList (): Promise<any> {
const options = Object.assign({}, this.baseOptions) as S3ClientConfig const options = Object.assign({}, this.baseOptions) as S3ClientConfig
options.region = 'us-east-1'
const result = [] as IStringKeyMap[] const result = [] as IStringKeyMap[]
const endpoint = options.endpoint as string || '' as string const endpoint = options.endpoint as string || '' as string
options.region = endpoint.indexOf('cloudflarestorage') !== -1 ? 'auto' : 'us-east-1'
try { try {
const client = new S3Client(options) const client = new S3Client(options)
const command = new ListBucketsCommand({}) const command = new ListBucketsCommand({})
@@ -180,6 +181,64 @@ class S3plistApi {
return result return result
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
let marker
const cancelTask = [false]
ipcMain.on(cancelDownloadLoadingFileList, (_evt: IpcMainEvent, token: string) => {
if (token === cancelToken) {
cancelTask[0] = true
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
})
let res = {} as ListObjectsV2CommandOutput
const result = {
fullList: <any>[],
success: false,
finished: false
}
try {
do {
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
options.region = region || 'us-east-1'
const client = new S3Client(options)
const command = new ListObjectsV2Command({
Bucket: bucket,
Prefix: slicedPrefix === '' ? undefined : slicedPrefix,
MaxKeys: 1000,
ContinuationToken: marker
})
res = await client.send(command)
if (res.$metadata.httpStatusCode === 200) {
res.Contents && res.Contents.forEach((item: _Object) => {
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
window.webContents.send(refreshDownloadFileTransferList, result)
} else {
this.logParam(res, 'getBucketListRecursively')
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return
}
marker = res.NextContinuationToken
} while (res.IsTruncated && !cancelTask[0])
} catch (error) {
this.logParam(error, 'getBucketListRecursively')
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return
}
result.success = !cancelTask[0]
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap
@@ -221,7 +280,7 @@ class S3plistApi {
}) })
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
} else { } else {
this.logParam(res, 'getBucketFileList') this.logParam(res, 'getBucketListBackstage')
result.finished = true result.finished = true
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
@@ -230,12 +289,13 @@ class S3plistApi {
marker = res.NextContinuationToken marker = res.NextContinuationToken
} while (res.IsTruncated && !cancelTask[0]) } while (res.IsTruncated && !cancelTask[0])
} catch (error) { } catch (error) {
this.logParam(error, 'getBucketFileList') this.logParam(error, 'getBucketListBackstage')
result.finished = true result.finished = true
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
return
} }
result.success = true result.success = !cancelTask[0]
result.finished = true result.finished = true
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
+1 -1
View File
@@ -100,7 +100,7 @@ class SmmsApi {
} }
marker++ marker++
} while (!cancelTask[0] && res && res.status === 200 && res.data && res.data.success && res.data.CurrentPage < res.data.TotalPages) } while (!cancelTask[0] && res && res.status === 200 && res.data && res.data.success && res.data.CurrentPage < res.data.TotalPages)
result.success = true result.success = !cancelTask[0]
result.finished = true result.finished = true
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
+50 -1
View File
@@ -14,6 +14,7 @@ import UpDownTaskQueue,
downloadTaskSpecialStatus downloadTaskSpecialStatus
} from '../datastore/upDownTaskQueue' } from '../datastore/upDownTaskQueue'
import { ManageLogger } from '../utils/logger' import { ManageLogger } from '../utils/logger'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
class TcyunApi { class TcyunApi {
ctx: COS ctx: COS
@@ -115,6 +116,53 @@ class TcyunApi {
return res && res.statusCode === 200 return res && res.statusCode === 200
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const bucket = configMap.bucketName
const region = configMap.bucketConfig.Location
const prefix = configMap.prefix as string
const slicedPrefix = prefix.slice(1, prefix.length)
const urlPrefix = configMap.customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
let marker
const cancelToken = configMap.cancelToken as string
const cancelTask = [false]
ipcMain.on(cancelDownloadLoadingFileList, (_evt: IpcMainEvent, token: string) => {
if (token === cancelToken) {
cancelTask[0] = true
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
})
let res = {} as COS.GetBucketResult
const result = {
fullList: <any>[],
success: false,
finished: false
}
do {
res = await this.ctx.getBucket({
Bucket: bucket,
Region: region,
Prefix: slicedPrefix === '' ? undefined : slicedPrefix,
Marker: marker
})
if (res && res.statusCode === 200) {
res.Contents.forEach((item: COS.CosObject) =>
parseInt(item.Size) !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)))
window.webContents.send(refreshDownloadFileTransferList, result)
} else {
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return
}
marker = res.NextMarker
} while (res.IsTruncated === 'true' && !cancelTask[0])
result.success = !cancelTask[0]
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise < any > { async getBucketListBackstage (configMap: IStringKeyMap): Promise < any > {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const bucket = configMap.bucketName const bucket = configMap.bucketName
@@ -159,7 +207,7 @@ class TcyunApi {
} }
marker = res.NextMarker marker = res.NextMarker
} while (res.IsTruncated === 'true' && !cancelTask[0]) } while (res.IsTruncated === 'true' && !cancelTask[0])
result.success = true result.success = !cancelTask[0]
result.finished = true result.finished = true
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
@@ -481,6 +529,7 @@ class TcyunApi {
sourceFileName: fileName, sourceFileName: fileName,
targetFilePath: path.join(downloadPath, fileName) targetFilePath: path.join(downloadPath, fileName)
}) })
fs.ensureDirSync(path.dirname(path.join(downloadPath, fileName)))
this.ctx.downloadFile({ this.ctx.downloadFile({
Bucket: bucketName, Bucket: bucketName,
Region: region, Region: region,
+54 -1
View File
@@ -14,6 +14,7 @@ import UpDownTaskQueue,
commonTaskStatus commonTaskStatus
} from '../datastore/upDownTaskQueue' } from '../datastore/upDownTaskQueue'
import { ManageLogger } from '../utils/logger' import { ManageLogger } from '../utils/logger'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
class UpyunApi { class UpyunApi {
ser: Upyun.Service ser: Upyun.Service
@@ -91,6 +92,58 @@ class UpyunApi {
return this.bucket return this.bucket
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, prefix, cancelToken } = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `http://${bucket}.test.upcdn.net`
const cancelTask = [false]
ipcMain.on(cancelDownloadLoadingFileList, (_evt: IpcMainEvent, token: string) => {
if (token === cancelToken) {
cancelTask[0] = true
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
})
let res = {} as any
const result = {
fullList: <any>[],
success: false,
finished: false
}
const folderQueue = [prefix]
const getFolderFile = async (folder: any) => {
let marker = ''
const key = folder
do {
res = await this.cli.listDir(key, {
limit: 10000,
iter: marker
})
if (res) {
res.files && res.files.forEach((item: any) => {
item.type === 'F' && folderQueue.push(`${slicedPrefix}${item.name}/`)
item.type === 'N' && result.fullList.push(this.formatFile(item, folder, urlPrefix))
})
window.webContents.send(refreshDownloadFileTransferList, result)
} else {
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return
}
marker = res.next
} while (!cancelTask[0] && res.next !== this.stopMarker)
}
while (folderQueue.length) {
const folder = folderQueue.shift()
await getFolderFile(folder)
}
result.success = !cancelTask[0]
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, prefix, cancelToken } = configMap const { bucketName: bucket, prefix, cancelToken } = configMap
@@ -129,7 +182,7 @@ class UpyunApi {
} }
marker = res.next marker = res.next
} while (!cancelTask[0] && res.next !== this.stopMarker) } while (!cancelTask[0] && res.next !== this.stopMarker)
result.success = true result.success = !cancelTask[0]
result.finished = true result.finished = true
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
+51
View File
@@ -14,6 +14,7 @@ import UpDownTaskQueue,
} from '../datastore/upDownTaskQueue' } from '../datastore/upDownTaskQueue'
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path' import path from 'path'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
class WebdavplistApi { class WebdavplistApi {
endpoint: string endpoint: string
@@ -85,6 +86,55 @@ class WebdavplistApi {
isRequestSuccess = (code: number) => code >= 200 && code < 300 isRequestSuccess = (code: number) => code >= 200 && code < 300
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken } = configMap
const urlPrefix = customUrl || this.endpoint
const cancelTask = [false]
ipcMain.on(cancelDownloadLoadingFileList, (_evt: IpcMainEvent, token: string) => {
if (token === cancelToken) {
cancelTask[0] = true
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
})
let res = {} as any
const result = {
fullList: <any>[],
success: false,
finished: false
}
try {
res = await this.ctx.getDirectoryContents(prefix, {
deep: true,
details: true
})
if (this.isRequestSuccess(res.status)) {
if (res.data && res.data.length) {
res.data.forEach((item: FileStat) => {
if (item.type !== 'directory') {
result.fullList.push(this.formatFile(item, urlPrefix))
}
})
}
} else {
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return
}
} catch (error) {
this.logParam(error, 'getBucketListRecursively')
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return
}
result.success = true
result.finished = true
window.webContents.send(refreshDownloadFileTransferList, result)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
}
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> { async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { prefix, customUrl, cancelToken } = configMap const { prefix, customUrl, cancelToken } = configMap
@@ -128,6 +178,7 @@ class WebdavplistApi {
result.finished = true result.finished = true
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
ipcMain.removeAllListeners('cancelLoadingFileList') ipcMain.removeAllListeners('cancelLoadingFileList')
return
} }
result.success = true result.success = true
result.finished = true result.finished = true
+5
View File
@@ -56,6 +56,11 @@ export const manageIpcList = {
return manage.getBucketListBackstage(param) return manage.getBucketListBackstage(param)
}) })
ipcMain.on('getBucketListRecursively', async (_evt: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.getBucketListRecursively(param)
})
ipcMain.handle('openFileSelectDialog', async () => { ipcMain.handle('openFileSelectDialog', async () => {
const res = await dialog.showOpenDialog({ const res = await dialog.showOpenDialog({
properties: ['openFile', 'multiSelections'] properties: ['openFile', 'multiSelections']
+39
View File
@@ -18,6 +18,7 @@ import API from './apis/api'
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum' import { IWindowList } from '#/types/enum'
import { ipcMain } from 'electron' import { ipcMain } from 'electron'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
export class ManageApi extends EventEmitter implements ManageApiType { export class ManageApi extends EventEmitter implements ManageApiType {
private _config!: Partial<ManageConfigType> private _config!: Partial<ManageConfigType>
@@ -293,6 +294,44 @@ export class ManageApi extends EventEmitter implements ManageApiType {
} }
} }
async getBucketListRecursively (
param?: IStringKeyMap
): Promise<IStringKeyMap | ManageError> {
let client
let window
const defaultResult = {
fullList: [],
success: false,
finished: true
}
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
case 'aliyun':
case 'qiniu':
case 'upyun':
case 'smms':
case 'github':
case 'imgur':
case 's3plist':
case 'webdavplist':
try {
client = this.createClient() as any
return await client.getBucketListRecursively(param!)
} catch (error: any) {
this.errorMsg(error, this.getMsgParam('getBucketListRecursively'))
window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(refreshDownloadFileTransferList, defaultResult)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return {}
}
default:
window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(refreshDownloadFileTransferList, defaultResult)
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
return {}
}
}
/** /**
* 后台更新bucket文件列表 * 后台更新bucket文件列表
* @param param * @param param
+161 -15
View File
@@ -286,6 +286,7 @@ ea/*
size="small" size="small"
type="primary" type="primary"
plain plain
style="margin-right: 2px;"
@click="handleCheckAllChange" @click="handleCheckAllChange"
> >
全选 全选
@@ -300,6 +301,7 @@ ea/*
size="small" size="small"
type="warning" type="warning"
plain plain
style="margin-right: 2px;"
@click="handelCancelCheck" @click="handelCancelCheck"
> >
取消 取消
@@ -309,6 +311,7 @@ ea/*
size="small" size="small"
type="primary" type="primary"
plain plain
style="margin-right: 2px;"
@click="handleReverseCheck" @click="handleReverseCheck"
> >
反选 反选
@@ -318,6 +321,7 @@ ea/*
size="small" size="small"
type="primary" type="primary"
plain plain
style="margin-right: 2px;"
@click="handleCheckAllChange" @click="handleCheckAllChange"
> >
全选 全选
@@ -328,9 +332,10 @@ ea/*
type="success" type="success"
plain plain
:icon="Download" :icon="Download"
style="margin-right: 2px;"
@click="handelBatchDownload" @click="handelBatchDownload"
> >
下载({{ selectedItems.length }}) 下载({{ selectedItems.filter(item => item.isDir === false).length }})
</el-button> </el-button>
<el-button <el-button
class="btn" class="btn"
@@ -532,7 +537,7 @@ https://www.baidu.com/img/bd_logo1.png"
> >
<el-row> <el-row>
<el-icon <el-icon
v-if="!item.isDir || !showRenameFileIcon" v-if="!item.isDir && showRenameFileIcon"
size="20" size="20"
style="cursor: pointer;" style="cursor: pointer;"
color="#409EFF" color="#409EFF"
@@ -540,6 +545,15 @@ https://www.baidu.com/img/bd_logo1.png"
> >
<Edit /> <Edit />
</el-icon> </el-icon>
<el-icon
v-if="item.isDir"
size="20"
style="cursor: pointer;"
color="crimson"
@click="handelFolderBatchDownload(item)"
>
<Download />
</el-icon>
<el-dropdown> <el-dropdown>
<template #default> <template #default>
<el-icon <el-icon
@@ -687,20 +701,34 @@ https://www.baidu.com/img/bd_logo1.png"
加载中点击取消 加载中点击取消
</el-button> </el-button>
</el-affix> </el-affix>
<el-affix
v-if="isLoadingDownloadData"
style="position: fixed;top: 50px;right: 0px;"
@click="cancelDownloadLoading"
>
<el-button
type="warning"
icon="el-icon-loading"
style="font-size: 12px;font-weight: 500;"
:loading="isLoadingDownloadData"
>
准备下载中点击取消
</el-button>
</el-affix>
<el-drawer <el-drawer
v-model="isShowUploadPanel" v-model="isShowUploadPanel"
size="60%" size="60%"
@open="startRefreshUploadTask" @open="startRefreshUploadTask"
@close="stopRefreshUploadTask" @close="stopRefreshUploadTask"
> >
<template #header> <template #header>
<el-switch <el-switch
v-model="isUploadKeepDirStructure" v-model="isUploadKeepDirStructure"
@change="handleUploadKeepDirChange" active-text="保持目录结构"
active-text="保持目录结构" inactive-text="保持目录结构"
inactive-text="不保持目录结构" @change="handleUploadKeepDirChange"
/> />
</template> </template>
<div <div
id="upload-area" id="upload-area"
:class="{ 'is-dragover': dragover }" :class="{ 'is-dragover': dragover }"
@@ -1204,6 +1232,7 @@ import { useRoute } from 'vue-router'
import { Grid, Fold, Close, Folder, FolderAdd, Upload, CircleClose, Loading, CopyDocument, Edit, DocumentAdd, Link, Refresh, ArrowRight, HomeFilled, Document, Coin, Download, DeleteFilled, Sort, FolderOpened } from '@element-plus/icons-vue' import { Grid, Fold, Close, Folder, FolderAdd, Upload, CircleClose, Loading, CopyDocument, Edit, DocumentAdd, Link, Refresh, ArrowRight, HomeFilled, Document, Coin, Download, DeleteFilled, Sort, FolderOpened } from '@element-plus/icons-vue'
import { useManageStore } from '../store/manageStore' import { useManageStore } from '../store/manageStore'
import { renameFile, formatLink, formatFileName, getFileIconPath, formatFileSize, getExtension, isValidUrl, svg } from '../utils/common' import { renameFile, formatLink, formatFileName, getFileIconPath, formatFileSize, getExtension, isValidUrl, svg } from '../utils/common'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '../utils/static'
import { ipcRenderer, clipboard, IpcRendererEvent } from 'electron' import { ipcRenderer, clipboard, IpcRendererEvent } from 'electron'
import { fileCacheDbInstance } from '../store/bucketFileDb' import { fileCacheDbInstance } from '../store/bucketFileDb'
import { trimPath } from '~/main/manage/utils/common' import { trimPath } from '~/main/manage/utils/common'
@@ -1225,7 +1254,7 @@ import {
ElCard ElCard
} from 'element-plus' } from 'element-plus'
import type { Column, RowClassNameGetter } from 'element-plus' import type { Column, RowClassNameGetter } from 'element-plus'
import { useFileTransferStore } from '@/manage/store/manageStore' import { useFileTransferStore, useDownloadFileTransferStore } from '@/manage/store/manageStore'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import path from 'path' import path from 'path'
import { IUploadTask, IDownloadTask } from '~/main/manage/datastore/upDownTaskQueue' import { IUploadTask, IDownloadTask } from '~/main/manage/datastore/upDownTaskQueue'
@@ -1267,6 +1296,7 @@ const fileSortTimeReverse = ref(false)
const fileSortSizeReverse = ref(false) const fileSortSizeReverse = ref(false)
const fileSortExtReverse = ref(false) const fileSortExtReverse = ref(false)
const currentPageFilesInfo = reactive([] as any[]) const currentPageFilesInfo = reactive([] as any[])
const currentDownloadFileList = reactive([] as any[])
const route = useRoute() const route = useRoute()
const configMap = reactive(JSON.parse(route.query.configMap as string)) const configMap = reactive(JSON.parse(route.query.configMap as string))
const selectedItems = reactive([] as any[]) const selectedItems = reactive([] as any[])
@@ -1282,7 +1312,9 @@ const elTable = ref(null as any)
const isShiftKeyPress = ref<boolean>(false) const isShiftKeyPress = ref<boolean>(false)
const lastChoosed = ref<number>(-1) const lastChoosed = ref<number>(-1)
const isLoadingData = ref(false) const isLoadingData = ref(false)
const isLoadingDownloadData = ref(false)
const cancelToken = ref('') const cancelToken = ref('')
const downloadCancelToken = ref('')
const isShowUploadPanel = ref(false) const isShowUploadPanel = ref(false)
const isShowDownloadPanel = ref(false) const isShowDownloadPanel = ref(false)
const dragover = ref(false) const dragover = ref(false)
@@ -1823,11 +1855,16 @@ async function resetParam (force: boolean = false) {
isLoadingData.value = false isLoadingData.value = false
ipcRenderer.send('cancelLoadingFileList', cancelToken.value) ipcRenderer.send('cancelLoadingFileList', cancelToken.value)
} }
if (isLoadingDownloadData.value) {
isLoadingDownloadData.value = false
ipcRenderer.send(cancelDownloadLoadingFileList, downloadCancelToken.value)
}
cancelToken.value = '' cancelToken.value = ''
pagingMarker.value = '' pagingMarker.value = ''
currentPrefix.value = configMap.prefix currentPrefix.value = configMap.prefix
currentPage.value = 1 currentPage.value = 1
currentPageFilesInfo.length = 0 currentPageFilesInfo.length = 0
currentDownloadFileList.length = 0
selectedItems.length = 0 selectedItems.length = 0
searchText.value = '' searchText.value = ''
urlToUpload.value = '' urlToUpload.value = ''
@@ -2141,6 +2178,90 @@ function handleCheckChange (item: any) {
} }
} }
async function handelFolderBatchDownload (item: any) {
ElMessageBox.confirm('确定要下载该文件夹吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const defaultDownloadPath = await ipcRenderer.invoke('getDefaultDownloadFolder')
const param = {
downloadPath: manageStore.config.settings.downloadDir ?? defaultDownloadPath,
maxDownloadFileCount: manageStore.config.settings.maxDownloadFileCount ? manageStore.config.settings.maxDownloadFileCount : 5,
fileArray: [] as any[]
}
cancelToken.value = uuidv4()
const paramGet = {
// tcyun
bucketName: configMap.bucketName,
bucketConfig: {
Location: configMap.bucketConfig.Location
},
paging: paging.value,
prefix: `/${item.key.replace(/\/+$/, '').replace(/^\/+/, '')}/`,
marker: pagingMarker.value,
itemsPerPage: itemsPerPage.value,
customUrl: currentCustomUrl.value,
currentPage: currentPage.value,
cancelToken: cancelToken.value,
cdnUrl: configMap.cdnUrl
}
isLoadingDownloadData.value = true
const downloadFileTransferStore = useDownloadFileTransferStore()
downloadFileTransferStore.resetDownloadFileTransferList()
ipcRenderer.send('getBucketListRecursively', configMap.alias, paramGet)
ipcRenderer.on(refreshDownloadFileTransferList, (evt: IpcRendererEvent, data) => {
downloadFileTransferStore.refreshDownloadFileTransferList(data)
})
const interval = setInterval(() => {
const currentFileList = downloadFileTransferStore.getDownloadFileTransferList()
currentDownloadFileList.length = 0
currentDownloadFileList.push(...currentFileList)
if (downloadFileTransferStore.isFinished()) {
clearInterval(interval)
isLoadingDownloadData.value = false
if (downloadFileTransferStore.isSuccess()) {
ElNotification.success({
title: '提示',
message: '获取下载列表成功',
duration: 500
})
if (currentDownloadFileList.length) {
currentDownloadFileList.forEach((item: any) => {
param.fileArray.push({
alias: configMap.alias,
bucketName: configMap.bucketName,
region: configMap.bucketConfig.Location,
key: item.key,
fileName: [undefined, true].includes(manageStore.config.settings.isDownloadFolderKeepDirStructure) ? `/${item.key.replace(/\/+$/, '').replace(/^\/+/, '')}` : item.fileName,
customUrl: currentCustomUrl.value,
downloadUrl: item.downloadUrl,
githubUrl: item.url,
githubPrivate: configMap.bucketConfig.private
})
})
}
ipcRenderer.send('downloadBucketFile', configMap.alias, param)
isShowDownloadPanel.value = true
} else {
ElNotification.error({
title: '提示',
message: '获取失败',
duration: 500
})
}
downloadFileTransferStore.resetDownloadFileTransferList()
}
}, 500)
}).catch(() => {
ElNotification.info({
title: '提示',
message: '已取消',
duration: 500
})
})
}
async function handelBatchDownload () { async function handelBatchDownload () {
const defaultDownloadPath = await ipcRenderer.invoke('getDefaultDownloadFolder') const defaultDownloadPath = await ipcRenderer.invoke('getDefaultDownloadFolder')
const param = { const param = {
@@ -2155,7 +2276,7 @@ async function handelBatchDownload () {
bucketName: configMap.bucketName, bucketName: configMap.bucketName,
region: configMap.bucketConfig.Location, region: configMap.bucketConfig.Location,
key: item.key, key: item.key,
fileName: item.fileName, fileName: manageStore.config.settings.isDownloadFileKeepDirStructure ? `/${item.key.replace(/\/+$/, '').replace(/^\/+/, '')}` : item.fileName,
customUrl: currentCustomUrl.value, customUrl: currentCustomUrl.value,
downloadUrl: item.downloadUrl, downloadUrl: item.downloadUrl,
githubUrl: item.url, githubUrl: item.url,
@@ -2313,6 +2434,18 @@ function cancelLoading () {
}).catch(() => { }) }).catch(() => { })
} }
function cancelDownloadLoading () {
ElMessageBox.confirm('是否停止下载文件获取?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
isLoadingData.value = false
ipcRenderer.send(cancelDownloadLoadingFileList, downloadCancelToken.value)
ElMessage.success('下载文件获取已停止')
}).catch(() => { })
}
async function getBucketFileListBackStage () { async function getBucketFileListBackStage () {
cancelToken.value = uuidv4() cancelToken.value = uuidv4()
const param = { const param = {
@@ -2364,13 +2497,13 @@ async function getBucketFileListBackStage () {
ElNotification.success({ ElNotification.success({
title: '提示', title: '提示',
message: '获取文件列表成功', message: '获取文件列表成功',
duration: 1000 duration: 500
}) })
} else { } else {
ElNotification.error({ ElNotification.error({
title: '提示', title: '提示',
message: '部分文件获取失败', message: '部分文件获取失败',
duration: 1000 duration: 500
}) })
} }
fileTransferStore.resetFileTransferList() fileTransferStore.resetFileTransferList()
@@ -2992,7 +3125,16 @@ const columns: Column<any>[] = [
cellRenderer: ({ rowData: item }) => ( cellRenderer: ({ rowData: item }) => (
item.match || !searchText.value item.match || !searchText.value
? item.isDir || !showRenameFileIcon.value ? item.isDir || !showRenameFileIcon.value
? <span></span> ? item.isDir
? <ElIcon
size="20"
style="cursor: pointer;"
color="#409EFF"
onClick={() => handelFolderBatchDownload(item)}
>
<Download />
</ElIcon>
: <template></template>
: <ElIcon : <ElIcon
size="20" size="20"
style="cursor: pointer;" style="cursor: pointer;"
@@ -3178,7 +3320,11 @@ onBeforeUnmount(() => {
if (isLoadingData.value) { if (isLoadingData.value) {
ipcRenderer.send('cancelLoadingFileList', cancelToken.value) ipcRenderer.send('cancelLoadingFileList', cancelToken.value)
} }
if (isLoadingDownloadData.value) {
ipcRenderer.send(cancelDownloadLoadingFileList, downloadCancelToken.value)
}
ipcRenderer.removeAllListeners('refreshFileTransferList') ipcRenderer.removeAllListeners('refreshFileTransferList')
ipcRenderer.removeAllListeners(refreshDownloadFileTransferList)
}) })
</script> </script>
+40 -8
View File
@@ -174,10 +174,10 @@
<template #label> <template #label>
<span <span
style="position:absolute;left: 0;" style="position:absolute;left: 0;"
>下载时保留目录结构 ><span>下载</span><span style="color: orange;">文件</span><span>时保留目录结构</span>
<el-tooltip <el-tooltip
effect="dark" effect="dark"
content="开启后,下载文件时会保留目录结构,关闭后会将所有文件展开到下载目录下" content="开启后,下载文件时会保留原始目录结构"
placement="right" placement="right"
> >
<el-icon> <el-icon>
@@ -187,11 +187,35 @@
</span> </span>
</template> </template>
<el-switch <el-switch
v-model="form.isDownloadKeepDirStructure" v-model="form.isDownloadFileKeepDirStructure"
style="position:absolute;right: 0;" style="position:absolute;right: 0;"
active-color="#13ce66" active-color="#13ce66"
inactive-color="#ff4949" inactive-color="#ff4949"
@change="handelIsDownloadKeepDirStructureChange" @change="handelIsDownloadFileKeepDirStructureChange"
/>
</el-form-item>
<el-form-item>
<template #label>
<span
style="position:absolute;left: 0;"
><span>下载</span><span style="color: coral;">文件夹</span><span>时保留目录结构</span>
<el-tooltip
effect="dark"
content="开启后,下载文件夹时会保留目录结构,关闭后会将所有文件展开到下载目录下"
placement="right"
>
<el-icon>
<InfoFilled />
</el-icon>
</el-tooltip>
</span>
</template>
<el-switch
v-model="form.isDownloadFolderKeepDirStructure"
style="position:absolute;right: 0;"
active-color="#13ce66"
inactive-color="#ff4949"
@change="handelIsDownloadFolderKeepDirStructureChange"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@@ -499,7 +523,8 @@ const form = reactive<IStringKeyMap>({
isForceCustomUrlHttps: false, isForceCustomUrlHttps: false,
isShowList: false, isShowList: false,
isUploadKeepDirStructure: true, isUploadKeepDirStructure: true,
isDownloadKeepDirStructure: true isDownloadFileKeepDirStructure: false,
isDownloadFolderKeepDirStructure: true
}) })
const downloadDir = ref('') const downloadDir = ref('')
@@ -571,7 +596,8 @@ async function initData () {
PreSignedExpire.value = config.settings.PreSignedExpire ?? 14400 PreSignedExpire.value = config.settings.PreSignedExpire ?? 14400
maxDownloadFileCount.value = config.settings.maxDownloadFileCount ?? 5 maxDownloadFileCount.value = config.settings.maxDownloadFileCount ?? 5
form.isUploadKeepDirStructure = config.settings.isUploadKeepDirStructure ?? true form.isUploadKeepDirStructure = config.settings.isUploadKeepDirStructure ?? true
form.isDownloadKeepDirStructure = config.settings.isDownloadKeepDirStructure ?? true form.isDownloadFileKeepDirStructure = config.settings.isDownloadKeepDirStructure ?? false
form.isDownloadFolderKeepDirStructure = config.settings.isDownloadFolderKeepDirStructure ?? true
} }
async function handleDownloadDirClick () { async function handleDownloadDirClick () {
@@ -611,9 +637,15 @@ function handelIsUploadKeepDirStructureChange (val:ICheckBoxValueType) {
}) })
} }
function handelIsDownloadKeepDirStructureChange (val:ICheckBoxValueType) { function handelIsDownloadFileKeepDirStructureChange (val:ICheckBoxValueType) {
saveConfig({ saveConfig({
'settings.isDownloadKeepDirStructure': val 'settings.isDownloadFileKeepDirStructure': val
})
}
function handelIsDownloadFolderKeepDirStructureChange (val:ICheckBoxValueType) {
saveConfig({
'settings.isDownloadFolderKeepDirStructure': val
}) })
} }
+31
View File
@@ -45,3 +45,34 @@ export const useFileTransferStore = defineStore('fileTransfer', {
} }
} }
}) })
export const useDownloadFileTransferStore = defineStore('downloadFileTransfer', {
state: () => {
return {
downloadFileTransferList: [] as IStringKeyMap[],
success: false,
finished: false
}
},
actions: {
refreshDownloadFileTransferList (newData: IStringKeyMap) {
this.downloadFileTransferList = newData.fullList ?? []
this.success = newData.success
this.finished = newData.finished
},
resetDownloadFileTransferList () {
this.downloadFileTransferList = []
this.success = false
this.finished = false
},
getDownloadFileTransferList () {
return this.downloadFileTransferList
},
isFinished () {
return this.finished
},
isSuccess () {
return this.success
}
}
})
+2
View File
@@ -0,0 +1,2 @@
export const cancelDownloadLoadingFileList = 'cancelDownloadLoadingFileList'
export const refreshDownloadFileTransferList = 'refreshDownloadFileTransferList'
+6
View File
@@ -57,6 +57,12 @@ interface ManageApiType {
* unset manage config to ctx && will not save to configPath * unset manage config to ctx && will not save to configPath
*/ */
unsetConfig: (key: string, propName: string) => void unsetConfig: (key: string, propName: string) => void
/**
* get bucket list
*/
getBucketListRecursively: (
param?: IStringKeyMap
) => Promise<any | ManageError>;
/** /**
* get bucket list * get bucket list
*/ */