Implemented native channel file transfer

This commit is contained in:
WolverinDEV 2020-06-11 13:50:37 +02:00
parent 3b8ae31ff0
commit 4ef78b125e
15 changed files with 1011 additions and 610 deletions

2
github

@ -1 +1 @@
Subproject commit a1f980c623d33a448c9acf4a97f1aa795175dcb3 Subproject commit 08b8d258af3fb887707511625542376e2f222067

View File

@ -16,10 +16,12 @@ DefaultDirName={pf}\TeaSpeak\Client
OutputBaseFilename=<%= executable_name %> OutputBaseFilename=<%= executable_name %>
OutputDir=<%= dest_dir %> OutputDir=<%= dest_dir %>
SetupIconFile=<%= icon_file %> SetupIconFile=<%= icon_file %>
UninstallDisplayName=uninstall
Compression=lzma Compression=lzma
SolidCompression=yes SolidCompression=yes
DisableDirPage=no DisableDirPage=no
DisableWelcomePage=no DisableWelcomePage=no
SignTool=parameter <%- sign_arguments %>
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
@ -31,8 +33,30 @@ Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescrip
[Files] [Files]
Source: "<%= source_dir %>"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "<%= source_dir %>"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[UninstallDelete]
; NOTE: Don't use "Flags: ignoreversion" on any shared system files Type: files; Name: "{app}\app_version.json"
Type: files; Name: "{app}\ChangeLog.txt"
Type: files; Name: "{app}\chrome_100_percent.pak"
Type: files; Name: "{app}\chrome_200_percent.pak"
Type: files; Name: "{app}\d3dcompiler_47.dll"
Type: files; Name: "{app}\ffmpeg.dll"
Type: files; Name: "{app}\icudtl.dat"
Type: files; Name: "{app}\libEGL.dll"
Type: files; Name: "{app}\libGLESv2.dll"
Type: files; Name: "{app}\LICENSE"
Type: files; Name: "{app}\LICENSES.chromium.html"
Type: filesandordirs; Name: "{app}\locales"
Type: filesandordirs; Name: "{app}\resources"
Type: files; Name: "{app}\resources.pak"
Type: files; Name: "{app}\snapshot_blob.bin"
Type: filesandordirs; Name: "{app}\swiftshader"
Type: files; Name: "{app}\TeaClient.exe"
Type: files; Name: "{app}\update-installer.exe"
Type: files; Name: "{app}\v8_context_snapshot.bin"
Type: files; Name: "{app}\version"
Type: files; Name: "{app}\vk_swiftshader.dll"
Type: files; Name: "{app}\vk_swiftshader_icd.json"
Type: dirifempty; Name: "{app}\"
[Icons] [Icons]
Name: "{commonprograms}\TeaSpeak"; Filename: "{app}\TeaClient.exe" Name: "{commonprograms}\TeaSpeak"; Filename: "{app}\TeaClient.exe"

View File

@ -1,5 +1,6 @@
import * as packager from "./package"; import * as packager from "./package";
import * as deployer from "./deploy"; import * as deployer from "./deploy";
import * as glob from "glob";
import {parse_version, Version} from "../modules/shared/version"; import {parse_version, Version} from "../modules/shared/version";
const fs = require("fs-extra"); const fs = require("fs-extra");
@ -19,13 +20,25 @@ const symbol_binary_path = package_path + "/resources/natives/";
let dest_path = undefined; let dest_path = undefined;
let info; let info;
let version: Version; let version: Version;
function sign_command(file: string, description: string) {
const base = "signtool sign /v /tr http://timestamp.digicert.com?alg=sha256 /td SHA256 /fd SHA256 /d \"" + description + "\" /du \"https://www.teaspeak.de/\"";
return base + " /f " + path.join(__dirname, "../../../CodeSign-Certificate.pfx") + " /p qmsQBxpN2exj " + file;
}
async function sign_file(file: string, description: string) {
const { stdout, stderr } = await exec(sign_command(file, description), {maxBuffer: 1024 * 1024 * 1024});
console.log(stdout.toString());
}
async function make_template() : Promise<string> { async function make_template() : Promise<string> {
const content = await ejs_render("installer/WinInstall.ejs", { const content = await ejs_render("installer/WinInstall.ejs", {
source_dir: path.resolve(package_path) + "/*", source_dir: path.resolve(package_path) + "/*",
dest_dir: path.resolve(dest_path), dest_dir: path.resolve(dest_path),
icon_file: path.resolve("resources/logo.ico"), icon_file: path.resolve("resources/logo.ico"),
version: info["version"], version: info["version"],
executable_name: filename_installer.substr(0, filename_installer.length - 4) //Remove the .exe executable_name: filename_installer.substr(0, filename_installer.length - 4), //Remove the .exe
sign_arguments: sign_command("$f", "TeaClient installer")
}, {}); }, {});
await fs.mkdirs(dest_path); await fs.mkdirs(dest_path);
@ -42,7 +55,39 @@ if(process.argv.length < 3) {
process.exit(1); process.exit(1);
} }
packager.pack_info(package_path).then(async _info => { packager.pack_info(package_path).then(async info => {
const asyncGlob = util.promisify(glob);
const executables = await asyncGlob(package_path + "/**/*.exe");
const ddls = await asyncGlob(package_path + "/**/*.dll");
const nodeModules = await asyncGlob(package_path + "/**/*.node");
const exe_description_padding = {
"TeaClient.exe": "TeaClient v" + info["version"],
"update-installer.exe": "TeaClient update installer"
};
const node_description_padding = {
"teaclient_connection.node": "TeaClients connection library",
"teaclient_crash_handler.node": "TeaClients crash handler",
"teaclient_dns.node": "TeaClients dns resolve module",
"teaclient_ppt.node": "TeaClients push to talk module"
};
console.log("Signing executables:");
for(const executable of executables) {
const desc = exe_description_padding[path.basename(executable)];
if(!desc) throw "Missing description for " + executable;
await sign_file(executable, "");
}
for(const dll of ddls)
await sign_file(dll, "");
for(const module of nodeModules) {
const desc = node_description_padding[path.basename(module)];
if(!desc) throw "Missing description for " + module;
await sign_file(module, "");
}
return info;
}).then(async _info => {
info = _info; info = _info;
version = parse_version(_info["version"]); version = parse_version(_info["version"]);
dest_path = "build/output/" + process.argv[2] + "/" + version.toString() + "/"; dest_path = "build/output/" + process.argv[2] + "/" + version.toString() + "/";

View File

@ -122,5 +122,5 @@ function deploy_client() {
#install_npm #install_npm
#compile_scripts #compile_scripts
#compile_native #compile_native
package_client #package_client
#deploy_client deploy_client

View File

@ -1,181 +1,397 @@
import {
BrowserFileTransferSource,
BufferTransferSource,
FileDownloadTransfer, FileTransfer,
FileTransferState, FileTransferTarget,
FileUploadTransfer,
ResponseTransferTarget,
TextTransferSource,
TransferProvider,
TransferSourceType,
TransferTargetType
} from "tc-shared/file/Transfer";
import * as native from "tc-native/connection"; import * as native from "tc-native/connection";
import {tr} from "tc-shared/i18n/localize";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {base64_encode_ab} from "tc-shared/utils/buffers";
import * as path from "path"; import * as path from "path";
import {DownloadKey, DownloadTransfer, UploadKey, UploadTransfer} from "tc-shared/file/FileManager"; import * as electron from "electron";
import {base64_encode_ab, str2ab8} from "tc-shared/utils/buffers";
import {set_transfer_provider, TransferKey, TransferProvider} from "tc-shared/file/FileManager";
class NativeFileDownload implements DownloadTransfer { const executeTransfer = (transfer: FileTransfer, object: native.ft.TransferObject, callbackFinished: () => void) => {
readonly key: DownloadKey; const address = transfer.transferProperties().addresses[0];
private _handle: native.ft.NativeFileTransfer; let ntransfer: native.ft.NativeFileTransfer;
private _buffer: Uint8Array; try {
ntransfer = native.ft.spawn_connection({
private _result: Promise<void>; client_transfer_id: transfer.clientTransferId,
private _response: Response; server_transfer_id: -1,
object: object,
private _result_success: () => any; remote_address: address.serverAddress,
private _result_error: (error: any) => any; remote_port: address.serverPort,
transfer_key: transfer.transferProperties().transferKey
constructor(key: DownloadKey) {
this.key = key;
this._buffer = new Uint8Array(key.total_size);
this._handle = native.ft.spawn_connection({
client_transfer_id: key.client_transfer_id,
server_transfer_id: key.server_transfer_id,
remote_address: key.peer.hosts[0],
remote_port: key.peer.port,
transfer_key: key.key,
object: native.ft.download_transfer_object_from_buffer(this._buffer.buffer)
}); });
} catch (error) {
let message = typeof error === "object" ? 'message' in error ? error.message : typeof error === "string" ? error : undefined : undefined;
if(!message)
log.error(LogCategory.FILE_TRANSFER, tr("Failed to create file transfer handle: %o"), error);
transfer.setFailed({
error: "connection",
reason: "handle-initialize-error",
extraMessage: message ? message : tr("Lookup the console")
}, message ? message : tr("Lookup the console"));
return;
} }
get_key(): DownloadKey { ntransfer.callback_start = () => {
return this.key; if(!transfer.isFinished()) {
} transfer.setTransferState(FileTransferState.RUNNING);
transfer.lastStateUpdate = Date.now();
async request_file(): Promise<Response> {
if(this._response)
return this._response;
try {
await (this._result || this._start_transfer());
} catch(error) {
throw error;
} }
};
if(this._response) ntransfer.callback_failed = error => {
return this._response; if(transfer.isFinished())
return;
const buffer = this._buffer.buffer.slice(this._buffer.byteOffset, this._buffer.byteOffset + Math.min(64, this._buffer.byteLength)); transfer.lastStateUpdate = Date.now();
transfer.setFailed({
error: "connection",
reason: "network-error",
extraMessage: error
}, error);
};
/* may another task has been stepped by and already set the response */ ntransfer.callback_finished = aborted => {
return this._response || (this._response = new Response(this._buffer, { if(transfer.isFinished())
return;
callbackFinished();
transfer.setTransferState(aborted ? FileTransferState.CANCELED : FileTransferState.FINISHED);
transfer.lastStateUpdate = Date.now();
};
ntransfer.callback_progress = (current, max) => {
if(transfer.isFinished())
return;
const transferInfo = transfer.lastProgressInfo();
/* ATTENTION: transferInfo.timestamp | 0 does not work since 1591875114970 > 2^32 (1591875114970 | 0 => -1557751846) */
if(transferInfo && Date.now() - (typeof transferInfo.timestamp === "number" ? transferInfo.timestamp : 0) < 2000 && !(transferInfo as any).native_info)
return;
transfer.updateProgress({
network_current_speed: 0,
network_average_speed: 0,
network_bytes_send: 0,
network_bytes_received: 0,
file_current_offset: current,
file_total_size: max,
file_bytes_transferred: current,
file_start_offset: 0,
timestamp: Date.now(),
native_info: true
} as any);
transfer.lastStateUpdate = Date.now();
};
try {
if(!ntransfer.start())
throw tr("failed to start transfer");
} catch (error) {
if(typeof error !== "string")
log.error(LogCategory.FILE_TRANSFER, tr("Failed to start file transfer: %o"), error);
transfer.setFailed({
error: "connection",
reason: "network-error",
extraMessage: typeof error === "string" ? error : tr("Lookup the console")
}, typeof error === "string" ? error : tr("Lookup the console"));
return;
}
};
TransferProvider.setProvider(new class extends TransferProvider {
executeFileDownload(transfer: FileDownloadTransfer) {
try {
if(!transfer.target) throw tr("transfer target is undefined");
transfer.setTransferState(FileTransferState.CONNECTING);
let nativeTarget: native.ft.FileTransferTarget;
if(transfer.target instanceof ResponseTransferTargetImpl) {
transfer.target.initialize(transfer.transferProperties().fileSize);
nativeTarget = transfer.target.nativeTarget;
} else if(transfer.target instanceof FileTransferTargetImpl) {
nativeTarget = transfer.target.getNativeTarget(transfer.properties.name, transfer.transferProperties().fileSize);
} else {
transfer.setFailed({
error: "io",
reason: "unsupported-target"
}, tr("invalid transfer target type"));
return;
}
executeTransfer(transfer, nativeTarget, () => {
if(transfer.target instanceof ResponseTransferTargetImpl)
transfer.target.createResponseFromBuffer();
});
} catch (error) {
if(typeof error !== "string")
log.error(LogCategory.FILE_TRANSFER, tr("Failed to initialize transfer target: %o"), error);
transfer.setFailed({
error: "io",
reason: "failed-to-initialize-target",
extraMessage: typeof error === "string" ? error : tr("Lookup the console")
}, typeof error === "string" ? error : tr("Lookup the console"));
}
}
executeFileUpload(transfer: FileUploadTransfer) {
try {
if(!transfer.source) throw tr("transfer source is undefined");
let nativeSource: native.ft.FileTransferSource;
if(transfer.source instanceof BrowserFileTransferSourceImpl) {
nativeSource = transfer.source.getNativeSource();
} else if(transfer.source instanceof TextTransferSourceImpl) {
nativeSource = transfer.source.getNativeSource();
} else if(transfer.source instanceof BufferTransferSourceImpl) {
nativeSource = transfer.source.getNativeSource();
} else {
console.log(transfer.source);
transfer.setFailed({
error: "io",
reason: "unsupported-target"
}, tr("invalid transfer target type"));
return;
}
executeTransfer(transfer, nativeSource, () => { });
} catch (error) {
if(typeof error !== "string")
log.error(LogCategory.FILE_TRANSFER, tr("Failed to initialize transfer source: %o"), error);
transfer.setFailed({
error: "io",
reason: "failed-to-initialize-target",
extraMessage: typeof error === "string" ? error : tr("Lookup the console")
}, typeof error === "string" ? error : tr("Lookup the console"));
}
}
sourceSupported(type: TransferSourceType) {
switch (type) {
case TransferSourceType.TEXT:
case TransferSourceType.BUFFER:
case TransferSourceType.BROWSER_FILE:
return true;
default:
return false;
}
}
targetSupported(type: TransferTargetType) {
switch (type) {
case TransferTargetType.RESPONSE:
case TransferTargetType.FILE:
return true;
case TransferTargetType.DOWNLOAD:
default:
return false;
}
}
async createTextSource(text: string): Promise<TextTransferSource> {
return new TextTransferSourceImpl(text);
}
async createBufferSource(buffer: ArrayBuffer): Promise<BufferTransferSource> {
return new BufferTransferSourceImpl(buffer);
}
async createBrowserFileSource(file: File): Promise<BrowserFileTransferSource> {
return new BrowserFileTransferSourceImpl(file);
}
async createResponseTarget(): Promise<ResponseTransferTarget> {
return new ResponseTransferTargetImpl();
}
async createFileTarget(path?: string, name?: string): Promise<FileTransferTarget> {
const target = new FileTransferTargetImpl(path, name);
await target.requestPath();
return target;
}
});
class TextTransferSourceImpl extends TextTransferSource {
private readonly text: string;
private buffer: ArrayBuffer;
private nativeSource: native.ft.FileTransferSource;
constructor(text: string) {
super();
this.text = text;
}
getText(): string {
return this.text;
}
async fileSize(): Promise<number> {
return this.getArrayBuffer().byteLength;
}
getArrayBuffer() : ArrayBuffer {
if(this.buffer) return this.buffer;
const encoder = new TextEncoder();
this.buffer = encoder.encode(this.text);
return this.buffer;
}
getNativeSource() {
if(this.nativeSource) return this.nativeSource;
this.nativeSource = native.ft.upload_transfer_object_from_buffer(this.getArrayBuffer());
return this.nativeSource;
}
}
class BufferTransferSourceImpl extends BufferTransferSource {
private readonly buffer: ArrayBuffer;
private nativeSource: native.ft.FileTransferSource;
constructor(buffer: ArrayBuffer) {
super();
this.buffer = buffer;
}
async fileSize(): Promise<number> {
return this.buffer.byteLength;
}
getNativeSource() {
if(this.nativeSource) return this.nativeSource;
this.nativeSource = native.ft.upload_transfer_object_from_buffer(this.buffer);
return this.nativeSource;
}
getBuffer(): ArrayBuffer {
return this.buffer;
}
}
class BrowserFileTransferSourceImpl extends BrowserFileTransferSource {
private readonly file: File;
private nativeSource: native.ft.FileTransferSource;
constructor(file: File) {
super();
this.file = file;
}
async fileSize(): Promise<number> {
return this.file.size;
}
getNativeSource() {
if(this.nativeSource) return this.nativeSource;
this.nativeSource = native.ft.upload_transfer_object_from_file(path.dirname(this.file.path), path.basename(this.file.path));
return this.nativeSource;
}
getFile(): File {
return this.file;
}
}
class ResponseTransferTargetImpl extends ResponseTransferTarget {
nativeTarget: native.ft.FileTransferTarget;
buffer: Uint8Array;
private response: Response;
constructor() {
super();
}
initialize(bufferSize: number) {
this.buffer = new Uint8Array(bufferSize);
this.nativeTarget = native.ft.download_transfer_object_from_buffer(this.buffer.buffer);
}
getResponse(): Response {
return this.response;
}
hasResponse(): boolean {
return typeof this.response === "object";
}
createResponseFromBuffer() {
const buffer = this.buffer.buffer.slice(this.buffer.byteOffset, this.buffer.byteOffset + Math.min(64, this.buffer.byteLength));
this.response = new Response(this.buffer, {
status: 200, status: 200,
statusText: "success", statusText: "success",
headers: { headers: {
"X-media-bytes": base64_encode_ab(buffer) "X-media-bytes": base64_encode_ab(buffer)
} }
}));
}
_start_transfer() : Promise<void> {
return this._result = new Promise((resolve, reject) => {
this._result_error = (error) => {
this._result_error = undefined;
this._result_success = undefined;
reject(error);
};
this._result_success = () => {
this._result_error = undefined;
this._result_success = undefined;
resolve();
};
this._handle.callback_failed = this._result_error;
this._handle.callback_finished = aborted => {
if(aborted)
this._result_error("aborted");
else
this._result_success();
};
this._handle.start();
}); });
} }
} }
class NativeFileUpload implements UploadTransfer { class FileTransferTargetImpl extends FileTransferTarget {
readonly transfer_key: UploadKey; private path: string;
private _handle: native.ft.NativeFileTransfer; private name: string;
private _result: Promise<void>; constructor(path: string, name?: string) {
super();
private _result_success: () => any; this.path = path;
private _result_error: (error: any) => any; this.name = name;
constructor(key: UploadKey) {
this.transfer_key = key;
} }
async put_data(data: BlobPart | File) : Promise<void> { async requestPath() {
if(this._result) { if(typeof this.path === "string")
await this._result;
return; return;
}
let buffer: native.ft.FileTransferSource; const result = await electron.remote.dialog.showSaveDialog({ defaultPath: this.name });
if(result.canceled)
throw tr("download canceled");
if(data instanceof File) { if(!result.filePath)
if(data.size != this.transfer_key.total_size) throw tr("invalid result path");
throw "invalid size";
buffer = native.ft.upload_transfer_object_from_file(path.dirname(data.path), data.name); this.path = path.dirname(result.filePath);
} else if(typeof(data) === "string") { this.name = path.basename(result.filePath);
if(data.length != this.transfer_key.total_size)
throw "invalid size";
buffer = native.ft.upload_transfer_object_from_buffer(str2ab8(data));
} else {
let buf = <BufferSource>data;
if(buf.byteLength != this.transfer_key.total_size)
throw "invalid size";
if(ArrayBuffer.isView(buf))
buf = buf.buffer.slice(buf.byteOffset);
buffer = native.ft.upload_transfer_object_from_buffer(buf);
}
this._handle = native.ft.spawn_connection({
client_transfer_id: this.transfer_key.client_transfer_id,
server_transfer_id: this.transfer_key.server_transfer_id,
remote_address: this.transfer_key.peer.hosts[0],
remote_port: this.transfer_key.peer.port,
transfer_key: this.transfer_key.key,
object: buffer
});
await (this._result = new Promise((resolve, reject) => {
this._result_error = (error) => {
this._result_error = undefined;
this._result_success = undefined;
reject(error);
};
this._result_success = () => {
this._result_error = undefined;
this._result_success = undefined;
resolve();
};
this._handle.callback_failed = this._result_error;
this._handle.callback_finished = aborted => {
if(aborted)
this._result_error("aborted");
else
this._result_success();
};
this._handle.start();
}));
} }
get_key(): UploadKey { getNativeTarget(fallbackName: string, expectedSize: number) {
return this.transfer_key; this.name = this.name || fallbackName;
}
}
set_transfer_provider(new class implements TransferProvider { return native.ft.download_transfer_object_from_file(this.path, this.name, expectedSize);
spawn_upload_transfer(key: TransferKey): UploadTransfer {
return new NativeFileUpload(key);
} }
spawn_download_transfer(key: TransferKey): DownloadTransfer { getFilePath(): string {
return new NativeFileDownload(key); return this.path;
} }
});
getFileName(): string {
return this.name;
}
hasFileName(): boolean {
return typeof this.name === "string";
}
}

View File

@ -17,94 +17,94 @@ import AbstractVoiceConnection = voice.AbstractVoiceConnection;
import {VoiceConnection} from "./VoiceConnection"; import {VoiceConnection} from "./VoiceConnection";
class ErrorCommandHandler extends AbstractCommandHandler { class ErrorCommandHandler extends AbstractCommandHandler {
private _handle: ServerConnection; private _handle: ServerConnection;
constructor(handle: ServerConnection) { constructor(handle: ServerConnection) {
super(handle); super(handle);
this._handle = handle; this._handle = handle;
} }
handle_command(command: ServerCommand): boolean { handle_command(command: ServerCommand): boolean {
if(command.command === "error") { if(command.command === "error") {
const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"]; const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"];
const data = command.arguments[0]; const data = command.arguments[0];
let return_code : string = data["return_code"]; let return_code : string = data["return_code"];
if(!return_code) { if(!return_code) {
const listener = return_listener["last_command"] || return_listener["_clientinit"]; const listener = return_listener["last_command"] || return_listener["_clientinit"];
if(typeof(listener) === "function") { if(typeof(listener) === "function") {
console.warn(tr("Received error without return code. Using last command (%o)"), listener); console.warn(tr("Received error without return code. Using last command (%o)"), listener);
listener(new CommandResult(data)); listener(new CommandResult(command.arguments));
delete return_listener["last_command"]; delete return_listener["last_command"];
delete return_listener["_clientinit"];
} else {
console.warn(tr("Received error without return code."), data);
}
return false;
}
if(return_listener[return_code]) {
return_listener[return_code](new CommandResult(data));
} else {
console.warn(tr("Error received for no handler! (%o)"), data);
}
return true;
} else if(command.command == "initivexpand") {
if(command.arguments[0]["teaspeak"] == true) {
console.log("Using TeaSpeak identity type");
this._handle.handshake_handler().startHandshake();
}
return true;
} else if(command.command == "initivexpand2") {
/* its TeamSpeak or TeaSpeak with experimental 3.1 and not up2date */
this._handle["_do_teamspeak"] = true;
} else if(command.command == "initserver") {
const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"];
if(typeof(return_listener["_clientinit"]) === "function") {
return_listener["_clientinit"](new CommandResult({id: 0, message: ""}));
delete return_listener["_clientinit"]; delete return_listener["_clientinit"];
} else {
console.warn(tr("Received error without return code."), data);
} }
} else if(command.command == "notifyconnectioninforequest") { return false;
this._handle.send_command("setconnectioninfo",
{
//TODO calculate
connection_ping: 0.0000,
connection_ping_deviation: 0.0,
connection_packets_sent_speech: 0,
connection_packets_sent_keepalive: 0,
connection_packets_sent_control: 0,
connection_bytes_sent_speech: 0,
connection_bytes_sent_keepalive: 0,
connection_bytes_sent_control: 0,
connection_packets_received_speech: 0,
connection_packets_received_keepalive: 0,
connection_packets_received_control: 0,
connection_bytes_received_speech: 0,
connection_bytes_received_keepalive: 0,
connection_bytes_received_control: 0,
connection_server2client_packetloss_speech: 0.0000,
connection_server2client_packetloss_keepalive: 0.0000,
connection_server2client_packetloss_control: 0.0000,
connection_server2client_packetloss_total: 0.0000,
connection_bandwidth_sent_last_second_speech: 0,
connection_bandwidth_sent_last_second_keepalive: 0,
connection_bandwidth_sent_last_second_control: 0,
connection_bandwidth_sent_last_minute_speech: 0,
connection_bandwidth_sent_last_minute_keepalive: 0,
connection_bandwidth_sent_last_minute_control: 0,
connection_bandwidth_received_last_second_speech: 0,
connection_bandwidth_received_last_second_keepalive: 0,
connection_bandwidth_received_last_second_control: 0,
connection_bandwidth_received_last_minute_speech: 0,
connection_bandwidth_received_last_minute_keepalive: 0,
connection_bandwidth_received_last_minute_control: 0
}
);
} }
return false;
if(return_listener[return_code]) {
return_listener[return_code](new CommandResult(command.arguments));
} else {
console.warn(tr("Error received for no handler! (%o)"), data);
}
return true;
} else if(command.command == "initivexpand") {
if(command.arguments[0]["teaspeak"] == true) {
console.log("Using TeaSpeak identity type");
this._handle.handshake_handler().startHandshake();
}
return true;
} else if(command.command == "initivexpand2") {
/* its TeamSpeak or TeaSpeak with experimental 3.1 and not up2date */
this._handle["_do_teamspeak"] = true;
} else if(command.command == "initserver") {
const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"];
if(typeof(return_listener["_clientinit"]) === "function") {
return_listener["_clientinit"](new CommandResult([{id: 0, message: ""}]));
delete return_listener["_clientinit"];
}
} else if(command.command == "notifyconnectioninforequest") {
this._handle.send_command("setconnectioninfo",
{
//TODO calculate
connection_ping: 0.0000,
connection_ping_deviation: 0.0,
connection_packets_sent_speech: 0,
connection_packets_sent_keepalive: 0,
connection_packets_sent_control: 0,
connection_bytes_sent_speech: 0,
connection_bytes_sent_keepalive: 0,
connection_bytes_sent_control: 0,
connection_packets_received_speech: 0,
connection_packets_received_keepalive: 0,
connection_packets_received_control: 0,
connection_bytes_received_speech: 0,
connection_bytes_received_keepalive: 0,
connection_bytes_received_control: 0,
connection_server2client_packetloss_speech: 0.0000,
connection_server2client_packetloss_keepalive: 0.0000,
connection_server2client_packetloss_control: 0.0000,
connection_server2client_packetloss_total: 0.0000,
connection_bandwidth_sent_last_second_speech: 0,
connection_bandwidth_sent_last_second_keepalive: 0,
connection_bandwidth_sent_last_second_control: 0,
connection_bandwidth_sent_last_minute_speech: 0,
connection_bandwidth_sent_last_minute_keepalive: 0,
connection_bandwidth_sent_last_minute_control: 0,
connection_bandwidth_received_last_second_speech: 0,
connection_bandwidth_received_last_second_keepalive: 0,
connection_bandwidth_received_last_second_control: 0,
connection_bandwidth_received_last_minute_speech: 0,
connection_bandwidth_received_last_minute_keepalive: 0,
connection_bandwidth_received_last_minute_control: 0
}
);
} }
return false;
}
} }
export class ServerConnection extends AbstractServerConnection { export class ServerConnection extends AbstractServerConnection {

View File

@ -119,8 +119,8 @@ export class VoiceConnection extends AbstractVoiceConnection {
support_latency_settings() { return true; }, support_latency_settings() { return true; },
reset_latency_settings: function() { reset_latency_settings: function() {
const stream = this.get_stream(); const stream = this.get_stream();
stream.set_buffer_latency(0.040); stream.set_buffer_latency(0.080);
stream.set_buffer_max_latency(0.2); stream.set_buffer_max_latency(0.5);
return this.latency_settings(); return this.latency_settings();
}, },
latency_settings: function (settings?: LatencySettings) : LatencySettings { latency_settings: function (settings?: LatencySettings) : LatencySettings {

View File

@ -24,15 +24,14 @@ declare global {
} }
} }
rh.initialize(path.join(__dirname, "backend-impl")); rh.initialize(path.join(__dirname, "backend-impl"));
/* --------------- main initialize --------------- */ /* --------------- main initialize --------------- */
import {Arguments, parse_arguments, process_args} from "../shared/process-arguments"; import {Arguments, parse_arguments, process_args} from "../shared/process-arguments";
import * as electron from "electron"; import * as electron from "electron";
import {remote} from "electron"; import {remote} from "electron";
import * as os from "os";
import * as loader from "tc-loader"; import * as loader from "tc-loader";
import ipcRenderer = electron.ipcRenderer; import ipcRenderer = electron.ipcRenderer;
/* we use out own jquery resource */ /* we use out own jquery resource */
loader.register_task(loader.Stage.JAVASCRIPT, { loader.register_task(loader.Stage.JAVASCRIPT, {
name: "teaclient jquery", name: "teaclient jquery",
@ -99,6 +98,7 @@ loader.register_task(loader.Stage.INITIALIZING, {
parse_arguments(); parse_arguments();
if(process_args.has_value(Arguments.DUMMY_CRASH_RENDERER)) if(process_args.has_value(Arguments.DUMMY_CRASH_RENDERER))
crash_handler.handler.crash(); crash_handler.handler.crash();
if(!process_args.has_flag(Arguments.DEBUG)) { if(!process_args.has_flag(Arguments.DEBUG)) {
window.open_connected_question = () => remote.dialog.showMessageBox(remote.getCurrentWindow(), { window.open_connected_question = () => remote.dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'question', type: 'question',
@ -107,6 +107,14 @@ loader.register_task(loader.Stage.INITIALIZING, {
message: 'Are you really sure?\nYou\'re still connected!' message: 'Are you really sure?\nYou\'re still connected!'
}).then(result => result.response === 0); }).then(result => result.response === 0);
} }
/* loader url setup */
{
const baseUrl = process_args.value(Arguments.SERVER_URL);
if(typeof baseUrl === "string") {
loader.config.baseUrl = baseUrl;
}
}
}, },
priority: 110 priority: 110
}); });

View File

@ -126,7 +126,9 @@ declare module "tc-native/connection" {
export function upload_transfer_object_from_file(path: string, name: string) : FileTransferSource; export function upload_transfer_object_from_file(path: string, name: string) : FileTransferSource;
export function upload_transfer_object_from_buffer(buffer: ArrayBuffer) : FileTransferSource; export function upload_transfer_object_from_buffer(buffer: ArrayBuffer) : FileTransferSource;
export function download_transfer_object_from_buffer(target_buffer: ArrayBuffer) : TransferObject;
export function download_transfer_object_from_buffer(target_buffer: ArrayBuffer) : FileTransferTarget;
export function download_transfer_object_from_file(path: string, name: string, expectedSize: number) : FileTransferTarget;
export function destroy_connection(connection: NativeFileTransfer); export function destroy_connection(connection: NativeFileTransfer);
export function spawn_connection(transfer: TransferOptions) : NativeFileTransfer; export function spawn_connection(transfer: TransferOptions) : NativeFileTransfer;

View File

@ -229,10 +229,14 @@ NAN_MODULE_INIT(init) {
Nan::New<v8::String>("download_transfer_object_from_buffer").ToLocalChecked(), Nan::New<v8::String>("download_transfer_object_from_buffer").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferJSBufferTarget::create_from_buffer)).ToLocalChecked() Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferJSBufferTarget::create_from_buffer)).ToLocalChecked()
); );
Nan::Set(ft_namespace, Nan::Set(ft_namespace,
Nan::New<v8::String>("upload_transfer_object_from_file").ToLocalChecked(), Nan::New<v8::String>("upload_transfer_object_from_file").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferFileSource::create)).ToLocalChecked() Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferFileSource::create)).ToLocalChecked()
); );
Nan::Set(ft_namespace,
Nan::New<v8::String>("download_transfer_object_from_file").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferFileTarget::create)).ToLocalChecked()
);
//spawn_file_connection destroy_file_connection //spawn_file_connection destroy_file_connection
JSTransfer::Init(ft_namespace); JSTransfer::Init(ft_namespace);

View File

@ -1,9 +1,10 @@
#include "FileTransferManager.h" #include "FileTransferManager.h"
#include "FileTransferObject.h" #include "FileTransferObject.h"
#include <ThreadPool/ThreadHelper.h>
#include <misc/net.h> #include <misc/net.h>
#include <algorithm> #include <algorithm>
#include <iostream> #include <utility>
#ifndef WIN32 #ifndef WIN32
#include <unistd.h> #include <unistd.h>
@ -14,6 +15,7 @@
#endif #endif
#else #else
#include <ws2tcpip.h> #include <ws2tcpip.h>
#define SOCK_NONBLOCK (0) #define SOCK_NONBLOCK (0)
#define MSG_DONTWAIT (0) #define MSG_DONTWAIT (0)
#endif #endif
@ -70,18 +72,18 @@ bool Transfer::initialize(std::string &error) {
} }
log_info(category::file_transfer, tr("Setting remote port to {}"), net::to_string(this->remote_address)); log_info(category::file_transfer, tr("Setting remote port to {}"), net::to_string(this->remote_address));
this->_socket = (int) ::socket(this->remote_address.ss_family, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP); this->_socket = (int) ::socket(this->remote_address.ss_family, (unsigned) SOCK_STREAM | (unsigned) SOCK_NONBLOCK, IPPROTO_TCP);
if(this->_socket < 0) { if(this->_socket < 0) {
this->finalize(); this->finalize(true, true);
error = tr("failed to spawn socket"); error = tr("failed to spawn socket");
return false; return false;
} }
#ifdef WIN32 #ifdef WIN32
u_long enabled = 0; u_long enabled = 0;
auto non_block_rs = ioctlsocket(this->_socket, FIONBIO, &enabled); auto non_block_rs = ioctlsocket(this->_socket, (long) FIONBIO, &enabled);
if (non_block_rs != NO_ERROR) { if (non_block_rs != NO_ERROR) {
this->finalize(); this->finalize(true, true);
error = "failed to enable non blocking more"; error = "failed to enable non blocking more";
return false; return false;
} }
@ -90,7 +92,7 @@ bool Transfer::initialize(std::string &error) {
{ {
lock_guard lock(this->event_lock); lock_guard lock(this->event_lock);
this->event_read = event_new(this->event_io, this->_socket, EV_READ | EV_PERSIST, &Transfer::_callback_read, this); this->event_read = event_new(this->event_io, this->_socket, (unsigned) EV_READ | (unsigned) EV_PERSIST, &Transfer::_callback_read, this);
this->event_write = event_new(this->event_io, this->_socket, EV_WRITE, &Transfer::_callback_write, this); this->event_write = event_new(this->event_io, this->_socket, EV_WRITE, &Transfer::_callback_write, this);
} }
return true; return true;
@ -105,7 +107,7 @@ bool Transfer::connect() {
if(error != WSAEWOULDBLOCK) { if(error != WSAEWOULDBLOCK) {
wchar_t *s = nullptr; wchar_t *s = nullptr;
FormatMessageW( FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, (DWORD) FORMAT_MESSAGE_ALLOCATE_BUFFER | (DWORD) FORMAT_MESSAGE_FROM_SYSTEM | (DWORD) FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, nullptr,
error, error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
@ -120,7 +122,7 @@ bool Transfer::connect() {
log_trace(category::file_transfer, tr("Failed to connect with code: {} => {}/{}"), result, error, std::string{s, s + wcslen(s)}.c_str()); log_trace(category::file_transfer, tr("Failed to connect with code: {} => {}/{}"), result, error, std::string{s, s + wcslen(s)}.c_str());
LocalFree(s); LocalFree(s);
this->finalize(); this->finalize(true, true);
return false; return false;
} }
#else #else
@ -139,14 +141,13 @@ bool Transfer::connect() {
timeval connect_timeout{5, 0}; timeval connect_timeout{5, 0};
event_add(this->event_write, &connect_timeout); /* enabled if socket is connected */ event_add(this->event_write, &connect_timeout); /* enabled if socket is connected */
////event_add(this->event_read, &connect_timeout); /* enabled if socket is connected */ ////event_add(this->event_read, &connect_timeout); /* enabled if socket is connected */
this->handle()->execute_event_loop();
if(this->_state == state::CONNECTED) if(this->_state == state::CONNECTED)
this->handle_connected(); this->handle_connected();
return true; return true;
} }
void Transfer::finalize(bool blocking) { void Transfer::finalize(bool blocking, bool aborted) {
if(this->_state == state::UNINITIALIZED) if(this->_state == state::UNINITIALIZED)
return; return;
@ -154,23 +155,23 @@ void Transfer::finalize(bool blocking) {
{ {
unique_lock lock(this->event_lock); unique_lock lock(this->event_lock);
auto event_read = this->event_read, event_write = this->event_write; auto ev_read = std::exchange(this->event_read, nullptr);
this->event_read = nullptr; auto ev_write = std::exchange(this->event_write, nullptr);
this->event_write = nullptr;
lock.unlock(); lock.unlock();
if(event_read) {
if(ev_read) {
if(blocking) if(blocking)
event_del_block(event_read); event_del_block(ev_read);
else else
event_del_noblock(event_read); event_del_noblock(ev_read);
event_free(event_read); event_free(ev_read);
} }
if(event_write) { if(ev_write) {
if(blocking) if(blocking)
event_del_block(event_write); event_del_block(ev_write);
else else
event_del_noblock(event_write); event_del_noblock(ev_write);
event_free(event_write); event_free(ev_write);
} }
} }
@ -184,8 +185,7 @@ void Transfer::finalize(bool blocking) {
this->_socket = 0; this->_socket = 0;
} }
this->_transfer_object->finalize(); this->_transfer_object->finalize(aborted);
this->_handle->remove_transfer(this); this->_handle->remove_transfer(this);
} }
@ -201,14 +201,14 @@ void Transfer::callback_read(short flags) {
if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING) if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING)
return; return;
if(flags & EV_TIMEOUT) { if((unsigned) flags & (unsigned) EV_TIMEOUT) {
auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object); auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object);
if(target) { if(target) {
if(this->last_target_write.time_since_epoch().count() == 0) if(this->last_target_write.time_since_epoch().count() == 0) {
this->last_target_write = system_clock::now(); this->last_target_write = system_clock::now();
else if(system_clock::now() - this->last_target_write > seconds(5)) { } else if(system_clock::now() - this->last_target_write > seconds(5)) {
this->call_callback_failed("timeout (write)"); this->call_callback_failed("timeout (write)");
this->finalize(false); this->finalize(false, true);
return; return;
} }
} else { } else {
@ -216,7 +216,7 @@ void Transfer::callback_read(short flags) {
this->last_source_read = system_clock::now(); this->last_source_read = system_clock::now();
else if(system_clock::now() - this->last_source_read > seconds(5)) { else if(system_clock::now() - this->last_source_read > seconds(5)) {
this->call_callback_failed("timeout (read)"); this->call_callback_failed("timeout (read)");
this->finalize(false); this->finalize(false, true);
return; return;
} }
} }
@ -225,12 +225,11 @@ void Transfer::callback_read(short flags) {
lock_guard lock(this->event_lock); lock_guard lock(this->event_lock);
if(this->event_read) { if(this->event_read) {
event_add(this->event_read, &this->alive_check_timeout); event_add(this->event_read, &this->alive_check_timeout);
this->handle()->execute_event_loop();
} }
} }
} }
if(flags & EV_READ) { if((unsigned) flags & (unsigned) EV_READ) {
if(this->_state == state::CONNECTING) { if(this->_state == state::CONNECTING) {
log_debug(category::file_transfer, tr("Connected (read event)")); log_debug(category::file_transfer, tr("Connected (read event)"));
this->handle_connected(); this->handle_connected();
@ -251,31 +250,30 @@ void Transfer::callback_read(short flags) {
return; return;
#endif #endif
log_error(category::file_transfer, tr("Received an error while receivig data: {}/{}"), errno, strerror(errno)); log_error(category::file_transfer, tr("Received an error while receiving data: {}/{}"), errno, strerror(errno));
//TODO may handle this error message? //TODO may handle this error message?
this->handle_disconnect(); this->handle_disconnect(false);
return; return;
} else if(buffer_length == 0) { } else if(buffer_length == 0) {
log_info(category::file_transfer, tr("Received an disconnect")); log_info(category::file_transfer, tr("Received an disconnect"));
this->handle_disconnect(); this->handle_disconnect(false);
return; return;
} }
auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object); auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object);
if(target) { if(target) {
log_trace(category::file_transfer, tr("Read {} bytes"), buffer_length);
string error; string error;
auto state = target->write_bytes(error, (uint8_t*) buffer, buffer_length); auto state = target->write_bytes(error, (uint8_t*) buffer, buffer_length);
this->last_target_write = system_clock::now(); this->last_target_write = system_clock::now();
if(state == error::out_of_space) { if(state == error::out_of_space) {
log_error(category::file_transfer, tr("Failed to write read data (out of space)")); log_error(category::file_transfer, tr("Failed to write read data (out of space)"));
this->call_callback_failed(tr("out of local space")); this->call_callback_failed(tr("out of local space"));
this->finalize(true); this->finalize(true, true);
return; return;
} else if(state == error::custom) { } else if(state == error::custom) {
log_error(category::file_transfer, tr("Failed to write read data ({})"), error); log_error(category::file_transfer, tr("Failed to write read data ({})"), error);
this->call_callback_failed(error); this->call_callback_failed(error);
this->finalize(true); this->finalize(true, true);
return; return;
} else if(state == error::custom_recoverable) { } else if(state == error::custom_recoverable) {
log_error(category::file_transfer, tr("Failed to write read data ({})"), error); log_error(category::file_transfer, tr("Failed to write read data ({})"), error);
@ -287,7 +285,7 @@ void Transfer::callback_read(short flags) {
auto expected_bytes = target->expected_length(); auto expected_bytes = target->expected_length();
if(stream_index >= expected_bytes) { if(stream_index >= expected_bytes) {
this->call_callback_finished(false); this->call_callback_finished(false);
this->finalize(false); this->finalize(false, false);
} }
this->call_callback_process(stream_index, expected_bytes); this->call_callback_process(stream_index, expected_bytes);
} else { } else {
@ -300,17 +298,17 @@ void Transfer::callback_write(short flags) {
if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING) if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING)
return; return;
if(flags & EV_TIMEOUT) { if((unsigned) flags & (unsigned) EV_TIMEOUT) {
//we received a timeout! (May just for creating buffers) //we received a timeout! (May just for creating buffers)
if(this->_state == state::CONNECTING) { if(this->_state == state::CONNECTING) {
this->call_callback_failed(tr("failed to connect")); this->call_callback_failed(tr("failed to connect"));
this->finalize(false); this->finalize(false, true);
return; return;
} }
} }
bool readd_write = false, readd_write_for_read = false; bool readd_write = false, readd_write_for_read = false;
if(flags & EV_WRITE) { if((unsigned) flags & (unsigned) EV_WRITE) {
if(this->_state == state::CONNECTING) if(this->_state == state::CONNECTING)
this->handle_connected(); this->handle_connected();
@ -347,19 +345,19 @@ void Transfer::callback_write(short flags) {
if(_error == EAGAIN || _error == WSAEWOULDBLOCK) { if(_error == EAGAIN || _error == WSAEWOULDBLOCK) {
break; /* event will be added with e.g. a timeout */ break; /* event will be added with e.g. a timeout */
} else if(_error == ECONNREFUSED || _error == WSAECONNREFUSED) { } else if(_error == ECONNREFUSED || _error == WSAECONNREFUSED) {
this->call_callback_failed("connection refused"); this->call_callback_failed(tr("connection refused"));
this->finalize(false); this->finalize(false, true);
} else if(_error == ECONNRESET || _error == WSAECONNRESET) { } else if(_error == ECONNRESET || _error == WSAECONNRESET) {
this->call_callback_failed("connection reset"); this->call_callback_failed(tr("connection reset"));
this->finalize(false); this->finalize(false, true);
} else if(_error == ENOTCONN || _error == WSAENOTCONN) { } else if(_error == ENOTCONN || _error == WSAENOTCONN) {
this->call_callback_failed("not connected"); this->call_callback_failed(tr("not connected"));
this->finalize(false); this->finalize(false, true);
} else if(written == 0) { } else if(written == 0) {
this->handle_disconnect(); this->handle_disconnect(true);
} else { } else {
log_error(category::file_transfer, "Encountered write error: {}/{}", _error, strerror(_error)); log_error(category::file_transfer, tr("Encountered write error: {}/{}"), _error, strerror(_error));
this->handle_disconnect(); this->handle_disconnect(true);
} }
return; return;
} }
@ -396,14 +394,14 @@ void Transfer::callback_write(short flags) {
break; break;
} else if(read_status == error::custom) { } else if(read_status == error::custom) {
this->call_callback_failed(tr("failed to read from source: ") + error); this->call_callback_failed(tr("failed to read from source: ") + error);
this->finalize(false); this->finalize(false, true);
return; return;
} else if(read_status == error::custom_recoverable) { } else if(read_status == error::custom_recoverable) {
log_warn(category::file_transfer, tr("Failed to read from source (but its recoverable): {}"), error); log_warn(category::file_transfer, tr("Failed to read from source (but its recoverable): {}"), error);
break; break;
} else { } else {
log_error(category::file_transfer, tr("invalid source read status ({})"), read_status); log_error(category::file_transfer, tr("invalid source read status ({})"), read_status);
this->finalize(false); this->finalize(false, true);
return; return;
} }
} else if(buffer_size == 0) { } else if(buffer_size == 0) {
@ -425,7 +423,7 @@ void Transfer::callback_write(short flags) {
if(queue_length == 0) { if(queue_length == 0) {
if(source->stream_index() == source->byte_length()) { if(source->stream_index() == source->byte_length()) {
this->call_callback_finished(false); this->call_callback_finished(false);
this->finalize(false); this->finalize(false, false);
return; return;
} }
} }
@ -450,10 +448,7 @@ void Transfer::callback_write(short flags) {
timeout.tv_usec = 50000; timeout.tv_usec = 50000;
} }
event_add(this->event_write, &timeout); event_add(this->event_write, &timeout);
this->handle()->execute_event_loop();
} }
} else {
log_debug(category::general, tr("No readd"));
} }
} }
@ -466,24 +461,24 @@ void Transfer::_write_message(const pipes::buffer_view &buffer) {
lock_guard lock(this->event_lock); lock_guard lock(this->event_lock);
if(this->event_write) { if(this->event_write) {
event_add(this->event_write, nullptr); event_add(this->event_write, nullptr);
this->handle()->execute_event_loop();
} }
} }
} }
void Transfer::handle_disconnect() { void Transfer::handle_disconnect(bool write_disconnect) {
if(this->_state != state::DISCONNECTING) { if(this->_state != state::DISCONNECTING) {
auto source = dynamic_pointer_cast<TransferSource>(this->_transfer_object); auto source = dynamic_pointer_cast<TransferSource>(this->_transfer_object);
auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object); auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object);
auto mode = std::string{write_disconnect ? "write" : "read"};
if(source && source->stream_index() != source->byte_length()) { if(source && source->stream_index() != source->byte_length()) {
this->call_callback_failed("received disconnect while transmitting (" + to_string(target->stream_index()) + "/" + to_string(target->expected_length()) + ")"); this->call_callback_failed("received " + mode + " disconnect while transmitting data (" + to_string(target->stream_index()) + "/" + to_string(target->expected_length()) + ")");
} else if(target && target->stream_index() != target->expected_length()) { } else if(target && target->stream_index() != target->expected_length()) {
this->call_callback_failed("received disconnect while receiving (" + to_string(target->stream_index()) + "/" + to_string(target->expected_length()) + ")"); this->call_callback_failed("received " + mode + " disconnect while receiving data (" + to_string(target->stream_index()) + "/" + to_string(target->expected_length()) + ")");
} else } else
this->call_callback_finished(false); this->call_callback_finished(false);
} }
this->finalize(false); this->finalize(false, false);
} }
void Transfer::handle_connected() { void Transfer::handle_connected() {
@ -491,7 +486,6 @@ void Transfer::handle_connected() {
this->_state = state::CONNECTED; this->_state = state::CONNECTED;
event_add(this->event_read, &this->alive_check_timeout); event_add(this->event_read, &this->alive_check_timeout);
this->handle()->execute_event_loop();
this->_write_message(pipes::buffer_view{this->_options->transfer_key.data(), this->_options->transfer_key.length()}); this->_write_message(pipes::buffer_view{this->_options->transfer_key.data(), this->_options->transfer_key.length()});
this->call_callback_connected(); this->call_callback_connected();
@ -523,8 +517,8 @@ void Transfer::call_callback_process(size_t current, size_t max) {
this->callback_process(current, max); this->callback_process(current, max);
} }
FileTransferManager::FileTransferManager() {} FileTransferManager::FileTransferManager() = default;
FileTransferManager::~FileTransferManager() {} FileTransferManager::~FileTransferManager() = default;
void FileTransferManager::initialize() { void FileTransferManager::initialize() {
this->event_io_canceled = false; this->event_io_canceled = false;
@ -534,9 +528,9 @@ void FileTransferManager::initialize() {
void FileTransferManager::finalize() { void FileTransferManager::finalize() {
this->event_io_canceled = true; this->event_io_canceled = true;
this->event_io_condition.notify_all();
event_base_loopexit(this->event_io, nullptr); event_base_loopbreak(this->event_io);
this->event_io_thread.join(); threads::save_join(this->event_io_thread, false);
//TODO drop all file transfers! //TODO drop all file transfers!
event_base_free(this->event_io); event_base_free(this->event_io);
@ -544,18 +538,8 @@ void FileTransferManager::finalize() {
} }
void FileTransferManager::_execute_event_loop() { void FileTransferManager::_execute_event_loop() {
while(!this->event_io_canceled) { while(!this->event_io_canceled)
this->event_execute = false; event_base_loop(this->event_io, EVLOOP_NO_EXIT_ON_EMPTY);
event_base_loop(this->event_io, 0);
if(this->running_transfers().size() > 0) {
this_thread::sleep_for(milliseconds(50));
} else {
unique_lock lock(this->event_io_lock);
this->event_io_condition.wait_for(lock, minutes(1), [&]{
return this->event_io_canceled || this->event_execute;
});
}
}
} }
std::shared_ptr<Transfer> FileTransferManager::register_transfer(std::string& error, const std::shared_ptr<tc::ft::TransferObject> &object, std::unique_ptr<tc::ft::TransferOptions> options) { std::shared_ptr<Transfer> FileTransferManager::register_transfer(std::string& error, const std::shared_ptr<tc::ft::TransferObject> &object, std::unique_ptr<tc::ft::TransferOptions> options) {
@ -575,7 +559,7 @@ std::shared_ptr<Transfer> FileTransferManager::register_transfer(std::string& er
} }
void FileTransferManager::drop_transfer(const std::shared_ptr<Transfer> &transfer) { void FileTransferManager::drop_transfer(const std::shared_ptr<Transfer> &transfer) {
transfer->finalize(true); transfer->finalize(true, true);
{ {
lock_guard lock(this->_transfer_lock); lock_guard lock(this->_transfer_lock);
auto it = find(this->_running_transfers.begin(), this->_running_transfers.end(), transfer); auto it = find(this->_running_transfers.begin(), this->_running_transfers.end(), transfer);
@ -683,9 +667,10 @@ NAN_METHOD(JSTransfer::NewInstance) {
} }
JSTransfer::JSTransfer(std::shared_ptr<tc::ft::Transfer> transfer) : _transfer(move(transfer)) { JSTransfer::JSTransfer(std::shared_ptr<tc::ft::Transfer> transfer) : _transfer(move(transfer)) {
log_allocate("JSTransfer", this);
this->call_failed = Nan::async_callback([&](std::string error) { this->call_failed = Nan::async_callback([&](std::string error) {
Nan::HandleScope scope; Nan::HandleScope scope;
this->callback_failed(error); this->callback_failed(std::move(error));
}); });
this->call_finished = Nan::async_callback([&](bool f) { this->call_finished = Nan::async_callback([&](bool f) {
Nan::HandleScope scope; Nan::HandleScope scope;
@ -707,11 +692,11 @@ JSTransfer::JSTransfer(std::shared_ptr<tc::ft::Transfer> transfer) : _transfer(m
} }
JSTransfer::~JSTransfer() { JSTransfer::~JSTransfer() {
cout << "JS dealloc" << endl; log_free("JSTransfer", this);
this->_transfer->callback_failed = NULL; this->_transfer->callback_failed = nullptr;
this->_transfer->callback_finished = NULL; this->_transfer->callback_finished = nullptr;
this->_transfer->callback_start = NULL; this->_transfer->callback_start = nullptr;
this->_transfer->callback_process = NULL; this->_transfer->callback_process = nullptr;
} }
NAN_METHOD(JSTransfer::destory_transfer) { NAN_METHOD(JSTransfer::destory_transfer) {
@ -731,7 +716,6 @@ NAN_METHOD(JSTransfer::start) {
log_info(category::file_transfer, tr("Connecting to {}:{}"), this->_transfer->options().remote_address, this->_transfer->options().remote_port); log_info(category::file_transfer, tr("Connecting to {}:{}"), this->_transfer->options().remote_address, this->_transfer->options().remote_port);
info.GetReturnValue().Set(Nan::New<v8::Boolean>(true)); info.GetReturnValue().Set(Nan::New<v8::Boolean>(true));
return;
} }
NAN_METHOD(JSTransfer::_abort) { NAN_METHOD(JSTransfer::_abort) {
@ -754,7 +738,7 @@ void JSTransfer::callback_finished(bool flag) {
v8::Local<v8::Value> arguments[1]; v8::Local<v8::Value> arguments[1];
arguments[0] = Nan::New<v8::Boolean>(flag); arguments[0] = Nan::New<v8::Boolean>(flag);
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments); (void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments);
} }
void JSTransfer::callback_start() { void JSTransfer::callback_start() {
@ -762,7 +746,7 @@ void JSTransfer::callback_start() {
if(callback.IsEmpty() || !callback->IsFunction()) if(callback.IsEmpty() || !callback->IsFunction())
return; return;
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr); (void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
} }
void JSTransfer::callback_progress(uint64_t a, uint64_t b) { void JSTransfer::callback_progress(uint64_t a, uint64_t b) {
@ -773,7 +757,7 @@ void JSTransfer::callback_progress(uint64_t a, uint64_t b) {
v8::Local<v8::Value> arguments[2]; v8::Local<v8::Value> arguments[2];
arguments[0] = Nan::New<v8::Number>((uint32_t) a); arguments[0] = Nan::New<v8::Number>((uint32_t) a);
arguments[1] = Nan::New<v8::Number>((uint32_t) b); arguments[1] = Nan::New<v8::Number>((uint32_t) b);
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 2, arguments); (void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 2, arguments);
} }
void JSTransfer::callback_failed(std::string error) { void JSTransfer::callback_failed(std::string error) {
@ -786,7 +770,7 @@ void JSTransfer::callback_failed(std::string error) {
return; return;
v8::Local<v8::Value> arguments[1]; v8::Local<v8::Value> arguments[1];
arguments[0] = Nan::New<v8::String>(error).ToLocalChecked(); arguments[0] = Nan::New<v8::String>(std::move(error)).ToLocalChecked();
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments); (void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments);
} }
#endif #endif

View File

@ -20,208 +20,200 @@
#include <include/NanEventCallback.h> #include <include/NanEventCallback.h>
#endif #endif
namespace tc { namespace tc::ft {
namespace ft { namespace error {
namespace error { enum value : int8_t {
enum value : int8_t { success = 0,
success = 0, custom = 1,
custom = 1, custom_recoverable = 2,
custom_recoverable = 2, would_block = 3,
would_block = 3, out_of_space = 4
out_of_space = 4 };
}; }
} class TransferObject {
class TransferObject { public:
public: explicit TransferObject() = default;
explicit TransferObject() {}
virtual std::string name() const = 0; [[nodiscard]] virtual std::string name() const = 0;
virtual bool initialize(std::string& /* error */) = 0; virtual bool initialize(std::string& /* error */) = 0;
virtual void finalize() = 0; virtual void finalize(bool /* aborted */) = 0;
}; };
class TransferSource : public TransferObject { class TransferSource : public TransferObject {
public: public:
virtual uint64_t byte_length() const = 0; [[nodiscard]] virtual uint64_t byte_length() const = 0;
virtual uint64_t stream_index() const = 0; [[nodiscard]] virtual uint64_t stream_index() const = 0;
virtual error::value read_bytes(std::string& /* error */, uint8_t* /* buffer */, uint64_t& /* max length/result length */) = 0; virtual error::value read_bytes(std::string& /* error */, uint8_t* /* buffer */, uint64_t& /* max length/result length */) = 0;
private: private:
}; };
class TransferTarget : public TransferObject { class TransferTarget : public TransferObject {
public: public:
TransferTarget() {} TransferTarget() = default;
virtual uint64_t expected_length() const = 0; [[nodiscard]] virtual uint64_t expected_length() const = 0;
[[nodiscard]] virtual uint64_t stream_index() const = 0;
virtual uint64_t stream_index() const = 0; virtual error::value write_bytes(std::string& /* error */, uint8_t* /* buffer */, uint64_t /* max length */) = 0;
virtual error::value write_bytes(std::string& /* error */, uint8_t* /* buffer */, uint64_t /* max length */) = 0; };
};
struct TransferOptions { struct TransferOptions {
std::string remote_address; std::string remote_address;
uint16_t remote_port = 0; uint16_t remote_port = 0;
std::string transfer_key{}; std::string transfer_key{};
uint32_t client_transfer_id = 0; uint32_t client_transfer_id = 0;
uint32_t server_transfer_id = 0; uint32_t server_transfer_id = 0;
}; };
class FileTransferManager; class FileTransferManager;
class Transfer { class Transfer {
friend class FileTransferManager; friend class FileTransferManager;
public: public:
struct state { struct state {
enum value { enum value {
UNINITIALIZED, UNINITIALIZED,
CONNECTING, CONNECTING,
CONNECTED, CONNECTED,
DISCONNECTING DISCONNECTING
}; };
}; };
typedef std::function<void()> callback_start_t; typedef std::function<void()> callback_start_t;
typedef std::function<void(bool /* aborted */)> callback_finished_t; typedef std::function<void(bool /* aborted */)> callback_finished_t;
typedef std::function<void(const std::string& /* error */)> callback_failed_t; typedef std::function<void(const std::string& /* error */)> callback_failed_t;
typedef std::function<void(uint64_t /* current index */, uint64_t /* max index */)> callback_process_t; typedef std::function<void(uint64_t /* current index */, uint64_t /* max index */)> callback_process_t;
explicit Transfer(FileTransferManager* handle, std::shared_ptr<TransferObject> transfer_object, std::unique_ptr<TransferOptions> options) : explicit Transfer(FileTransferManager* handle, std::shared_ptr<TransferObject> transfer_object, std::unique_ptr<TransferOptions> options) :
_transfer_object(std::move(transfer_object)), _transfer_object(std::move(transfer_object)),
_handle(handle), _handle(handle),
_options(std::move(options)) { _options(std::move(options)) {
log_allocate("Transfer", this); log_allocate("Transfer", this);
} }
~Transfer(); ~Transfer();
bool initialize(std::string& /* error */); bool initialize(std::string& /* error */);
void finalize(bool /* blocking */ = true); void finalize(bool /* blocking */, bool /* aborted */);
bool connect(); bool connect();
bool connected() { return this->_state > state::UNINITIALIZED; } bool connected() { return this->_state > state::UNINITIALIZED; }
FileTransferManager* handle() { return this->_handle; } FileTransferManager* handle() { return this->_handle; }
std::shared_ptr<TransferObject> transfer_object() { return this->_transfer_object; } std::shared_ptr<TransferObject> transfer_object() { return this->_transfer_object; }
const TransferOptions& options() { return *this->_options; } const TransferOptions& options() { return *this->_options; }
callback_start_t callback_start{nullptr}; callback_start_t callback_start{nullptr};
callback_finished_t callback_finished{nullptr}; callback_finished_t callback_finished{nullptr};
callback_failed_t callback_failed{nullptr}; callback_failed_t callback_failed{nullptr};
callback_process_t callback_process{nullptr}; callback_process_t callback_process{nullptr};
private: private:
static void _callback_read(evutil_socket_t, short, void*); static void _callback_read(evutil_socket_t, short, void*);
static void _callback_write(evutil_socket_t, short, void*); static void _callback_write(evutil_socket_t, short, void*);
sockaddr_storage remote_address{}; sockaddr_storage remote_address{};
FileTransferManager* _handle; FileTransferManager* _handle;
std::unique_ptr<TransferOptions> _options; std::unique_ptr<TransferOptions> _options;
state::value _state = state::UNINITIALIZED; state::value _state = state::UNINITIALIZED;
std::shared_ptr<TransferObject> _transfer_object; std::shared_ptr<TransferObject> _transfer_object;
std::mutex event_lock; std::mutex event_lock;
event_base* event_io = nullptr; /* gets assigned by the manager */ event_base* event_io = nullptr; /* gets assigned by the manager */
::event* event_read = nullptr; ::event* event_read = nullptr;
::event* event_write = nullptr; ::event* event_write = nullptr;
std::chrono::system_clock::time_point last_source_read; std::chrono::system_clock::time_point last_source_read;
std::chrono::system_clock::time_point last_target_write; std::chrono::system_clock::time_point last_target_write;
std::mutex queue_lock; std::mutex queue_lock;
std::deque<pipes::buffer> write_queue; std::deque<pipes::buffer> write_queue;
void _write_message(const pipes::buffer_view& /* buffer */); void _write_message(const pipes::buffer_view& /* buffer */);
int _socket = 0; int _socket = 0;
timeval alive_check_timeout{1, 0}; timeval alive_check_timeout{1, 0};
timeval write_timeout{1, 0}; timeval write_timeout{1, 0};
/* /*
* Upload mode: * Upload mode:
* Write the buffers left in write_queue, and if the queue length is less then 12 create new buffers. * Write the buffers left in write_queue, and if the queue length is less then 12 create new buffers.
* This event will as well be triggered every second as timeout, to create new buffers if needed * This event will as well be triggered every second as timeout, to create new buffers if needed
*/ */
void callback_write(short /* flags */); void callback_write(short /* flags */);
void callback_read(short /* flags */); void callback_read(short /* flags */);
/* called within the write/read callback */ /* called within the write/read callback */
void handle_disconnect(); void handle_disconnect(bool /* write disconnect */);
void handle_connected(); void handle_connected();
void call_callback_connected(); void call_callback_connected();
void call_callback_failed(const std::string& /* reason */); void call_callback_failed(const std::string& /* reason */);
void call_callback_finished(bool /* aborted */); void call_callback_finished(bool /* aborted */);
void call_callback_process(size_t /* current */, size_t /* max */); void call_callback_process(size_t /* current */, size_t /* max */);
std::chrono::system_clock::time_point last_process_call; std::chrono::system_clock::time_point last_process_call;
}; };
class FileTransferManager { class FileTransferManager {
public: public:
FileTransferManager(); FileTransferManager();
~FileTransferManager(); ~FileTransferManager();
void initialize(); void initialize();
void finalize(); void finalize();
std::shared_ptr<Transfer> register_transfer(std::string& error, const std::shared_ptr<TransferObject>& /* object */, std::unique_ptr<TransferOptions> /* options */); std::shared_ptr<Transfer> register_transfer(std::string& error, const std::shared_ptr<TransferObject>& /* object */, std::unique_ptr<TransferOptions> /* options */);
std::deque<std::shared_ptr<Transfer>> running_transfers() { std::deque<std::shared_ptr<Transfer>> running_transfers() {
std::lock_guard lock(this->_transfer_lock); std::lock_guard lock(this->_transfer_lock);
return this->_running_transfers; return this->_running_transfers;
} }
void drop_transfer(const std::shared_ptr<Transfer>& /* transfer */); void drop_transfer(const std::shared_ptr<Transfer>& /* transfer */);
void remove_transfer(Transfer*); /* internal use */ void remove_transfer(Transfer*); /* internal use */
inline void execute_event_loop() { private:
this->event_execute = true; bool event_execute = false;
this->event_io_condition.notify_all(); bool event_io_canceled = false;
} std::thread event_io_thread;
private: event_base* event_io = nullptr;
bool event_execute = false; ::event* event_cleanup = nullptr;
bool event_io_canceled = false;
std::mutex event_io_lock;
std::condition_variable event_io_condition;
std::thread event_io_thread;
event_base* event_io = nullptr;
::event* event_cleanup = nullptr;
std::mutex _transfer_lock; std::mutex _transfer_lock;
std::deque<std::shared_ptr<Transfer>> _running_transfers; std::deque<std::shared_ptr<Transfer>> _running_transfers;
void _execute_event_loop(); void _execute_event_loop();
}; };
#ifdef NODEJS_API #ifdef NODEJS_API
class JSTransfer : public Nan::ObjectWrap { class JSTransfer : public Nan::ObjectWrap {
public: public:
static NAN_MODULE_INIT(Init); static NAN_MODULE_INIT(Init);
static NAN_METHOD(NewInstance); static NAN_METHOD(NewInstance);
static inline Nan::Persistent<v8::Function> & constructor() { static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor; static Nan::Persistent<v8::Function> my_constructor;
return my_constructor; return my_constructor;
} }
explicit JSTransfer(std::shared_ptr<Transfer> transfer); explicit JSTransfer(std::shared_ptr<Transfer> transfer);
~JSTransfer(); ~JSTransfer() override;
NAN_METHOD(start); NAN_METHOD(start);
NAN_METHOD(abort); NAN_METHOD(abort);
static NAN_METHOD(destory_transfer); static NAN_METHOD(destory_transfer);
private: private:
static NAN_METHOD(_start); static NAN_METHOD(_start);
static NAN_METHOD(_abort); static NAN_METHOD(_abort);
std::shared_ptr<Transfer> _transfer; std::shared_ptr<Transfer> _transfer;
Nan::callback_t<bool> call_finished; Nan::callback_t<bool> call_finished;
Nan::callback_t<> call_start; Nan::callback_t<> call_start;
Nan::callback_t<uint64_t, uint64_t> call_progress; Nan::callback_t<uint64_t, uint64_t> call_progress;
Nan::callback_t<std::string> call_failed; Nan::callback_t<std::string> call_failed;
void callback_finished(bool); void callback_finished(bool);
void callback_start(); void callback_start();
void callback_progress(uint64_t, uint64_t); void callback_progress(uint64_t, uint64_t);
void callback_failed(std::string); void callback_failed(std::string);
bool _self_ref = false; bool _self_ref = false;
}; };
#endif #endif
}
} }
extern tc::ft::FileTransferManager* transfer_manager; extern tc::ft::FileTransferManager* transfer_manager;

View File

@ -32,7 +32,7 @@ bool TransferJSBufferTarget::initialize(std::string &error) {
return true; /* we've already have data */ return true; /* we've already have data */
} }
void TransferJSBufferTarget::finalize() { } void TransferJSBufferTarget::finalize(bool) { }
uint64_t TransferJSBufferTarget::stream_index() const { uint64_t TransferJSBufferTarget::stream_index() const {
return this->_js_buffer_index; return this->_js_buffer_index;
@ -90,7 +90,7 @@ TransferJSBufferSource::TransferJSBufferSource() {
bool TransferJSBufferSource::initialize(std::string &string) { return true; } bool TransferJSBufferSource::initialize(std::string &string) { return true; }
void TransferJSBufferSource::finalize() { } void TransferJSBufferSource::finalize(bool) { }
uint64_t TransferJSBufferSource::stream_index() const { uint64_t TransferJSBufferSource::stream_index() const {
return this->_js_buffer_index; return this->_js_buffer_index;
@ -169,14 +169,14 @@ void TransferObjectWrap::do_wrap(v8::Local<v8::Object> object) {
if(source) { if(source) {
Nan::Set(object, Nan::Set(object,
Nan::New<v8::String>("total_size").ToLocalChecked(), Nan::New<v8::String>("total_size").ToLocalChecked(),
Nan::New<v8::Number>((uint32_t) source->byte_length()) Nan::New<v8::Number>((double) source->byte_length())
); );
} }
if(target) { if(target) {
Nan::Set(object, Nan::Set(object,
Nan::New<v8::String>("expected_size").ToLocalChecked(), Nan::New<v8::String>("expected_size").ToLocalChecked(),
Nan::New<v8::Number>((uint32_t) target->expected_length()) Nan::New<v8::Number>((double) target->expected_length())
); );
} }
@ -214,8 +214,7 @@ bool TransferFileSource::initialize(std::string &error) {
if(!fs::exists(file)) { if(!fs::exists(file)) {
error = "file not found"; error = "file not found";
return false; return false;
} } else if(errc) {
if(errc) {
error = "failed to test for file existence: " + to_string(errc.value()) + "/" + errc.message(); error = "failed to test for file existence: " + to_string(errc.value()) + "/" + errc.message();
return false; return false;
} }
@ -246,7 +245,7 @@ bool TransferFileSource::initialize(std::string &error) {
return true; return true;
} }
void TransferFileSource::finalize() { void TransferFileSource::finalize(bool) {
if(this->file_stream) if(this->file_stream)
this->file_stream.close(); this->file_stream.close();
@ -290,10 +289,100 @@ uint64_t TransferFileSource::stream_index() const {
return this->position; return this->position;
} }
TransferFileTarget::TransferFileTarget(std::string path, std::string name, size_t target_size) : path_{std::move(path)}, name_{std::move(name)}, target_file_size{target_size} {
if(!this->path_.empty()) {
if(this->path_.back() == '/')
this->path_.pop_back();
#ifdef WIN32
if(this->path_.back() == '\\')
this->path_.pop_back();
#endif
}
}
TransferFileTarget::~TransferFileTarget() = default;
bool TransferFileTarget::initialize(std::string &error) {
auto targetFile = fs::u8path(this->path_) / fs::u8path(this->name_);
auto downloadFile = fs::u8path(this->path_) / fs::u8path(this->name_ + ".download");
log_debug(category::file_transfer, tr("Opening target file for transfer: {}"), downloadFile.string());
std::error_code fs_error;
if(fs::exists(fs::u8path(this->path_), fs_error)) {
if(fs::exists(downloadFile, fs_error)) {
if(!fs::remove(downloadFile, fs_error) || fs_error)
log_warn(category::file_transfer, tr("Failed to remove old temporary .download file for {}: {}/{}"), downloadFile.string(), fs_error.value(), fs_error.message());
} else if(fs_error) {
log_warn(category::file_transfer, tr("Failed to check for temp download file existence at {}: {}/{}"), downloadFile.string(), fs_error.value(), fs_error.message());
}
} else if(fs_error) {
log_error(category::file_transfer, tr("Failed to check for directory existence at {}: {}/{}"), this->path_, fs_error.value(), fs_error.message());
error = tr("failed to check for directory existence");
return false;
} else if(!fs::create_directories(fs::u8path(this->path_), fs_error) || fs_error) {
error = tr("failed to create directories: ") + std::to_string(fs_error.value()) + "/" + fs_error.message();
return false;
}
if(fs::exists(targetFile, fs_error)) {
if(!fs::remove(targetFile, fs_error) || fs_error) {
error = tr("failed to delete old file: ") + std::to_string(fs_error.value()) + "/" + fs_error.message();
return false;
}
} else if(fs_error) {
log_warn(category::file_transfer, tr("Failed to check for target file existence at {}: {}/{}. Assuming it does not exists."), targetFile.string(), fs_error.value(), fs_error.message());
}
this->file_stream = std::ofstream{downloadFile, std::ofstream::out | std::ofstream::binary};
if(!this->file_stream) {
error = tr("file to open file: ") + std::string{strerror(errno)};
return false;
}
this->position = 0;
return true;
}
void TransferFileTarget::finalize(bool aborted) {
if(this->file_stream)
this->file_stream.close();
std::error_code fs_error{};
auto downloadFile = fs::u8path(this->path_) / fs::u8path(this->name_ + ".download");
if(aborted) {
if(!fs::remove(downloadFile, fs_error) || fs_error)
log_warn(category::file_transfer, tr("Failed to remove .download file from aborted transfer for {}: {}/{}."), downloadFile.string(), fs_error.value(), fs_error.message());
} else {
auto target = fs::u8path(this->path_) / fs::u8path(this->name_);
if(this->file_stream)
this->file_stream.close();
fs::rename(downloadFile, target, fs_error);
if(fs_error)
log_warn(category::file_transfer, tr("Failed to rename file {} to {}: {}/{}"), downloadFile.string(), target.string(), fs_error.value(), fs_error.message());
}
this->position = 0;
}
error::value TransferFileTarget::write_bytes(std::string &error, uint8_t *buffer, uint64_t length) {
this->file_stream.write((char*) buffer, length);
this->position += length;
if(!this->file_stream) {
if(this->file_stream.eof())
error = "eof reached";
else
error = "io error. failed to write";
}
return error.empty() ? error::success : error::custom;
}
#ifdef NODEJS_API #ifdef NODEJS_API
NAN_METHOD(TransferFileSource::create) { NAN_METHOD(TransferFileSource::create) {
if(info.Length() != 2 || !info[0]->IsString() || !info[1]->IsString()) { if(info.Length() != 2 || !info[0]->IsString() || !info[1]->IsString()) {
Nan::ThrowError("invalid argument"); Nan::ThrowError("invalid arguments");
return; return;
} }
@ -303,4 +392,17 @@ NAN_METHOD(TransferFileSource::create) {
object_wrap->do_wrap(object); object_wrap->do_wrap(object);
info.GetReturnValue().Set(object); info.GetReturnValue().Set(object);
} }
NAN_METHOD(TransferFileTarget::create) {
if(info.Length() != 3 || !info[0]->IsString() || !info[1]->IsString() || !info[2]->IsNumber()) {
Nan::ThrowError("invalid arguments");
return;
}
auto instance = make_shared<TransferFileTarget>(*Nan::Utf8String{info[0]}, *Nan::Utf8String{info[1]}, info[2]->IntegerValue());
auto object_wrap = new TransferObjectWrap(instance);
auto object = Nan::NewInstance(Nan::New(TransferObjectWrap::constructor()), 0, nullptr).ToLocalChecked();
object_wrap->do_wrap(object);
info.GetReturnValue().Set(object);
}
#endif #endif

View File

@ -4,113 +4,137 @@
#include <optional> #include <optional>
#include "FileTransferManager.h" #include "FileTransferManager.h"
namespace tc { namespace tc::ft {
namespace ft { class TransferFileSource : public TransferSource {
class TransferFileSource : public TransferSource { public:
public: TransferFileSource(std::string /* path */, std::string /* name */);
TransferFileSource(std::string /* path */, std::string /* name */);
[[nodiscard]] inline std::string file_path() const { return this->_path; } [[nodiscard]] inline std::string file_path() const { return this->_path; }
[[nodiscard]] inline std::string file_name() const { return this->_name; } [[nodiscard]] inline std::string file_name() const { return this->_name; }
std::string name() const override { return "TransferFileSource"; } std::string name() const override { return "TransferFileSource"; }
bool initialize(std::string &string) override; bool initialize(std::string &string) override;
void finalize() override; void finalize(bool) override;
uint64_t byte_length() const override; uint64_t byte_length() const override;
uint64_t stream_index() const override; uint64_t stream_index() const override;
error::value read_bytes(std::string &string, uint8_t *uint8, uint64_t &uint64) override; error::value read_bytes(std::string &string, uint8_t *uint8, uint64_t &uint64) override;
#ifdef NODEJS_API #ifdef NODEJS_API
static NAN_METHOD(create); static NAN_METHOD(create);
#endif #endif
private: private:
std::string _path; std::string _path;
std::string _name; std::string _name;
uint64_t position{0}; uint64_t position{0};
std::ifstream file_stream{}; std::ifstream file_stream{};
mutable std::optional<size_t> file_size; mutable std::optional<size_t> file_size;
}; };
class TransferFileTarget : public TransferTarget {
public:
TransferFileTarget(std::string /* path */, std::string /* name */, size_t /* target size */);
virtual ~TransferFileTarget();
[[nodiscard]] std::string name() const override { return "TransferFileTarget"; }
bool initialize(std::string &string) override;
void finalize(bool) override;
[[nodiscard]] uint64_t stream_index() const override { return this->position; }
[[nodiscard]] uint64_t expected_length() const override { return this->target_file_size; }
error::value write_bytes(std::string &string, uint8_t *uint8, uint64_t uint64) override;
#ifdef NODEJS_API #ifdef NODEJS_API
class TransferObjectWrap : public Nan::ObjectWrap { static NAN_METHOD(create);
public: #endif
static NAN_MODULE_INIT(Init); private:
static NAN_METHOD(NewInstance); std::string path_;
std::string name_;
static inline bool is_wrap(const v8::Local<v8::Value>& value) {
if(value.As<v8::Object>().IsEmpty()) uint64_t position{0};
return false; std::ofstream file_stream{};
size_t target_file_size{};
return value->InstanceOf(Nan::GetCurrentContext(), Nan::New<v8::Function>(constructor())).FromMaybe(false); };
}
#ifdef NODEJS_API
static inline Nan::Persistent<v8::Function> & constructor() { class TransferObjectWrap : public Nan::ObjectWrap {
static Nan::Persistent<v8::Function> my_constructor; public:
return my_constructor; static NAN_MODULE_INIT(Init);
} static NAN_METHOD(NewInstance);
explicit TransferObjectWrap(std::shared_ptr<TransferObject> object) : _transfer(std::move(object)) { static inline bool is_wrap(const v8::Local<v8::Value>& value) {
} if(value.As<v8::Object>().IsEmpty())
return false;
~TransferObjectWrap() = default;
return value->InstanceOf(Nan::GetCurrentContext(), Nan::New<v8::Function>(constructor())).FromMaybe(false);
void do_wrap(v8::Local<v8::Object> object); }
std::shared_ptr<TransferObject> target() { return this->_transfer; } static inline Nan::Persistent<v8::Function> & constructor() {
private: static Nan::Persistent<v8::Function> my_constructor;
std::shared_ptr<TransferObject> _transfer; return my_constructor;
}; }
class TransferJSBufferSource : public TransferSource { explicit TransferObjectWrap(std::shared_ptr<TransferObject> object) : _transfer(std::move(object)) {
public: }
TransferJSBufferSource();
virtual ~TransferJSBufferSource(); ~TransferObjectWrap() override = default;
std::string name() const override { return "TransferJSBufferSource"; } void do_wrap(v8::Local<v8::Object> object);
bool initialize(std::string &string) override;
std::shared_ptr<TransferObject> target() { return this->_transfer; }
void finalize() override; private:
std::shared_ptr<TransferObject> _transfer;
uint64_t stream_index() const override; };
uint64_t byte_length() const override; class TransferJSBufferSource : public TransferSource {
public:
error::value read_bytes(std::string &string, uint8_t *uint8, uint64_t &uint64) override; TransferJSBufferSource();
virtual ~TransferJSBufferSource();
static NAN_METHOD(create_from_buffer);
[[nodiscard]] std::string name() const override { return "TransferJSBufferSource"; }
private: bool initialize(std::string &string) override;
v8::Global<v8::ArrayBuffer> _js_buffer;
void* _js_buffer_source; void finalize(bool) override;
uint64_t _js_buffer_length;
uint64_t _js_buffer_index; [[nodiscard]] uint64_t stream_index() const override;
};
[[nodiscard]] uint64_t byte_length() const override;
class TransferJSBufferTarget : public TransferTarget {
public: error::value read_bytes(std::string &string, uint8_t *uint8, uint64_t &uint64) override;
TransferJSBufferTarget();
virtual ~TransferJSBufferTarget(); static NAN_METHOD(create_from_buffer);
std::string name() const override { return "TransferJSBufferTarget"; } private:
bool initialize(std::string &string) override; v8::Global<v8::ArrayBuffer> _js_buffer;
void* _js_buffer_source;
void finalize() override; uint64_t _js_buffer_length;
uint64_t _js_buffer_index;
uint64_t stream_index() const override; };
uint64_t expected_length() const override { return this->_js_buffer_length; }
class TransferJSBufferTarget : public TransferTarget {
error::value write_bytes(std::string &string, uint8_t *uint8, uint64_t uint64) override; public:
TransferJSBufferTarget();
static NAN_METHOD(create_from_buffer); virtual ~TransferJSBufferTarget();
private: [[nodiscard]] std::string name() const override { return "TransferJSBufferTarget"; }
v8::Global<v8::ArrayBuffer> _js_buffer; bool initialize(std::string &string) override;
void* _js_buffer_source;
uint64_t _js_buffer_length; void finalize(bool) override;
uint64_t _js_buffer_index;
}; [[nodiscard]] uint64_t stream_index() const override;
[[nodiscard]] uint64_t expected_length() const override { return this->_js_buffer_length; }
error::value write_bytes(std::string &string, uint8_t *uint8, uint64_t uint64) override;
static NAN_METHOD(create_from_buffer);
private:
v8::Global<v8::ArrayBuffer> _js_buffer;
void* _js_buffer_source;
uint64_t _js_buffer_length;
uint64_t _js_buffer_index;
};
#endif #endif
}
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "TeaClient", "name": "TeaClient",
"version": "1.4.6-2", "version": "1.4.7",
"description": "", "description": "",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {