Implemented native channel file transfer
This commit is contained in:
parent
3b8ae31ff0
commit
4ef78b125e
2
github
2
github
@ -1 +1 @@
|
||||
Subproject commit a1f980c623d33a448c9acf4a97f1aa795175dcb3
|
||||
Subproject commit 08b8d258af3fb887707511625542376e2f222067
|
@ -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"
|
||||
|
@ -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() + "/";
|
||||
|
@ -122,5 +122,5 @@ function deploy_client() {
|
||||
#install_npm
|
||||
#compile_scripts
|
||||
#compile_native
|
||||
package_client
|
||||
#deploy_client
|
||||
#package_client
|
||||
deploy_client
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
});
|
||||
|
4
native/serverconnection/exports/exports.d.ts
vendored
4
native/serverconnection/exports/exports.d.ts
vendored
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
@ -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;
|
@ -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
|
@ -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
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "TeaClient",
|
||||
"version": "1.4.6-2",
|
||||
"version": "1.4.7",
|
||||
"description": "",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
Loading…
Reference in New Issue
Block a user