diff --git a/github b/github index a1f980c..08b8d25 160000 --- a/github +++ b/github @@ -1 +1 @@ -Subproject commit a1f980c623d33a448c9acf4a97f1aa795175dcb3 +Subproject commit 08b8d258af3fb887707511625542376e2f222067 diff --git a/installer/WinInstall.ejs b/installer/WinInstall.ejs index a732b58..2d48806 100644 --- a/installer/WinInstall.ejs +++ b/installer/WinInstall.ejs @@ -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" diff --git a/installer/package_windows.ts b/installer/package_windows.ts index 7863dec..740d64f 100644 --- a/installer/package_windows.ts +++ b/installer/package_windows.ts @@ -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 { 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() + "/"; diff --git a/jenkins/create_build.sh b/jenkins/create_build.sh index d96f108..08ef2e3 100755 --- a/jenkins/create_build.sh +++ b/jenkins/create_build.sh @@ -122,5 +122,5 @@ function deploy_client() { #install_npm #compile_scripts #compile_native -package_client -#deploy_client +#package_client +deploy_client diff --git a/modules/renderer/connection/FileTransfer.ts b/modules/renderer/connection/FileTransfer.ts index 239344f..9008e33 100644 --- a/modules/renderer/connection/FileTransfer.ts +++ b/modules/renderer/connection/FileTransfer.ts @@ -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; - 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 { - 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 { + return new TextTransferSourceImpl(text); + } + + async createBufferSource(buffer: ArrayBuffer): Promise { + return new BufferTransferSourceImpl(buffer); + } + + async createBrowserFileSource(file: File): Promise { + return new BrowserFileTransferSourceImpl(file); + } + + async createResponseTarget(): Promise { + return new ResponseTransferTargetImpl(); + } + + async createFileTarget(path?: string, name?: string): Promise { + 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 { + 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 { + 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 { + 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 { - 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; + 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 { - 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 = 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; } -}); \ No newline at end of file + + getFileName(): string { + return this.name; + } + + hasFileName(): boolean { + return typeof this.name === "string"; + } +} \ No newline at end of file diff --git a/modules/renderer/connection/ServerConnection.ts b/modules/renderer/connection/ServerConnection.ts index fd59e2e..7257bbb 100644 --- a/modules/renderer/connection/ServerConnection.ts +++ b/modules/renderer/connection/ServerConnection.ts @@ -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 { diff --git a/modules/renderer/connection/VoiceConnection.ts b/modules/renderer/connection/VoiceConnection.ts index b490f31..86e05e3 100644 --- a/modules/renderer/connection/VoiceConnection.ts +++ b/modules/renderer/connection/VoiceConnection.ts @@ -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 { diff --git a/modules/renderer/index.ts b/modules/renderer/index.ts index 84cd7f3..17d7c5a 100644 --- a/modules/renderer/index.ts +++ b/modules/renderer/index.ts @@ -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 }); diff --git a/native/serverconnection/exports/exports.d.ts b/native/serverconnection/exports/exports.d.ts index 822ea99..ba3d668 100644 --- a/native/serverconnection/exports/exports.d.ts +++ b/native/serverconnection/exports/exports.d.ts @@ -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; diff --git a/native/serverconnection/src/bindings.cpp b/native/serverconnection/src/bindings.cpp index 49c1990..2e4be85 100644 --- a/native/serverconnection/src/bindings.cpp +++ b/native/serverconnection/src/bindings.cpp @@ -229,10 +229,14 @@ NAN_MODULE_INIT(init) { Nan::New("download_transfer_object_from_buffer").ToLocalChecked(), Nan::GetFunction(Nan::New(TransferJSBufferTarget::create_from_buffer)).ToLocalChecked() ); - Nan::Set(ft_namespace, - Nan::New("upload_transfer_object_from_file").ToLocalChecked(), - Nan::GetFunction(Nan::New(TransferFileSource::create)).ToLocalChecked() - ); + Nan::Set(ft_namespace, + Nan::New("upload_transfer_object_from_file").ToLocalChecked(), + Nan::GetFunction(Nan::New(TransferFileSource::create)).ToLocalChecked() + ); + Nan::Set(ft_namespace, + Nan::New("download_transfer_object_from_file").ToLocalChecked(), + Nan::GetFunction(Nan::New(TransferFileTarget::create)).ToLocalChecked() + ); //spawn_file_connection destroy_file_connection JSTransfer::Init(ft_namespace); diff --git a/native/serverconnection/src/connection/ft/FileTransferManager.cpp b/native/serverconnection/src/connection/ft/FileTransferManager.cpp index b3e05f5..5c2ff18 100644 --- a/native/serverconnection/src/connection/ft/FileTransferManager.cpp +++ b/native/serverconnection/src/connection/ft/FileTransferManager.cpp @@ -1,9 +1,10 @@ #include "FileTransferManager.h" #include "FileTransferObject.h" +#include #include #include -#include +#include #ifndef WIN32 #include @@ -14,6 +15,7 @@ #endif #else #include + #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(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(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(this->_transfer_object); auto target = dynamic_pointer_cast(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 FileTransferManager::register_transfer(std::string& error, const std::shared_ptr &object, std::unique_ptr options) { @@ -575,7 +559,7 @@ std::shared_ptr FileTransferManager::register_transfer(std::string& er } void FileTransferManager::drop_transfer(const std::shared_ptr &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 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 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(true)); - return; } NAN_METHOD(JSTransfer::_abort) { @@ -754,7 +738,7 @@ void JSTransfer::callback_finished(bool flag) { v8::Local arguments[1]; arguments[0] = Nan::New(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 arguments[2]; arguments[0] = Nan::New((uint32_t) a); arguments[1] = Nan::New((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 arguments[1]; - arguments[0] = Nan::New(error).ToLocalChecked(); - callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments); + arguments[0] = Nan::New(std::move(error)).ToLocalChecked(); + (void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments); } #endif \ No newline at end of file diff --git a/native/serverconnection/src/connection/ft/FileTransferManager.h b/native/serverconnection/src/connection/ft/FileTransferManager.h index d8bc3d8..d85a1a7 100644 --- a/native/serverconnection/src/connection/ft/FileTransferManager.h +++ b/native/serverconnection/src/connection/ft/FileTransferManager.h @@ -20,208 +20,200 @@ #include #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 callback_start_t; - typedef std::function callback_finished_t; - typedef std::function callback_failed_t; - typedef std::function callback_process_t; + class FileTransferManager; + class Transfer { + friend class FileTransferManager; + public: + struct state { + enum value { + UNINITIALIZED, + CONNECTING, + CONNECTED, + DISCONNECTING + }; + }; + typedef std::function callback_start_t; + typedef std::function callback_finished_t; + typedef std::function callback_failed_t; + typedef std::function callback_process_t; - explicit Transfer(FileTransferManager* handle, std::shared_ptr transfer_object, std::unique_ptr 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 transfer_object, std::unique_ptr 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 transfer_object() { return this->_transfer_object; } - const TransferOptions& options() { return *this->_options; } + FileTransferManager* handle() { return this->_handle; } + std::shared_ptr 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 _options; - state::value _state = state::UNINITIALIZED; - std::shared_ptr _transfer_object; + sockaddr_storage remote_address{}; + FileTransferManager* _handle; + std::unique_ptr _options; + state::value _state = state::UNINITIALIZED; + std::shared_ptr _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 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 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 register_transfer(std::string& error, const std::shared_ptr& /* object */, std::unique_ptr /* options */); - std::deque> running_transfers() { - std::lock_guard lock(this->_transfer_lock); - return this->_running_transfers; - } - void drop_transfer(const std::shared_ptr& /* 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 register_transfer(std::string& error, const std::shared_ptr& /* object */, std::unique_ptr /* options */); + std::deque> running_transfers() { + std::lock_guard lock(this->_transfer_lock); + return this->_running_transfers; + } + void drop_transfer(const std::shared_ptr& /* 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> _running_transfers; + std::mutex _transfer_lock; + std::deque> _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 & constructor() { - static Nan::Persistent my_constructor; - return my_constructor; - } + static inline Nan::Persistent & constructor() { + static Nan::Persistent my_constructor; + return my_constructor; + } - explicit JSTransfer(std::shared_ptr transfer); - ~JSTransfer(); + explicit JSTransfer(std::shared_ptr 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; + std::shared_ptr _transfer; - Nan::callback_t call_finished; - Nan::callback_t<> call_start; - Nan::callback_t call_progress; - Nan::callback_t call_failed; + Nan::callback_t call_finished; + Nan::callback_t<> call_start; + Nan::callback_t call_progress; + Nan::callback_t 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; \ No newline at end of file diff --git a/native/serverconnection/src/connection/ft/FileTransferObject.cpp b/native/serverconnection/src/connection/ft/FileTransferObject.cpp index df58e27..b433d9e 100644 --- a/native/serverconnection/src/connection/ft/FileTransferObject.cpp +++ b/native/serverconnection/src/connection/ft/FileTransferObject.cpp @@ -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 object) { if(source) { Nan::Set(object, Nan::New("total_size").ToLocalChecked(), - Nan::New((uint32_t) source->byte_length()) + Nan::New((double) source->byte_length()) ); } if(target) { Nan::Set(object, Nan::New("expected_size").ToLocalChecked(), - Nan::New((uint32_t) target->expected_length()) + Nan::New((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(*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 \ No newline at end of file diff --git a/native/serverconnection/src/connection/ft/FileTransferObject.h b/native/serverconnection/src/connection/ft/FileTransferObject.h index da3c919..76647fe 100644 --- a/native/serverconnection/src/connection/ft/FileTransferObject.h +++ b/native/serverconnection/src/connection/ft/FileTransferObject.h @@ -4,113 +4,137 @@ #include #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 file_size; - }; + uint64_t position{0}; + std::ifstream file_stream{}; + mutable std::optional 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& value) { - if(value.As().IsEmpty()) - return false; - - return value->InstanceOf(Nan::GetCurrentContext(), Nan::New(constructor())).FromMaybe(false); - } - - static inline Nan::Persistent & constructor() { - static Nan::Persistent my_constructor; - return my_constructor; - } - - explicit TransferObjectWrap(std::shared_ptr object) : _transfer(std::move(object)) { - } - - ~TransferObjectWrap() = default; - - void do_wrap(v8::Local object); - - std::shared_ptr target() { return this->_transfer; } - private: - std::shared_ptr _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 _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 _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& value) { + if(value.As().IsEmpty()) + return false; + + return value->InstanceOf(Nan::GetCurrentContext(), Nan::New(constructor())).FromMaybe(false); + } + + static inline Nan::Persistent & constructor() { + static Nan::Persistent my_constructor; + return my_constructor; + } + + explicit TransferObjectWrap(std::shared_ptr object) : _transfer(std::move(object)) { + } + + ~TransferObjectWrap() override = default; + + void do_wrap(v8::Local object); + + std::shared_ptr target() { return this->_transfer; } + private: + std::shared_ptr _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 _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 _js_buffer; + void* _js_buffer_source; + uint64_t _js_buffer_length; + uint64_t _js_buffer_index; + }; #endif - } } \ No newline at end of file diff --git a/package.json b/package.json index 6566c9c..805c396 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "TeaClient", - "version": "1.4.6-2", + "version": "1.4.7", "description": "", "main": "main.js", "scripts": {