diff --git a/src/common/ipc-actions/upload.ts b/src/common/ipc-actions/upload.ts index dbe128fd..82aed38d 100644 --- a/src/common/ipc-actions/upload.ts +++ b/src/common/ipc-actions/upload.ts @@ -37,6 +37,7 @@ export enum UploadAction { StopJobsByOffline = "StopJobsByOffline", StartJobsByOnline = "StartJobsByOnline", RemoveAllJobs = "RemoveAllJobs", + ClearRegionsCache = "ClearRegionsCache", // common UpdateUiData = "UpdateUiData", @@ -178,6 +179,11 @@ export interface RemoveAllJobsMessage { data?: {}, } +export interface ClearRegionsCacheMessage { + action: UploadAction.ClearRegionsCache, + data?: {}, +} + export interface JobCompletedReplyMessage { action: UploadAction.JobCompleted, data: { @@ -208,6 +214,7 @@ export type UploadMessage = UpdateConfigMessage | StopJobsByOfflineMessage | StartJobsByOnlineMessage | RemoveAllJobsMessage + | ClearRegionsCacheMessage export type UploadReplyMessage = UpdateUiDataReplyMessage | AddedJobsReplyMessage @@ -319,4 +326,12 @@ export class UploadActionFns { data: {}, }); } + + clearRegionsCache() { + // if only ucUrl and s3RegionId are provided, + // it will clear all regions cache + this.sender.send(this.channel, { + action: UploadAction.ClearRegionsCache, + }); + } } diff --git a/src/common/models/job/upload-job.ts b/src/common/models/job/upload-job.ts index 62eba582..2797452f 100644 --- a/src/common/models/job/upload-job.ts +++ b/src/common/models/job/upload-job.ts @@ -201,6 +201,7 @@ export default class UploadJob extends TransferJob { ...super.uiData, from: this.options.from, to: this.options.to, + accelerateUploading: this.accelerateUploading, }; } diff --git a/src/main/kv-store/data-store.ts b/src/main/kv-store/data-store.ts index d607858a..beb5cc87 100644 --- a/src/main/kv-store/data-store.ts +++ b/src/main/kv-store/data-store.ts @@ -45,9 +45,9 @@ export class DataStore { constructor({ workingDirectory, - thresholdDuration, - thresholdSize, - thresholdChangesSize, + thresholdDuration, // ms, trigger compact when thresholdDuration elapsed after last compact + thresholdSize, // trigger compact when memTable size exceeds thresholdSize + thresholdChangesSize, // trigger compact when memTable changes size exceeds thresholdChangesSize memTable, memTableReadOnly, diff --git a/src/main/upload-worker.ts b/src/main/upload-worker.ts index bb11c1ec..b78aa02d 100644 --- a/src/main/upload-worker.ts +++ b/src/main/upload-worker.ts @@ -1,4 +1,5 @@ import {Region} from "kodo-s3-adapter-sdk"; +import {KodoHttpClient} from "kodo-s3-adapter-sdk/dist/kodo-http-client"; import { AddedJobsReplyMessage, @@ -139,6 +140,10 @@ process.on("message", (message: UploadMessage) => { uploadManager.removeAllJobs(); break; } + case UploadAction.ClearRegionsCache: { + KodoHttpClient.clearCache(); + break; + } default: { console.warn("Upload Manager received unknown action, message:", message); } diff --git a/src/renderer/components/modals/file/upload-files-confirm/index.tsx b/src/renderer/components/modals/file/upload-files-confirm/index.tsx index b6e22c92..428884c7 100644 --- a/src/renderer/components/modals/file/upload-files-confirm/index.tsx +++ b/src/renderer/components/modals/file/upload-files-confirm/index.tsx @@ -2,7 +2,7 @@ import {promises as fsPromises} from "fs"; import path from "path"; import React, {Fragment, useEffect, useMemo, useState} from "react"; -import {Button, Form, Modal, ModalProps, Spinner} from "react-bootstrap"; +import {Button, Form, Modal, ModalProps, OverlayTrigger, Popover, Spinner} from "react-bootstrap"; import {SubmitHandler, useForm} from "react-hook-form"; import {toast} from "react-hot-toast"; @@ -18,6 +18,60 @@ import {useEndpointConfig} from "@renderer/modules/user-config-store"; import LoadingHolder from "@renderer/components/loading-holder"; +interface TipPopoverProps { + className: string, + onClickRefresh: () => void, +} + +const TipPopover: React.FC = ({ + className, + onClickRefresh, +}) => { + const [show, setShow] = useState(false); + const handleToggle = (nextShow: boolean) => { + if (!show) { + setShow(nextShow); + } + }; + const handleMouseEnter = () => { + setShow(true); + }; + const handleMouseLeave = () => { + setShow(false); + }; + return ( +
+ + + 已经开通加速域名,但没有显示使用加速域名的开关? +
+ onClickRefresh()} + onKeyUp={e => e.code === "Space" && onClickRefresh()} + > + 点击这里 + + 刷新试试。 +
+ + } + > + +
+
+ ); +}; + interface UploadFilesConfirmProps { filePaths: string[], maxShowFiles?: number, @@ -26,6 +80,7 @@ interface UploadFilesConfirmProps { bucketName: string, destPath: string, canAccelerateUploading?: boolean, + onClickRefreshCanAccelerateUploading?: () => void, } interface FileShowItem { @@ -63,6 +118,7 @@ const UploadFilesConfirm: React.FC = ({ bucketName, destPath, canAccelerateUploading = false, + onClickRefreshCanAccelerateUploading, ...modalProps }) => { const {currentLanguage, translate} = useI18n(); @@ -324,6 +380,13 @@ const UploadFilesConfirm: React.FC = ({ + { + onClickRefreshCanAccelerateUploading && + + } { !memoFilePaths.length ? null diff --git a/src/renderer/components/transfer-panel/job-item.tsx b/src/renderer/components/transfer-panel/job-item.tsx index 2fd539cc..fb5d24b7 100644 --- a/src/renderer/components/transfer-panel/job-item.tsx +++ b/src/renderer/components/transfer-panel/job-item.tsx @@ -8,12 +8,12 @@ import TransferJob from "@common/models/job/transfer-job"; import {ADDR_KODO_PROTOCOL} from "@renderer/const/kodo-nav"; import TooltipText from "@renderer/components/tooltip-text"; -import {translate} from "@renderer/modules/i18n"; +import {useI18n} from "@renderer/modules/i18n"; import {Status2I18nKey} from "@renderer/modules/i18n/extra"; interface JobItemProps { namePrefix?: string, - data: TransferJob["uiData"], + data: TransferJob["uiData"] & {accelerateUploading?: boolean}, operationButtons?: React.ReactNode, } @@ -22,6 +22,8 @@ const JobItem: React.FC = ({ data, operationButtons, }) => { + const {translate} = useI18n(); + const { status, from, @@ -72,14 +74,28 @@ const JobItem: React.FC = ({ } - +
+ + + + +
{ diff --git a/src/renderer/components/transfer-panel/transfer-panel.scss b/src/renderer/components/transfer-panel/transfer-panel.scss index db4d11b8..a0d5c885 100644 --- a/src/renderer/components/transfer-panel/transfer-panel.scss +++ b/src/renderer/components/transfer-panel/transfer-panel.scss @@ -38,6 +38,12 @@ --bs-progress-height: 0.5rem; } + & .acc-uploading-icon { + min-width: 0.75rem; + font-size: 0.75rem; + color: var(--bs-yellow); + } + & .job-item-name { flex: 0 0 30%; min-width: 30%; diff --git a/src/renderer/modules/i18n/lang/dict.ts b/src/renderer/modules/i18n/lang/dict.ts index a3d2b2a0..da42e5ed 100644 --- a/src/renderer/modules/i18n/lang/dict.ts +++ b/src/renderer/modules/i18n/lang/dict.ts @@ -252,6 +252,7 @@ export default interface Dictionary { removeConfirmOk: string, unknownError: string, fileDuplicated: string, + accelerateUploading: string, }, upload: { dropZone: { @@ -793,6 +794,11 @@ export default interface Dictionary { label: string, }, }, + popupHint: { + question: string, + clickHere: string, + refreshIt: string, + } }, preview: { diff --git a/src/renderer/modules/i18n/lang/en-us.ts b/src/renderer/modules/i18n/lang/en-us.ts index a5540192..0e1b8943 100644 --- a/src/renderer/modules/i18n/lang/en-us.ts +++ b/src/renderer/modules/i18n/lang/en-us.ts @@ -246,6 +246,7 @@ const dict: Dictionary = { removeConfirmOk: "Remove", unknownError: "unknown error", fileDuplicated: "File duplicated", + accelerateUploading: "Accelerate uploading", }, upload: { dropZone: { @@ -798,6 +799,11 @@ const dict: Dictionary = { label: "Storage Class:", }, }, + popupHint: { + question: "Already enabled accelerate uploading for this bucket, but not see the switch?", + clickHere: "Click here", + refreshIt: " refresh it." + } }, preview: { diff --git a/src/renderer/modules/i18n/lang/ja-jp.ts b/src/renderer/modules/i18n/lang/ja-jp.ts index a71f0acb..29048b5b 100644 --- a/src/renderer/modules/i18n/lang/ja-jp.ts +++ b/src/renderer/modules/i18n/lang/ja-jp.ts @@ -245,6 +245,7 @@ const dict: Dictionary = { removeConfirmOk: "削除", unknownError: "不明なエラーです", fileDuplicated: "ファイルは既に存在", + accelerateUploading: "アップロードを加速する", }, upload: { dropZone: { @@ -797,6 +798,11 @@ const dict: Dictionary = { label: "保管タイプ:", }, }, + popupHint: { + question: "すでにアクセラレーションドメインを有効にしましたが、アクセラレーションドメインを使用するスイッチが表示されませんか?", + clickHere: "こちらをクリック", + refreshIt: "リフレッシュしてみてください", + } }, preview: { diff --git a/src/renderer/modules/i18n/lang/zh-cn.ts b/src/renderer/modules/i18n/lang/zh-cn.ts index abc5ad43..c0ba009d 100644 --- a/src/renderer/modules/i18n/lang/zh-cn.ts +++ b/src/renderer/modules/i18n/lang/zh-cn.ts @@ -245,6 +245,7 @@ const dict: Dictionary = { removeConfirmOk: "移除", unknownError: "未知错误", fileDuplicated: "文件已存在", + accelerateUploading: "加速上传", }, upload: { dropZone: { @@ -797,6 +798,11 @@ const dict: Dictionary = { label: "存储类型:", }, }, + popupHint: { + question: "已经开通加速域名,但没有显示使用加速域名的开关?", + clickHere: "点击这里", + refreshIt: "刷新试试", + } }, preview: { diff --git a/src/renderer/modules/qiniu-client/utils.ts b/src/renderer/modules/qiniu-client/utils.ts index d9b49de8..07a1d234 100644 --- a/src/renderer/modules/qiniu-client/utils.ts +++ b/src/renderer/modules/qiniu-client/utils.ts @@ -1,5 +1,6 @@ import { KODO_MODE, Region } from "kodo-s3-adapter-sdk"; import { Domain } from "kodo-s3-adapter-sdk/dist/adapter"; +import { ServiceName } from "kodo-s3-adapter-sdk/dist/kodo-http-client"; import { Path as QiniuPath } from "qiniu-path/dist/src/path"; import * as AppConfig from "@common/const/app-config"; @@ -94,18 +95,23 @@ export function checkFileOrDirectoryExists( export async function isAccelerateUploadingAvailable( user: AkItem, - bucketName: string, + bucket: string, + opt: GetAdapterOptionParam, + withoutCache = false, ): Promise { if (user.specialType === AkSpecialType.STS) { return false; } - const result = await Region.query({ - accessKey: user.accessKey, - bucketName: bucketName, - appName: 'kodo-browser', - appVersion: AppConfig.app.version, - requestCallback: debugRequest(KODO_MODE), - responseCallback: debugResponse(KODO_MODE), + + return await getDefaultClient(opt).enter("isAccelerateUploadingAvailable", async client => { + if (withoutCache) { + client.client.clearCache(); + } + const accUrls = await client.client.getServiceUrls({ + serviceName: ServiceName.UpAcc, + bucketName: bucket, + withFallback: false, + }); + return accUrls && accUrls.length > 0; }); - return result.upAccUrls.length > 0; } diff --git a/src/renderer/pages/browse/files/index.tsx b/src/renderer/pages/browse/files/index.tsx index c78b07d6..d3e61c70 100644 --- a/src/renderer/pages/browse/files/index.tsx +++ b/src/renderer/pages/browse/files/index.tsx @@ -23,6 +23,7 @@ import {useFileOperation} from "@renderer/modules/file-operation"; import DropZone from "@renderer/components/drop-zone"; import {useDisplayModal, useIsShowAnyModal} from "@renderer/components/modals/hooks"; import UploadFilesConfirm from "@renderer/components/modals/file/upload-files-confirm"; +import ipcUploadManager from "@renderer/modules/electron-ipc-manages/ipc-upload-manager"; import FileToolBar from "./file-tool-bar"; import FileContent from "./file-content"; @@ -181,22 +182,47 @@ const Files: React.FC = (props) => { // bucket accelerate uploading const [canAccelerateUploading, setCanAccelerateUploading] = useState(false); - useEffect(() => { + const fetchAccelerateUploading = ({ + needFeedback, + withoutCache, + }: { + needFeedback?: boolean, + withoutCache?: boolean, + } = {}) => { if (!currentUser || !props.bucket) { return; } - isAccelerateUploadingAvailable( + const opt = { + id: currentUser.accessKey, + secret: currentUser.accessSecret, + endpointType: currentUser.endpointType, + } + let p = isAccelerateUploadingAvailable( currentUser, props.bucket?.name, - ) - .then(res => { - setCanAccelerateUploading(res); - }) - .catch(err => { - toast.error(err.toString()); - LocalLogger.error(err); + opt, + withoutCache, + ); + if (needFeedback) { + p = toast.promise(p, { + loading: translate("common.refreshing"), + success: translate("common.refreshed"), + error: err => `${translate("common.failed")}: ${err}`, }); + } + p.then(setCanAccelerateUploading) + .catch(err => LocalLogger.error(err)); + }; + useEffect(() => { + fetchAccelerateUploading(); }, [currentUser?.accessKey, props.bucket]); + const refreshCanAccelerateUploading = () => { + ipcUploadManager.clearRegionsCache(); + fetchAccelerateUploading({ + needFeedback: true, + withoutCache: true, + }); + }; // domains loader and selector const { @@ -434,6 +460,7 @@ const Files: React.FC = (props) => { filePaths={filePathsForUploadConfirm} storageClasses={Object.values(availableStorageClasses ?? {})} canAccelerateUploading={canAccelerateUploading} + onClickRefreshCanAccelerateUploading={() => refreshCanAccelerateUploading()} /> }