Fixed some minor transfer bugs as well added some more features required for the native clients file transfer

canary
WolverinDEV 2020-06-10 22:44:50 +02:00
parent 52d998000d
commit 5af68c0a1c
12 changed files with 152 additions and 63 deletions

View File

@ -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,

View File

@ -47,7 +47,8 @@ export type TransferSourceSupplier = (transfer: FileUploadTransfer) => Promise<T
/* Transfer target types */
export enum TransferTargetType {
RESPONSE,
DOWNLOAD
DOWNLOAD,
FILE
}
export abstract class TransferTarget {
@ -72,6 +73,18 @@ export abstract class ResponseTransferTarget extends TransferTarget {
abstract hasResponse() : boolean;
abstract getResponse() : Response;
}
export abstract class FileTransferTarget extends TransferTarget {
protected constructor() {
super(TransferTargetType.FILE);
}
abstract getFilePath() : string;
abstract hasFileName() : boolean;
abstract getFileName() : string;
}
export type TransferTargetSupplier = (transfer: FileDownloadTransfer) => Promise<TransferTarget>;
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<ResponseTransferTarget> { throw tr("response target isn't supported"); }
async createDownloadTarget(filename?: string) : Promise<DownloadTransferTarget> { throw tr("download target isn't supported"); }
async createFileTarget(path?: string, filename?: string) : Promise<FileTransferTarget> { throw tr("file target isn't supported"); }
async createBufferSource(buffer: ArrayBuffer) : Promise<BufferTransferSource> { throw tr("buffer source isn't supported"); }
async createTextSource(text: string) : Promise<TextTransferSource> { throw tr("text source isn't supported"); };

View File

@ -219,7 +219,7 @@ export namespace CryptoHelper {
}
}
class TeaSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler {
export class TeaSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler {
identity: TeaSpeakIdentity;
handler: HandshakeCommandHandler<TeaSpeakHandshakeHandler>;

View File

@ -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<string> = {
key: "transfer_download_folder",
description: "The download folder for the file transfer downloads",
/* default_value: <users download directory> */
};
static readonly FN_INVITE_LINK_SETTING: (name: string) => SettingsKey<string> = name => {
return {
key: 'invite_link_setting_' + name

View File

@ -426,7 +426,7 @@ const FileSize = (props: { path: string, events: Registry<FileBrowserEvents>, fi
setSize(event.fileSize);
});
if(size < 0 && props.file.size < 0)
if(size < 0 && (props.file.size < 0 || typeof props.file.size === "undefined"))
return <a key={"size-invalid"}><Translatable>unknown</Translatable></a>;
return <a key={"size"}>{network.format_bytes(size >= 0 ? size : props.file.size, { unit: "B", time: "", exact: false })}</a>;
};
@ -439,7 +439,6 @@ const FileTransferIndicator = (props: { file: ListedFileInfo, events: Registry<F
if(event.path !== props.file.path || event.name !== props.file.name)
return;
console.error(props.file.transfer);
setTransferStatus("pending");
});
@ -456,7 +455,6 @@ const FileTransferIndicator = (props: { file: ListedFileInfo, events: Registry<F
if(event.id !== props.file.transfer?.id)
return;
console.error("Progress: " + event.progress);
setTransferProgress(event.progress);
setTransferStatus(event.status);
});
@ -609,43 +607,11 @@ const FileListEntry = (props: { row: TableRow<ListedFileInfo>, 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}
>
<FileTransferIndicator events={props.events} file={props.row.userData} />
</TableRowElement>
@ -772,6 +738,23 @@ export class FileBrowser extends ReactComponentBase<FileListTableProperties, Fil
onHeaderContextMenu={e => 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<ListedFileInfo>, columns, uniqueId) => <FileListEntry columns={columns} row={row} key={uniqueId} events={this.props.events} />}
/>
@ -786,6 +769,46 @@ export class FileBrowser extends ReactComponentBase<FileListTableProperties, Fil
});
}
private onDrop(event: React.DragEvent) {
const types = event.dataTransfer.types;
if(types.length !== 1)
return;
event.stopPropagation();
let targetPath;
{
let currentTarget = event.target as HTMLElement;
while(currentTarget && !currentTarget.hasAttribute("x-drag-upload-path"))
currentTarget = currentTarget.parentElement;
targetPath = currentTarget?.getAttribute("x-drag-upload-path") || this.currentPath;
console.log("Target: %o %s", currentTarget, targetPath);
}
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("/") + "/";
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<FileListTableProperties, Fil
}
} else {
element.transfer.status = event.status;
if(element.mode === "uploading") {
if(element.mode === "uploading" && event.status === "finished") {
/* upload finished, the element rerenders already with the correct values */
element.size = event.fileSize;
element.mode = "normal";

View File

@ -4,10 +4,27 @@
.container {
padding: 1em;
position: relative;
padding-bottom: 4em; /* for the transfer info */
display: flex;
flex-direction: column;
justify-content: stretch;
flex-shrink: 1;
flex-grow: 1;
width: 90em;
min-width: 10em;
max-width: 100%;
height: 55em;
max-height: 100%;
min-height: 10em;
.navigation {
flex-grow: 0;
flex-shrink: 0;
.containerIcon {
margin: auto .25em;
padding: .2em;
@ -79,11 +96,11 @@
}
}
.fileTable {
min-height: 5em;
max-height: 40em;
height: 400px;
flex-grow: 1;
flex-shrink: 1;
margin-top: 1em;

View File

@ -212,7 +212,7 @@ class FileTransferModal extends Modal {
renderBody() {
const path = this.defaultChannelId ? "/" + channelPathPrefix + this.defaultChannelId + "/" : "/";
return <div className={cssStyle.container} style={{width: "600px"}}>
return <div className={cssStyle.container}>
<NavigationBar events={this.remoteBrowseEvents} currentPath={path} />
<FileBrowser events={this.remoteBrowseEvents} currentPath={path} />
<TransferInfo events={this.transferInfoEvents} />

View File

@ -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:

View File

@ -135,7 +135,8 @@
overflow-y: auto;
overflow-x: auto;
display: block;
display: flex;
flex-direction: column;
}
}
}

View File

@ -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<TableRowElement>;
}
@ -140,7 +142,10 @@ export class Table extends React.Component<TableProperties, TableState> {
}
return (
<div className={cssStyle.container + " " + (this.props.className || " ")}>
<div
className={cssStyle.container + " " + (this.props.className || " ")}
onDrop={e => this.props.onDrop && this.props.onDrop(e)}
onDragOver={e => this.props.onDragOver && this.props.onDragOver(e)}>
<div
ref={this.refHeader}
className={cssStyle.header + " " + (this.props.headerClassName || " ")}

View File

@ -19,7 +19,7 @@ debugger;
debugger;
const zzz = true ? "yyy" : "bbb";
const y = "";
const zy = "";
debugger;
debugger;
debugger;

View File

@ -60,16 +60,15 @@ TransferProvider.setProvider(new class extends TransferProvider {
}
executeFileDownload(transfer: FileDownloadTransfer) {
transfer.targetSupplier(transfer).then(target => {
if(!target) throw tr("transfer target is undefined");
transfer.target = target;
try {
if(!transfer.target) throw tr("transfer target is undefined");
let response: Promise<void>;
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) {