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 %> | OutputBaseFilename=<%= executable_name %> | ||||||
| OutputDir=<%= dest_dir %> | OutputDir=<%= dest_dir %> | ||||||
| SetupIconFile=<%= icon_file %> | SetupIconFile=<%= icon_file %> | ||||||
|  | UninstallDisplayName=uninstall | ||||||
| Compression=lzma | Compression=lzma | ||||||
| SolidCompression=yes | SolidCompression=yes | ||||||
| DisableDirPage=no | DisableDirPage=no | ||||||
| DisableWelcomePage=no | DisableWelcomePage=no | ||||||
|  | SignTool=parameter <%- sign_arguments %> | ||||||
| 
 | 
 | ||||||
| [Languages] | [Languages] | ||||||
| Name: "english"; MessagesFile: "compiler:Default.isl" | Name: "english"; MessagesFile: "compiler:Default.isl" | ||||||
| @ -31,8 +33,30 @@ Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescrip | |||||||
| [Files] | [Files] | ||||||
| Source: "<%= source_dir %>"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs | Source: "<%= source_dir %>"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs | ||||||
| 
 | 
 | ||||||
| 
 | [UninstallDelete] | ||||||
| ; NOTE: Don't use "Flags: ignoreversion" on any shared system files | Type: files; Name: "{app}\app_version.json" | ||||||
|  | Type: files; Name: "{app}\ChangeLog.txt" | ||||||
|  | Type: files; Name: "{app}\chrome_100_percent.pak" | ||||||
|  | Type: files; Name: "{app}\chrome_200_percent.pak" | ||||||
|  | Type: files; Name: "{app}\d3dcompiler_47.dll" | ||||||
|  | Type: files; Name: "{app}\ffmpeg.dll" | ||||||
|  | Type: files; Name: "{app}\icudtl.dat" | ||||||
|  | Type: files; Name: "{app}\libEGL.dll" | ||||||
|  | Type: files; Name: "{app}\libGLESv2.dll" | ||||||
|  | Type: files; Name: "{app}\LICENSE" | ||||||
|  | Type: files; Name: "{app}\LICENSES.chromium.html" | ||||||
|  | Type: filesandordirs; Name: "{app}\locales" | ||||||
|  | Type: filesandordirs; Name: "{app}\resources" | ||||||
|  | Type: files; Name: "{app}\resources.pak" | ||||||
|  | Type: files; Name: "{app}\snapshot_blob.bin" | ||||||
|  | Type: filesandordirs; Name: "{app}\swiftshader" | ||||||
|  | Type: files; Name: "{app}\TeaClient.exe" | ||||||
|  | Type: files; Name: "{app}\update-installer.exe" | ||||||
|  | Type: files; Name: "{app}\v8_context_snapshot.bin" | ||||||
|  | Type: files; Name: "{app}\version" | ||||||
|  | Type: files; Name: "{app}\vk_swiftshader.dll" | ||||||
|  | Type: files; Name: "{app}\vk_swiftshader_icd.json" | ||||||
|  | Type: dirifempty; Name: "{app}\" | ||||||
| 
 | 
 | ||||||
| [Icons] | [Icons] | ||||||
| Name: "{commonprograms}\TeaSpeak"; Filename: "{app}\TeaClient.exe" | Name: "{commonprograms}\TeaSpeak"; Filename: "{app}\TeaClient.exe" | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import * as packager from "./package"; | import * as packager from "./package"; | ||||||
| import * as deployer from "./deploy"; | import * as deployer from "./deploy"; | ||||||
|  | import * as glob from "glob"; | ||||||
| import {parse_version, Version} from "../modules/shared/version"; | import {parse_version, Version} from "../modules/shared/version"; | ||||||
| 
 | 
 | ||||||
| const fs = require("fs-extra"); | const fs = require("fs-extra"); | ||||||
| @ -19,13 +20,25 @@ const symbol_binary_path = package_path + "/resources/natives/"; | |||||||
| let dest_path = undefined; | let dest_path = undefined; | ||||||
| let info; | let info; | ||||||
| let version: Version; | let version: Version; | ||||||
|  | 
 | ||||||
|  | function sign_command(file: string, description: string) { | ||||||
|  |     const base = "signtool sign /v /tr http://timestamp.digicert.com?alg=sha256 /td SHA256 /fd SHA256 /d \"" + description + "\" /du \"https://www.teaspeak.de/\""; | ||||||
|  |     return base + " /f " + path.join(__dirname, "../../../CodeSign-Certificate.pfx") + " /p qmsQBxpN2exj " + file; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function sign_file(file: string, description: string) { | ||||||
|  |     const { stdout, stderr } = await exec(sign_command(file, description), {maxBuffer: 1024 * 1024 * 1024}); | ||||||
|  |     console.log(stdout.toString()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| async function make_template() : Promise<string> { | async function make_template() : Promise<string> { | ||||||
|     const content = await ejs_render("installer/WinInstall.ejs", { |     const content = await ejs_render("installer/WinInstall.ejs", { | ||||||
|         source_dir: path.resolve(package_path) + "/*", |         source_dir: path.resolve(package_path) + "/*", | ||||||
|         dest_dir: path.resolve(dest_path), |         dest_dir: path.resolve(dest_path), | ||||||
|         icon_file: path.resolve("resources/logo.ico"), |         icon_file: path.resolve("resources/logo.ico"), | ||||||
|         version: info["version"], |         version: info["version"], | ||||||
|         executable_name: filename_installer.substr(0, filename_installer.length - 4) //Remove the .exe
 |         executable_name: filename_installer.substr(0, filename_installer.length - 4), //Remove the .exe
 | ||||||
|  |         sign_arguments:  sign_command("$f", "TeaClient installer") | ||||||
|     }, {}); |     }, {}); | ||||||
| 
 | 
 | ||||||
|     await fs.mkdirs(dest_path); |     await fs.mkdirs(dest_path); | ||||||
| @ -42,7 +55,39 @@ if(process.argv.length < 3) { | |||||||
|     process.exit(1); |     process.exit(1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| packager.pack_info(package_path).then(async _info => { | packager.pack_info(package_path).then(async info => { | ||||||
|  |     const asyncGlob = util.promisify(glob); | ||||||
|  |     const executables = await asyncGlob(package_path + "/**/*.exe"); | ||||||
|  |     const ddls = await asyncGlob(package_path + "/**/*.dll"); | ||||||
|  |     const nodeModules = await asyncGlob(package_path + "/**/*.node"); | ||||||
|  | 
 | ||||||
|  |     const exe_description_padding = { | ||||||
|  |         "TeaClient.exe": "TeaClient v" + info["version"], | ||||||
|  |         "update-installer.exe": "TeaClient update installer" | ||||||
|  |     }; | ||||||
|  |     const node_description_padding = { | ||||||
|  |         "teaclient_connection.node": "TeaClients connection library", | ||||||
|  |         "teaclient_crash_handler.node": "TeaClients crash handler", | ||||||
|  |         "teaclient_dns.node": "TeaClients dns resolve module", | ||||||
|  |         "teaclient_ppt.node": "TeaClients push to talk module" | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     console.log("Signing executables:"); | ||||||
|  |     for(const executable of executables) { | ||||||
|  |         const desc = exe_description_padding[path.basename(executable)]; | ||||||
|  |         if(!desc) throw "Missing description for " + executable; | ||||||
|  |         await sign_file(executable, ""); | ||||||
|  |     } | ||||||
|  |     for(const dll of ddls) | ||||||
|  |         await sign_file(dll, ""); | ||||||
|  |     for(const module of nodeModules) { | ||||||
|  |         const desc = node_description_padding[path.basename(module)]; | ||||||
|  |         if(!desc) throw "Missing description for " + module; | ||||||
|  |         await sign_file(module, ""); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return info; | ||||||
|  | }).then(async _info => { | ||||||
|     info = _info; |     info = _info; | ||||||
|     version = parse_version(_info["version"]); |     version = parse_version(_info["version"]); | ||||||
|     dest_path = "build/output/" + process.argv[2] + "/" + version.toString() + "/"; |     dest_path = "build/output/" + process.argv[2] + "/" + version.toString() + "/"; | ||||||
|  | |||||||
| @ -122,5 +122,5 @@ function deploy_client() { | |||||||
| #install_npm | #install_npm | ||||||
| #compile_scripts | #compile_scripts | ||||||
| #compile_native | #compile_native | ||||||
| package_client | #package_client | ||||||
| #deploy_client | deploy_client | ||||||
|  | |||||||
| @ -1,181 +1,397 @@ | |||||||
|  | import { | ||||||
|  |     BrowserFileTransferSource, | ||||||
|  |     BufferTransferSource, | ||||||
|  |     FileDownloadTransfer, FileTransfer, | ||||||
|  |     FileTransferState, FileTransferTarget, | ||||||
|  |     FileUploadTransfer, | ||||||
|  |     ResponseTransferTarget, | ||||||
|  |     TextTransferSource, | ||||||
|  |     TransferProvider, | ||||||
|  |     TransferSourceType, | ||||||
|  |     TransferTargetType | ||||||
|  | } from "tc-shared/file/Transfer"; | ||||||
| import * as native from "tc-native/connection"; | import * as native from "tc-native/connection"; | ||||||
|  | import {tr} from "tc-shared/i18n/localize"; | ||||||
|  | import * as log from "tc-shared/log"; | ||||||
|  | import {LogCategory} from "tc-shared/log"; | ||||||
|  | import {base64_encode_ab} from "tc-shared/utils/buffers"; | ||||||
| import * as path from "path"; | import * as path from "path"; | ||||||
| import {DownloadKey, DownloadTransfer, UploadKey, UploadTransfer} from "tc-shared/file/FileManager"; | import * as electron from "electron"; | ||||||
| import {base64_encode_ab, str2ab8} from "tc-shared/utils/buffers"; |  | ||||||
| import {set_transfer_provider, TransferKey, TransferProvider} from "tc-shared/file/FileManager"; |  | ||||||
| 
 | 
 | ||||||
| class NativeFileDownload implements DownloadTransfer { | const executeTransfer = (transfer: FileTransfer, object: native.ft.TransferObject, callbackFinished: () => void) => { | ||||||
|     readonly key: DownloadKey; |     const address = transfer.transferProperties().addresses[0]; | ||||||
|     private _handle: native.ft.NativeFileTransfer; |     let ntransfer: native.ft.NativeFileTransfer; | ||||||
|     private _buffer: Uint8Array; |     try { | ||||||
| 
 |         ntransfer = native.ft.spawn_connection({ | ||||||
|     private _result: Promise<void>; |             client_transfer_id: transfer.clientTransferId, | ||||||
|     private _response: Response; |             server_transfer_id: -1, | ||||||
| 
 |             object: object, | ||||||
|     private _result_success: () => any; |             remote_address: address.serverAddress, | ||||||
|     private _result_error: (error: any) => any; |             remote_port: address.serverPort, | ||||||
| 
 |             transfer_key: transfer.transferProperties().transferKey | ||||||
|     constructor(key: DownloadKey) { |  | ||||||
|         this.key = key; |  | ||||||
|         this._buffer = new Uint8Array(key.total_size); |  | ||||||
|         this._handle = native.ft.spawn_connection({ |  | ||||||
|             client_transfer_id: key.client_transfer_id, |  | ||||||
|             server_transfer_id: key.server_transfer_id, |  | ||||||
| 
 |  | ||||||
|             remote_address: key.peer.hosts[0], |  | ||||||
|             remote_port: key.peer.port, |  | ||||||
| 
 |  | ||||||
|             transfer_key: key.key, |  | ||||||
| 
 |  | ||||||
|             object: native.ft.download_transfer_object_from_buffer(this._buffer.buffer) |  | ||||||
|         }); |         }); | ||||||
|  |     } catch (error) { | ||||||
|  |         let message = typeof error === "object" ? 'message' in error ? error.message : typeof error === "string" ? error : undefined : undefined; | ||||||
|  |         if(!message) | ||||||
|  |             log.error(LogCategory.FILE_TRANSFER, tr("Failed to create file transfer handle: %o"), error); | ||||||
|  | 
 | ||||||
|  |         transfer.setFailed({ | ||||||
|  |             error: "connection", | ||||||
|  |             reason: "handle-initialize-error", | ||||||
|  |             extraMessage: message ? message : tr("Lookup the console") | ||||||
|  |         }, message ? message : tr("Lookup the console")); | ||||||
|  |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     get_key(): DownloadKey { |     ntransfer.callback_start = () => { | ||||||
|         return this.key; |         if(!transfer.isFinished()) { | ||||||
|     } |             transfer.setTransferState(FileTransferState.RUNNING); | ||||||
| 
 |             transfer.lastStateUpdate = Date.now(); | ||||||
|     async request_file(): Promise<Response> { |  | ||||||
|         if(this._response) |  | ||||||
|             return this._response; |  | ||||||
| 
 |  | ||||||
|         try { |  | ||||||
|             await (this._result || this._start_transfer()); |  | ||||||
|         } catch(error) { |  | ||||||
|             throw error; |  | ||||||
|         } |         } | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|         if(this._response) |     ntransfer.callback_failed = error => { | ||||||
|             return this._response; |         if(transfer.isFinished()) | ||||||
|  |             return; | ||||||
| 
 | 
 | ||||||
|         const buffer = this._buffer.buffer.slice(this._buffer.byteOffset, this._buffer.byteOffset + Math.min(64, this._buffer.byteLength)); |         transfer.lastStateUpdate = Date.now(); | ||||||
|  |         transfer.setFailed({ | ||||||
|  |             error: "connection", | ||||||
|  |             reason: "network-error", | ||||||
|  |             extraMessage: error | ||||||
|  |         }, error); | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|         /* may another task has been stepped by and already set the response */ |     ntransfer.callback_finished = aborted => { | ||||||
|         return this._response || (this._response = new Response(this._buffer, { |         if(transfer.isFinished()) | ||||||
|  |             return; | ||||||
|  | 
 | ||||||
|  |         callbackFinished(); | ||||||
|  |         transfer.setTransferState(aborted ? FileTransferState.CANCELED : FileTransferState.FINISHED); | ||||||
|  |         transfer.lastStateUpdate = Date.now(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     ntransfer.callback_progress = (current, max) => { | ||||||
|  |         if(transfer.isFinished()) | ||||||
|  |             return; | ||||||
|  | 
 | ||||||
|  |         const transferInfo = transfer.lastProgressInfo(); | ||||||
|  |         /* ATTENTION: transferInfo.timestamp | 0 does not work since 1591875114970 > 2^32 (1591875114970 | 0 => -1557751846) */ | ||||||
|  |         if(transferInfo && Date.now() - (typeof transferInfo.timestamp === "number" ? transferInfo.timestamp : 0) < 2000 && !(transferInfo as any).native_info) | ||||||
|  |             return; | ||||||
|  | 
 | ||||||
|  |         transfer.updateProgress({ | ||||||
|  |             network_current_speed: 0, | ||||||
|  |             network_average_speed: 0, | ||||||
|  | 
 | ||||||
|  |             network_bytes_send: 0, | ||||||
|  |             network_bytes_received: 0, | ||||||
|  | 
 | ||||||
|  |             file_current_offset: current, | ||||||
|  |             file_total_size: max, | ||||||
|  |             file_bytes_transferred: current, | ||||||
|  |             file_start_offset: 0, | ||||||
|  |             timestamp: Date.now(), | ||||||
|  | 
 | ||||||
|  |             native_info: true | ||||||
|  |         } as any); | ||||||
|  |         transfer.lastStateUpdate = Date.now(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |         if(!ntransfer.start()) | ||||||
|  |             throw tr("failed to start transfer"); | ||||||
|  |     } catch (error) { | ||||||
|  |         if(typeof error !== "string") | ||||||
|  |             log.error(LogCategory.FILE_TRANSFER, tr("Failed to start file transfer: %o"), error); | ||||||
|  | 
 | ||||||
|  |         transfer.setFailed({ | ||||||
|  |             error: "connection", | ||||||
|  |             reason: "network-error", | ||||||
|  |             extraMessage: typeof error === "string" ? error : tr("Lookup the console") | ||||||
|  |         }, typeof error === "string" ? error : tr("Lookup the console")); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | TransferProvider.setProvider(new class extends TransferProvider { | ||||||
|  |     executeFileDownload(transfer: FileDownloadTransfer) { | ||||||
|  |         try { | ||||||
|  |             if(!transfer.target) throw tr("transfer target is undefined"); | ||||||
|  |             transfer.setTransferState(FileTransferState.CONNECTING); | ||||||
|  | 
 | ||||||
|  |             let nativeTarget: native.ft.FileTransferTarget; | ||||||
|  |             if(transfer.target instanceof ResponseTransferTargetImpl) { | ||||||
|  |                 transfer.target.initialize(transfer.transferProperties().fileSize); | ||||||
|  |                 nativeTarget = transfer.target.nativeTarget; | ||||||
|  |             } else if(transfer.target instanceof FileTransferTargetImpl) { | ||||||
|  |                 nativeTarget = transfer.target.getNativeTarget(transfer.properties.name, transfer.transferProperties().fileSize); | ||||||
|  |             } else { | ||||||
|  |                 transfer.setFailed({ | ||||||
|  |                     error: "io", | ||||||
|  |                     reason: "unsupported-target" | ||||||
|  |                 }, tr("invalid transfer target type")); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             executeTransfer(transfer, nativeTarget, () => { | ||||||
|  |                 if(transfer.target instanceof ResponseTransferTargetImpl) | ||||||
|  |                     transfer.target.createResponseFromBuffer(); | ||||||
|  |             }); | ||||||
|  |         } catch (error) { | ||||||
|  |             if(typeof error !== "string") | ||||||
|  |                 log.error(LogCategory.FILE_TRANSFER, tr("Failed to initialize transfer target: %o"), error); | ||||||
|  | 
 | ||||||
|  |             transfer.setFailed({ | ||||||
|  |                 error: "io", | ||||||
|  |                 reason: "failed-to-initialize-target", | ||||||
|  |                 extraMessage: typeof error === "string" ? error : tr("Lookup the console") | ||||||
|  |             }, typeof error === "string" ? error : tr("Lookup the console")); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     executeFileUpload(transfer: FileUploadTransfer) { | ||||||
|  |         try { | ||||||
|  |             if(!transfer.source) throw tr("transfer source is undefined"); | ||||||
|  | 
 | ||||||
|  |             let nativeSource: native.ft.FileTransferSource; | ||||||
|  |             if(transfer.source instanceof BrowserFileTransferSourceImpl) { | ||||||
|  |                 nativeSource = transfer.source.getNativeSource(); | ||||||
|  |             } else if(transfer.source instanceof TextTransferSourceImpl) { | ||||||
|  |                 nativeSource = transfer.source.getNativeSource(); | ||||||
|  |             } else if(transfer.source instanceof BufferTransferSourceImpl) { | ||||||
|  |                 nativeSource = transfer.source.getNativeSource(); | ||||||
|  |             } else { | ||||||
|  |                 console.log(transfer.source); | ||||||
|  |                 transfer.setFailed({ | ||||||
|  |                     error: "io", | ||||||
|  |                     reason: "unsupported-target" | ||||||
|  |                 }, tr("invalid transfer target type")); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             executeTransfer(transfer, nativeSource, () => { }); | ||||||
|  |         } catch (error) { | ||||||
|  |             if(typeof error !== "string") | ||||||
|  |                 log.error(LogCategory.FILE_TRANSFER, tr("Failed to initialize transfer source: %o"), error); | ||||||
|  | 
 | ||||||
|  |             transfer.setFailed({ | ||||||
|  |                 error: "io", | ||||||
|  |                 reason: "failed-to-initialize-target", | ||||||
|  |                 extraMessage: typeof error === "string" ? error : tr("Lookup the console") | ||||||
|  |             }, typeof error === "string" ? error : tr("Lookup the console")); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     sourceSupported(type: TransferSourceType) { | ||||||
|  |         switch (type) { | ||||||
|  |             case TransferSourceType.TEXT: | ||||||
|  |             case TransferSourceType.BUFFER: | ||||||
|  |             case TransferSourceType.BROWSER_FILE: | ||||||
|  |                 return true; | ||||||
|  | 
 | ||||||
|  |             default: | ||||||
|  |                 return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     targetSupported(type: TransferTargetType) { | ||||||
|  |         switch (type) { | ||||||
|  |             case TransferTargetType.RESPONSE: | ||||||
|  |             case TransferTargetType.FILE: | ||||||
|  |                 return true; | ||||||
|  | 
 | ||||||
|  |             case TransferTargetType.DOWNLOAD: | ||||||
|  |             default: | ||||||
|  |                 return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async createTextSource(text: string): Promise<TextTransferSource> { | ||||||
|  |         return new TextTransferSourceImpl(text); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async createBufferSource(buffer: ArrayBuffer): Promise<BufferTransferSource> { | ||||||
|  |         return new BufferTransferSourceImpl(buffer); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async createBrowserFileSource(file: File): Promise<BrowserFileTransferSource> { | ||||||
|  |         return new BrowserFileTransferSourceImpl(file); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async createResponseTarget(): Promise<ResponseTransferTarget> { | ||||||
|  |         return new ResponseTransferTargetImpl(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async createFileTarget(path?: string, name?: string): Promise<FileTransferTarget> { | ||||||
|  |         const target = new FileTransferTargetImpl(path, name); | ||||||
|  |         await target.requestPath(); | ||||||
|  |         return target; | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | class TextTransferSourceImpl extends TextTransferSource { | ||||||
|  |     private readonly text: string; | ||||||
|  |     private buffer: ArrayBuffer; | ||||||
|  |     private nativeSource: native.ft.FileTransferSource; | ||||||
|  | 
 | ||||||
|  |     constructor(text: string) { | ||||||
|  |         super(); | ||||||
|  |         this.text = text; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getText(): string { | ||||||
|  |         return this.text; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     async fileSize(): Promise<number> { | ||||||
|  |         return this.getArrayBuffer().byteLength; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getArrayBuffer() : ArrayBuffer { | ||||||
|  |         if(this.buffer) return this.buffer; | ||||||
|  | 
 | ||||||
|  |         const encoder = new TextEncoder(); | ||||||
|  |         this.buffer = encoder.encode(this.text); | ||||||
|  |         return this.buffer; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getNativeSource() { | ||||||
|  |         if(this.nativeSource) return this.nativeSource; | ||||||
|  | 
 | ||||||
|  |         this.nativeSource = native.ft.upload_transfer_object_from_buffer(this.getArrayBuffer()); | ||||||
|  |         return this.nativeSource; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class BufferTransferSourceImpl extends BufferTransferSource { | ||||||
|  |     private readonly buffer: ArrayBuffer; | ||||||
|  |     private nativeSource: native.ft.FileTransferSource; | ||||||
|  | 
 | ||||||
|  |     constructor(buffer: ArrayBuffer) { | ||||||
|  |         super(); | ||||||
|  |         this.buffer = buffer; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     async fileSize(): Promise<number> { | ||||||
|  |         return this.buffer.byteLength; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getNativeSource() { | ||||||
|  |         if(this.nativeSource) return this.nativeSource; | ||||||
|  | 
 | ||||||
|  |         this.nativeSource = native.ft.upload_transfer_object_from_buffer(this.buffer); | ||||||
|  |         return this.nativeSource; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getBuffer(): ArrayBuffer { | ||||||
|  |         return this.buffer; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class BrowserFileTransferSourceImpl extends BrowserFileTransferSource { | ||||||
|  |     private readonly file: File; | ||||||
|  |     private nativeSource: native.ft.FileTransferSource; | ||||||
|  | 
 | ||||||
|  |     constructor(file: File) { | ||||||
|  |         super(); | ||||||
|  |         this.file = file; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     async fileSize(): Promise<number> { | ||||||
|  |         return this.file.size; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getNativeSource() { | ||||||
|  |         if(this.nativeSource) return this.nativeSource; | ||||||
|  | 
 | ||||||
|  |         this.nativeSource = native.ft.upload_transfer_object_from_file(path.dirname(this.file.path), path.basename(this.file.path)); | ||||||
|  |         return this.nativeSource; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getFile(): File { | ||||||
|  |         return this.file; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class ResponseTransferTargetImpl extends ResponseTransferTarget { | ||||||
|  |     nativeTarget: native.ft.FileTransferTarget; | ||||||
|  |     buffer: Uint8Array; | ||||||
|  |     private response: Response; | ||||||
|  | 
 | ||||||
|  |     constructor() { | ||||||
|  |         super(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     initialize(bufferSize: number) { | ||||||
|  |         this.buffer = new Uint8Array(bufferSize); | ||||||
|  |         this.nativeTarget = native.ft.download_transfer_object_from_buffer(this.buffer.buffer); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getResponse(): Response { | ||||||
|  |         return this.response; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     hasResponse(): boolean { | ||||||
|  |         return typeof this.response === "object"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     createResponseFromBuffer() { | ||||||
|  |         const buffer = this.buffer.buffer.slice(this.buffer.byteOffset, this.buffer.byteOffset + Math.min(64, this.buffer.byteLength)); | ||||||
|  |         this.response = new Response(this.buffer, { | ||||||
|             status: 200, |             status: 200, | ||||||
|             statusText: "success", |             statusText: "success", | ||||||
|             headers: { |             headers: { | ||||||
|                 "X-media-bytes": base64_encode_ab(buffer) |                 "X-media-bytes": base64_encode_ab(buffer) | ||||||
| 
 |  | ||||||
|             } |             } | ||||||
|         })); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _start_transfer() : Promise<void> { |  | ||||||
|         return this._result = new Promise((resolve, reject) => { |  | ||||||
|             this._result_error = (error) => { |  | ||||||
|                 this._result_error = undefined; |  | ||||||
|                 this._result_success = undefined; |  | ||||||
|                 reject(error); |  | ||||||
|             }; |  | ||||||
|             this._result_success = () => { |  | ||||||
|                 this._result_error = undefined; |  | ||||||
|                 this._result_success = undefined; |  | ||||||
|                 resolve(); |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             this._handle.callback_failed = this._result_error; |  | ||||||
|             this._handle.callback_finished = aborted => { |  | ||||||
|                 if(aborted) |  | ||||||
|                     this._result_error("aborted"); |  | ||||||
|                 else |  | ||||||
|                     this._result_success(); |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             this._handle.start(); |  | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class NativeFileUpload implements UploadTransfer { | class FileTransferTargetImpl extends FileTransferTarget { | ||||||
|     readonly transfer_key: UploadKey; |     private path: string; | ||||||
|     private _handle: native.ft.NativeFileTransfer; |     private name: string; | ||||||
| 
 | 
 | ||||||
|     private _result: Promise<void>; |     constructor(path: string, name?: string) { | ||||||
|  |         super(); | ||||||
| 
 | 
 | ||||||
|     private _result_success: () => any; |         this.path = path; | ||||||
|     private _result_error: (error: any) => any; |         this.name = name; | ||||||
| 
 |  | ||||||
|     constructor(key: UploadKey) { |  | ||||||
|         this.transfer_key = key; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async put_data(data: BlobPart | File) : Promise<void> { |     async requestPath() { | ||||||
|         if(this._result) { |         if(typeof this.path === "string") | ||||||
|             await this._result; |  | ||||||
|             return; |             return; | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         let buffer: native.ft.FileTransferSource; |         const result = await electron.remote.dialog.showSaveDialog({ defaultPath: this.name }); | ||||||
|  |         if(result.canceled) | ||||||
|  |             throw tr("download canceled"); | ||||||
| 
 | 
 | ||||||
|         if(data instanceof File) { |         if(!result.filePath) | ||||||
|             if(data.size != this.transfer_key.total_size) |             throw tr("invalid result path"); | ||||||
|                 throw "invalid size"; |  | ||||||
| 
 | 
 | ||||||
|             buffer = native.ft.upload_transfer_object_from_file(path.dirname(data.path), data.name); |         this.path = path.dirname(result.filePath); | ||||||
|         } else if(typeof(data) === "string") { |         this.name = path.basename(result.filePath); | ||||||
|             if(data.length != this.transfer_key.total_size) |  | ||||||
|                 throw "invalid size"; |  | ||||||
| 
 |  | ||||||
|             buffer = native.ft.upload_transfer_object_from_buffer(str2ab8(data)); |  | ||||||
|         } else { |  | ||||||
|             let buf = <BufferSource>data; |  | ||||||
|             if(buf.byteLength != this.transfer_key.total_size) |  | ||||||
|                 throw "invalid size"; |  | ||||||
| 
 |  | ||||||
|             if(ArrayBuffer.isView(buf)) |  | ||||||
|                 buf = buf.buffer.slice(buf.byteOffset); |  | ||||||
| 
 |  | ||||||
|             buffer = native.ft.upload_transfer_object_from_buffer(buf); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this._handle = native.ft.spawn_connection({ |  | ||||||
|             client_transfer_id: this.transfer_key.client_transfer_id, |  | ||||||
|             server_transfer_id: this.transfer_key.server_transfer_id, |  | ||||||
| 
 |  | ||||||
|             remote_address: this.transfer_key.peer.hosts[0], |  | ||||||
|             remote_port: this.transfer_key.peer.port, |  | ||||||
| 
 |  | ||||||
|             transfer_key: this.transfer_key.key, |  | ||||||
| 
 |  | ||||||
|             object: buffer |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         await (this._result = new Promise((resolve, reject) => { |  | ||||||
|             this._result_error = (error) => { |  | ||||||
|                 this._result_error = undefined; |  | ||||||
|                 this._result_success = undefined; |  | ||||||
|                 reject(error); |  | ||||||
|             }; |  | ||||||
|             this._result_success = () => { |  | ||||||
|                 this._result_error = undefined; |  | ||||||
|                 this._result_success = undefined; |  | ||||||
|                 resolve(); |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             this._handle.callback_failed = this._result_error; |  | ||||||
|             this._handle.callback_finished = aborted => { |  | ||||||
|                 if(aborted) |  | ||||||
|                     this._result_error("aborted"); |  | ||||||
|                 else |  | ||||||
|                     this._result_success(); |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             this._handle.start(); |  | ||||||
|         })); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     get_key(): UploadKey { |     getNativeTarget(fallbackName: string, expectedSize: number) { | ||||||
|         return this.transfer_key; |         this.name = this.name || fallbackName; | ||||||
|  | 
 | ||||||
|  |         return native.ft.download_transfer_object_from_file(this.path, this.name, expectedSize); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getFilePath(): string { | ||||||
|  |         return this.path; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getFileName(): string { | ||||||
|  |         return this.name; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     hasFileName(): boolean { | ||||||
|  |         return typeof this.name === "string"; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| set_transfer_provider(new class implements TransferProvider { |  | ||||||
|     spawn_upload_transfer(key: TransferKey): UploadTransfer { |  | ||||||
|         return new NativeFileUpload(key); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     spawn_download_transfer(key: TransferKey): DownloadTransfer { |  | ||||||
|         return new NativeFileDownload(key); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
| @ -17,94 +17,94 @@ import AbstractVoiceConnection = voice.AbstractVoiceConnection; | |||||||
| import {VoiceConnection} from "./VoiceConnection"; | import {VoiceConnection} from "./VoiceConnection"; | ||||||
| 
 | 
 | ||||||
| class ErrorCommandHandler extends AbstractCommandHandler { | class ErrorCommandHandler extends AbstractCommandHandler { | ||||||
|         private _handle: ServerConnection; |     private _handle: ServerConnection; | ||||||
| 
 | 
 | ||||||
|         constructor(handle: ServerConnection) { |     constructor(handle: ServerConnection) { | ||||||
|             super(handle); |         super(handle); | ||||||
|             this._handle = handle; |         this._handle = handle; | ||||||
|         } |     } | ||||||
| 
 | 
 | ||||||
|         handle_command(command: ServerCommand): boolean { |     handle_command(command: ServerCommand): boolean { | ||||||
|             if(command.command === "error") { |         if(command.command === "error") { | ||||||
|                 const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"]; |             const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"]; | ||||||
|                 const data = command.arguments[0]; |             const data = command.arguments[0]; | ||||||
| 
 | 
 | ||||||
|                 let return_code : string = data["return_code"]; |             let return_code : string = data["return_code"]; | ||||||
|                 if(!return_code) { |             if(!return_code) { | ||||||
|                     const listener = return_listener["last_command"] || return_listener["_clientinit"]; |                 const listener = return_listener["last_command"] || return_listener["_clientinit"]; | ||||||
|                     if(typeof(listener) === "function") { |                 if(typeof(listener) === "function") { | ||||||
|                         console.warn(tr("Received error without return code. Using last command (%o)"), listener); |                     console.warn(tr("Received error without return code. Using last command (%o)"), listener); | ||||||
|                         listener(new CommandResult(data)); |                     listener(new CommandResult(command.arguments)); | ||||||
|                         delete return_listener["last_command"]; |                     delete return_listener["last_command"]; | ||||||
|                         delete return_listener["_clientinit"]; |  | ||||||
|                     } else { |  | ||||||
|                         console.warn(tr("Received error without return code."), data); |  | ||||||
|                     } |  | ||||||
|                     return false; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 if(return_listener[return_code]) { |  | ||||||
|                     return_listener[return_code](new CommandResult(data)); |  | ||||||
|                 } else { |  | ||||||
|                     console.warn(tr("Error received for no handler! (%o)"), data); |  | ||||||
|                 } |  | ||||||
|                 return true; |  | ||||||
|             } else if(command.command == "initivexpand") { |  | ||||||
|                 if(command.arguments[0]["teaspeak"] == true) { |  | ||||||
|                     console.log("Using TeaSpeak identity type"); |  | ||||||
|                     this._handle.handshake_handler().startHandshake(); |  | ||||||
|                 } |  | ||||||
|                 return true; |  | ||||||
|             } else if(command.command == "initivexpand2") { |  | ||||||
|                 /* its TeamSpeak or TeaSpeak with experimental 3.1 and not up2date */ |  | ||||||
|                 this._handle["_do_teamspeak"] = true; |  | ||||||
|             } else if(command.command == "initserver") { |  | ||||||
|                 const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"]; |  | ||||||
| 
 |  | ||||||
|                 if(typeof(return_listener["_clientinit"]) === "function") { |  | ||||||
|                     return_listener["_clientinit"](new CommandResult({id: 0, message: ""})); |  | ||||||
|                     delete return_listener["_clientinit"]; |                     delete return_listener["_clientinit"]; | ||||||
|  |                 } else { | ||||||
|  |                     console.warn(tr("Received error without return code."), data); | ||||||
|                 } |                 } | ||||||
|             } else if(command.command == "notifyconnectioninforequest") { |                 return false; | ||||||
|                 this._handle.send_command("setconnectioninfo", |  | ||||||
|                     { |  | ||||||
|                         //TODO calculate
 |  | ||||||
|                         connection_ping: 0.0000, |  | ||||||
|                         connection_ping_deviation: 0.0, |  | ||||||
| 
 |  | ||||||
|                         connection_packets_sent_speech: 0, |  | ||||||
|                         connection_packets_sent_keepalive: 0, |  | ||||||
|                         connection_packets_sent_control: 0, |  | ||||||
|                         connection_bytes_sent_speech: 0, |  | ||||||
|                         connection_bytes_sent_keepalive: 0, |  | ||||||
|                         connection_bytes_sent_control: 0, |  | ||||||
|                         connection_packets_received_speech: 0, |  | ||||||
|                         connection_packets_received_keepalive: 0, |  | ||||||
|                         connection_packets_received_control: 0, |  | ||||||
|                         connection_bytes_received_speech: 0, |  | ||||||
|                         connection_bytes_received_keepalive: 0, |  | ||||||
|                         connection_bytes_received_control: 0, |  | ||||||
|                         connection_server2client_packetloss_speech: 0.0000, |  | ||||||
|                         connection_server2client_packetloss_keepalive: 0.0000, |  | ||||||
|                         connection_server2client_packetloss_control: 0.0000, |  | ||||||
|                         connection_server2client_packetloss_total: 0.0000, |  | ||||||
|                         connection_bandwidth_sent_last_second_speech: 0, |  | ||||||
|                         connection_bandwidth_sent_last_second_keepalive: 0, |  | ||||||
|                         connection_bandwidth_sent_last_second_control: 0, |  | ||||||
|                         connection_bandwidth_sent_last_minute_speech: 0, |  | ||||||
|                         connection_bandwidth_sent_last_minute_keepalive: 0, |  | ||||||
|                         connection_bandwidth_sent_last_minute_control: 0, |  | ||||||
|                         connection_bandwidth_received_last_second_speech: 0, |  | ||||||
|                         connection_bandwidth_received_last_second_keepalive: 0, |  | ||||||
|                         connection_bandwidth_received_last_second_control: 0, |  | ||||||
|                         connection_bandwidth_received_last_minute_speech: 0, |  | ||||||
|                         connection_bandwidth_received_last_minute_keepalive: 0, |  | ||||||
|                         connection_bandwidth_received_last_minute_control: 0 |  | ||||||
|                     } |  | ||||||
|                 ); |  | ||||||
|             } |             } | ||||||
|             return false; | 
 | ||||||
|  |             if(return_listener[return_code]) { | ||||||
|  |                 return_listener[return_code](new CommandResult(command.arguments)); | ||||||
|  |             } else { | ||||||
|  |                 console.warn(tr("Error received for no handler! (%o)"), data); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         } else if(command.command == "initivexpand") { | ||||||
|  |             if(command.arguments[0]["teaspeak"] == true) { | ||||||
|  |                 console.log("Using TeaSpeak identity type"); | ||||||
|  |                 this._handle.handshake_handler().startHandshake(); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         } else if(command.command == "initivexpand2") { | ||||||
|  |             /* its TeamSpeak or TeaSpeak with experimental 3.1 and not up2date */ | ||||||
|  |             this._handle["_do_teamspeak"] = true; | ||||||
|  |         } else if(command.command == "initserver") { | ||||||
|  |             const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"]; | ||||||
|  | 
 | ||||||
|  |             if(typeof(return_listener["_clientinit"]) === "function") { | ||||||
|  |                 return_listener["_clientinit"](new CommandResult([{id: 0, message: ""}])); | ||||||
|  |                 delete return_listener["_clientinit"]; | ||||||
|  |             } | ||||||
|  |         } else if(command.command == "notifyconnectioninforequest") { | ||||||
|  |             this._handle.send_command("setconnectioninfo", | ||||||
|  |                 { | ||||||
|  |                     //TODO calculate
 | ||||||
|  |                     connection_ping: 0.0000, | ||||||
|  |                     connection_ping_deviation: 0.0, | ||||||
|  | 
 | ||||||
|  |                     connection_packets_sent_speech: 0, | ||||||
|  |                     connection_packets_sent_keepalive: 0, | ||||||
|  |                     connection_packets_sent_control: 0, | ||||||
|  |                     connection_bytes_sent_speech: 0, | ||||||
|  |                     connection_bytes_sent_keepalive: 0, | ||||||
|  |                     connection_bytes_sent_control: 0, | ||||||
|  |                     connection_packets_received_speech: 0, | ||||||
|  |                     connection_packets_received_keepalive: 0, | ||||||
|  |                     connection_packets_received_control: 0, | ||||||
|  |                     connection_bytes_received_speech: 0, | ||||||
|  |                     connection_bytes_received_keepalive: 0, | ||||||
|  |                     connection_bytes_received_control: 0, | ||||||
|  |                     connection_server2client_packetloss_speech: 0.0000, | ||||||
|  |                     connection_server2client_packetloss_keepalive: 0.0000, | ||||||
|  |                     connection_server2client_packetloss_control: 0.0000, | ||||||
|  |                     connection_server2client_packetloss_total: 0.0000, | ||||||
|  |                     connection_bandwidth_sent_last_second_speech: 0, | ||||||
|  |                     connection_bandwidth_sent_last_second_keepalive: 0, | ||||||
|  |                     connection_bandwidth_sent_last_second_control: 0, | ||||||
|  |                     connection_bandwidth_sent_last_minute_speech: 0, | ||||||
|  |                     connection_bandwidth_sent_last_minute_keepalive: 0, | ||||||
|  |                     connection_bandwidth_sent_last_minute_control: 0, | ||||||
|  |                     connection_bandwidth_received_last_second_speech: 0, | ||||||
|  |                     connection_bandwidth_received_last_second_keepalive: 0, | ||||||
|  |                     connection_bandwidth_received_last_second_control: 0, | ||||||
|  |                     connection_bandwidth_received_last_minute_speech: 0, | ||||||
|  |                     connection_bandwidth_received_last_minute_keepalive: 0, | ||||||
|  |                     connection_bandwidth_received_last_minute_control: 0 | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|         } |         } | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class ServerConnection extends AbstractServerConnection { | export class ServerConnection extends AbstractServerConnection { | ||||||
|  | |||||||
| @ -119,8 +119,8 @@ export class VoiceConnection extends AbstractVoiceConnection { | |||||||
|             support_latency_settings() { return true; }, |             support_latency_settings() { return true; }, | ||||||
|             reset_latency_settings: function() { |             reset_latency_settings: function() { | ||||||
|                 const stream = this.get_stream(); |                 const stream = this.get_stream(); | ||||||
|                 stream.set_buffer_latency(0.040); |                 stream.set_buffer_latency(0.080); | ||||||
|                 stream.set_buffer_max_latency(0.2); |                 stream.set_buffer_max_latency(0.5); | ||||||
|                 return this.latency_settings(); |                 return this.latency_settings(); | ||||||
|             }, |             }, | ||||||
|             latency_settings: function (settings?: LatencySettings) : LatencySettings { |             latency_settings: function (settings?: LatencySettings) : LatencySettings { | ||||||
|  | |||||||
| @ -24,15 +24,14 @@ declare global { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| rh.initialize(path.join(__dirname, "backend-impl")); | rh.initialize(path.join(__dirname, "backend-impl")); | ||||||
|  | 
 | ||||||
| /* --------------- main initialize  --------------- */ | /* --------------- main initialize  --------------- */ | ||||||
| import {Arguments, parse_arguments, process_args} from "../shared/process-arguments"; | import {Arguments, parse_arguments, process_args} from "../shared/process-arguments"; | ||||||
| import * as electron from "electron"; | import * as electron from "electron"; | ||||||
| import {remote} from "electron"; | import {remote} from "electron"; | ||||||
| import * as os from "os"; |  | ||||||
| import * as loader from "tc-loader"; | import * as loader from "tc-loader"; | ||||||
| import ipcRenderer = electron.ipcRenderer; | import ipcRenderer = electron.ipcRenderer; | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| /* we use out own jquery resource */ | /* we use out own jquery resource */ | ||||||
| loader.register_task(loader.Stage.JAVASCRIPT, { | loader.register_task(loader.Stage.JAVASCRIPT, { | ||||||
|     name: "teaclient jquery", |     name: "teaclient jquery", | ||||||
| @ -99,6 +98,7 @@ loader.register_task(loader.Stage.INITIALIZING, { | |||||||
|         parse_arguments(); |         parse_arguments(); | ||||||
|         if(process_args.has_value(Arguments.DUMMY_CRASH_RENDERER)) |         if(process_args.has_value(Arguments.DUMMY_CRASH_RENDERER)) | ||||||
|             crash_handler.handler.crash(); |             crash_handler.handler.crash(); | ||||||
|  | 
 | ||||||
|         if(!process_args.has_flag(Arguments.DEBUG)) { |         if(!process_args.has_flag(Arguments.DEBUG)) { | ||||||
|             window.open_connected_question = () => remote.dialog.showMessageBox(remote.getCurrentWindow(), { |             window.open_connected_question = () => remote.dialog.showMessageBox(remote.getCurrentWindow(), { | ||||||
|                 type: 'question', |                 type: 'question', | ||||||
| @ -107,6 +107,14 @@ loader.register_task(loader.Stage.INITIALIZING, { | |||||||
|                 message: 'Are you really sure?\nYou\'re still connected!' |                 message: 'Are you really sure?\nYou\'re still connected!' | ||||||
|             }).then(result => result.response === 0); |             }).then(result => result.response === 0); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         /* loader url setup */ | ||||||
|  |         { | ||||||
|  |             const baseUrl = process_args.value(Arguments.SERVER_URL); | ||||||
|  |             if(typeof baseUrl === "string") { | ||||||
|  |                 loader.config.baseUrl = baseUrl; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     }, |     }, | ||||||
|     priority: 110 |     priority: 110 | ||||||
| }); | }); | ||||||
|  | |||||||
							
								
								
									
										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_file(path: string, name: string) : FileTransferSource; | ||||||
|         export function upload_transfer_object_from_buffer(buffer: ArrayBuffer) : FileTransferSource; |         export function upload_transfer_object_from_buffer(buffer: ArrayBuffer) : FileTransferSource; | ||||||
|         export function download_transfer_object_from_buffer(target_buffer: ArrayBuffer) : TransferObject; | 
 | ||||||
|  |         export function download_transfer_object_from_buffer(target_buffer: ArrayBuffer) : FileTransferTarget; | ||||||
|  |         export function download_transfer_object_from_file(path: string, name: string, expectedSize: number) : FileTransferTarget; | ||||||
| 
 | 
 | ||||||
|         export function destroy_connection(connection: NativeFileTransfer); |         export function destroy_connection(connection: NativeFileTransfer); | ||||||
|         export function spawn_connection(transfer: TransferOptions) : NativeFileTransfer; |         export function spawn_connection(transfer: TransferOptions) : NativeFileTransfer; | ||||||
|  | |||||||
| @ -229,10 +229,14 @@ NAN_MODULE_INIT(init) { | |||||||
| 				Nan::New<v8::String>("download_transfer_object_from_buffer").ToLocalChecked(), | 				Nan::New<v8::String>("download_transfer_object_from_buffer").ToLocalChecked(), | ||||||
| 				Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferJSBufferTarget::create_from_buffer)).ToLocalChecked() | 				Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferJSBufferTarget::create_from_buffer)).ToLocalChecked() | ||||||
| 		); | 		); | ||||||
| 		Nan::Set(ft_namespace, |         Nan::Set(ft_namespace, | ||||||
| 		         Nan::New<v8::String>("upload_transfer_object_from_file").ToLocalChecked(), |                  Nan::New<v8::String>("upload_transfer_object_from_file").ToLocalChecked(), | ||||||
| 		         Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferFileSource::create)).ToLocalChecked() |                  Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferFileSource::create)).ToLocalChecked() | ||||||
| 		); |         ); | ||||||
|  |         Nan::Set(ft_namespace, | ||||||
|  |                  Nan::New<v8::String>("download_transfer_object_from_file").ToLocalChecked(), | ||||||
|  |                  Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferFileTarget::create)).ToLocalChecked() | ||||||
|  |         ); | ||||||
| 
 | 
 | ||||||
| 		//spawn_file_connection destroy_file_connection
 | 		//spawn_file_connection destroy_file_connection
 | ||||||
| 		JSTransfer::Init(ft_namespace); | 		JSTransfer::Init(ft_namespace); | ||||||
|  | |||||||
| @ -1,9 +1,10 @@ | |||||||
| #include "FileTransferManager.h" | #include "FileTransferManager.h" | ||||||
| #include "FileTransferObject.h" | #include "FileTransferObject.h" | ||||||
| 
 | 
 | ||||||
|  | #include <ThreadPool/ThreadHelper.h> | ||||||
| #include <misc/net.h> | #include <misc/net.h> | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <iostream> | #include <utility> | ||||||
| 
 | 
 | ||||||
| #ifndef WIN32 | #ifndef WIN32 | ||||||
|     #include <unistd.h> |     #include <unistd.h> | ||||||
| @ -14,6 +15,7 @@ | |||||||
| 	#endif | 	#endif | ||||||
| #else | #else | ||||||
|     #include <ws2tcpip.h> |     #include <ws2tcpip.h> | ||||||
|  | 
 | ||||||
|     #define SOCK_NONBLOCK (0) |     #define SOCK_NONBLOCK (0) | ||||||
|     #define MSG_DONTWAIT (0) |     #define MSG_DONTWAIT (0) | ||||||
| #endif | #endif | ||||||
| @ -70,18 +72,18 @@ bool Transfer::initialize(std::string &error) { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	log_info(category::file_transfer, tr("Setting remote port to {}"), net::to_string(this->remote_address)); | 	log_info(category::file_transfer, tr("Setting remote port to {}"), net::to_string(this->remote_address)); | ||||||
| 	this->_socket = (int) ::socket(this->remote_address.ss_family, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP); | 	this->_socket = (int) ::socket(this->remote_address.ss_family, (unsigned) SOCK_STREAM | (unsigned) SOCK_NONBLOCK, IPPROTO_TCP); | ||||||
| 	if(this->_socket < 0) { | 	if(this->_socket < 0) { | ||||||
| 		this->finalize(); | 		this->finalize(true, true); | ||||||
| 		error = tr("failed to spawn socket"); | 		error = tr("failed to spawn socket"); | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| #ifdef WIN32 | #ifdef WIN32 | ||||||
|     u_long enabled = 0; |     u_long enabled = 0; | ||||||
|     auto non_block_rs = ioctlsocket(this->_socket, FIONBIO, &enabled); |     auto non_block_rs = ioctlsocket(this->_socket, (long) FIONBIO, &enabled); | ||||||
|     if (non_block_rs != NO_ERROR) { |     if (non_block_rs != NO_ERROR) { | ||||||
|         this->finalize(); |         this->finalize(true, true); | ||||||
|         error = "failed to enable non blocking more"; |         error = "failed to enable non blocking more"; | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| @ -90,7 +92,7 @@ bool Transfer::initialize(std::string &error) { | |||||||
| 	{ | 	{ | ||||||
| 
 | 
 | ||||||
| 		lock_guard lock(this->event_lock); | 		lock_guard lock(this->event_lock); | ||||||
| 		this->event_read = event_new(this->event_io, this->_socket, EV_READ | EV_PERSIST, &Transfer::_callback_read, this); | 		this->event_read = event_new(this->event_io, this->_socket, (unsigned) EV_READ | (unsigned) EV_PERSIST, &Transfer::_callback_read, this); | ||||||
| 		this->event_write = event_new(this->event_io, this->_socket, EV_WRITE, &Transfer::_callback_write, this); | 		this->event_write = event_new(this->event_io, this->_socket, EV_WRITE, &Transfer::_callback_write, this); | ||||||
| 	} | 	} | ||||||
| 	return true; | 	return true; | ||||||
| @ -105,7 +107,7 @@ bool Transfer::connect() { | |||||||
|         if(error != WSAEWOULDBLOCK) { |         if(error != WSAEWOULDBLOCK) { | ||||||
|             wchar_t *s = nullptr; |             wchar_t *s = nullptr; | ||||||
|             FormatMessageW( |             FormatMessageW( | ||||||
|                     FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |                     (DWORD) FORMAT_MESSAGE_ALLOCATE_BUFFER | (DWORD) FORMAT_MESSAGE_FROM_SYSTEM | (DWORD) FORMAT_MESSAGE_IGNORE_INSERTS, | ||||||
|                     nullptr, |                     nullptr, | ||||||
|                     error, |                     error, | ||||||
|                     MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |                     MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | ||||||
| @ -120,7 +122,7 @@ bool Transfer::connect() { | |||||||
|             log_trace(category::file_transfer, tr("Failed to connect with code: {} => {}/{}"), result, error, std::string{s, s + wcslen(s)}.c_str()); |             log_trace(category::file_transfer, tr("Failed to connect with code: {} => {}/{}"), result, error, std::string{s, s + wcslen(s)}.c_str()); | ||||||
|             LocalFree(s); |             LocalFree(s); | ||||||
|              |              | ||||||
|             this->finalize(); |             this->finalize(true, true); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| #else | #else | ||||||
| @ -139,14 +141,13 @@ bool Transfer::connect() { | |||||||
| 	timeval connect_timeout{5, 0}; | 	timeval connect_timeout{5, 0}; | ||||||
| 	event_add(this->event_write, &connect_timeout); /* enabled if socket is connected */ | 	event_add(this->event_write, &connect_timeout); /* enabled if socket is connected */ | ||||||
| 	////event_add(this->event_read, &connect_timeout); /* enabled if socket is connected */
 | 	////event_add(this->event_read, &connect_timeout); /* enabled if socket is connected */
 | ||||||
| 	this->handle()->execute_event_loop(); |  | ||||||
| 
 | 
 | ||||||
| 	if(this->_state == state::CONNECTED) | 	if(this->_state == state::CONNECTED) | ||||||
| 		this->handle_connected(); | 		this->handle_connected(); | ||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Transfer::finalize(bool blocking) { | void Transfer::finalize(bool blocking, bool aborted) { | ||||||
| 	if(this->_state == state::UNINITIALIZED) | 	if(this->_state == state::UNINITIALIZED) | ||||||
| 		return; | 		return; | ||||||
| 
 | 
 | ||||||
| @ -154,23 +155,23 @@ void Transfer::finalize(bool blocking) { | |||||||
| 
 | 
 | ||||||
| 	{ | 	{ | ||||||
| 		unique_lock lock(this->event_lock); | 		unique_lock lock(this->event_lock); | ||||||
| 		auto event_read = this->event_read, event_write = this->event_write; | 		auto ev_read = std::exchange(this->event_read, nullptr); | ||||||
| 		this->event_read = nullptr; |         auto ev_write = std::exchange(this->event_write, nullptr); | ||||||
| 		this->event_write = nullptr; |  | ||||||
| 		lock.unlock(); | 		lock.unlock(); | ||||||
| 		if(event_read) { | 
 | ||||||
|  | 		if(ev_read) { | ||||||
| 			if(blocking) | 			if(blocking) | ||||||
| 				event_del_block(event_read); | 				event_del_block(ev_read); | ||||||
| 			else | 			else | ||||||
| 				event_del_noblock(event_read); | 				event_del_noblock(ev_read); | ||||||
| 			event_free(event_read); | 			event_free(ev_read); | ||||||
| 		} | 		} | ||||||
| 		if(event_write) { | 		if(ev_write) { | ||||||
| 			if(blocking) | 			if(blocking) | ||||||
| 				event_del_block(event_write); | 				event_del_block(ev_write); | ||||||
| 			else | 			else | ||||||
| 				event_del_noblock(event_write); | 				event_del_noblock(ev_write); | ||||||
| 			event_free(event_write); | 			event_free(ev_write); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -184,8 +185,7 @@ void Transfer::finalize(bool blocking) { | |||||||
| 		this->_socket = 0; | 		this->_socket = 0; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	this->_transfer_object->finalize(); | 	this->_transfer_object->finalize(aborted); | ||||||
| 
 |  | ||||||
| 	this->_handle->remove_transfer(this); | 	this->_handle->remove_transfer(this); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -201,14 +201,14 @@ void Transfer::callback_read(short flags) { | |||||||
| 	if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING) | 	if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING) | ||||||
| 		return; | 		return; | ||||||
| 
 | 
 | ||||||
| 	if(flags & EV_TIMEOUT) { | 	if((unsigned) flags & (unsigned) EV_TIMEOUT) { | ||||||
| 		auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object); | 		auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object); | ||||||
| 		if(target) { | 		if(target) { | ||||||
| 		    if(this->last_target_write.time_since_epoch().count() == 0) | 		    if(this->last_target_write.time_since_epoch().count() == 0) { | ||||||
|                 this->last_target_write = system_clock::now(); |                 this->last_target_write = system_clock::now(); | ||||||
| 			else if(system_clock::now() - this->last_target_write > seconds(5)) { | 		    } else if(system_clock::now() - this->last_target_write > seconds(5)) { | ||||||
| 				this->call_callback_failed("timeout (write)"); | 				this->call_callback_failed("timeout (write)"); | ||||||
| 				this->finalize(false); | 				this->finalize(false, true); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| @ -216,7 +216,7 @@ void Transfer::callback_read(short flags) { | |||||||
|                 this->last_source_read = system_clock::now(); |                 this->last_source_read = system_clock::now(); | ||||||
|             else if(system_clock::now() - this->last_source_read > seconds(5)) { |             else if(system_clock::now() - this->last_source_read > seconds(5)) { | ||||||
| 				this->call_callback_failed("timeout (read)"); | 				this->call_callback_failed("timeout (read)"); | ||||||
| 				this->finalize(false); | 				this->finalize(false, true); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @ -225,12 +225,11 @@ void Transfer::callback_read(short flags) { | |||||||
| 			lock_guard lock(this->event_lock); | 			lock_guard lock(this->event_lock); | ||||||
| 			if(this->event_read) { | 			if(this->event_read) { | ||||||
| 				event_add(this->event_read, &this->alive_check_timeout); | 				event_add(this->event_read, &this->alive_check_timeout); | ||||||
| 				this->handle()->execute_event_loop(); |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(flags & EV_READ) { | 	if((unsigned) flags & (unsigned) EV_READ) { | ||||||
| 		if(this->_state == state::CONNECTING) { | 		if(this->_state == state::CONNECTING) { | ||||||
| 			log_debug(category::file_transfer, tr("Connected (read event)")); | 			log_debug(category::file_transfer, tr("Connected (read event)")); | ||||||
| 			this->handle_connected(); | 			this->handle_connected(); | ||||||
| @ -251,31 +250,30 @@ void Transfer::callback_read(short flags) { | |||||||
| 				return; | 				return; | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| 			log_error(category::file_transfer, tr("Received an error while receivig data: {}/{}"), errno, strerror(errno)); | 			log_error(category::file_transfer, tr("Received an error while receiving data: {}/{}"), errno, strerror(errno)); | ||||||
| 			//TODO may handle this error message?
 | 			//TODO may handle this error message?
 | ||||||
| 			this->handle_disconnect(); | 			this->handle_disconnect(false); | ||||||
| 			return; | 			return; | ||||||
| 		} else if(buffer_length == 0) { | 		} else if(buffer_length == 0) { | ||||||
| 			log_info(category::file_transfer, tr("Received an disconnect")); | 			log_info(category::file_transfer, tr("Received an disconnect")); | ||||||
| 			this->handle_disconnect(); | 			this->handle_disconnect(false); | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object); | 		auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object); | ||||||
| 		if(target) { | 		if(target) { | ||||||
| 			log_trace(category::file_transfer, tr("Read {} bytes"), buffer_length); |  | ||||||
| 			string error; | 			string error; | ||||||
| 			auto state = target->write_bytes(error, (uint8_t*) buffer, buffer_length); | 			auto state = target->write_bytes(error, (uint8_t*) buffer, buffer_length); | ||||||
| 			this->last_target_write = system_clock::now(); | 			this->last_target_write = system_clock::now(); | ||||||
| 			if(state == error::out_of_space) { | 			if(state == error::out_of_space) { | ||||||
| 				log_error(category::file_transfer, tr("Failed to write read data (out of space)")); | 				log_error(category::file_transfer, tr("Failed to write read data (out of space)")); | ||||||
| 				this->call_callback_failed(tr("out of local space")); | 				this->call_callback_failed(tr("out of local space")); | ||||||
| 				this->finalize(true); | 				this->finalize(true, true); | ||||||
| 				return; | 				return; | ||||||
| 			} else if(state == error::custom) { | 			} else if(state == error::custom) { | ||||||
| 				log_error(category::file_transfer, tr("Failed to write read data ({})"), error); | 				log_error(category::file_transfer, tr("Failed to write read data ({})"), error); | ||||||
| 				this->call_callback_failed(error); | 				this->call_callback_failed(error); | ||||||
| 				this->finalize(true); | 				this->finalize(true, true); | ||||||
| 				return; | 				return; | ||||||
| 			} else if(state == error::custom_recoverable) { | 			} else if(state == error::custom_recoverable) { | ||||||
| 				log_error(category::file_transfer, tr("Failed to write read data ({})"), error); | 				log_error(category::file_transfer, tr("Failed to write read data ({})"), error); | ||||||
| @ -287,7 +285,7 @@ void Transfer::callback_read(short flags) { | |||||||
| 			auto expected_bytes = target->expected_length(); | 			auto expected_bytes = target->expected_length(); | ||||||
| 			if(stream_index >= expected_bytes) { | 			if(stream_index >= expected_bytes) { | ||||||
| 				this->call_callback_finished(false); | 				this->call_callback_finished(false); | ||||||
| 				this->finalize(false); | 				this->finalize(false, false); | ||||||
| 			} | 			} | ||||||
| 			this->call_callback_process(stream_index, expected_bytes); | 			this->call_callback_process(stream_index, expected_bytes); | ||||||
| 		} else { | 		} else { | ||||||
| @ -300,17 +298,17 @@ void Transfer::callback_write(short flags) { | |||||||
| 	if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING) | 	if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING) | ||||||
| 		return; | 		return; | ||||||
| 
 | 
 | ||||||
| 	if(flags & EV_TIMEOUT) { | 	if((unsigned) flags & (unsigned) EV_TIMEOUT) { | ||||||
| 		//we received a timeout! (May just for creating buffers)
 | 		//we received a timeout! (May just for creating buffers)
 | ||||||
| 		if(this->_state == state::CONNECTING) { | 		if(this->_state == state::CONNECTING) { | ||||||
| 			this->call_callback_failed(tr("failed to connect")); | 			this->call_callback_failed(tr("failed to connect")); | ||||||
| 			this->finalize(false); | 			this->finalize(false, true); | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool readd_write = false, readd_write_for_read = false; | 	bool readd_write = false, readd_write_for_read = false; | ||||||
| 	if(flags & EV_WRITE) { | 	if((unsigned) flags & (unsigned) EV_WRITE) { | ||||||
| 		if(this->_state == state::CONNECTING) | 		if(this->_state == state::CONNECTING) | ||||||
| 			this->handle_connected(); | 			this->handle_connected(); | ||||||
| 
 | 
 | ||||||
| @ -347,19 +345,19 @@ void Transfer::callback_write(short flags) { | |||||||
| 				if(_error == EAGAIN || _error == WSAEWOULDBLOCK) { | 				if(_error == EAGAIN || _error == WSAEWOULDBLOCK) { | ||||||
| 					break; /* event will be added with e.g. a timeout */ | 					break; /* event will be added with e.g. a timeout */ | ||||||
| 				} else if(_error == ECONNREFUSED || _error == WSAECONNREFUSED) { | 				} else if(_error == ECONNREFUSED || _error == WSAECONNREFUSED) { | ||||||
| 					this->call_callback_failed("connection refused"); | 					this->call_callback_failed(tr("connection refused")); | ||||||
| 					this->finalize(false); | 					this->finalize(false, true); | ||||||
| 				} else if(_error == ECONNRESET || _error == WSAECONNRESET) { | 				} else if(_error == ECONNRESET || _error == WSAECONNRESET) { | ||||||
|                     this->call_callback_failed("connection reset"); |                     this->call_callback_failed(tr("connection reset")); | ||||||
|                     this->finalize(false); |                     this->finalize(false, true); | ||||||
|                 } else if(_error ==  ENOTCONN || _error == WSAENOTCONN) { |                 } else if(_error ==  ENOTCONN || _error == WSAENOTCONN) { | ||||||
|                     this->call_callback_failed("not connected"); |                     this->call_callback_failed(tr("not connected")); | ||||||
|                     this->finalize(false); |                     this->finalize(false, true); | ||||||
| 				} else if(written == 0) { | 				} else if(written == 0) { | ||||||
| 					this->handle_disconnect(); | 					this->handle_disconnect(true); | ||||||
| 				} else { | 				} else { | ||||||
| 					log_error(category::file_transfer, "Encountered write error: {}/{}", _error, strerror(_error)); | 					log_error(category::file_transfer, tr("Encountered write error: {}/{}"), _error, strerror(_error)); | ||||||
| 					this->handle_disconnect(); | 					this->handle_disconnect(true); | ||||||
| 				} | 				} | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| @ -396,14 +394,14 @@ void Transfer::callback_write(short flags) { | |||||||
| 						break; | 						break; | ||||||
| 					} else if(read_status == error::custom) { | 					} else if(read_status == error::custom) { | ||||||
| 						this->call_callback_failed(tr("failed to read from source: ") + error); | 						this->call_callback_failed(tr("failed to read from source: ") + error); | ||||||
| 						this->finalize(false); | 						this->finalize(false, true); | ||||||
| 						return; | 						return; | ||||||
| 					} else if(read_status == error::custom_recoverable) { | 					} else if(read_status == error::custom_recoverable) { | ||||||
| 						log_warn(category::file_transfer, tr("Failed to read from source (but its recoverable): {}"), error); | 						log_warn(category::file_transfer, tr("Failed to read from source (but its recoverable): {}"), error); | ||||||
| 						break; | 						break; | ||||||
| 					} else { | 					} else { | ||||||
| 						log_error(category::file_transfer, tr("invalid source read status ({})"), read_status); | 						log_error(category::file_transfer, tr("invalid source read status ({})"), read_status); | ||||||
| 						this->finalize(false); | 						this->finalize(false, true); | ||||||
| 						return; | 						return; | ||||||
| 					} | 					} | ||||||
| 				} else if(buffer_size == 0) { | 				} else if(buffer_size == 0) { | ||||||
| @ -425,7 +423,7 @@ void Transfer::callback_write(short flags) { | |||||||
| 			if(queue_length == 0) { | 			if(queue_length == 0) { | ||||||
| 				if(source->stream_index() == source->byte_length()) { | 				if(source->stream_index() == source->byte_length()) { | ||||||
| 					this->call_callback_finished(false); | 					this->call_callback_finished(false); | ||||||
| 					this->finalize(false); | 					this->finalize(false, false); | ||||||
| 					return; | 					return; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @ -450,10 +448,7 @@ void Transfer::callback_write(short flags) { | |||||||
| 				timeout.tv_usec = 50000; | 				timeout.tv_usec = 50000; | ||||||
| 			} | 			} | ||||||
| 			event_add(this->event_write, &timeout); | 			event_add(this->event_write, &timeout); | ||||||
| 			this->handle()->execute_event_loop(); |  | ||||||
| 		} | 		} | ||||||
| 	} else { |  | ||||||
| 		log_debug(category::general, tr("No readd")); |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -466,24 +461,24 @@ void Transfer::_write_message(const pipes::buffer_view &buffer) { | |||||||
| 		lock_guard lock(this->event_lock); | 		lock_guard lock(this->event_lock); | ||||||
| 		if(this->event_write) { | 		if(this->event_write) { | ||||||
| 			event_add(this->event_write, nullptr); | 			event_add(this->event_write, nullptr); | ||||||
| 			this->handle()->execute_event_loop(); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Transfer::handle_disconnect() { | void Transfer::handle_disconnect(bool write_disconnect) { | ||||||
| 	if(this->_state != state::DISCONNECTING) { | 	if(this->_state != state::DISCONNECTING) { | ||||||
| 		auto source = dynamic_pointer_cast<TransferSource>(this->_transfer_object); | 		auto source = dynamic_pointer_cast<TransferSource>(this->_transfer_object); | ||||||
| 		auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object); | 		auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object); | ||||||
| 
 | 
 | ||||||
|  | 		auto mode = std::string{write_disconnect ? "write" : "read"}; | ||||||
| 		if(source && source->stream_index() != source->byte_length()) { | 		if(source && source->stream_index() != source->byte_length()) { | ||||||
| 			this->call_callback_failed("received disconnect while transmitting (" + to_string(target->stream_index()) + "/" + to_string(target->expected_length()) +  ")"); | 			this->call_callback_failed("received " + mode + " disconnect while transmitting data (" + to_string(target->stream_index()) + "/" + to_string(target->expected_length()) +  ")"); | ||||||
| 		} else if(target && target->stream_index() != target->expected_length()) { | 		} else if(target && target->stream_index() != target->expected_length()) { | ||||||
| 			this->call_callback_failed("received disconnect while receiving (" + to_string(target->stream_index()) + "/" + to_string(target->expected_length()) +  ")"); | 			this->call_callback_failed("received " + mode + " disconnect while receiving data (" + to_string(target->stream_index()) + "/" + to_string(target->expected_length()) +  ")"); | ||||||
| 		} else | 		} else | ||||||
| 			this->call_callback_finished(false); | 			this->call_callback_finished(false); | ||||||
| 	} | 	} | ||||||
| 	this->finalize(false); | 	this->finalize(false, false); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Transfer::handle_connected() { | void Transfer::handle_connected() { | ||||||
| @ -491,7 +486,6 @@ void Transfer::handle_connected() { | |||||||
| 
 | 
 | ||||||
| 	this->_state = state::CONNECTED; | 	this->_state = state::CONNECTED; | ||||||
| 	event_add(this->event_read, &this->alive_check_timeout); | 	event_add(this->event_read, &this->alive_check_timeout); | ||||||
| 	this->handle()->execute_event_loop(); |  | ||||||
| 
 | 
 | ||||||
| 	this->_write_message(pipes::buffer_view{this->_options->transfer_key.data(), this->_options->transfer_key.length()}); | 	this->_write_message(pipes::buffer_view{this->_options->transfer_key.data(), this->_options->transfer_key.length()}); | ||||||
| 	this->call_callback_connected(); | 	this->call_callback_connected(); | ||||||
| @ -523,8 +517,8 @@ void Transfer::call_callback_process(size_t current, size_t max) { | |||||||
| 		this->callback_process(current, max); | 		this->callback_process(current, max); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| FileTransferManager::FileTransferManager() {} | FileTransferManager::FileTransferManager() = default; | ||||||
| FileTransferManager::~FileTransferManager() {} | FileTransferManager::~FileTransferManager() = default; | ||||||
| 
 | 
 | ||||||
| void FileTransferManager::initialize() { | void FileTransferManager::initialize() { | ||||||
| 	this->event_io_canceled = false; | 	this->event_io_canceled = false; | ||||||
| @ -534,9 +528,9 @@ void FileTransferManager::initialize() { | |||||||
| 
 | 
 | ||||||
| void FileTransferManager::finalize() { | void FileTransferManager::finalize() { | ||||||
| 	this->event_io_canceled = true; | 	this->event_io_canceled = true; | ||||||
| 	this->event_io_condition.notify_all(); | 
 | ||||||
| 	event_base_loopexit(this->event_io, nullptr); | 	event_base_loopbreak(this->event_io); | ||||||
| 	this->event_io_thread.join(); | 	threads::save_join(this->event_io_thread, false); | ||||||
| 
 | 
 | ||||||
| 	//TODO drop all file transfers!
 | 	//TODO drop all file transfers!
 | ||||||
| 	event_base_free(this->event_io); | 	event_base_free(this->event_io); | ||||||
| @ -544,18 +538,8 @@ void FileTransferManager::finalize() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void FileTransferManager::_execute_event_loop() { | void FileTransferManager::_execute_event_loop() { | ||||||
| 	while(!this->event_io_canceled) { |     while(!this->event_io_canceled) | ||||||
| 		this->event_execute = false; |         event_base_loop(this->event_io, EVLOOP_NO_EXIT_ON_EMPTY); | ||||||
| 		event_base_loop(this->event_io, 0); |  | ||||||
| 		if(this->running_transfers().size() > 0) { |  | ||||||
| 			this_thread::sleep_for(milliseconds(50)); |  | ||||||
| 		} else { |  | ||||||
| 			unique_lock lock(this->event_io_lock); |  | ||||||
| 			this->event_io_condition.wait_for(lock, minutes(1), [&]{ |  | ||||||
| 				return this->event_io_canceled || this->event_execute; |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::shared_ptr<Transfer> FileTransferManager::register_transfer(std::string& error, const std::shared_ptr<tc::ft::TransferObject> &object, std::unique_ptr<tc::ft::TransferOptions> options) { | std::shared_ptr<Transfer> FileTransferManager::register_transfer(std::string& error, const std::shared_ptr<tc::ft::TransferObject> &object, std::unique_ptr<tc::ft::TransferOptions> options) { | ||||||
| @ -575,7 +559,7 @@ std::shared_ptr<Transfer> FileTransferManager::register_transfer(std::string& er | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void FileTransferManager::drop_transfer(const std::shared_ptr<Transfer> &transfer) { | void FileTransferManager::drop_transfer(const std::shared_ptr<Transfer> &transfer) { | ||||||
| 	transfer->finalize(true); | 	transfer->finalize(true, true); | ||||||
| 	{ | 	{ | ||||||
| 		lock_guard lock(this->_transfer_lock); | 		lock_guard lock(this->_transfer_lock); | ||||||
| 		auto it = find(this->_running_transfers.begin(), this->_running_transfers.end(), transfer); | 		auto it = find(this->_running_transfers.begin(), this->_running_transfers.end(), transfer); | ||||||
| @ -683,9 +667,10 @@ NAN_METHOD(JSTransfer::NewInstance) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| JSTransfer::JSTransfer(std::shared_ptr<tc::ft::Transfer> transfer) : _transfer(move(transfer)) { | JSTransfer::JSTransfer(std::shared_ptr<tc::ft::Transfer> transfer) : _transfer(move(transfer)) { | ||||||
|  |     log_allocate("JSTransfer", this); | ||||||
| 	this->call_failed = Nan::async_callback([&](std::string error) { | 	this->call_failed = Nan::async_callback([&](std::string error) { | ||||||
| 		Nan::HandleScope scope; | 		Nan::HandleScope scope; | ||||||
| 		this->callback_failed(error); | 		this->callback_failed(std::move(error)); | ||||||
| 	}); | 	}); | ||||||
| 	this->call_finished = Nan::async_callback([&](bool f) { | 	this->call_finished = Nan::async_callback([&](bool f) { | ||||||
| 		Nan::HandleScope scope; | 		Nan::HandleScope scope; | ||||||
| @ -707,11 +692,11 @@ JSTransfer::JSTransfer(std::shared_ptr<tc::ft::Transfer> transfer) : _transfer(m | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| JSTransfer::~JSTransfer() { | JSTransfer::~JSTransfer() { | ||||||
| 	cout << "JS dealloc" << endl; |     log_free("JSTransfer", this); | ||||||
| 	this->_transfer->callback_failed = NULL; | 	this->_transfer->callback_failed = nullptr; | ||||||
| 	this->_transfer->callback_finished = NULL; | 	this->_transfer->callback_finished = nullptr; | ||||||
| 	this->_transfer->callback_start = NULL; | 	this->_transfer->callback_start = nullptr; | ||||||
| 	this->_transfer->callback_process = NULL; | 	this->_transfer->callback_process = nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(JSTransfer::destory_transfer) { | NAN_METHOD(JSTransfer::destory_transfer) { | ||||||
| @ -731,7 +716,6 @@ NAN_METHOD(JSTransfer::start) { | |||||||
| 
 | 
 | ||||||
| 	log_info(category::file_transfer, tr("Connecting to {}:{}"), this->_transfer->options().remote_address, this->_transfer->options().remote_port); | 	log_info(category::file_transfer, tr("Connecting to {}:{}"), this->_transfer->options().remote_address, this->_transfer->options().remote_port); | ||||||
|     info.GetReturnValue().Set(Nan::New<v8::Boolean>(true)); |     info.GetReturnValue().Set(Nan::New<v8::Boolean>(true)); | ||||||
|     return; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_METHOD(JSTransfer::_abort) { | NAN_METHOD(JSTransfer::_abort) { | ||||||
| @ -754,7 +738,7 @@ void JSTransfer::callback_finished(bool flag) { | |||||||
| 
 | 
 | ||||||
| 	v8::Local<v8::Value> arguments[1]; | 	v8::Local<v8::Value> arguments[1]; | ||||||
| 	arguments[0] = Nan::New<v8::Boolean>(flag); | 	arguments[0] = Nan::New<v8::Boolean>(flag); | ||||||
| 	callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments); |     (void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void JSTransfer::callback_start() { | void JSTransfer::callback_start() { | ||||||
| @ -762,7 +746,7 @@ void JSTransfer::callback_start() { | |||||||
| 	if(callback.IsEmpty() || !callback->IsFunction()) | 	if(callback.IsEmpty() || !callback->IsFunction()) | ||||||
| 		return; | 		return; | ||||||
| 
 | 
 | ||||||
| 	callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr); |     (void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void JSTransfer::callback_progress(uint64_t a, uint64_t b) { | void JSTransfer::callback_progress(uint64_t a, uint64_t b) { | ||||||
| @ -773,7 +757,7 @@ void JSTransfer::callback_progress(uint64_t a, uint64_t b) { | |||||||
| 	v8::Local<v8::Value> arguments[2]; | 	v8::Local<v8::Value> arguments[2]; | ||||||
| 	arguments[0] = Nan::New<v8::Number>((uint32_t) a); | 	arguments[0] = Nan::New<v8::Number>((uint32_t) a); | ||||||
| 	arguments[1] = Nan::New<v8::Number>((uint32_t) b); | 	arguments[1] = Nan::New<v8::Number>((uint32_t) b); | ||||||
| 	callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 2, arguments); |     (void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 2, arguments); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void JSTransfer::callback_failed(std::string error) { | void JSTransfer::callback_failed(std::string error) { | ||||||
| @ -786,7 +770,7 @@ void JSTransfer::callback_failed(std::string error) { | |||||||
| 		return; | 		return; | ||||||
| 
 | 
 | ||||||
| 	v8::Local<v8::Value> arguments[1]; | 	v8::Local<v8::Value> arguments[1]; | ||||||
| 	arguments[0] = Nan::New<v8::String>(error).ToLocalChecked(); | 	arguments[0] = Nan::New<v8::String>(std::move(error)).ToLocalChecked(); | ||||||
| 	callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments); |     (void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @ -20,208 +20,200 @@ | |||||||
| 	#include <include/NanEventCallback.h> | 	#include <include/NanEventCallback.h> | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| namespace tc { | namespace tc::ft { | ||||||
| 	namespace ft { |     namespace error { | ||||||
| 		namespace error { |         enum value : int8_t { | ||||||
| 			enum value : int8_t { |             success = 0, | ||||||
| 				success = 0, |             custom = 1, | ||||||
| 				custom = 1, |             custom_recoverable = 2, | ||||||
| 				custom_recoverable = 2, |             would_block = 3, | ||||||
| 				would_block = 3, |             out_of_space = 4 | ||||||
| 				out_of_space = 4 |         }; | ||||||
| 			}; |     } | ||||||
| 		} |     class TransferObject { | ||||||
| 		class TransferObject { |         public: | ||||||
| 			public: |             explicit TransferObject() = default; | ||||||
| 				explicit TransferObject() {} |  | ||||||
| 
 | 
 | ||||||
| 				virtual std::string name() const = 0; |             [[nodiscard]] virtual std::string name() const = 0; | ||||||
| 				virtual bool initialize(std::string& /* error */) = 0; |             virtual bool initialize(std::string& /* error */) = 0; | ||||||
| 				virtual void finalize() = 0; |             virtual void finalize(bool /* aborted */) = 0; | ||||||
| 		}; |     }; | ||||||
| 
 | 
 | ||||||
| 		class TransferSource : public TransferObject { |     class TransferSource : public TransferObject { | ||||||
| 			public: |         public: | ||||||
| 				virtual uint64_t byte_length() const = 0; |             [[nodiscard]] virtual uint64_t byte_length() const = 0; | ||||||
| 				virtual uint64_t stream_index() const = 0; |             [[nodiscard]] virtual uint64_t stream_index() const = 0; | ||||||
| 
 | 
 | ||||||
| 				virtual error::value read_bytes(std::string& /* error */, uint8_t* /* buffer */, uint64_t& /* max length/result length */) = 0; |             virtual error::value read_bytes(std::string& /* error */, uint8_t* /* buffer */, uint64_t& /* max length/result length */) = 0; | ||||||
| 			private: |         private: | ||||||
| 		}; |     }; | ||||||
| 
 | 
 | ||||||
| 		class TransferTarget : public TransferObject { |     class TransferTarget : public TransferObject { | ||||||
| 			public: |         public: | ||||||
| 				TransferTarget() {} |             TransferTarget() = default; | ||||||
| 
 | 
 | ||||||
| 				virtual uint64_t expected_length() const = 0; |             [[nodiscard]] virtual uint64_t expected_length() const = 0; | ||||||
|  |             [[nodiscard]] virtual uint64_t stream_index() const = 0; | ||||||
| 
 | 
 | ||||||
| 				virtual uint64_t stream_index() const = 0; |             virtual error::value write_bytes(std::string& /* error */, uint8_t* /* buffer */, uint64_t /* max length */) = 0; | ||||||
| 				virtual error::value write_bytes(std::string& /* error */, uint8_t* /* buffer */, uint64_t /* max length */) = 0; |     }; | ||||||
| 		}; |  | ||||||
| 
 | 
 | ||||||
| 		struct TransferOptions { |     struct TransferOptions { | ||||||
| 			std::string remote_address; |         std::string remote_address; | ||||||
| 			uint16_t remote_port = 0; |         uint16_t remote_port = 0; | ||||||
| 			std::string transfer_key{}; |         std::string transfer_key{}; | ||||||
| 			uint32_t client_transfer_id = 0; |         uint32_t client_transfer_id = 0; | ||||||
| 			uint32_t server_transfer_id = 0; |         uint32_t server_transfer_id = 0; | ||||||
| 		}; |     }; | ||||||
| 
 | 
 | ||||||
| 		class FileTransferManager; |     class FileTransferManager; | ||||||
| 		class Transfer { |     class Transfer { | ||||||
| 				friend class FileTransferManager; |             friend class FileTransferManager; | ||||||
| 			public: |         public: | ||||||
| 				struct state { |             struct state { | ||||||
| 					enum value { |                 enum value { | ||||||
| 						UNINITIALIZED, |                     UNINITIALIZED, | ||||||
| 						CONNECTING, |                     CONNECTING, | ||||||
| 						CONNECTED, |                     CONNECTED, | ||||||
| 						DISCONNECTING |                     DISCONNECTING | ||||||
| 					}; |                 }; | ||||||
| 				}; |             }; | ||||||
| 				typedef std::function<void()> callback_start_t; |             typedef std::function<void()> callback_start_t; | ||||||
| 				typedef std::function<void(bool /* aborted */)> callback_finished_t; |             typedef std::function<void(bool /* aborted */)> callback_finished_t; | ||||||
| 				typedef std::function<void(const std::string& /* error */)> callback_failed_t; |             typedef std::function<void(const std::string& /* error */)> callback_failed_t; | ||||||
| 				typedef std::function<void(uint64_t /* current index */, uint64_t /* max index */)> callback_process_t; |             typedef std::function<void(uint64_t /* current index */, uint64_t /* max index */)> callback_process_t; | ||||||
| 
 | 
 | ||||||
| 				explicit Transfer(FileTransferManager* handle, std::shared_ptr<TransferObject> transfer_object, std::unique_ptr<TransferOptions> options) : |             explicit Transfer(FileTransferManager* handle, std::shared_ptr<TransferObject> transfer_object, std::unique_ptr<TransferOptions> options) : | ||||||
| 							_transfer_object(std::move(transfer_object)), |                         _transfer_object(std::move(transfer_object)), | ||||||
| 							_handle(handle), |                         _handle(handle), | ||||||
| 							_options(std::move(options)) { |                         _options(std::move(options)) { | ||||||
| 					log_allocate("Transfer", this); |                 log_allocate("Transfer", this); | ||||||
| 				} |             } | ||||||
| 				~Transfer(); |             ~Transfer(); | ||||||
| 
 | 
 | ||||||
| 				bool initialize(std::string& /* error */); |             bool initialize(std::string& /* error */); | ||||||
| 				void finalize(bool /* blocking */ = true); |             void finalize(bool /* blocking */, bool /* aborted */); | ||||||
| 
 | 
 | ||||||
| 				bool connect(); |             bool connect(); | ||||||
| 				bool connected() { return this->_state > state::UNINITIALIZED; } |             bool connected() { return this->_state > state::UNINITIALIZED; } | ||||||
| 
 | 
 | ||||||
| 				FileTransferManager* handle() { return this->_handle; } |             FileTransferManager* handle() { return this->_handle; } | ||||||
| 				std::shared_ptr<TransferObject> transfer_object() { return this->_transfer_object; } |             std::shared_ptr<TransferObject> transfer_object() { return this->_transfer_object; } | ||||||
| 				const TransferOptions& options() { return *this->_options; } |             const TransferOptions& options() { return *this->_options; } | ||||||
| 
 | 
 | ||||||
| 				callback_start_t callback_start{nullptr}; |             callback_start_t callback_start{nullptr}; | ||||||
| 				callback_finished_t callback_finished{nullptr}; |             callback_finished_t callback_finished{nullptr}; | ||||||
| 				callback_failed_t callback_failed{nullptr}; |             callback_failed_t callback_failed{nullptr}; | ||||||
| 				callback_process_t callback_process{nullptr}; |             callback_process_t callback_process{nullptr}; | ||||||
| 			private: |         private: | ||||||
| 				static void _callback_read(evutil_socket_t, short, void*); |             static void _callback_read(evutil_socket_t, short, void*); | ||||||
| 				static void _callback_write(evutil_socket_t, short, void*); |             static void _callback_write(evutil_socket_t, short, void*); | ||||||
| 
 | 
 | ||||||
| 				sockaddr_storage remote_address{}; |             sockaddr_storage remote_address{}; | ||||||
| 				FileTransferManager* _handle; |             FileTransferManager* _handle; | ||||||
| 				std::unique_ptr<TransferOptions> _options; |             std::unique_ptr<TransferOptions> _options; | ||||||
| 				state::value _state = state::UNINITIALIZED; |             state::value _state = state::UNINITIALIZED; | ||||||
| 				std::shared_ptr<TransferObject> _transfer_object; |             std::shared_ptr<TransferObject> _transfer_object; | ||||||
| 
 | 
 | ||||||
| 				std::mutex event_lock; |             std::mutex event_lock; | ||||||
| 				event_base* event_io = nullptr; /* gets assigned by the manager */ |             event_base* event_io = nullptr; /* gets assigned by the manager */ | ||||||
| 				::event* event_read = nullptr; |             ::event* event_read = nullptr; | ||||||
| 				::event* event_write = nullptr; |             ::event* event_write = nullptr; | ||||||
| 
 | 
 | ||||||
| 				std::chrono::system_clock::time_point last_source_read; |             std::chrono::system_clock::time_point last_source_read; | ||||||
| 				std::chrono::system_clock::time_point last_target_write; |             std::chrono::system_clock::time_point last_target_write; | ||||||
| 				std::mutex queue_lock; |             std::mutex queue_lock; | ||||||
| 				std::deque<pipes::buffer> write_queue; |             std::deque<pipes::buffer> write_queue; | ||||||
| 				void _write_message(const pipes::buffer_view& /* buffer */); |             void _write_message(const pipes::buffer_view& /* buffer */); | ||||||
| 				int _socket = 0; |             int _socket = 0; | ||||||
| 
 | 
 | ||||||
| 				timeval alive_check_timeout{1, 0}; |             timeval alive_check_timeout{1, 0}; | ||||||
| 				timeval write_timeout{1, 0}; |             timeval write_timeout{1, 0}; | ||||||
| 
 | 
 | ||||||
| 				/*
 |             /*
 | ||||||
| 				 * Upload mode: |              * Upload mode: | ||||||
| 				 *  Write the buffers left in write_queue, and if the queue length is less then 12 create new buffers. |              *  Write the buffers left in write_queue, and if the queue length is less then 12 create new buffers. | ||||||
| 				 *  This event will as well be triggered every second as timeout, to create new buffers if needed |              *  This event will as well be triggered every second as timeout, to create new buffers if needed | ||||||
| 				 */ |              */ | ||||||
| 				void callback_write(short /* flags */); |             void callback_write(short /* flags */); | ||||||
| 				void callback_read(short /* flags */); |             void callback_read(short /* flags */); | ||||||
| 
 | 
 | ||||||
| 				/* called within the write/read callback */ |             /* called within the write/read callback */ | ||||||
| 				void handle_disconnect(); |             void handle_disconnect(bool /* write disconnect */); | ||||||
| 				void handle_connected(); |             void handle_connected(); | ||||||
| 
 | 
 | ||||||
| 				void call_callback_connected(); |             void call_callback_connected(); | ||||||
| 				void call_callback_failed(const std::string& /* reason */); |             void call_callback_failed(const std::string& /* reason */); | ||||||
| 				void call_callback_finished(bool /* aborted */); |             void call_callback_finished(bool /* aborted */); | ||||||
| 				void call_callback_process(size_t /* current */, size_t /* max */); |             void call_callback_process(size_t /* current */, size_t /* max */); | ||||||
| 
 | 
 | ||||||
| 				std::chrono::system_clock::time_point last_process_call; |             std::chrono::system_clock::time_point last_process_call; | ||||||
| 		}; |     }; | ||||||
| 
 | 
 | ||||||
| 		class FileTransferManager { |     class FileTransferManager { | ||||||
| 			public: |         public: | ||||||
| 				FileTransferManager(); |             FileTransferManager(); | ||||||
| 				~FileTransferManager(); |             ~FileTransferManager(); | ||||||
| 
 | 
 | ||||||
| 				void initialize(); |             void initialize(); | ||||||
| 				void finalize(); |             void finalize(); | ||||||
| 
 | 
 | ||||||
| 				std::shared_ptr<Transfer> register_transfer(std::string& error, const std::shared_ptr<TransferObject>& /* object */, std::unique_ptr<TransferOptions> /* options */); |             std::shared_ptr<Transfer> register_transfer(std::string& error, const std::shared_ptr<TransferObject>& /* object */, std::unique_ptr<TransferOptions> /* options */); | ||||||
| 				std::deque<std::shared_ptr<Transfer>> running_transfers() { |             std::deque<std::shared_ptr<Transfer>> running_transfers() { | ||||||
| 					std::lock_guard lock(this->_transfer_lock); |                 std::lock_guard lock(this->_transfer_lock); | ||||||
| 					return this->_running_transfers; |                 return this->_running_transfers; | ||||||
| 				} |             } | ||||||
| 				void drop_transfer(const std::shared_ptr<Transfer>& /* transfer */); |             void drop_transfer(const std::shared_ptr<Transfer>& /* transfer */); | ||||||
| 				void remove_transfer(Transfer*); /* internal use */ |             void remove_transfer(Transfer*); /* internal use */ | ||||||
| 				inline void execute_event_loop() { |         private: | ||||||
| 					this->event_execute = true; |             bool event_execute = false; | ||||||
| 					this->event_io_condition.notify_all(); |             bool event_io_canceled = false; | ||||||
| 				} |             std::thread event_io_thread; | ||||||
| 			private: |             event_base* event_io = nullptr; | ||||||
| 				bool event_execute = false; |             ::event* event_cleanup = nullptr; | ||||||
| 				bool event_io_canceled = false; |  | ||||||
| 				std::mutex event_io_lock; |  | ||||||
| 				std::condition_variable event_io_condition; |  | ||||||
| 				std::thread event_io_thread; |  | ||||||
| 				event_base* event_io = nullptr; |  | ||||||
| 				::event* event_cleanup = nullptr; |  | ||||||
| 
 | 
 | ||||||
| 				std::mutex _transfer_lock; |             std::mutex _transfer_lock; | ||||||
| 				std::deque<std::shared_ptr<Transfer>> _running_transfers; |             std::deque<std::shared_ptr<Transfer>> _running_transfers; | ||||||
| 
 | 
 | ||||||
| 				void _execute_event_loop(); |             void _execute_event_loop(); | ||||||
| 		}; |     }; | ||||||
| 
 | 
 | ||||||
| #ifdef NODEJS_API | #ifdef NODEJS_API | ||||||
| 		class JSTransfer : public Nan::ObjectWrap { |     class JSTransfer : public Nan::ObjectWrap { | ||||||
| 			public: |         public: | ||||||
| 				static NAN_MODULE_INIT(Init); |             static NAN_MODULE_INIT(Init); | ||||||
| 				static NAN_METHOD(NewInstance); |             static NAN_METHOD(NewInstance); | ||||||
| 
 | 
 | ||||||
| 				static inline Nan::Persistent<v8::Function> & constructor() { |             static inline Nan::Persistent<v8::Function> & constructor() { | ||||||
| 					static Nan::Persistent<v8::Function> my_constructor; |                 static Nan::Persistent<v8::Function> my_constructor; | ||||||
| 					return my_constructor; |                 return my_constructor; | ||||||
| 				} |             } | ||||||
| 
 | 
 | ||||||
| 				explicit JSTransfer(std::shared_ptr<Transfer> transfer); |             explicit JSTransfer(std::shared_ptr<Transfer> transfer); | ||||||
| 				~JSTransfer(); |             ~JSTransfer() override; | ||||||
| 
 | 
 | ||||||
| 				NAN_METHOD(start); |             NAN_METHOD(start); | ||||||
| 				NAN_METHOD(abort); |             NAN_METHOD(abort); | ||||||
| 
 | 
 | ||||||
| 				static NAN_METHOD(destory_transfer); |             static NAN_METHOD(destory_transfer); | ||||||
| 			private: |         private: | ||||||
| 				static NAN_METHOD(_start); |             static NAN_METHOD(_start); | ||||||
| 				static NAN_METHOD(_abort); |             static NAN_METHOD(_abort); | ||||||
| 
 | 
 | ||||||
| 				std::shared_ptr<Transfer> _transfer; |             std::shared_ptr<Transfer> _transfer; | ||||||
| 
 | 
 | ||||||
| 				Nan::callback_t<bool> call_finished; |             Nan::callback_t<bool> call_finished; | ||||||
| 				Nan::callback_t<> call_start; |             Nan::callback_t<> call_start; | ||||||
| 				Nan::callback_t<uint64_t, uint64_t> call_progress; |             Nan::callback_t<uint64_t, uint64_t> call_progress; | ||||||
| 				Nan::callback_t<std::string> call_failed; |             Nan::callback_t<std::string> call_failed; | ||||||
| 
 | 
 | ||||||
| 				void callback_finished(bool); |             void callback_finished(bool); | ||||||
| 				void callback_start(); |             void callback_start(); | ||||||
| 				void callback_progress(uint64_t, uint64_t); |             void callback_progress(uint64_t, uint64_t); | ||||||
| 				void callback_failed(std::string); |             void callback_failed(std::string); | ||||||
| 
 | 
 | ||||||
| 				bool _self_ref = false; |             bool _self_ref = false; | ||||||
| 		}; |     }; | ||||||
| #endif | #endif | ||||||
| 	} |  | ||||||
| } | } | ||||||
| extern tc::ft::FileTransferManager* transfer_manager; | extern tc::ft::FileTransferManager* transfer_manager; | ||||||
| @ -32,7 +32,7 @@ bool TransferJSBufferTarget::initialize(std::string &error) { | |||||||
| 	return true; /* we've already have data */ | 	return true; /* we've already have data */ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void TransferJSBufferTarget::finalize() { } | void TransferJSBufferTarget::finalize(bool) { } | ||||||
| 
 | 
 | ||||||
| uint64_t TransferJSBufferTarget::stream_index() const { | uint64_t TransferJSBufferTarget::stream_index() const { | ||||||
| 	return this->_js_buffer_index; | 	return this->_js_buffer_index; | ||||||
| @ -90,7 +90,7 @@ TransferJSBufferSource::TransferJSBufferSource() { | |||||||
| 
 | 
 | ||||||
| bool TransferJSBufferSource::initialize(std::string &string) { return true; } | bool TransferJSBufferSource::initialize(std::string &string) { return true; } | ||||||
| 
 | 
 | ||||||
| void TransferJSBufferSource::finalize() { } | void TransferJSBufferSource::finalize(bool) { } | ||||||
| 
 | 
 | ||||||
| uint64_t TransferJSBufferSource::stream_index() const { | uint64_t TransferJSBufferSource::stream_index() const { | ||||||
| 	return this->_js_buffer_index; | 	return this->_js_buffer_index; | ||||||
| @ -169,14 +169,14 @@ void TransferObjectWrap::do_wrap(v8::Local<v8::Object> object) { | |||||||
| 	if(source) { | 	if(source) { | ||||||
| 		Nan::Set(object, | 		Nan::Set(object, | ||||||
| 		         Nan::New<v8::String>("total_size").ToLocalChecked(), | 		         Nan::New<v8::String>("total_size").ToLocalChecked(), | ||||||
| 		         Nan::New<v8::Number>((uint32_t) source->byte_length()) | 		         Nan::New<v8::Number>((double) source->byte_length()) | ||||||
| 		); | 		); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(target) { | 	if(target) { | ||||||
| 		Nan::Set(object, | 		Nan::Set(object, | ||||||
| 		         Nan::New<v8::String>("expected_size").ToLocalChecked(), | 		         Nan::New<v8::String>("expected_size").ToLocalChecked(), | ||||||
| 		         Nan::New<v8::Number>((uint32_t) target->expected_length()) | 		         Nan::New<v8::Number>((double) target->expected_length()) | ||||||
| 		); | 		); | ||||||
| 
 | 
 | ||||||
| 	} | 	} | ||||||
| @ -214,8 +214,7 @@ bool TransferFileSource::initialize(std::string &error) { | |||||||
| 	if(!fs::exists(file)) { | 	if(!fs::exists(file)) { | ||||||
| 		error = "file not found"; | 		error = "file not found"; | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} else if(errc) { | ||||||
| 	if(errc) { |  | ||||||
| 		error = "failed to test for file existence: " + to_string(errc.value()) + "/" + errc.message(); | 		error = "failed to test for file existence: " + to_string(errc.value()) + "/" + errc.message(); | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| @ -246,7 +245,7 @@ bool TransferFileSource::initialize(std::string &error) { | |||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void TransferFileSource::finalize() { | void TransferFileSource::finalize(bool) { | ||||||
| 	if(this->file_stream) | 	if(this->file_stream) | ||||||
| 		this->file_stream.close(); | 		this->file_stream.close(); | ||||||
| 
 | 
 | ||||||
| @ -290,10 +289,100 @@ uint64_t TransferFileSource::stream_index() const { | |||||||
| 	return this->position; | 	return this->position; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | TransferFileTarget::TransferFileTarget(std::string path, std::string name, size_t target_size) : path_{std::move(path)}, name_{std::move(name)}, target_file_size{target_size} { | ||||||
|  |     if(!this->path_.empty()) { | ||||||
|  |         if(this->path_.back() == '/') | ||||||
|  |             this->path_.pop_back(); | ||||||
|  | #ifdef WIN32 | ||||||
|  |         if(this->path_.back() == '\\') | ||||||
|  |             this->path_.pop_back(); | ||||||
|  | #endif | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | TransferFileTarget::~TransferFileTarget() = default; | ||||||
|  | 
 | ||||||
|  | bool TransferFileTarget::initialize(std::string &error) { | ||||||
|  |     auto targetFile = fs::u8path(this->path_) / fs::u8path(this->name_); | ||||||
|  |     auto downloadFile = fs::u8path(this->path_) / fs::u8path(this->name_ + ".download"); | ||||||
|  |     log_debug(category::file_transfer, tr("Opening target file for transfer: {}"), downloadFile.string()); | ||||||
|  | 
 | ||||||
|  |     std::error_code fs_error; | ||||||
|  |     if(fs::exists(fs::u8path(this->path_), fs_error)) { | ||||||
|  |         if(fs::exists(downloadFile, fs_error)) { | ||||||
|  |             if(!fs::remove(downloadFile, fs_error) || fs_error) | ||||||
|  |                 log_warn(category::file_transfer, tr("Failed to remove old temporary .download file for {}: {}/{}"), downloadFile.string(), fs_error.value(), fs_error.message()); | ||||||
|  |         } else if(fs_error) { | ||||||
|  |             log_warn(category::file_transfer, tr("Failed to check for temp download file existence at {}: {}/{}"), downloadFile.string(), fs_error.value(), fs_error.message()); | ||||||
|  |         } | ||||||
|  |     } else if(fs_error) { | ||||||
|  |         log_error(category::file_transfer, tr("Failed to check for directory existence at {}: {}/{}"), this->path_, fs_error.value(), fs_error.message()); | ||||||
|  |         error = tr("failed to check for directory existence"); | ||||||
|  |         return false; | ||||||
|  |     } else if(!fs::create_directories(fs::u8path(this->path_), fs_error) || fs_error) { | ||||||
|  |         error = tr("failed to create directories: ") + std::to_string(fs_error.value()) + "/" + fs_error.message(); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(fs::exists(targetFile, fs_error)) { | ||||||
|  |         if(!fs::remove(targetFile, fs_error) || fs_error) { | ||||||
|  |             error = tr("failed to delete old file: ") + std::to_string(fs_error.value()) + "/" + fs_error.message(); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } else if(fs_error) { | ||||||
|  |         log_warn(category::file_transfer, tr("Failed to check for target file existence at {}: {}/{}. Assuming it does not exists."), targetFile.string(), fs_error.value(), fs_error.message()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this->file_stream = std::ofstream{downloadFile, std::ofstream::out | std::ofstream::binary}; | ||||||
|  |     if(!this->file_stream) { | ||||||
|  |         error = tr("file to open file: ") + std::string{strerror(errno)}; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this->position = 0; | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void TransferFileTarget::finalize(bool aborted) { | ||||||
|  |     if(this->file_stream) | ||||||
|  |         this->file_stream.close(); | ||||||
|  | 
 | ||||||
|  |     std::error_code fs_error{}; | ||||||
|  |     auto downloadFile = fs::u8path(this->path_) / fs::u8path(this->name_ + ".download"); | ||||||
|  |     if(aborted) { | ||||||
|  |         if(!fs::remove(downloadFile, fs_error) || fs_error) | ||||||
|  |             log_warn(category::file_transfer, tr("Failed to remove .download file from aborted transfer for {}: {}/{}."), downloadFile.string(), fs_error.value(), fs_error.message()); | ||||||
|  |     } else { | ||||||
|  |         auto target = fs::u8path(this->path_) / fs::u8path(this->name_); | ||||||
|  | 
 | ||||||
|  |         if(this->file_stream) | ||||||
|  |             this->file_stream.close(); | ||||||
|  | 
 | ||||||
|  |         fs::rename(downloadFile, target, fs_error); | ||||||
|  |         if(fs_error) | ||||||
|  |             log_warn(category::file_transfer, tr("Failed to rename file {} to {}: {}/{}"), downloadFile.string(), target.string(), fs_error.value(), fs_error.message()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this->position = 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | error::value TransferFileTarget::write_bytes(std::string &error, uint8_t *buffer, uint64_t length) { | ||||||
|  |     this->file_stream.write((char*) buffer, length); | ||||||
|  |     this->position += length; | ||||||
|  | 
 | ||||||
|  |     if(!this->file_stream) { | ||||||
|  |         if(this->file_stream.eof()) | ||||||
|  |             error = "eof reached"; | ||||||
|  |         else | ||||||
|  |             error = "io error. failed to write"; | ||||||
|  |     } | ||||||
|  |     return error.empty() ? error::success : error::custom; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #ifdef NODEJS_API | #ifdef NODEJS_API | ||||||
| NAN_METHOD(TransferFileSource::create) { | NAN_METHOD(TransferFileSource::create) { | ||||||
| 	if(info.Length() != 2 || !info[0]->IsString() || !info[1]->IsString()) { | 	if(info.Length() != 2 || !info[0]->IsString() || !info[1]->IsString()) { | ||||||
| 		Nan::ThrowError("invalid argument"); | 		Nan::ThrowError("invalid arguments"); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -303,4 +392,17 @@ NAN_METHOD(TransferFileSource::create) { | |||||||
| 	object_wrap->do_wrap(object); | 	object_wrap->do_wrap(object); | ||||||
| 	info.GetReturnValue().Set(object); | 	info.GetReturnValue().Set(object); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | NAN_METHOD(TransferFileTarget::create) { | ||||||
|  | 	if(info.Length() != 3 || !info[0]->IsString() || !info[1]->IsString() || !info[2]->IsNumber()) { | ||||||
|  | 		Nan::ThrowError("invalid arguments"); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto instance = make_shared<TransferFileTarget>(*Nan::Utf8String{info[0]}, *Nan::Utf8String{info[1]}, info[2]->IntegerValue()); | ||||||
|  | 	auto object_wrap = new TransferObjectWrap(instance); | ||||||
|  | 	auto object = Nan::NewInstance(Nan::New(TransferObjectWrap::constructor()), 0, nullptr).ToLocalChecked(); | ||||||
|  | 	object_wrap->do_wrap(object); | ||||||
|  | 	info.GetReturnValue().Set(object); | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
| @ -4,113 +4,137 @@ | |||||||
| #include <optional> | #include <optional> | ||||||
| #include "FileTransferManager.h" | #include "FileTransferManager.h" | ||||||
| 
 | 
 | ||||||
| namespace tc { | namespace tc::ft { | ||||||
| 	namespace ft { |     class TransferFileSource : public TransferSource { | ||||||
| 		class TransferFileSource : public TransferSource { |         public: | ||||||
| 			public: |             TransferFileSource(std::string /* path */, std::string /* name */); | ||||||
| 				TransferFileSource(std::string /* path */, std::string /* name */); |  | ||||||
| 
 | 
 | ||||||
| 				[[nodiscard]] inline std::string file_path() const { return this->_path; } |             [[nodiscard]] inline std::string file_path() const { return this->_path; } | ||||||
| 				[[nodiscard]] inline std::string file_name() const { return this->_name; } |             [[nodiscard]] inline std::string file_name() const { return this->_name; } | ||||||
| 
 | 
 | ||||||
| 				std::string name() const override { return "TransferFileSource"; } |             std::string name() const override { return "TransferFileSource"; } | ||||||
| 				bool initialize(std::string &string) override; |             bool initialize(std::string &string) override; | ||||||
| 				void finalize() override; |             void finalize(bool) override; | ||||||
| 
 | 
 | ||||||
| 				uint64_t byte_length() const override; |             uint64_t byte_length() const override; | ||||||
| 				uint64_t stream_index() const override; |             uint64_t stream_index() const override; | ||||||
| 				error::value read_bytes(std::string &string, uint8_t *uint8, uint64_t &uint64) override; |             error::value read_bytes(std::string &string, uint8_t *uint8, uint64_t &uint64) override; | ||||||
| 
 | 
 | ||||||
| #ifdef NODEJS_API | #ifdef NODEJS_API | ||||||
| 				static NAN_METHOD(create); |             static NAN_METHOD(create); | ||||||
| #endif | #endif | ||||||
| 			private: |         private: | ||||||
| 				std::string _path; |             std::string _path; | ||||||
| 				std::string _name; |             std::string _name; | ||||||
| 
 | 
 | ||||||
| 				uint64_t position{0}; |             uint64_t position{0}; | ||||||
| 				std::ifstream file_stream{}; |             std::ifstream file_stream{}; | ||||||
| 				mutable std::optional<size_t> file_size; |             mutable std::optional<size_t> file_size; | ||||||
| 		}; |     }; | ||||||
|  | 
 | ||||||
|  |     class TransferFileTarget : public TransferTarget { | ||||||
|  |         public: | ||||||
|  |             TransferFileTarget(std::string /* path */, std::string /* name */, size_t /* target size */); | ||||||
|  |             virtual ~TransferFileTarget(); | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] std::string name() const override { return "TransferFileTarget"; } | ||||||
|  |             bool initialize(std::string &string) override; | ||||||
|  | 
 | ||||||
|  |             void finalize(bool) override; | ||||||
|  | 
 | ||||||
|  |             [[nodiscard]] uint64_t stream_index() const override { return this->position; } | ||||||
|  |             [[nodiscard]] uint64_t expected_length() const override { return this->target_file_size; } | ||||||
|  | 
 | ||||||
|  |             error::value write_bytes(std::string &string, uint8_t *uint8, uint64_t uint64) override; | ||||||
| 
 | 
 | ||||||
| #ifdef NODEJS_API | #ifdef NODEJS_API | ||||||
| 		class TransferObjectWrap : public Nan::ObjectWrap { |             static NAN_METHOD(create); | ||||||
| 			public: | #endif | ||||||
| 				static NAN_MODULE_INIT(Init); |         private: | ||||||
| 				static NAN_METHOD(NewInstance); |             std::string path_; | ||||||
| 
 |             std::string name_; | ||||||
| 				static inline bool is_wrap(const v8::Local<v8::Value>& value) { | 
 | ||||||
| 					if(value.As<v8::Object>().IsEmpty()) |             uint64_t position{0}; | ||||||
| 						return false; |             std::ofstream file_stream{}; | ||||||
| 
 |             size_t target_file_size{}; | ||||||
| 					return value->InstanceOf(Nan::GetCurrentContext(), Nan::New<v8::Function>(constructor())).FromMaybe(false); |     }; | ||||||
| 				} | 
 | ||||||
| 
 | #ifdef NODEJS_API | ||||||
| 				static inline Nan::Persistent<v8::Function> & constructor() { |     class TransferObjectWrap : public Nan::ObjectWrap { | ||||||
| 					static Nan::Persistent<v8::Function> my_constructor; |         public: | ||||||
| 					return my_constructor; |             static NAN_MODULE_INIT(Init); | ||||||
| 				} |             static NAN_METHOD(NewInstance); | ||||||
| 
 | 
 | ||||||
| 				explicit TransferObjectWrap(std::shared_ptr<TransferObject> object) :  _transfer(std::move(object)) { |             static inline bool is_wrap(const v8::Local<v8::Value>& value) { | ||||||
| 				} |                 if(value.As<v8::Object>().IsEmpty()) | ||||||
| 
 |                     return false; | ||||||
| 				~TransferObjectWrap() = default; | 
 | ||||||
| 
 |                 return value->InstanceOf(Nan::GetCurrentContext(), Nan::New<v8::Function>(constructor())).FromMaybe(false); | ||||||
| 				void do_wrap(v8::Local<v8::Object> object); |             } | ||||||
| 
 | 
 | ||||||
| 				std::shared_ptr<TransferObject> target() { return this->_transfer; } |             static inline Nan::Persistent<v8::Function> & constructor() { | ||||||
| 			private: |                 static Nan::Persistent<v8::Function> my_constructor; | ||||||
| 				std::shared_ptr<TransferObject> _transfer; |                 return my_constructor; | ||||||
| 		}; |             } | ||||||
| 
 | 
 | ||||||
| 		class TransferJSBufferSource : public TransferSource { |             explicit TransferObjectWrap(std::shared_ptr<TransferObject> object) :  _transfer(std::move(object)) { | ||||||
| 			public: |             } | ||||||
| 				TransferJSBufferSource(); | 
 | ||||||
| 				virtual ~TransferJSBufferSource(); |             ~TransferObjectWrap() override = default; | ||||||
| 
 | 
 | ||||||
| 				std::string name() const override { return "TransferJSBufferSource"; } |             void do_wrap(v8::Local<v8::Object> object); | ||||||
| 				bool initialize(std::string &string) override; | 
 | ||||||
| 
 |             std::shared_ptr<TransferObject> target() { return this->_transfer; } | ||||||
| 				void finalize() override; |         private: | ||||||
| 
 |             std::shared_ptr<TransferObject> _transfer; | ||||||
| 				uint64_t stream_index() const override; |     }; | ||||||
| 
 | 
 | ||||||
| 				uint64_t byte_length() const override; |     class TransferJSBufferSource : public TransferSource { | ||||||
| 
 |         public: | ||||||
| 				error::value read_bytes(std::string &string, uint8_t *uint8, uint64_t &uint64) override; |             TransferJSBufferSource(); | ||||||
| 
 |             virtual ~TransferJSBufferSource(); | ||||||
| 				static NAN_METHOD(create_from_buffer); | 
 | ||||||
| 
 |             [[nodiscard]] std::string name() const override { return "TransferJSBufferSource"; } | ||||||
| 			private: |             bool initialize(std::string &string) override; | ||||||
| 				v8::Global<v8::ArrayBuffer> _js_buffer; | 
 | ||||||
| 				void* _js_buffer_source; |             void finalize(bool) override; | ||||||
| 				uint64_t _js_buffer_length; | 
 | ||||||
| 				uint64_t _js_buffer_index; |             [[nodiscard]] uint64_t stream_index() const override; | ||||||
| 		}; | 
 | ||||||
| 
 |             [[nodiscard]] uint64_t byte_length() const override; | ||||||
| 		class TransferJSBufferTarget : public TransferTarget { | 
 | ||||||
| 			public: |             error::value read_bytes(std::string &string, uint8_t *uint8, uint64_t &uint64) override; | ||||||
| 				TransferJSBufferTarget(); | 
 | ||||||
| 				virtual ~TransferJSBufferTarget(); |             static NAN_METHOD(create_from_buffer); | ||||||
| 
 | 
 | ||||||
| 				std::string name() const override { return "TransferJSBufferTarget"; } |         private: | ||||||
| 				bool initialize(std::string &string) override; |             v8::Global<v8::ArrayBuffer> _js_buffer; | ||||||
| 
 |             void* _js_buffer_source; | ||||||
| 				void finalize() override; |             uint64_t _js_buffer_length; | ||||||
| 
 |             uint64_t _js_buffer_index; | ||||||
| 				uint64_t stream_index() const override; |     }; | ||||||
| 				uint64_t expected_length() const override { return this->_js_buffer_length; } | 
 | ||||||
| 
 |     class TransferJSBufferTarget : public TransferTarget { | ||||||
| 				error::value write_bytes(std::string &string, uint8_t *uint8, uint64_t uint64) override; |         public: | ||||||
| 
 |             TransferJSBufferTarget(); | ||||||
| 				static NAN_METHOD(create_from_buffer); |             virtual ~TransferJSBufferTarget(); | ||||||
| 
 | 
 | ||||||
| 			private: |             [[nodiscard]] std::string name() const override { return "TransferJSBufferTarget"; } | ||||||
| 				v8::Global<v8::ArrayBuffer> _js_buffer; |             bool initialize(std::string &string) override; | ||||||
| 				void* _js_buffer_source; | 
 | ||||||
| 				uint64_t _js_buffer_length; |             void finalize(bool) override; | ||||||
| 				uint64_t _js_buffer_index; | 
 | ||||||
| 		}; |             [[nodiscard]] uint64_t stream_index() const override; | ||||||
|  |             [[nodiscard]] uint64_t expected_length() const override { return this->_js_buffer_length; } | ||||||
|  | 
 | ||||||
|  |             error::value write_bytes(std::string &string, uint8_t *uint8, uint64_t uint64) override; | ||||||
|  | 
 | ||||||
|  |             static NAN_METHOD(create_from_buffer); | ||||||
|  |         private: | ||||||
|  |             v8::Global<v8::ArrayBuffer> _js_buffer; | ||||||
|  |             void* _js_buffer_source; | ||||||
|  |             uint64_t _js_buffer_length; | ||||||
|  |             uint64_t _js_buffer_index; | ||||||
|  |     }; | ||||||
| #endif | #endif | ||||||
| 	} |  | ||||||
| } | } | ||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "TeaClient", |   "name": "TeaClient", | ||||||
|   "version": "1.4.6-2", |   "version": "1.4.7", | ||||||
|   "description": "", |   "description": "", | ||||||
|   "main": "main.js", |   "main": "main.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user