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 %>
OutputDir=<%= dest_dir %>
SetupIconFile=<%= icon_file %>
UninstallDisplayName=uninstall
Compression=lzma
SolidCompression=yes
DisableDirPage=no
DisableWelcomePage=no
SignTool=parameter <%- sign_arguments %>
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
@ -31,8 +33,30 @@ Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescrip
[Files]
Source: "<%= source_dir %>"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[UninstallDelete]
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]
Name: "{commonprograms}\TeaSpeak"; Filename: "{app}\TeaClient.exe"

View File

@ -1,5 +1,6 @@
import * as packager from "./package";
import * as deployer from "./deploy";
import * as glob from "glob";
import {parse_version, Version} from "../modules/shared/version";
const fs = require("fs-extra");
@ -19,13 +20,25 @@ const symbol_binary_path = package_path + "/resources/natives/";
let dest_path = undefined;
let info;
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> {
const content = await ejs_render("installer/WinInstall.ejs", {
source_dir: path.resolve(package_path) + "/*",
dest_dir: path.resolve(dest_path),
icon_file: path.resolve("resources/logo.ico"),
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);
@ -42,7 +55,39 @@ if(process.argv.length < 3) {
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;
version = parse_version(_info["version"]);
dest_path = "build/output/" + process.argv[2] + "/" + version.toString() + "/";

View File

@ -122,5 +122,5 @@ function deploy_client() {
#install_npm
#compile_scripts
#compile_native
package_client
#deploy_client
#package_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 {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 {DownloadKey, DownloadTransfer, UploadKey, UploadTransfer} from "tc-shared/file/FileManager";
import {base64_encode_ab, str2ab8} from "tc-shared/utils/buffers";
import {set_transfer_provider, TransferKey, TransferProvider} from "tc-shared/file/FileManager";
import * as electron from "electron";
class NativeFileDownload implements DownloadTransfer {
readonly key: DownloadKey;
private _handle: native.ft.NativeFileTransfer;
private _buffer: Uint8Array;
private _result: Promise<void>;
private _response: Response;
private _result_success: () => any;
private _result_error: (error: any) => any;
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)
const executeTransfer = (transfer: FileTransfer, object: native.ft.TransferObject, callbackFinished: () => void) => {
const address = transfer.transferProperties().addresses[0];
let ntransfer: native.ft.NativeFileTransfer;
try {
ntransfer = native.ft.spawn_connection({
client_transfer_id: transfer.clientTransferId,
server_transfer_id: -1,
object: object,
remote_address: address.serverAddress,
remote_port: address.serverPort,
transfer_key: transfer.transferProperties().transferKey
});
} 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 {
return this.key;
}
async request_file(): Promise<Response> {
if(this._response)
return this._response;
try {
await (this._result || this._start_transfer());
} catch(error) {
throw error;
ntransfer.callback_start = () => {
if(!transfer.isFinished()) {
transfer.setTransferState(FileTransferState.RUNNING);
transfer.lastStateUpdate = Date.now();
}
};
if(this._response)
return this._response;
ntransfer.callback_failed = error => {
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 */
return this._response || (this._response = new Response(this._buffer, {
ntransfer.callback_finished = aborted => {
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,
statusText: "success",
headers: {
"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 {
readonly transfer_key: UploadKey;
private _handle: native.ft.NativeFileTransfer;
class FileTransferTargetImpl extends FileTransferTarget {
private path: string;
private name: string;
private _result: Promise<void>;
constructor(path: string, name?: string) {
super();
private _result_success: () => any;
private _result_error: (error: any) => any;
constructor(key: UploadKey) {
this.transfer_key = key;
this.path = path;
this.name = name;
}
async put_data(data: BlobPart | File) : Promise<void> {
if(this._result) {
await this._result;
async requestPath() {
if(typeof this.path === "string")
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(data.size != this.transfer_key.total_size)
throw "invalid size";
if(!result.filePath)
throw tr("invalid result path");
buffer = native.ft.upload_transfer_object_from_file(path.dirname(data.path), data.name);
} else if(typeof(data) === "string") {
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();
}));
this.path = path.dirname(result.filePath);
this.name = path.basename(result.filePath);
}
get_key(): UploadKey {
return this.transfer_key;
}
}
getNativeTarget(fallbackName: string, expectedSize: number) {
this.name = this.name || fallbackName;
set_transfer_provider(new class implements TransferProvider {
spawn_upload_transfer(key: TransferKey): UploadTransfer {
return new NativeFileUpload(key);
return native.ft.download_transfer_object_from_file(this.path, this.name, expectedSize);
}
spawn_download_transfer(key: TransferKey): DownloadTransfer {
return new NativeFileDownload(key);
getFilePath(): string {
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";
class ErrorCommandHandler extends AbstractCommandHandler {
private _handle: ServerConnection;
private _handle: ServerConnection;
constructor(handle: ServerConnection) {
super(handle);
this._handle = handle;
}
constructor(handle: ServerConnection) {
super(handle);
this._handle = handle;
}
handle_command(command: ServerCommand): boolean {
if(command.command === "error") {
const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"];
const data = command.arguments[0];
handle_command(command: ServerCommand): boolean {
if(command.command === "error") {
const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"];
const data = command.arguments[0];
let return_code : string = data["return_code"];
if(!return_code) {
const listener = return_listener["last_command"] || return_listener["_clientinit"];
if(typeof(listener) === "function") {
console.warn(tr("Received error without return code. Using last command (%o)"), listener);
listener(new CommandResult(data));
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: ""}));
let return_code : string = data["return_code"];
if(!return_code) {
const listener = return_listener["last_command"] || return_listener["_clientinit"];
if(typeof(listener) === "function") {
console.warn(tr("Received error without return code. Using last command (%o)"), listener);
listener(new CommandResult(command.arguments));
delete return_listener["last_command"];
delete return_listener["_clientinit"];
} else {
console.warn(tr("Received error without return code."), data);
}
} 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;
}
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 {

View File

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

View File

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

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_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 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::GetFunction(Nan::New<v8::FunctionTemplate>(TransferJSBufferTarget::create_from_buffer)).ToLocalChecked()
);
Nan::Set(ft_namespace,
Nan::New<v8::String>("upload_transfer_object_from_file").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferFileSource::create)).ToLocalChecked()
);
Nan::Set(ft_namespace,
Nan::New<v8::String>("upload_transfer_object_from_file").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
JSTransfer::Init(ft_namespace);

View File

@ -1,9 +1,10 @@
#include "FileTransferManager.h"
#include "FileTransferObject.h"
#include <ThreadPool/ThreadHelper.h>
#include <misc/net.h>
#include <algorithm>
#include <iostream>
#include <utility>
#ifndef WIN32
#include <unistd.h>
@ -14,6 +15,7 @@
#endif
#else
#include <ws2tcpip.h>
#define SOCK_NONBLOCK (0)
#define MSG_DONTWAIT (0)
#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));
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) {
this->finalize();
this->finalize(true, true);
error = tr("failed to spawn socket");
return false;
}
#ifdef WIN32
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) {
this->finalize();
this->finalize(true, true);
error = "failed to enable non blocking more";
return false;
}
@ -90,7 +92,7 @@ bool Transfer::initialize(std::string &error) {
{
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);
}
return true;
@ -105,7 +107,7 @@ bool Transfer::connect() {
if(error != WSAEWOULDBLOCK) {
wchar_t *s = nullptr;
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,
error,
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());
LocalFree(s);
this->finalize();
this->finalize(true, true);
return false;
}
#else
@ -139,14 +141,13 @@ bool Transfer::connect() {
timeval connect_timeout{5, 0};
event_add(this->event_write, &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)
this->handle_connected();
return true;
}
void Transfer::finalize(bool blocking) {
void Transfer::finalize(bool blocking, bool aborted) {
if(this->_state == state::UNINITIALIZED)
return;
@ -154,23 +155,23 @@ void Transfer::finalize(bool blocking) {
{
unique_lock lock(this->event_lock);
auto event_read = this->event_read, event_write = this->event_write;
this->event_read = nullptr;
this->event_write = nullptr;
auto ev_read = std::exchange(this->event_read, nullptr);
auto ev_write = std::exchange(this->event_write, nullptr);
lock.unlock();
if(event_read) {
if(ev_read) {
if(blocking)
event_del_block(event_read);
event_del_block(ev_read);
else
event_del_noblock(event_read);
event_free(event_read);
event_del_noblock(ev_read);
event_free(ev_read);
}
if(event_write) {
if(ev_write) {
if(blocking)
event_del_block(event_write);
event_del_block(ev_write);
else
event_del_noblock(event_write);
event_free(event_write);
event_del_noblock(ev_write);
event_free(ev_write);
}
}
@ -184,8 +185,7 @@ void Transfer::finalize(bool blocking) {
this->_socket = 0;
}
this->_transfer_object->finalize();
this->_transfer_object->finalize(aborted);
this->_handle->remove_transfer(this);
}
@ -201,14 +201,14 @@ void Transfer::callback_read(short flags) {
if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING)
return;
if(flags & EV_TIMEOUT) {
if((unsigned) flags & (unsigned) EV_TIMEOUT) {
auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object);
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();
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->finalize(false);
this->finalize(false, true);
return;
}
} else {
@ -216,7 +216,7 @@ void Transfer::callback_read(short flags) {
this->last_source_read = system_clock::now();
else if(system_clock::now() - this->last_source_read > seconds(5)) {
this->call_callback_failed("timeout (read)");
this->finalize(false);
this->finalize(false, true);
return;
}
}
@ -225,12 +225,11 @@ void Transfer::callback_read(short flags) {
lock_guard lock(this->event_lock);
if(this->event_read) {
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) {
log_debug(category::file_transfer, tr("Connected (read event)"));
this->handle_connected();
@ -251,31 +250,30 @@ void Transfer::callback_read(short flags) {
return;
#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?
this->handle_disconnect();
this->handle_disconnect(false);
return;
} else if(buffer_length == 0) {
log_info(category::file_transfer, tr("Received an disconnect"));
this->handle_disconnect();
this->handle_disconnect(false);
return;
}
auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object);
if(target) {
log_trace(category::file_transfer, tr("Read {} bytes"), buffer_length);
string error;
auto state = target->write_bytes(error, (uint8_t*) buffer, buffer_length);
this->last_target_write = system_clock::now();
if(state == error::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->finalize(true);
this->finalize(true, true);
return;
} else if(state == error::custom) {
log_error(category::file_transfer, tr("Failed to write read data ({})"), error);
this->call_callback_failed(error);
this->finalize(true);
this->finalize(true, true);
return;
} else if(state == error::custom_recoverable) {
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();
if(stream_index >= expected_bytes) {
this->call_callback_finished(false);
this->finalize(false);
this->finalize(false, false);
}
this->call_callback_process(stream_index, expected_bytes);
} else {
@ -300,17 +298,17 @@ void Transfer::callback_write(short flags) {
if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING)
return;
if(flags & EV_TIMEOUT) {
if((unsigned) flags & (unsigned) EV_TIMEOUT) {
//we received a timeout! (May just for creating buffers)
if(this->_state == state::CONNECTING) {
this->call_callback_failed(tr("failed to connect"));
this->finalize(false);
this->finalize(false, true);
return;
}
}
bool readd_write = false, readd_write_for_read = false;
if(flags & EV_WRITE) {
if((unsigned) flags & (unsigned) EV_WRITE) {
if(this->_state == state::CONNECTING)
this->handle_connected();
@ -347,19 +345,19 @@ void Transfer::callback_write(short flags) {
if(_error == EAGAIN || _error == WSAEWOULDBLOCK) {
break; /* event will be added with e.g. a timeout */
} else if(_error == ECONNREFUSED || _error == WSAECONNREFUSED) {
this->call_callback_failed("connection refused");
this->finalize(false);
this->call_callback_failed(tr("connection refused"));
this->finalize(false, true);
} else if(_error == ECONNRESET || _error == WSAECONNRESET) {
this->call_callback_failed("connection reset");
this->finalize(false);
this->call_callback_failed(tr("connection reset"));
this->finalize(false, true);
} else if(_error == ENOTCONN || _error == WSAENOTCONN) {
this->call_callback_failed("not connected");
this->finalize(false);
this->call_callback_failed(tr("not connected"));
this->finalize(false, true);
} else if(written == 0) {
this->handle_disconnect();
this->handle_disconnect(true);
} else {
log_error(category::file_transfer, "Encountered write error: {}/{}", _error, strerror(_error));
this->handle_disconnect();
log_error(category::file_transfer, tr("Encountered write error: {}/{}"), _error, strerror(_error));
this->handle_disconnect(true);
}
return;
}
@ -396,14 +394,14 @@ void Transfer::callback_write(short flags) {
break;
} else if(read_status == error::custom) {
this->call_callback_failed(tr("failed to read from source: ") + error);
this->finalize(false);
this->finalize(false, true);
return;
} else if(read_status == error::custom_recoverable) {
log_warn(category::file_transfer, tr("Failed to read from source (but its recoverable): {}"), error);
break;
} else {
log_error(category::file_transfer, tr("invalid source read status ({})"), read_status);
this->finalize(false);
this->finalize(false, true);
return;
}
} else if(buffer_size == 0) {
@ -425,7 +423,7 @@ void Transfer::callback_write(short flags) {
if(queue_length == 0) {
if(source->stream_index() == source->byte_length()) {
this->call_callback_finished(false);
this->finalize(false);
this->finalize(false, false);
return;
}
}
@ -450,10 +448,7 @@ void Transfer::callback_write(short flags) {
timeout.tv_usec = 50000;
}
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);
if(this->event_write) {
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) {
auto source = dynamic_pointer_cast<TransferSource>(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()) {
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()) {
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
this->call_callback_finished(false);
}
this->finalize(false);
this->finalize(false, false);
}
void Transfer::handle_connected() {
@ -491,7 +486,6 @@ void Transfer::handle_connected() {
this->_state = state::CONNECTED;
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->call_callback_connected();
@ -523,8 +517,8 @@ void Transfer::call_callback_process(size_t current, size_t max) {
this->callback_process(current, max);
}
FileTransferManager::FileTransferManager() {}
FileTransferManager::~FileTransferManager() {}
FileTransferManager::FileTransferManager() = default;
FileTransferManager::~FileTransferManager() = default;
void FileTransferManager::initialize() {
this->event_io_canceled = false;
@ -534,9 +528,9 @@ void FileTransferManager::initialize() {
void FileTransferManager::finalize() {
this->event_io_canceled = true;
this->event_io_condition.notify_all();
event_base_loopexit(this->event_io, nullptr);
this->event_io_thread.join();
event_base_loopbreak(this->event_io);
threads::save_join(this->event_io_thread, false);
//TODO drop all file transfers!
event_base_free(this->event_io);
@ -544,18 +538,8 @@ void FileTransferManager::finalize() {
}
void FileTransferManager::_execute_event_loop() {
while(!this->event_io_canceled) {
this->event_execute = false;
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;
});
}
}
while(!this->event_io_canceled)
event_base_loop(this->event_io, EVLOOP_NO_EXIT_ON_EMPTY);
}
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) {
transfer->finalize(true);
transfer->finalize(true, true);
{
lock_guard lock(this->_transfer_lock);
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)) {
log_allocate("JSTransfer", this);
this->call_failed = Nan::async_callback([&](std::string error) {
Nan::HandleScope scope;
this->callback_failed(error);
this->callback_failed(std::move(error));
});
this->call_finished = Nan::async_callback([&](bool f) {
Nan::HandleScope scope;
@ -707,11 +692,11 @@ JSTransfer::JSTransfer(std::shared_ptr<tc::ft::Transfer> transfer) : _transfer(m
}
JSTransfer::~JSTransfer() {
cout << "JS dealloc" << endl;
this->_transfer->callback_failed = NULL;
this->_transfer->callback_finished = NULL;
this->_transfer->callback_start = NULL;
this->_transfer->callback_process = NULL;
log_free("JSTransfer", this);
this->_transfer->callback_failed = nullptr;
this->_transfer->callback_finished = nullptr;
this->_transfer->callback_start = nullptr;
this->_transfer->callback_process = nullptr;
}
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);
info.GetReturnValue().Set(Nan::New<v8::Boolean>(true));
return;
}
NAN_METHOD(JSTransfer::_abort) {
@ -754,7 +738,7 @@ void JSTransfer::callback_finished(bool flag) {
v8::Local<v8::Value> arguments[1];
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() {
@ -762,7 +746,7 @@ void JSTransfer::callback_start() {
if(callback.IsEmpty() || !callback->IsFunction())
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) {
@ -773,7 +757,7 @@ void JSTransfer::callback_progress(uint64_t a, uint64_t b) {
v8::Local<v8::Value> arguments[2];
arguments[0] = Nan::New<v8::Number>((uint32_t) a);
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) {
@ -786,7 +770,7 @@ void JSTransfer::callback_failed(std::string error) {
return;
v8::Local<v8::Value> arguments[1];
arguments[0] = Nan::New<v8::String>(error).ToLocalChecked();
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments);
arguments[0] = Nan::New<v8::String>(std::move(error)).ToLocalChecked();
(void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments);
}
#endif

View File

@ -20,208 +20,200 @@
#include <include/NanEventCallback.h>
#endif
namespace tc {
namespace ft {
namespace error {
enum value : int8_t {
success = 0,
custom = 1,
custom_recoverable = 2,
would_block = 3,
out_of_space = 4
};
}
class TransferObject {
public:
explicit TransferObject() {}
namespace tc::ft {
namespace error {
enum value : int8_t {
success = 0,
custom = 1,
custom_recoverable = 2,
would_block = 3,
out_of_space = 4
};
}
class TransferObject {
public:
explicit TransferObject() = default;
virtual std::string name() const = 0;
virtual bool initialize(std::string& /* error */) = 0;
virtual void finalize() = 0;
};
[[nodiscard]] virtual std::string name() const = 0;
virtual bool initialize(std::string& /* error */) = 0;
virtual void finalize(bool /* aborted */) = 0;
};
class TransferSource : public TransferObject {
public:
virtual uint64_t byte_length() const = 0;
virtual uint64_t stream_index() const = 0;
class TransferSource : public TransferObject {
public:
[[nodiscard]] virtual uint64_t byte_length() 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;
private:
};
virtual error::value read_bytes(std::string& /* error */, uint8_t* /* buffer */, uint64_t& /* max length/result length */) = 0;
private:
};
class TransferTarget : public TransferObject {
public:
TransferTarget() {}
class TransferTarget : public TransferObject {
public:
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 {
std::string remote_address;
uint16_t remote_port = 0;
std::string transfer_key{};
uint32_t client_transfer_id = 0;
uint32_t server_transfer_id = 0;
};
struct TransferOptions {
std::string remote_address;
uint16_t remote_port = 0;
std::string transfer_key{};
uint32_t client_transfer_id = 0;
uint32_t server_transfer_id = 0;
};
class FileTransferManager;
class Transfer {
friend class FileTransferManager;
public:
struct state {
enum value {
UNINITIALIZED,
CONNECTING,
CONNECTED,
DISCONNECTING
};
};
typedef std::function<void()> callback_start_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(uint64_t /* current index */, uint64_t /* max index */)> callback_process_t;
class FileTransferManager;
class Transfer {
friend class FileTransferManager;
public:
struct state {
enum value {
UNINITIALIZED,
CONNECTING,
CONNECTED,
DISCONNECTING
};
};
typedef std::function<void()> callback_start_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(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) :
_transfer_object(std::move(transfer_object)),
_handle(handle),
_options(std::move(options)) {
log_allocate("Transfer", this);
}
~Transfer();
explicit Transfer(FileTransferManager* handle, std::shared_ptr<TransferObject> transfer_object, std::unique_ptr<TransferOptions> options) :
_transfer_object(std::move(transfer_object)),
_handle(handle),
_options(std::move(options)) {
log_allocate("Transfer", this);
}
~Transfer();
bool initialize(std::string& /* error */);
void finalize(bool /* blocking */ = true);
bool initialize(std::string& /* error */);
void finalize(bool /* blocking */, bool /* aborted */);
bool connect();
bool connected() { return this->_state > state::UNINITIALIZED; }
bool connect();
bool connected() { return this->_state > state::UNINITIALIZED; }
FileTransferManager* handle() { return this->_handle; }
std::shared_ptr<TransferObject> transfer_object() { return this->_transfer_object; }
const TransferOptions& options() { return *this->_options; }
FileTransferManager* handle() { return this->_handle; }
std::shared_ptr<TransferObject> transfer_object() { return this->_transfer_object; }
const TransferOptions& options() { return *this->_options; }
callback_start_t callback_start{nullptr};
callback_finished_t callback_finished{nullptr};
callback_failed_t callback_failed{nullptr};
callback_process_t callback_process{nullptr};
private:
static void _callback_read(evutil_socket_t, short, void*);
static void _callback_write(evutil_socket_t, short, void*);
callback_start_t callback_start{nullptr};
callback_finished_t callback_finished{nullptr};
callback_failed_t callback_failed{nullptr};
callback_process_t callback_process{nullptr};
private:
static void _callback_read(evutil_socket_t, short, void*);
static void _callback_write(evutil_socket_t, short, void*);
sockaddr_storage remote_address{};
FileTransferManager* _handle;
std::unique_ptr<TransferOptions> _options;
state::value _state = state::UNINITIALIZED;
std::shared_ptr<TransferObject> _transfer_object;
sockaddr_storage remote_address{};
FileTransferManager* _handle;
std::unique_ptr<TransferOptions> _options;
state::value _state = state::UNINITIALIZED;
std::shared_ptr<TransferObject> _transfer_object;
std::mutex event_lock;
event_base* event_io = nullptr; /* gets assigned by the manager */
::event* event_read = nullptr;
::event* event_write = nullptr;
std::mutex event_lock;
event_base* event_io = nullptr; /* gets assigned by the manager */
::event* event_read = nullptr;
::event* event_write = nullptr;
std::chrono::system_clock::time_point last_source_read;
std::chrono::system_clock::time_point last_target_write;
std::mutex queue_lock;
std::deque<pipes::buffer> write_queue;
void _write_message(const pipes::buffer_view& /* buffer */);
int _socket = 0;
std::chrono::system_clock::time_point last_source_read;
std::chrono::system_clock::time_point last_target_write;
std::mutex queue_lock;
std::deque<pipes::buffer> write_queue;
void _write_message(const pipes::buffer_view& /* buffer */);
int _socket = 0;
timeval alive_check_timeout{1, 0};
timeval write_timeout{1, 0};
timeval alive_check_timeout{1, 0};
timeval write_timeout{1, 0};
/*
* Upload mode:
* 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
*/
void callback_write(short /* flags */);
void callback_read(short /* flags */);
/*
* Upload mode:
* 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
*/
void callback_write(short /* flags */);
void callback_read(short /* flags */);
/* called within the write/read callback */
void handle_disconnect();
void handle_connected();
/* called within the write/read callback */
void handle_disconnect(bool /* write disconnect */);
void handle_connected();
void call_callback_connected();
void call_callback_failed(const std::string& /* reason */);
void call_callback_finished(bool /* aborted */);
void call_callback_process(size_t /* current */, size_t /* max */);
void call_callback_connected();
void call_callback_failed(const std::string& /* reason */);
void call_callback_finished(bool /* aborted */);
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 {
public:
FileTransferManager();
~FileTransferManager();
class FileTransferManager {
public:
FileTransferManager();
~FileTransferManager();
void initialize();
void finalize();
void initialize();
void finalize();
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::lock_guard lock(this->_transfer_lock);
return this->_running_transfers;
}
void drop_transfer(const std::shared_ptr<Transfer>& /* transfer */);
void remove_transfer(Transfer*); /* internal use */
inline void execute_event_loop() {
this->event_execute = true;
this->event_io_condition.notify_all();
}
private:
bool event_execute = false;
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::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::lock_guard lock(this->_transfer_lock);
return this->_running_transfers;
}
void drop_transfer(const std::shared_ptr<Transfer>& /* transfer */);
void remove_transfer(Transfer*); /* internal use */
private:
bool event_execute = false;
bool event_io_canceled = false;
std::thread event_io_thread;
event_base* event_io = nullptr;
::event* event_cleanup = nullptr;
std::mutex _transfer_lock;
std::deque<std::shared_ptr<Transfer>> _running_transfers;
std::mutex _transfer_lock;
std::deque<std::shared_ptr<Transfer>> _running_transfers;
void _execute_event_loop();
};
void _execute_event_loop();
};
#ifdef NODEJS_API
class JSTransfer : public Nan::ObjectWrap {
public:
static NAN_MODULE_INIT(Init);
static NAN_METHOD(NewInstance);
class JSTransfer : public Nan::ObjectWrap {
public:
static NAN_MODULE_INIT(Init);
static NAN_METHOD(NewInstance);
static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor;
return my_constructor;
}
static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor;
return my_constructor;
}
explicit JSTransfer(std::shared_ptr<Transfer> transfer);
~JSTransfer();
explicit JSTransfer(std::shared_ptr<Transfer> transfer);
~JSTransfer() override;
NAN_METHOD(start);
NAN_METHOD(abort);
NAN_METHOD(start);
NAN_METHOD(abort);
static NAN_METHOD(destory_transfer);
private:
static NAN_METHOD(_start);
static NAN_METHOD(_abort);
static NAN_METHOD(destory_transfer);
private:
static NAN_METHOD(_start);
static NAN_METHOD(_abort);
std::shared_ptr<Transfer> _transfer;
std::shared_ptr<Transfer> _transfer;
Nan::callback_t<bool> call_finished;
Nan::callback_t<> call_start;
Nan::callback_t<uint64_t, uint64_t> call_progress;
Nan::callback_t<std::string> call_failed;
Nan::callback_t<bool> call_finished;
Nan::callback_t<> call_start;
Nan::callback_t<uint64_t, uint64_t> call_progress;
Nan::callback_t<std::string> call_failed;
void callback_finished(bool);
void callback_start();
void callback_progress(uint64_t, uint64_t);
void callback_failed(std::string);
void callback_finished(bool);
void callback_start();
void callback_progress(uint64_t, uint64_t);
void callback_failed(std::string);
bool _self_ref = false;
};
bool _self_ref = false;
};
#endif
}
}
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 */
}
void TransferJSBufferTarget::finalize() { }
void TransferJSBufferTarget::finalize(bool) { }
uint64_t TransferJSBufferTarget::stream_index() const {
return this->_js_buffer_index;
@ -90,7 +90,7 @@ TransferJSBufferSource::TransferJSBufferSource() {
bool TransferJSBufferSource::initialize(std::string &string) { return true; }
void TransferJSBufferSource::finalize() { }
void TransferJSBufferSource::finalize(bool) { }
uint64_t TransferJSBufferSource::stream_index() const {
return this->_js_buffer_index;
@ -169,14 +169,14 @@ void TransferObjectWrap::do_wrap(v8::Local<v8::Object> object) {
if(source) {
Nan::Set(object,
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) {
Nan::Set(object,
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)) {
error = "file not found";
return false;
}
if(errc) {
} else if(errc) {
error = "failed to test for file existence: " + to_string(errc.value()) + "/" + errc.message();
return false;
}
@ -246,7 +245,7 @@ bool TransferFileSource::initialize(std::string &error) {
return true;
}
void TransferFileSource::finalize() {
void TransferFileSource::finalize(bool) {
if(this->file_stream)
this->file_stream.close();
@ -290,10 +289,100 @@ uint64_t TransferFileSource::stream_index() const {
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
NAN_METHOD(TransferFileSource::create) {
if(info.Length() != 2 || !info[0]->IsString() || !info[1]->IsString()) {
Nan::ThrowError("invalid argument");
Nan::ThrowError("invalid arguments");
return;
}
@ -303,4 +392,17 @@ NAN_METHOD(TransferFileSource::create) {
object_wrap->do_wrap(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

View File

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

View File

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