From 5af68c0a1ca9e919be8ffb63b4560aa18cddbee0 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Wed, 10 Jun 2020 22:44:50 +0200 Subject: [PATCH] Fixed some minor transfer bugs as well added some more features required for the native clients file transfer --- shared/js/file/FileManager.tsx | 4 + shared/js/file/Transfer.ts | 18 +++- .../profiles/identities/TeamSpeakIdentity.ts | 2 +- shared/js/settings.ts | 6 ++ shared/js/ui/modal/transfer/FileBrowser.tsx | 99 ++++++++++++------- .../ui/modal/transfer/ModalFileTransfer.scss | 25 ++++- .../ui/modal/transfer/ModalFileTransfer.tsx | 2 +- .../transfer/RemoteFileBrowserController.ts | 30 +++++- shared/js/ui/react-elements/Modal.scss | 3 +- shared/js/ui/react-elements/Table.tsx | 7 +- tools/trgen/test/test_01.ts | 2 +- web/js/FileTransfer.ts | 17 ++-- 12 files changed, 152 insertions(+), 63 deletions(-) diff --git a/shared/js/file/FileManager.tsx b/shared/js/file/FileManager.tsx index d3b6ab9f..626fb7a2 100644 --- a/shared/js/file/FileManager.tsx +++ b/shared/js/file/FileManager.tsx @@ -526,6 +526,10 @@ export class FileManager { const initializeCallback = async () => { try { + transfer.target = await transfer.targetSupplier(transfer); + if(!transfer.target) + throw tr("Failed to create transfer target"); + await this.connectionHandler.serverConnection.send_command("ftinitdownload", { "path": options.path, "name": options.name, diff --git a/shared/js/file/Transfer.ts b/shared/js/file/Transfer.ts index eb43a16b..459601c2 100644 --- a/shared/js/file/Transfer.ts +++ b/shared/js/file/Transfer.ts @@ -47,7 +47,8 @@ export type TransferSourceSupplier = (transfer: FileUploadTransfer) => Promise Promise; export enum FileTransferState { @@ -134,7 +147,7 @@ export interface TransferInitializeError { export interface TransferConnectError { error: "connection"; - reason: "missing-provider" | "provider-initialize-error" | "network-error"; + reason: "missing-provider" | "provider-initialize-error" | "handle-initialize-error" | "network-error"; extraMessage?: string; } @@ -410,6 +423,7 @@ export abstract class TransferProvider { async createResponseTarget() : Promise { throw tr("response target isn't supported"); } async createDownloadTarget(filename?: string) : Promise { throw tr("download target isn't supported"); } + async createFileTarget(path?: string, filename?: string) : Promise { throw tr("file target isn't supported"); } async createBufferSource(buffer: ArrayBuffer) : Promise { throw tr("buffer source isn't supported"); } async createTextSource(text: string) : Promise { throw tr("text source isn't supported"); }; diff --git a/shared/js/profiles/identities/TeamSpeakIdentity.ts b/shared/js/profiles/identities/TeamSpeakIdentity.ts index 06a0c9e2..d4ac0b86 100644 --- a/shared/js/profiles/identities/TeamSpeakIdentity.ts +++ b/shared/js/profiles/identities/TeamSpeakIdentity.ts @@ -219,7 +219,7 @@ export namespace CryptoHelper { } } -class TeaSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler { +export class TeaSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler { identity: TeaSpeakIdentity; handler: HandshakeCommandHandler; diff --git a/shared/js/settings.ts b/shared/js/settings.ts index 1afb5002..080009e9 100644 --- a/shared/js/settings.ts +++ b/shared/js/settings.ts @@ -360,6 +360,12 @@ export class Settings extends StaticSettings { description: "Show finished file transfers in the file transfer list" }; + static readonly KEY_TRANSFER_DOWNLOAD_FOLDER: SettingsKey = { + key: "transfer_download_folder", + description: "The download folder for the file transfer downloads", + /* default_value: */ + }; + static readonly FN_INVITE_LINK_SETTING: (name: string) => SettingsKey = name => { return { key: 'invite_link_setting_' + name diff --git a/shared/js/ui/modal/transfer/FileBrowser.tsx b/shared/js/ui/modal/transfer/FileBrowser.tsx index 190d6e91..ea99deec 100644 --- a/shared/js/ui/modal/transfer/FileBrowser.tsx +++ b/shared/js/ui/modal/transfer/FileBrowser.tsx @@ -426,7 +426,7 @@ const FileSize = (props: { path: string, events: Registry, fi setSize(event.fileSize); }); - if(size < 0 && props.file.size < 0) + if(size < 0 && (props.file.size < 0 || typeof props.file.size === "undefined")) return unknown; return {network.format_bytes(size >= 0 ? size : props.file.size, { unit: "B", time: "", exact: false })}; }; @@ -439,7 +439,6 @@ const FileTransferIndicator = (props: { file: ListedFileInfo, events: Registry, columns: TableCol event.preventDefault(); setDropHovered(true); }} - onDrop={event => { - const types = event.dataTransfer.types; - if(types.length !== 1) - return; - - if(props.row.userData.type !== FileType.DIRECTORY) { - event.stopPropagation(); - return; - } - - if(types[0] === FileTransferUrlMediaType) { - /* TODO: If cross move upload! */ - console.error(event.dataTransfer.getData(FileTransferUrlMediaType)); - const fileUrls = event.dataTransfer.getData(FileTransferUrlMediaType).split("&").map(e => decodeURIComponent(e)); - for(const fileUrl of fileUrls) { - const name = fileUrl.split("/").last(); - const oldPath = fileUrl.split("/").slice(0, -1).join("/") + "/"; - - props.events.fire("action_rename_file", { - newPath: props.row.userData.path + props.row.userData.name + "/", - oldPath: oldPath, - oldName: name, - newName: name - }); - } - } else if(types[0] === "Files") { - props.events.fire("action_start_upload", { path: props.row.userData.path + props.row.userData.name, mode: "files", files: [...event.dataTransfer.files] }); - } else { - log.warn(LogCategory.FILE_TRANSFER, tr("Received an unknown drop media type (%o)"), types); - event.preventDefault(); - return; - } - event.preventDefault(); - }} onDragLeave={() => setDropHovered(false)} onDragEnd={() => props.events.fire("notify_drag_ended")} + + x-drag-upload-path={props.row.userData.type === FileType.DIRECTORY ? props.row.userData.path + props.row.userData.name + "/" : undefined} > @@ -772,6 +738,23 @@ export class FileBrowser extends ReactComponentBase this.onHeaderContextMenu(e)} onBodyContextMenu={e => this.onBodyContextMenu(e)} + onDrop={e => this.onDrop(e)} + onDragOver={event => { + const types = event.dataTransfer.types; + if(types.length !== 1) + return; + + if(types[0] === FileTransferUrlMediaType) { + /* TODO: Detect if its remote move or internal move */ + event.dataTransfer.effectAllowed = "move"; + } else if(types[0] === "Files") { + event.dataTransfer.effectAllowed = "copy"; + } else { + return; + } + + event.preventDefault(); + }} renderRow={(row: TableRow, columns, uniqueId) => } /> @@ -786,6 +769,46 @@ export class FileBrowser extends ReactComponentBase decodeURIComponent(e)); + for(const fileUrl of fileUrls) { + const name = fileUrl.split("/").last(); + const oldPath = fileUrl.split("/").slice(0, -1).join("/") + "/"; + + this.props.events.fire("action_rename_file", { + newPath: targetPath, + oldPath: oldPath, + oldName: name, + newName: name + }); + } + } else if(types[0] === "Files") { + this.props.events.fire("action_start_upload", { path: targetPath, mode: "files", files: [...event.dataTransfer.files] }); + } else { + log.warn(LogCategory.FILE_TRANSFER, tr("Received an unknown drop media type (%o)"), types); + event.preventDefault(); + return; + } + event.preventDefault(); + } + private onHeaderContextMenu(event: React.MouseEvent) { event.preventDefault(); @@ -1038,7 +1061,7 @@ export class FileBrowser extends ReactComponentBase + return
diff --git a/shared/js/ui/modal/transfer/RemoteFileBrowserController.ts b/shared/js/ui/modal/transfer/RemoteFileBrowserController.ts index 0b3532a8..b064bc85 100644 --- a/shared/js/ui/modal/transfer/RemoteFileBrowserController.ts +++ b/shared/js/ui/modal/transfer/RemoteFileBrowserController.ts @@ -10,14 +10,23 @@ import * as ppt from "tc-backend/ppt"; import {SpecialKey} from "tc-shared/PPTListener"; import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo"; import {tra, traj} from "tc-shared/i18n/localize"; -import {FileTransfer, FileTransferState, FileUploadTransfer, TransferProvider} from "tc-shared/file/Transfer"; +import { + FileTransfer, + FileTransferState, + FileUploadTransfer, + TransferProvider, + TransferTargetType +} from "tc-shared/file/Transfer"; import {createErrorModal} from "tc-shared/ui/elements/Modal"; import { avatarsPathPrefix, channelPathPrefix, FileBrowserEvents, - iconPathPrefix, ListedFileInfo, PathInfo + iconPathPrefix, + ListedFileInfo, + PathInfo } from "tc-shared/ui/modal/transfer/ModalFileTransfer"; +import {Settings, settings} from "tc-shared/settings"; function parsePath(path: string, connection: ConnectionHandler) : PathInfo { if(path === "/" || !path) { @@ -649,6 +658,17 @@ export function initializeRemoteFileBrowserController(connection: ConnectionHand events.on("action_start_download", event => { event.files.forEach(file => { try { + let targetSupplier; + if(__build.target === "client" && TransferProvider.provider().targetSupported(TransferTargetType.FILE)) { + const target = TransferProvider.provider().createFileTarget(undefined, file.name); + targetSupplier = async () => target; + } else if(TransferProvider.provider().targetSupported(TransferTargetType.DOWNLOAD)) { + targetSupplier = async () => await TransferProvider.provider().createDownloadTarget(); + } else { + createErrorModal(tr("Failed to create transfer target"), tr("Failed to create transfer target.\nAll targets are unsupported")).open(); + return; + } + const fileName = file.name; const info = parsePath(file.path, connection); const transfer = connection.fileManager.initializeFileDownload({ @@ -656,7 +676,7 @@ export function initializeRemoteFileBrowserController(connection: ConnectionHand path: info.type === "channel" ? info.path : "", name: info.type === "channel" ? file.name : "/" + file.name, channelPassword: info.channel?.cached_password(), - targetSupplier: async () => TransferProvider.provider().createDownloadTarget() + targetSupplier: targetSupplier }); transfer.awaitFinished().then(() => { if(transfer.transferState() === FileTransferState.ERRORED) { @@ -736,12 +756,12 @@ export function initializeRemoteFileBrowserController(connection: ConnectionHand break; case FileTransferState.RUNNING: - events.fire("notify_transfer_status", { id: transfer.clientTransferId, status: "transferring", fileSize: transfer.transferProperties().fileSize }); + events.fire("notify_transfer_status", { id: transfer.clientTransferId, status: "transferring" }); break; case FileTransferState.FINISHED: case FileTransferState.CANCELED: - events.fire("notify_transfer_status", { id: transfer.clientTransferId, status: "finished" }); + events.fire("notify_transfer_status", { id: transfer.clientTransferId, status: "finished", fileSize: transfer.transferProperties().fileSize }); break; case FileTransferState.ERRORED: diff --git a/shared/js/ui/react-elements/Modal.scss b/shared/js/ui/react-elements/Modal.scss index 9155cea5..9885eb1a 100644 --- a/shared/js/ui/react-elements/Modal.scss +++ b/shared/js/ui/react-elements/Modal.scss @@ -135,7 +135,8 @@ overflow-y: auto; overflow-x: auto; - display: block; + display: flex; + flex-direction: column; } } } diff --git a/shared/js/ui/react-elements/Table.tsx b/shared/js/ui/react-elements/Table.tsx index 6a8259d3..aa23c134 100644 --- a/shared/js/ui/react-elements/Table.tsx +++ b/shared/js/ui/react-elements/Table.tsx @@ -35,6 +35,8 @@ export interface TableProperties { onHeaderContextMenu?: (event: React.MouseEvent) => void; onBodyContextMenu?: (event: React.MouseEvent) => void; + onDrop?: (event: React.DragEvent) => void; + onDragOver?: (event: React.DragEvent) => void; renderRow?: (row: TableRow, columns: TableColumn[], uniqueId: string) => React.ReactElement; } @@ -140,7 +142,10 @@ export class Table extends React.Component { } return ( -
+
this.props.onDrop && this.props.onDrop(e)} + onDragOver={e => this.props.onDragOver && this.props.onDragOver(e)}>
{ - if(!target) throw tr("transfer target is undefined"); - transfer.target = target; + try { + if(!transfer.target) throw tr("transfer target is undefined"); let response: Promise; transfer.setTransferState(FileTransferState.CONNECTING); - if(target instanceof ResponseTransferTargetImpl) { - response = responseFileDownload(transfer, target); - } else if(target instanceof DownloadTransferTargetImpl) { - response = downloadFileDownload(transfer, target); + if(transfer.target instanceof ResponseTransferTargetImpl) { + response = responseFileDownload(transfer, transfer.target); + } else if(transfer.target instanceof DownloadTransferTargetImpl) { + response = downloadFileDownload(transfer, transfer.target); } else { transfer.setFailed({ error: "io", @@ -93,7 +92,7 @@ TransferProvider.setProvider(new class extends TransferProvider { extraMessage: typeof error === "string" ? error : tr("Lookup the console") }, typeof error === "string" ? error : tr("Lookup the console")); }); - }).catch(error => { + } catch (error) { if(typeof error !== "string") log.error(LogCategory.FILE_TRANSFER, tr("Failed to initialize transfer target: %o"), error); @@ -102,7 +101,7 @@ TransferProvider.setProvider(new class extends TransferProvider { reason: "failed-to-initialize-target", extraMessage: typeof error === "string" ? error : tr("Lookup the console") }, typeof error === "string" ? error : tr("Lookup the console")); - }); + } } targetSupported(type: TransferTargetType) {