A lot of updates
This commit is contained in:
		
							parent
							
								
									53d3814f92
								
							
						
					
					
						commit
						b956bad3f7
					
				
							
								
								
									
										21
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,21 +0,0 @@ | ||||
| .idea/ | ||||
| .sass-cache/ | ||||
| tmp/ | ||||
| node_modules/ | ||||
| /build/ | ||||
| updater/postzip/test | ||||
| updater/postzip/cmake-build-*/ | ||||
| updater/postzip/.idea | ||||
| updater/postzip/TeaClient-linux.tar.gz | ||||
| 
 | ||||
| #For the main JS file | ||||
| *.js.map | ||||
| *.js | ||||
| 
 | ||||
| .deploy_secret | ||||
| 
 | ||||
| **/*.d.ts | ||||
| ! | ||||
| !modules/renderer/imports/.copy_*.d.ts | ||||
| 
 | ||||
| package-lock.json | ||||
							
								
								
									
										14
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @ -1,14 +0,0 @@ | ||||
| [submodule "native/codec/libraries/opus"] | ||||
| 	path = native/codec/libraries/opus | ||||
| 	url = https://github.com/xiph/opus.git | ||||
| [submodule "native/codec/libraries/speex"] | ||||
| 	path = native/codec/libraries/speex | ||||
| 	url = https://github.com/xiph/speex.git | ||||
| [submodule "native/codec/libraries/celt"] | ||||
| 	path = native/codec/libraries/celt | ||||
| 	url = https://github.com/WolverinDEV/celt-0.11.0.git | ||||
| [submodule "DbConnector"] | ||||
| 	branch = stable | ||||
| [submodule "github"] | ||||
| 	path = github | ||||
| 	url = https://github.com/TeaSpeak/TeaClient | ||||
							
								
								
									
										27
									
								
								bugs
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								bugs
									
									
									
									
									
								
							| @ -1,27 +0,0 @@ | ||||
| Linux: | ||||
|     Updater: | ||||
|         After updater has extracted the file set the executable flag again for the TeaClient binary | ||||
| 
 | ||||
| 
 | ||||
| Windows: | ||||
|     Updater: | ||||
|         Updater popups console which says that there are invalid arguments! Fix this! | ||||
|         Improve access to the update-install.exe (May request admin permissions) | ||||
| 
 | ||||
| 
 | ||||
| General: | ||||
|     Audio replay with TS3 is a bit buggy! | ||||
| 
 | ||||
| 
 | ||||
| Tasks designer: | ||||
|     TeaCup steam animated | ||||
|     Client redesign dark [+ Chat system] | ||||
|     Redesign loading animation (Web) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| Notice: | ||||
| electron-package-manager must be at 8.7.2 (Node 6 support)! | ||||
| 
 | ||||
| 
 | ||||
| FIXME: Test the new voice resampler! | ||||
| @ -1,117 +0,0 @@ | ||||
| #!/usr/bin/env bash | ||||
| 
 | ||||
| BASEDIR=$(dirname "$0") | ||||
| cd "${BASEDIR}" | ||||
| 
 | ||||
| file_paths=( | ||||
|     "$(pwd ~)/../../Web-Client/shared/declarations" | ||||
|     "$(pwd ~)/TeaSpeak/Web-Client/shared/declarations" | ||||
|     "$(pwd ~)/../../TeaSpeak/Web-Client/shared/declarations" | ||||
|     "app/dummy-declarations" | ||||
|     #TODO Windows path | ||||
| ) | ||||
| files=( | ||||
|     "exports_app.d.ts;imports_shared.d.ts" | ||||
|     "exports_loader_app.d.ts;imports_shared_loader.d.ts" | ||||
| #   "exports_loader.d.ts;imports_shared_loader.d.ts" | ||||
| ) | ||||
| 
 | ||||
| support_rel_linking=$(ln --help 2>&1 | grep -e "--relative" >/dev/null && echo "1" || echo "0") | ||||
| support_rel_linking=0 | ||||
| 
 | ||||
| path_target="./modules/renderer/imports" | ||||
| { | ||||
|     mkdir -p "${path_target}" | ||||
| 
 | ||||
|     for path in "${file_paths[@]}" | ||||
|     do | ||||
|         path_found=1 | ||||
|         for file_mapping in "${files[@]}" | ||||
|         do | ||||
|             file_mapping=($(echo ${file_mapping} | tr ";" " ")) | ||||
|             file=${file_mapping[0]} | ||||
| 
 | ||||
|             if [[ ! -f "${path}/${file}" ]]; then | ||||
|                 path_found=0 | ||||
|                 echo "path test ${path} failed to file ${file}" | ||||
|                 break | ||||
|             fi | ||||
|         done | ||||
|         [[ path_found -eq 1 ]] || continue | ||||
| 
 | ||||
|         for file in "${files[@]}" | ||||
|         do | ||||
|             file_mapping=($(echo ${file} | tr ";" "\n")) | ||||
|             src_file=${file_mapping[0]} | ||||
|             dst_file=${file_mapping[1]} | ||||
| 
 | ||||
|             if [[ -e "${path_target}/${dst_file}" ]] || [[ -L "${path_target}/${dst_file}" ]]; then | ||||
|                 rm "${path_target}/${dst_file}" | ||||
|             fi | ||||
| 
 | ||||
| 
 | ||||
|             if [[ ${support_rel_linking} -ne 0 ]]; then | ||||
|                 ln -rs "${path}/${src_file}" "${path_target}/${dst_file}" | ||||
|             else | ||||
|                 _source=$(realpath "${path}/${src_file}") | ||||
|                 _current_dir=$(pwd) | ||||
|                 cd ${path_target} | ||||
|                 [[ $? -ne 0 ]] && { | ||||
|                     echo "Failed to enter target directory" | ||||
|                     exit 1; | ||||
|                 } | ||||
|                 ln -s "${_source}" "${dst_file}" | ||||
|                 cd ${_current_dir} | ||||
|             fi | ||||
|             echo "Linking \"${path_target}/${dst_file}\" to \"${path}/${src_file}\"" | ||||
| 
 | ||||
|             cp "${path}/${src_file}" "${path_target}/.copy_${dst_file}" | ||||
|             echo "Create copy \"${path}/${src_file}\" to \"${path_target}/.copy_${dst_file}\"" | ||||
|         done | ||||
|         break | ||||
|     done | ||||
| } | ||||
| 
 | ||||
| if [[ ${path_found} -eq 0 ]]; then | ||||
|     echo "Could not import a link to shared imports. Trying copied import." | ||||
| 
 | ||||
|     for file in "${files[@]}" | ||||
|     do | ||||
|         file_mapping=($(echo ${file} | tr ";" "\n")) | ||||
|         dst_file=${file_mapping[1]} | ||||
| 
 | ||||
|         if [[ -e "${path_target}/${dst_file}" ]] || [[ -L "${path_target}/${dst_file}" ]]; then | ||||
|             echo "Hmm target file already exists even thou it hasn't been found yet... Deleting it!" | ||||
|             rm "${path_target}/${dst_file}" | ||||
|         fi | ||||
| 
 | ||||
|         if [[ ! -e "${path_target}/.copy_${dst_file}" ]]; then | ||||
|             echo "Missing copy of file ${dst_file} because we cant find any valid link!" | ||||
|             exit 1 | ||||
|         fi | ||||
| 
 | ||||
|         if [[ ${support_rel_linking} -ne 0 ]]; then | ||||
|             ln -rs "${path_target}/.copy_${dst_file}" "${path_target}/${dst_file}" | ||||
|         else | ||||
|             _source=$(realpath "${path_target}/.copy_${dst_file}") | ||||
|             _current_dir=$(pwd) | ||||
|             cd ${path_target} | ||||
|             [[ $? -ne 0 ]] && { | ||||
|                 echo "Failed to enter target directory" | ||||
|                 exit 1; | ||||
|             } | ||||
|             ln -s "${_source}" "${dst_file}" | ||||
|             cd ${_current_dir} | ||||
|         fi | ||||
|         echo "Linking \"${path_target}/${dst_file}\" to \"${path_target}/.copy_${dst_file}\"" | ||||
|     done | ||||
|     path_found=1 | ||||
| fi | ||||
| 
 | ||||
| if [[ path_found -eq 0 ]]; then | ||||
|     echo "Failed to find UI imports" | ||||
|     echo "Add your path to 'file_paths' and build the declarations first" | ||||
|     exit 1 | ||||
| fi | ||||
| 
 | ||||
| exit 0 | ||||
							
								
								
									
										1
									
								
								github
									
									
									
									
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										1
									
								
								github
									
									
									
									
									
								
							| @ -1 +0,0 @@ | ||||
| Subproject commit 14645dca78396c915ad4ad122d532f24fdfd2969 | ||||
| @ -1,44 +0,0 @@ | ||||
| ; Script generated by the Inno Setup Script Wizard. | ||||
| ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! | ||||
| 
 | ||||
| [Setup] | ||||
| ; NOTE: The value of AppId uniquely identifies this application. | ||||
| ; Do not use the same AppId value in installers for other applications. | ||||
| ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) | ||||
| AppId={{0F43B730-DF59-4A23-82AD-E895E72BE4AF} | ||||
| AppName=TeaSpeak Client | ||||
| AppVersion=<%= version %> | ||||
| AppPublisher=TeaSpeak | ||||
| AppPublisherURL=https://www.teaspeak.com/ | ||||
| AppSupportURL=https://www.forum.teaspeak.com/ | ||||
| AppUpdatesURL=https://www.teaspeak.com/ | ||||
| DefaultDirName={pf}\TeaSpeak | ||||
| OutputBaseFilename=<%= executable_name %> | ||||
| OutputDir=<%= dest_dir %> | ||||
| SetupIconFile=<%= icon_file %> | ||||
| Compression=lzma | ||||
| SolidCompression=yes | ||||
| DisableDirPage=no | ||||
| DisableWelcomePage=no | ||||
| 
 | ||||
| [Languages] | ||||
| Name: "english"; MessagesFile: "compiler:Default.isl" | ||||
| 
 | ||||
| [Tasks] | ||||
| Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked | ||||
| Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1 | ||||
| 
 | ||||
| [Files] | ||||
| Source: "<%= source_dir %>"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs | ||||
| 
 | ||||
| 
 | ||||
| ; NOTE: Don't use "Flags: ignoreversion" on any shared system files | ||||
| 
 | ||||
| [Icons] | ||||
| Name: "{commonprograms}\TeaSpeak"; Filename: "{app}\TeaClient.exe" | ||||
| Name: "{commondesktop}\TeaSpeak"; Filename: "{app}\TeaClient.exe"; Tasks: desktopicon | ||||
| Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\TeaSpeak"; Filename: "{app}\TeaClient.exe"; Tasks: quicklaunchicon | ||||
| 
 | ||||
| [Run] | ||||
| Filename: "{app}\TeaClient.exe"; Description: "{cm:LaunchProgram,TeaSpeak}"; Flags: nowait postinstall skipifsilent | ||||
| 
 | ||||
| @ -1,399 +0,0 @@ | ||||
| import {Options} from "electron-packager"; | ||||
| import * as packager from "electron-packager" | ||||
| const pkg = require('../package.json'); | ||||
| const dev_dependencies = Object.keys(pkg.devDependencies); | ||||
| 
 | ||||
| import * as fs from "fs-extra"; | ||||
| import * as path_helper from "path"; | ||||
| import {parse_version} from "../modules/shared/version"; | ||||
| import * as util from "util"; | ||||
| import * as child_process from "child_process"; | ||||
| import * as os from "os"; | ||||
| import * as asar from "asar"; | ||||
| import * as querystring from "querystring"; | ||||
| import request = require("request"); | ||||
| import * as deployer from "./deploy"; | ||||
| 
 | ||||
| let options: Options = {} as any; | ||||
| let version = parse_version(pkg.version); | ||||
| version.timestamp  = Date.now(); | ||||
| 
 | ||||
| options.dir = '.'; | ||||
| options.name = "TeaClient"; | ||||
| options.appVersion = pkg.version; | ||||
| options.appCopyright = "© 2018-2019 Markus Hadenfeldt All Rights Reserved"; | ||||
| options.out = "build/"; | ||||
| 
 | ||||
| if(!pkg.dependencies['electron']) { | ||||
|     console.error("Missing electron version"); | ||||
|     process.exit(1); | ||||
| } | ||||
| 
 | ||||
| options["version-string"] = { | ||||
|     'CompanyName': 'TeaSpeak', | ||||
|     'LegalCopyright': '© 2018-2019 Markus Hadenfeldt All Rights Reserved', | ||||
|     'FileDescription' : 'TeaSpeak-Client', | ||||
|     'OriginalFilename' : 'TeaClient.exe', | ||||
|     'FileVersion' : pkg.version, | ||||
|     'ProductVersion' : pkg.version, | ||||
|     'ProductName' : 'TeaSpeak-Client', | ||||
|     'InternalName' : 'TeaClient.exe' | ||||
| }; | ||||
| options.electronVersion = pkg.dependencies['electron']; | ||||
| options.protocols = [{name: "TeaSpeak - Connect", schemes: ["teaserver"]}]; | ||||
| options.overwrite = true; | ||||
| options.derefSymlinks = true; | ||||
| options.buildVersion = version.toString(true); | ||||
| 
 | ||||
| options.asar = { | ||||
|     unpackDir: "teaclient-unpacked" | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| interface ProjectEntry { | ||||
|     type: ProjectEntryType; | ||||
| } | ||||
| 
 | ||||
| interface ProjectFile extends ProjectEntry { | ||||
|     path: string; | ||||
|     name: string | RegExp; | ||||
| 
 | ||||
|     //target_name?: string | ((file: string) => string);
 | ||||
| } | ||||
| 
 | ||||
| interface ProjectDirectory extends ProjectEntry { | ||||
| 
 | ||||
|     path: string | RegExp; | ||||
|     children?: boolean; | ||||
|     files?: RegExp; | ||||
| } | ||||
| 
 | ||||
| enum ProjectEntryType { | ||||
|     FILE, | ||||
|     DIRECTORY | ||||
| } | ||||
| 
 | ||||
| const project_files: ProjectEntry[] = []; | ||||
| { /* general required files*/ | ||||
|     project_files.push({ | ||||
|         type: ProjectEntryType.FILE, | ||||
|         path: "/", | ||||
|         name: "package.json" | ||||
|     } as ProjectFile); | ||||
|     project_files.push({ | ||||
|         type: ProjectEntryType.FILE, | ||||
|         path: "/", | ||||
|         name: "main.js" | ||||
|     } as ProjectFile); | ||||
| 
 | ||||
|     project_files.push({ | ||||
|         type: ProjectEntryType.DIRECTORY, | ||||
|         path: "/node_modules", | ||||
|         children: true, | ||||
|         files: /\.(js|css|html|node|json)$/ | ||||
|     } as ProjectDirectory); | ||||
| 
 | ||||
|     project_files.push({ | ||||
|         type: ProjectEntryType.DIRECTORY, | ||||
|         path: "/node_modules/electron", | ||||
|         children: true, | ||||
|         files: /.*/ | ||||
|     } as ProjectDirectory); | ||||
| } | ||||
| 
 | ||||
| /* TeaClient modules */ | ||||
| project_files.push({ | ||||
|     type: ProjectEntryType.DIRECTORY, | ||||
|     path: "/modules", | ||||
|     children: true, | ||||
|     files: /.*\.(js|css|html|png|svg)$/ | ||||
| } as ProjectDirectory); | ||||
| 
 | ||||
| /* resource files */ | ||||
| project_files.push({ | ||||
|     type: ProjectEntryType.DIRECTORY, | ||||
|     path: "/resources" | ||||
| } as ProjectDirectory); | ||||
| 
 | ||||
| 
 | ||||
| if (process.argv[2] == "linux") { | ||||
|     options.arch = "x64"; | ||||
|     options.platform = "linux"; | ||||
|     options.icon = "resources/logo.svg"; | ||||
| } else if (process.argv[2] == "win32") { | ||||
|     options.arch = "x64"; | ||||
|     options.platform = "win32"; | ||||
|     options.icon = "resources/logo.ico"; | ||||
| } else { | ||||
|     console.error("Invalid system"); | ||||
|     process.exit(1); | ||||
| } | ||||
| 
 | ||||
| const path_validator = (path: string) => { | ||||
|     path = path.replace(/\\/g,"/"); | ||||
| 
 | ||||
|     const IGNORE = true; | ||||
|     const APPEND = false; | ||||
| 
 | ||||
|     const ppath = path_helper.parse(path); | ||||
|     const is_directory = ppath.ext == '' && fs.statSync(path_helper.join('.', ppath.dir, ppath.name)).isDirectory(); | ||||
|     const directory = (is_directory ? path_helper.join(ppath.dir, ppath.name) : ppath.dir).replace(/\\/g,"/"); | ||||
| 
 | ||||
|     //console.log("Is directory %o => %s", is_directory, directory);
 | ||||
|     //console.dir(ppath);
 | ||||
| 
 | ||||
|     for(const entry of project_files) { | ||||
|         if(entry.type == ProjectEntryType.DIRECTORY) { | ||||
|             const dir_entry = <ProjectDirectory>entry; | ||||
| 
 | ||||
|             if(typeof(dir_entry.path) === 'string') { | ||||
|                 //ppath.dir == dir_entry.path
 | ||||
|                 //console.log("'" + dir_entry.path + "' | '" + directory + "'");
 | ||||
|                 if(dir_entry.path.startsWith(directory)) { | ||||
|                     if(is_directory) | ||||
|                         return APPEND; | ||||
|                 } | ||||
|                 if(directory == dir_entry.path) { | ||||
|                     //console.log("Math: " + ppath.base + " to " + dir_entry.files);
 | ||||
|                     if(dir_entry.files) | ||||
|                         return ppath.base.match(dir_entry.files) ? APPEND : IGNORE; | ||||
|                     return APPEND; | ||||
|                 } | ||||
|                 if(directory.startsWith(dir_entry.path)) { | ||||
|                     if(dir_entry.children) { | ||||
|                         if(is_directory) | ||||
|                             return APPEND; | ||||
| 
 | ||||
|                         const sub_path = directory.substr(dir_entry.path.length); | ||||
|                         //console.log("Sub path: " + sub_path + ". Test: " + (path_helper.join(sub_path, ppath.base) + " against " + dir_entry.files);
 | ||||
| 
 | ||||
|                         if(dir_entry.files) | ||||
|                             return path_helper.join(sub_path, ppath.base).match(dir_entry.files) ? APPEND : IGNORE; | ||||
|                         return APPEND; | ||||
|                     } | ||||
|                     //TODO test for regex
 | ||||
|                 } | ||||
|             } else { | ||||
|                 //TODO
 | ||||
|             } | ||||
|         } else if(entry.type == ProjectEntryType.FILE) { | ||||
|             const file_entry = <ProjectFile>entry; | ||||
|             if(file_entry.path.startsWith(directory) && is_directory) | ||||
|                 return APPEND; | ||||
| 
 | ||||
|             if(directory == file_entry.path) { | ||||
|                 if(typeof(file_entry.name) === 'string' && ppath.base == file_entry.name) | ||||
|                     return APPEND; | ||||
|                 else if (ppath.base.match(file_entry.name)) | ||||
|                     return APPEND; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| }; | ||||
| 
 | ||||
| options.ignore = path => { | ||||
|     if(path.length == 0) return false; //Dont ignore root paths
 | ||||
| 
 | ||||
|     const ignore_path = path_validator(path); | ||||
|     if(!ignore_path) { | ||||
|         console.log(" + " + path); | ||||
|     } | ||||
|     return ignore_path; | ||||
| }; | ||||
| 
 | ||||
| async function copy_striped(source: string, target: string, symbol_directory: string) { | ||||
|     const exec = (command, options) => new Promise<{ stdout: Buffer | string, stderr: Buffer | string}>((resolve, reject) => child_process.exec(command, options, (error, out, err) => error ? reject(error) : resolve({stdout: out, stderr: err}))); | ||||
| 
 | ||||
|     if(process.argv[2] == "win32") { | ||||
|         await fs.copy(source, target); | ||||
|         return; | ||||
|     } | ||||
|     if(process.argv[2] != "linux") throw "invalid target type"; | ||||
| 
 | ||||
|     await fs.copy(source, target); | ||||
| 
 | ||||
|     { | ||||
|         const symbols_command = await exec("dump_syms " + target, { | ||||
|             maxBuffer: 1024 * 1024 * 512 | ||||
|         }); | ||||
|         if(symbols_command.stderr.length != 0) { | ||||
|             console.error("Failed to create sys dump: %o", symbols_command.stderr.toString()); | ||||
|             throw "symbol create failed"; | ||||
|         } | ||||
| 
 | ||||
|         const symbols = symbols_command.stdout.toString(); | ||||
|         const header = symbols.substr(0, symbols.indexOf('\n')); | ||||
|         const binary_name = path_helper.basename(target); | ||||
|         const dump_id = header.split(" ")[3]; | ||||
|         if(binary_name != header.split(" ")[4]) | ||||
|             throw "binary name missmatch"; | ||||
| 
 | ||||
|         const dump_dir = path_helper.join(symbol_directory, binary_name, dump_id); | ||||
|         const dump_file = path_helper.join(dump_dir, binary_name + ".sym"); | ||||
|         if(!(await fs.pathExists(dump_dir))) | ||||
|             await fs.mkdirp(dump_dir); | ||||
|         await fs.ensureDir(dump_dir); | ||||
| 
 | ||||
|         console.log("Writing file to %s", dump_file); | ||||
|         await fs.writeFile(dump_file, symbols); | ||||
|         console.log("Created dump file for binary %s (%s).", binary_name, dump_id); | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         console.log("Striping file"); | ||||
| 
 | ||||
|         //TODO: Keep node module names!
 | ||||
|         const strip_command = await exec("strip -s " + target, { | ||||
|             maxBuffer: 1024 * 1024 * 512 | ||||
|         }); | ||||
|         if(strip_command.stderr.length != 0) { | ||||
|             console.error("Failed to strip binary: %o", strip_command.stderr.toString()); | ||||
|             throw "strip failed"; | ||||
|         } | ||||
| 
 | ||||
|         const nm_command = await exec("nm " + target, { | ||||
|             maxBuffer: 1024 * 1024 * 512 | ||||
|         }); | ||||
|         console.log("File stripped. Symbols left: \n%s", nm_command.stderr.toString().trim() || nm_command.stdout.toString().trim()); | ||||
|     } | ||||
| } | ||||
| async function create_native_addons(target_directory: string, symbol_directory: string) { | ||||
|     let node_source = "native/build/" + os.platform() + "_" + os.arch() + "/"; | ||||
|     console.log("Native addon path: %s", node_source); | ||||
|     await fs.ensureDir(target_directory); | ||||
|     for(const file of await fs.readdir(node_source)) { | ||||
|         if(!file.endsWith(".node")) { | ||||
|             console.warn("Discovered non node file within node file out dir"); | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         await copy_striped(path_helper.join(node_source, file), path_helper.join(target_directory, file), symbol_directory); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| interface UIVersion { | ||||
|     channel: string; | ||||
|     version: string; | ||||
|     git_hash: string; | ||||
|     timestamp: number; | ||||
| 
 | ||||
|     required_client?: string; | ||||
|     filename?: string; | ||||
| } | ||||
| 
 | ||||
| async function create_default_ui_pack(target_directory: string) { | ||||
|     const remote_url = "https://clientapi.teaspeak.de/"; | ||||
|     const channel = "release"; | ||||
| 
 | ||||
|     const file = path_helper.join(target_directory, "default_ui.tar.gz"); | ||||
|     console.log("Creating default UI pack. Downloading from %s (channel: %s)", remote_url, channel); | ||||
|     await fs.ensureDir(target_directory); | ||||
| 
 | ||||
|     let ui_info: UIVersion; | ||||
|     await new Promise((resolve, reject) => { | ||||
|         request.get(remote_url + "api.php?" + querystring.stringify({ | ||||
|             type: "ui-download", | ||||
|             channel: channel, | ||||
|             version: "latest" | ||||
|         }), { | ||||
|             timeout: 5000 | ||||
|         }).on('response', function(response) { | ||||
|             if(response.statusCode != 200) | ||||
|                 reject("Failed to download UI files (Status code " + response.statusCode + ")"); | ||||
| 
 | ||||
|             ui_info = { | ||||
|                 channel: channel, | ||||
|                 version: response.headers["x-ui-version"] as string, | ||||
|                 git_hash: response.headers["x-ui-git-ref"] as string, | ||||
|                 required_client: response.headers["x-ui-required_client"] as string, | ||||
|                 timestamp: parseInt(response.headers["x-ui-timestamp"] as string), | ||||
|                 filename: path_helper.basename(file) | ||||
|             } | ||||
|         }).on('error', error => { | ||||
|             reject("Failed to download UI files: " + error); | ||||
|         }).pipe(fs.createWriteStream(file)).on('finish', resolve); | ||||
|     }); | ||||
| 
 | ||||
|     if(!ui_info) | ||||
|         throw "failed to generate ui info!"; | ||||
| 
 | ||||
|     await fs.writeJson(path_helper.join(target_directory, "default_ui_info.json"), ui_info); | ||||
|     console.log("UI-Pack downloaded!"); | ||||
| } | ||||
| 
 | ||||
| let path; | ||||
| new Promise((resolve, reject) => packager(options, (err, appPaths) => err ? reject(err) : resolve(appPaths))).then(async app_paths => { | ||||
|     console.log("Copying changelog file!"); | ||||
|     /* We dont have promisify in our build system */ | ||||
|     await fs.copy(path_helper.join(options.dir, "github", "ChangeLog.txt"), path_helper.join(app_paths[0], "ChangeLog.txt")); | ||||
|     return app_paths; | ||||
| }).then(async app_paths => { | ||||
|     await create_native_addons(path_helper.join(app_paths[0], "resources", "natives"), "build/symbols"); | ||||
|     return app_paths; | ||||
| }).then(async app_paths => { | ||||
|     await create_default_ui_pack(path_helper.join(app_paths[0], "resources", "ui")); | ||||
|     return app_paths; | ||||
| }).then(async appPaths => { | ||||
|     ///native/build/linux_amd64
 | ||||
|     path = appPaths[0]; | ||||
|     if(process.argv[2] == "linux") { | ||||
|         await copy_striped(options.dir + "/native/build/exe/update-installer", path + "/update-installer", "build/symbols"); | ||||
|     } else if (process.argv[2] == "win32") { | ||||
|         await copy_striped(options.dir + "/native/build/exe/update-installer.exe", path + "/update-installer.exe", "build/symbols"); | ||||
|     } | ||||
|     await fs.writeJson(path + "/app_version.json", { | ||||
|         version: version.toString(true), | ||||
|         timestamp: version.timestamp | ||||
|     }); | ||||
|     return appPaths; | ||||
| }).then(async app_path => { | ||||
|     console.log("Fixing versions file"); | ||||
|     let version = await fs.readFile(path_helper.join(app_path[0], "version"), 'UTF-8'); | ||||
|     if(!version.startsWith("v")) | ||||
|         version = "v" + version; | ||||
|     await fs.writeFile(path_helper.join(app_path[0], "version"), version); | ||||
|     return app_path; | ||||
| }).then(async () => { | ||||
|     if(process.argv[2] == "win32") { | ||||
|         console.log("Installing local PDB files"); | ||||
| 
 | ||||
|         const symbol_binary_path = "native/build/" + os.platform() + "_" + os.arch() + "/"; | ||||
|         const symbol_pdb_path =  "native/build/symbols/"; | ||||
|         const symbol_server_path = path_helper.join(__dirname, "..", "native", "build", "symbol-server"); | ||||
| 
 | ||||
|         const files = []; | ||||
|         for(const file of await fs.readdir(symbol_binary_path)) { | ||||
|             console.error(file); | ||||
|             if(!file.endsWith(".node")) | ||||
|                 continue; | ||||
|             let file_name = path_helper.basename(file); | ||||
|             if(file_name.endsWith(".node")) | ||||
|                 file_name = file_name.substr(0, file_name.length - 5); | ||||
|             const binary_path = path_helper.join(symbol_binary_path, file); | ||||
|             const pdb_path = path_helper.join(symbol_pdb_path, file_name + ".pdb"); | ||||
|             if(!fs.existsSync(pdb_path)) { | ||||
|                 console.warn("Missing PDB file for binary %s", file); | ||||
|                 continue; | ||||
|             } | ||||
|             files.push({ | ||||
|                 binary: binary_path, | ||||
|                 pdb: pdb_path | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         console.log("Gathered %d files", files.length); | ||||
|         await deployer.deploy_win_dbg_files(files, version, symbol_server_path); | ||||
|         console.log("PDB files deployed"); | ||||
|     } | ||||
| }).then(() => { | ||||
|     console.log("Package created"); | ||||
|     process.exit(0); | ||||
| }).catch(error => { | ||||
|     console.error(error); | ||||
|     console.error("Failed to create package!"); | ||||
|     process.exit(1); | ||||
| }); | ||||
| 
 | ||||
| export {} | ||||
| @ -1,143 +0,0 @@ | ||||
| import * as fs from "fs"; | ||||
| import * as path from "path"; | ||||
| import * as _node_ssh from "node-ssh"; | ||||
| import * as ssh2 from "ssh2"; | ||||
| import * as util from "util"; | ||||
| import * as crypto from "crypto"; | ||||
| 
 | ||||
| declare namespace node_ssh { | ||||
|     export type PutFilesOptions = { | ||||
|         sftp?: Object, | ||||
|         sftpOptions?: Object, | ||||
|         concurrency?: number, | ||||
|     } | ||||
|     export type PutDirectoryOptions = { | ||||
|         sftp?: Object, | ||||
|         sftpOptions?: Object, | ||||
|         concurrency?: number, | ||||
|         recursive?: boolean, | ||||
|         tick?: ((localPath: string, remotePath: string, error?: Error) => void), | ||||
|         validate?: ((localPath: string) => boolean), | ||||
|     } | ||||
|     export type ExecOptions = { | ||||
|         cwd?: string, | ||||
|         options?: Object // passed to ssh2.exec
 | ||||
|         stdin?: string, | ||||
|         stream?: 'stdout' | 'stderr' | 'both', | ||||
|         onStdout?: ((chunk: Buffer) => void), | ||||
|         onStderr?: ((chunk: Buffer) => void), | ||||
|     } | ||||
| 
 | ||||
|     export class Instance { | ||||
|         connect(config: ssh2.ConnectConfig): Promise<this> | ||||
|         requestSFTP(): Promise<ssh2.SFTPWrapper> | ||||
|         requestShell(): Promise<ssh2.ClientChannel> | ||||
|         mkdir(path: string, method: 'sftp' | 'exec', givenSftp?: Object): Promise<string> | ||||
|         exec(command: string, parameters: Array<string>, options: ExecOptions): Promise<Object | string> | ||||
|         execCommand(command: string, options?: { cwd: string, stdin: string }): Promise<{ stdout: string, options?: Object, stderr: string, signal?: string, code: number }> | ||||
|         putFile(localFile: string, remoteFile: string, sftp?: Object, opts?: Object): Promise<void> | ||||
|         getFile(localFile: string, remoteFile: string, sftp?: Object, opts?: Object): Promise<void> | ||||
|         putFiles(files: Array<{ local: string, remote: string }>, options: PutFilesOptions): Promise<void> | ||||
|         putDirectory(localDirectory: string, remoteDirectory: string, options: PutDirectoryOptions): Promise<boolean> | ||||
|         dispose(): void | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| let instance: node_ssh.Instance; | ||||
| export async function setup() { | ||||
|     if(instance) | ||||
|         throw "already initiaized"; | ||||
|     instance = new _node_ssh(); | ||||
|     try { | ||||
|         await instance.connect({ | ||||
|             host: 'deploy.teaspeak.de', | ||||
|             username: 'TeaSpeak-Jenkins-Client', | ||||
|             privateKey: path.join(__dirname, "ssh_key") | ||||
|         }) | ||||
|     } catch(error) { | ||||
|         try { instance.dispose(); } finally { instance = undefined; } | ||||
|         console.error("Failed to connect: %o", error); | ||||
|         throw "failed to connect"; | ||||
|     } | ||||
| } | ||||
| async function update_remote_file(local_path: string, remote_path: string) { | ||||
|     if(!instance) | ||||
|         throw "Invalid instance"; | ||||
| 
 | ||||
|     let sftp: ssh2.SFTPWrapper; | ||||
|     try { | ||||
|         let sha512_remote, sha512_local; | ||||
|         try { | ||||
|             const sha512_remote_result = await instance.execCommand('sha512sum ' + remote_path); | ||||
|             if(sha512_remote_result.code != 0) | ||||
|                 sha512_remote = undefined; /* file does not exists! */ | ||||
|             else { | ||||
|                 const result = sha512_remote_result.stdout.toString(); | ||||
|                 sha512_remote = result.split(" ")[0]; | ||||
|                 //console.log("File %s has a remote sha512: %o", remote_path, sha512_remote);
 | ||||
|             } | ||||
|         } catch(error) { | ||||
|             console.log("Failed to calculate remote sha521 for file %s: %o", remote_path, error); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if(sha512_remote) { /* if the remote hasn't the file then we've def a "new" version */ | ||||
|             const hash_processor = crypto.createHash('sha512'); | ||||
|             const local_stream = fs.createReadStream(local_path); | ||||
| 
 | ||||
|             await new Promise((resolve, reject) => { | ||||
|                 local_stream.on('error', reject); | ||||
|                 local_stream.on('data', chunk => hash_processor.update(chunk)); | ||||
|                 local_stream.on('end', resolve); | ||||
|             }); | ||||
|             sha512_local = hash_processor.digest('hex'); | ||||
|             local_stream.close(); | ||||
|         } | ||||
| 
 | ||||
|         if(sha512_remote) { | ||||
|             if(sha512_remote == sha512_local) { | ||||
|                 console.log("File %s (%s) is already up to date.", path.basename(local_path), local_path); | ||||
|                 return; | ||||
|             } else { | ||||
|                 console.log("Updating file %s (%s) at %s. Local sum: %s Remote sum: %s", path.basename(local_path), local_path, remote_path, sha512_local, sha512_remote); | ||||
|             } | ||||
|         } else { | ||||
|             console.log("Uploading file %s (%s) to %s.", path.basename(local_path), local_path, remote_path); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         try { | ||||
|             await instance.putFile(local_path, remote_path); | ||||
|         } catch(error) { | ||||
|             console.error("Failed to upload file %s (%s): %s", path.basename(local_path), local_path, error); | ||||
|             throw "Upload failed"; | ||||
|         } | ||||
|     } finally { | ||||
|         if(sftp) | ||||
|             sftp.end(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function deploy_crash_dumps(local_path: string, remote_path: string) { | ||||
|     console.log("Uploading crash dumps from %s to %s", local_path, remote_path); | ||||
| 
 | ||||
|     const do_dir = async (local_path, remote_path) => { | ||||
|         for(const file of await util.promisify(fs.readdir)(local_path)) { | ||||
|             const local_file = path.join(local_path, file); | ||||
|             const remote_file = remote_path + "/" + file; | ||||
| 
 | ||||
|             if((await util.promisify(fs.stat)(local_file)).isDirectory()) | ||||
|                 await do_dir(local_file, remote_file); | ||||
|             else | ||||
|                 await update_remote_file(local_file, remote_file); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     await do_dir(local_path, remote_path); | ||||
| } | ||||
| 
 | ||||
| const test = async () => { | ||||
|     await setup(); | ||||
|     await deploy_crash_dumps(path.join(__dirname, "../../build/symbols/"), "symbols"); | ||||
| }; | ||||
| test(); | ||||
| @ -1,3 +0,0 @@ | ||||
| ```$bash | ||||
| ssh-keygen -t rsa -b 4096 -f ssh_key -N '' | ||||
| ``` | ||||
| @ -1,239 +0,0 @@ | ||||
| import * as fs from "fs"; | ||||
| import * as path from "path"; | ||||
| import * as _node_ssh from "node-ssh"; | ||||
| import * as ssh2 from "ssh2"; | ||||
| import * as stream from "stream"; | ||||
| import * as child_process from "child_process"; | ||||
| import * as util from "util"; | ||||
| import {FileEntry} from "ssh2-streams"; | ||||
| 
 | ||||
| declare namespace node_ssh { | ||||
|     export type PutFilesOptions = { | ||||
|         sftp?: Object, | ||||
|         sftpOptions?: Object, | ||||
|         concurrency?: number, | ||||
|     } | ||||
|     export type PutDirectoryOptions = { | ||||
|         sftp?: Object, | ||||
|         sftpOptions?: Object, | ||||
|         concurrency?: number, | ||||
|         recursive?: boolean, | ||||
|         tick?: ((localPath: string, remotePath: string, error?: Error) => void), | ||||
|         validate?: ((localPath: string) => boolean), | ||||
|     } | ||||
|     export type ExecOptions = { | ||||
|         cwd?: string, | ||||
|         options?: Object // passed to ssh2.exec
 | ||||
|         stdin?: string, | ||||
|         stream?: 'stdout' | 'stderr' | 'both', | ||||
|         onStdout?: ((chunk: Buffer) => void), | ||||
|         onStderr?: ((chunk: Buffer) => void), | ||||
|     } | ||||
| 
 | ||||
|     export class Instance { | ||||
|         connect(config: ssh2.ConnectConfig): Promise<this> | ||||
|         requestSFTP(): Promise<ssh2.SFTPWrapper> | ||||
|         requestShell(): Promise<ssh2.ClientChannel> | ||||
|         mkdir(path: string, method: 'sftp' | 'exec', givenSftp?: Object): Promise<string> | ||||
|         exec(command: string, parameters: Array<string>, options: ExecOptions): Promise<Object | string> | ||||
|         execCommand(command: string, options?: { cwd: string, stdin: string }): Promise<{ stdout: string, options?: Object, stderr: string, signal?: string, code: number }> | ||||
|         putFile(localFile: string, remoteFile: string, sftp?: Object, opts?: Object): Promise<void> | ||||
|         getFile(localFile: string, remoteFile: string, sftp?: Object, opts?: Object): Promise<void> | ||||
|         putFiles(files: Array<{ local: string, remote: string }>, options: PutFilesOptions): Promise<void> | ||||
|         putDirectory(localDirectory: string, remoteDirectory: string, options: PutDirectoryOptions): Promise<boolean> | ||||
|         dispose(): void | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| let instance: node_ssh.Instance; | ||||
| export async function setup() { | ||||
|     if(instance) | ||||
|         throw "already initiaized"; | ||||
|     instance = new _node_ssh(); | ||||
|     try { | ||||
|         await instance.connect({ | ||||
|             host: 'deploy.teaspeak.de', | ||||
|             username: 'TeaSpeak-Jenkins-Client', | ||||
|             privateKey: path.join(__dirname, "ssh_key") | ||||
|         }) | ||||
|     } catch(error) { | ||||
|         try { instance.dispose(); } finally { instance = undefined; } | ||||
|         console.error("Failed to connect: %o", error); | ||||
|         throw "failed to connect"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function read_stream_full(stream: stream.Readable) : Promise<Buffer> { | ||||
|     return new Promise((resolve, reject) => { | ||||
|         const buffers = []; | ||||
|         stream.on('data', buffer => buffers.push(buffer)); | ||||
|         stream.on('end', () => resolve(Buffer.concat(buffers))); | ||||
|         stream.on('error', error => reject(error)); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export type PlatformSpecs = { | ||||
|     system: 'linux' | 'windows' | 'osx'; | ||||
|     arch: 'amd64' | 'x86'; | ||||
|     type: 'indev' | 'debug' | 'optimized' | 'stable'; | ||||
| } | ||||
| 
 | ||||
| export type Version = { | ||||
|     major: number; | ||||
|     minor: number; | ||||
|     patch: number; | ||||
|     type?: 'indev' | 'beta'; | ||||
| } | ||||
| 
 | ||||
| //<system>/<arch>_<type>/
 | ||||
| function platform_path(platform: PlatformSpecs) { | ||||
|     return platform.system + "/" + platform.arch + "_" + platform.type + "/"; | ||||
| } | ||||
| 
 | ||||
| function version_string(version: Version) { | ||||
|     return version.major + "." + version.minor + "." + version.patch + (version.type ? "-" + version.type : ""); | ||||
| } | ||||
| 
 | ||||
| export async function latest_version(platform: PlatformSpecs) { | ||||
|     const path = "versions/" + platform_path(platform); | ||||
|     if(!instance) | ||||
|         throw "Invalid instance"; | ||||
|     const sftp = await instance.requestSFTP(); | ||||
|     try { | ||||
|         if(!sftp) | ||||
|             throw "failed to request sftp"; | ||||
| 
 | ||||
|         try { | ||||
|             const data_stream = sftp.createReadStream(path + "latest"); | ||||
|             const data = await read_stream_full(data_stream); | ||||
|             return data.toString(); | ||||
|         } catch(error) { | ||||
|             if(error instanceof Error && error.message == "No such file") | ||||
|                 return undefined; | ||||
| 
 | ||||
|             console.log("Failed to receive last version: %o", error); | ||||
|             return undefined; | ||||
|         } | ||||
|     } finally { | ||||
|         if(sftp) | ||||
|             sftp.end(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function generate_build_index(platform: PlatformSpecs, version: Version) : Promise<number> { | ||||
|     const path = "versions/" + platform_path(platform); | ||||
|     const version_str = version_string(version); | ||||
|     if(!instance) | ||||
|         throw "Invalid instance"; | ||||
|     const sftp = await instance.requestSFTP(); | ||||
|     try { | ||||
|         if(!sftp) | ||||
|             throw "failed to request sftp"; | ||||
| 
 | ||||
|         try { | ||||
|             const files = await new Promise<FileEntry[]>((resolve, reject) => sftp.readdir(path, (error, result) => error ? reject(error) : resolve(result))); | ||||
|             const version_files = files.filter(e => e.filename.startsWith(version_str)); | ||||
|             if(version_files.length == 0) | ||||
|                 return 0; | ||||
|             let index = 1; | ||||
|             while(version_files.find(e => e.filename.toLowerCase() === version_str + "-" + index)) index++; | ||||
|             return index; | ||||
|         } catch(error) { | ||||
|             if(error instanceof Error && error.message == "No such file") | ||||
|                 return 0; | ||||
| 
 | ||||
|             console.log("Failed to receive versions list: %o", error); | ||||
|             return undefined; | ||||
|         } | ||||
|     } finally { | ||||
|         if(sftp) | ||||
|             sftp.end(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export type WinDbgFile = { | ||||
|     binary: string, | ||||
|     pdb: string; | ||||
| }; | ||||
| export async function deploy_win_dbg_files(files: WinDbgFile[], version: Version, path?: string) : Promise<void> { | ||||
|     //symstore add /r /f .\*.node /s \\deploy.teaspeak.de\symbols /t "TeaClient-Windows-amd64" /v "x.y.z"
 | ||||
|     //symstore add /r /f .\*.* /s \\deploy.teaspeak.de\symbols /t "TeaClient-Windows-amd64" /v "1.0.0"
 | ||||
|     const server_path = typeof(path) === "string" && path ? path : "\\\\deploy.teaspeak.de\\symbols\\symbols"; | ||||
|     const vstring = version_string(version); | ||||
|     const exec = util.promisify(child_process.exec); | ||||
|     for(const file of files) { | ||||
|         console.log("Deploying %s to %s", file, server_path); | ||||
|         let current_file; | ||||
|         try { | ||||
|             { | ||||
|                 const result = await exec("symstore add /r /f " + file.binary + " /s " + server_path + " /t \"TeaClient-Windows-amd64\" /v \"" + vstring + "\""); | ||||
|                 if(result.stdout) | ||||
|                     console.log("Stdout: %s", result.stdout); | ||||
|                 if(result.stderr) | ||||
|                     console.log("Stderr: %s", result.stderr); | ||||
|             } | ||||
|             { | ||||
|                 const result = await exec("symstore add /r /f " + file.pdb + " /s " + server_path + " /t \"TeaClient-Windows-amd64\" /v \"" + vstring + "\""); | ||||
|                 if(result.stdout) | ||||
|                     console.log("Stdout: %s", result.stdout); | ||||
|                 if(result.stderr) | ||||
|                     console.log("Stderr: %s", result.stderr); | ||||
|             } | ||||
|         } catch(error) { | ||||
|             if('killed' in error && 'code' in error) { | ||||
|                 const perror: { | ||||
|                     killed: boolean, | ||||
|                     code: number, | ||||
|                     signal: any, | ||||
|                     cmd: string, | ||||
|                     stdout: string, | ||||
|                     stderr: string | ||||
|                 } = error; | ||||
|                 console.error("Failed to deploy %s file %s:", current_file, file); | ||||
|                 console.log("  Code: %d", perror.code); | ||||
|                 { | ||||
|                     console.error("  Stdout: "); | ||||
|                     for(const element of perror.stdout.split("\n")) | ||||
|                         console.error("    %s", element); | ||||
|                 } | ||||
|                 { | ||||
|                     console.error("  Stderr: "); | ||||
|                     for(const element of perror.stderr.split("\n")) | ||||
|                         console.error("    %s", element); | ||||
|                 } | ||||
|             } else | ||||
|                 console.error("Failed to deploy %s file %s: %o", current_file, file, error); | ||||
|             throw "deploy failed"; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const test = async () => { | ||||
|     await setup(); | ||||
|     console.log(await latest_version({ | ||||
|         arch: 'amd64', | ||||
|         system: 'linux', | ||||
|         type: 'optimized' | ||||
|     })); | ||||
|     console.log(await generate_build_index({ | ||||
|         arch: 'amd64', | ||||
|         system: 'linux', | ||||
|         type: 'optimized' | ||||
|     }, { | ||||
|         type: 'beta', | ||||
|         patch: 19, | ||||
|         minor: 3, | ||||
|         major: 1 | ||||
|     })); | ||||
|     /* | ||||
|     console.log(await deploy_pdb_files( | ||||
|         [path.join(__dirname, "..", "..", "native", "build", "symbols", "teaclient_crash_handler.pdb")], { | ||||
|             type: 'beta', | ||||
|             patch: 19, | ||||
|             minor: 3, | ||||
|             major: 1 | ||||
|         } | ||||
|     )) | ||||
|     */ | ||||
| }; | ||||
| test(); | ||||
| @ -1,24 +0,0 @@ | ||||
| @echo off | ||||
| 
 | ||||
| set drive=T: | ||||
| 
 | ||||
| 
 | ||||
| net use z: /dele > nul | ||||
| if %ERRORLEVEL% neq 0 ( | ||||
| 	if %ERRORLEVEL% neq 2 ( | ||||
| 	   echo Failed to unmount old drive [%drive%\]: %errorlevel%. | ||||
| 	   exit /b %errorlevel% | ||||
| 	) else ( | ||||
| 		echo Drive [%drive%\] hasn't been mapped. Mapping device! | ||||
| 	) | ||||
| ) else ( | ||||
| 	echo Unmounted old device. Mapping new device! | ||||
| ) | ||||
| rem  /persistent:yes | ||||
| net use z: \\deploy.teaspeak.de\symbols T4j7CTADvCMev4Kr /user:deploy.teaspeak.de\TeaSpeak-Jenkins-Client | ||||
| if %errorlevel% neq 0 ( | ||||
|    echo Failed to mount drive [%drive%\]: %errorlevel% | ||||
|    exit /b %errorlevel% | ||||
| ) else ( | ||||
| 	echo New device has been mapped successfully! | ||||
| ) | ||||
| @ -1,51 +0,0 @@ | ||||
| -----BEGIN RSA PRIVATE KEY----- | ||||
| MIIJKAIBAAKCAgEAnr2tvPgMv9uLdFFHCV6sX9uUZyK57cQNHLSVK/OGjdilf95q | ||||
| RMjI8AkYxM0nc9ljnVNPuhSYWSASTCeCPaBS/VkvUn3krEJQw1Z9GPOWbZo0vLbE | ||||
| LzhZ8uyCgdOZSuWJpHui4Tchz10pxcKACi5wL4MFaouoJUcGX1MgFJ1Dcd/BxuVj | ||||
| 1hKrOYLBJy1cDuocmP4aTu8mgtzluRMieTjH1fRq7SAbJDx5RmzsEy5S6+6XDuLq | ||||
| uidXbsoM3GEYhcIHjtBTcbKVsOLmelqR/byGUSFP0tmOLcq85nGuOf0Uccb1DuKA | ||||
| RDZ53OfsGus2/Gbb0z9nipOtEkP6abgvhas3VNWI+JQ+YhLnwfBPy7hNytQFVN5q | ||||
| Q0+HaQ1SXdp+Ze3moKPqCHXrlbCvk8FpQoGhzEIteP/xq6DPeHfHZ/T7mH6T8JzF | ||||
| H3HfGuAfaDMk6f1dg5jHs1L+vaWtWI3SZohj/LeN31TQsPJJbQ9ltK2Eu1hsy9mY | ||||
| BAXv39yR+os1gpsx5imIQfVMFQ5Fg8W2lNyXjn2S/zWfY76G0vEDOomE4BhMjd2b | ||||
| Tn2ZzpAba9bPfgmyv6Kjo4mDXv+qVddHzsivKaV3u7bTAYZMpekcEDs7wLPHKC6+ | ||||
| Ca4Z0jXXEtwAeqW/wqop3Hi5gNruj4+FEWsRvC/431AxXnmyo1WEenx27o0CAwEA | ||||
| AQKCAgBvKjfsGO2cwhuj5nNPzXv7WSNRIpGAP4ZLLu80K0N0PF6R8GkNKGsHJeex | ||||
| klXpnDhVaY1wq5GRAJOvvw9HJupXP0iThVRJidtTIFNU0OjsckwyR++VfeoH47b1 | ||||
| QgCc4agFhwummlxUAlMJic7u0lx/+UomtgyXpmiBAw55QTSFH4RtTCEhPkuoZ7fq | ||||
| Pqq0SyChx3kXBAU9KYK6m/rNV4UigRsMWiqHss3fEtI0EIHDdX9VznVAzeI91MMy | ||||
| 5dAtg5aVXbDB86U+jXeIAbsxLQAG+sQSzYXy7YJiAwRW2bOihgkBVn8qxdeLauL+ | ||||
| avBDy6hwBHv3ILnYC8DmnjSxcBi7do/MNIEm5DEs90Lt8Oyh033e+riw7NpLyzY0 | ||||
| ZFJ/ScfLAzq/UIooCSGIIbpdPs7c8oER6kPj4X5Gv1unh3u9A0ZGGcrDtxS/f/eo | ||||
| /05ohAFwb31v4KM4eCvHqoaSqKVypFplrslObqxgPQsR/Ecw+gezBAqYmJnbMVRU | ||||
| 8RjNnPGbLulG4HnXxA2dLpqOEU7/FJN7bN8U7rdCqcm/WDbBlVUhNm7jjzDr2OFU | ||||
| exJMRZvXfRgdqgk59+RPizanRQIMs7GULCtVcJGWlSUfdo2KIuhfFJ/LVIcgm7oo | ||||
| dFTZVGMOc2KqoRfBnZz6yr74HcexHSlmQZSfIoQhbiiDNexOIQKCAQEA0UMDYrum | ||||
| FVzkisI0QSdq3h/9KFexYnp4ZYYP83FaZl2js7PAYIIcRcXxVb/i9AJx38FmPH9s | ||||
| 1uMb/PyLGdHGF/qvYrjYqZLZ64eKVlnQGWcU5eOQ9560l8Je5FUfR9IwC3LTG+3R | ||||
| 3QByPANOn19D0GunCJ7cTmwdoedTGPeUxyK7ZTodXn3YHBikLX9atroiRkMe+h4U | ||||
| fg8R2WV8Hul1PPcVqQ3Lz2aNm1mjfVe+nOghcNrmcI0t03BdXCCTcz3NLTJxwAeC | ||||
| 4TcXlENUo78OcxsZj4hq84xhauN3npT7sa/5JGcC7c7N+EULh/CTH/CK+zsFUYfD | ||||
| IHC88Vu9fnXcVQKCAQEAwjIHcVkru0apkF65f1AVIyEQJ2sWpndHslAAfSMxEUbL | ||||
| sMOii5QH5MKA8LRAGpDEoZY2XZrrOpuvnvxKIJt7Ijj4wfYF8ffYTyI0oK7gsR6v | ||||
| nvY7i+n18pzHZFJLKq5Z9n4zgEi2YBj802v0GfQyCjk3+8we7Man/Uz4jYZr8nxZ | ||||
| YPy+viBLP5nGp4R7y6nef93prfTL6iApXyr3MdFDMnJBGmiKNGW9UrpKFyFkwvE4 | ||||
| 7sdHUXj2JW/AM8P7CeNA3x2GQ1l0qS8QJ3R0X9GzTeq5tuIKUH2mDrPiBXisRG6k | ||||
| IYwKb0l8iv7sTLyfMv60T5uUCrS8hompHDtWsh0BWQKCAQBMSOSsEoIaGZIK738D | ||||
| HW586SZtlYJJxyGqyPN5qKHu3UX3FZkU1XmfCejPfLMshtOiYSt29HDl6Ubjs+C1 | ||||
| md5gEXfsQjxhnPIqRW/tyLHvAMACijHnwwhMpoPXMxzDHuF62vIQpWKy8R2zuPTp | ||||
| bl4XVZc/skHXqNwokF6fpGmtKoEsBsJ8Ft44Z9c56spUAIjMGl3pihuoVLAKE0/r | ||||
| KOofPme8CBZ7VgRbVJMf92O6aXj/Xh1RfHXvNXAjTJDUGvx39IK5IUPZ/C5xUxZA | ||||
| 1z5aQc/Qnkd2338H60JJIkCa5u6pEZBkxtYZInpwpQfNRfA0Y7CtpxM/+Tk3t1ze | ||||
| A/M9AoIBABmp7Ovg4fOk+gG3UwJtPe3fj7f14g9r0hDRm87t2K000vRwVknl7Ukh | ||||
| H1MwLwyTtzi3lkW2lIGxU3tKUi2O/q3eI5nWfqCkpXSHy7a0hcNCj+kNF399EuDW | ||||
| MU+jxIVGd2Mo+HtqoJeAleEG8kJ/0CEjwK9JIYkfE9JY2rwxWJC6OEGmBTsxH2Cv | ||||
| XN6ElquqrlntpNU1dcFiMLWAAx0VT7EaAlqQGDumeme1cNcvtZZBtMlxko5E0xrN | ||||
| cvQkYUfEPa1+xGCgMNeu/Y6JSFvlZbHVZGez5bMPd+OXiDY65WFB0fURAcwFRS1F | ||||
| VUsq3ksp+ABRSjZD/mo1RSETAnkVdjkCggEBAJwC6yt1x9yIAh9RL6AKN2VUAopJ | ||||
| Ot2vnAp6dHMEwkKQY3hseAJx7YkAb5KaTpEZ50VhXOxe5lOz+4eagXL2CelZG1xN | ||||
| jDTjIsReQflGn+jwYT1w35u+CPnDCizlA82cLaR5dlaOc/Np3hKWzvc6sAXzjmxN | ||||
| 05+YfB70gbz/u4GE+KFiylArQfJ+2WRxoyUpy3YmbOq7LhadV7GOzYb2ufYgv1cs | ||||
| US58bqWNx9NXSnJQ1uPDUaaSSfUvGJVJHd602h5m3PLxGIzBFrSsgwx9r6qLXPZ3 | ||||
| P6XiwAuMOqqEbFfBtJnCxUta9hNoAg4Vo8NV89c92uSFSEJ4xjlKHwOu0U8= | ||||
| -----END RSA PRIVATE KEY----- | ||||
| @ -1 +0,0 @@ | ||||
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCeva28+Ay/24t0UUcJXqxf25RnIrntxA0ctJUr84aN2KV/3mpEyMjwCRjEzSdz2WOdU0+6FJhZIBJMJ4I9oFL9WS9SfeSsQlDDVn0Y85ZtmjS8tsQvOFny7IKB05lK5Ymke6LhNyHPXSnFwoAKLnAvgwVqi6glRwZfUyAUnUNx38HG5WPWEqs5gsEnLVwO6hyY/hpO7yaC3OW5EyJ5OMfV9GrtIBskPHlGbOwTLlLr7pcO4uq6J1duygzcYRiFwgeO0FNxspWw4uZ6WpH9vIZRIU/S2Y4tyrzmca45/RRxxvUO4oBENnnc5+wa6zb8ZtvTP2eKk60SQ/ppuC+FqzdU1Yj4lD5iEufB8E/LuE3K1AVU3mpDT4dpDVJd2n5l7eago+oIdeuVsK+TwWlCgaHMQi14//GroM94d8dn9PuYfpPwnMUfcd8a4B9oMyTp/V2DmMezUv69pa1YjdJmiGP8t43fVNCw8kltD2W0rYS7WGzL2ZgEBe/f3JH6izWCmzHmKYhB9UwVDkWDxbaU3JeOfZL/NZ9jvobS8QM6iYTgGEyN3ZtOfZnOkBtr1s9+CbK/oqOjiYNe/6pV10fOyK8ppXe7ttMBhkyl6RwQOzvAs8coLr4JrhnSNdcS3AB6pb/CqinceLmA2u6Pj4URaxG8L/jfUDFeebKjVYR6fHbujQ== wolverindev@WolverinDEV | ||||
| @ -1,179 +0,0 @@ | ||||
| import * as fs from "fs-extra"; | ||||
| import * as tar from "tar-stream"; | ||||
| import * as zlib from "zlib"; | ||||
| import * as path from "path"; | ||||
| import * as asar from "asar"; | ||||
| 
 | ||||
| import {Pack} from "tar-stream"; | ||||
| import * as request from "request"; | ||||
| import {Version} from "../modules/shared/version"; | ||||
| 
 | ||||
| async function append_dir(parent: string, path: string, pack: Pack, excludes: (string | RegExp)[]) { | ||||
|     const entries = await fs.readdir(parent + "/" + path); | ||||
|     for(let entry of entries) { | ||||
|         console.log(entry); | ||||
|         const stat = await fs.stat(parent + "/" + path + "/" + entry); | ||||
|         if(stat.isDirectory()) { | ||||
|             console.log("Add sub: %s", entry); | ||||
|             await append_dir(parent, path + "/" + entry, pack, excludes); | ||||
|         } else { | ||||
|             let exclude = false; | ||||
|             for(const pattern of excludes) { | ||||
|                 if((path + "/" + entry).match(pattern)) { | ||||
|                     console.log("Excluding file %s", path + "/" + entry); | ||||
|                     exclude = true; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if(exclude) continue; | ||||
| 
 | ||||
|             let pentry = pack.entry({ | ||||
|                 name: path + "/" + entry, | ||||
|                 size: stat.size, | ||||
|                 mode: stat.mode | ||||
|             }, error => { | ||||
|                 if(error) throw error; | ||||
|             }); | ||||
| 
 | ||||
|             if(!pentry) throw "Failed to create new file"; | ||||
| 
 | ||||
|             const pipe = fs.createReadStream(parent + "/" + path + "/" + entry).pipe(pentry); | ||||
| 
 | ||||
|             await new Promise((resolve, reject) => { | ||||
|                 pipe.on('finish', resolve); | ||||
|                 pipe.on('error', reject); | ||||
|             }); | ||||
|             pentry.end(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function pack_update(source: string, dest: string) : Promise<string> { | ||||
|     await fs.mkdirs(path.dirname(dest)); | ||||
|     const target = fs.createWriteStream(dest); | ||||
|     const pack = tar.pack(); | ||||
|     const compress = zlib.createGzip(); | ||||
| 
 | ||||
|     pack.pipe(compress).pipe(target); | ||||
| 
 | ||||
|     await append_dir(source, ".", pack, [/.\/app_versions\/.*/]); ///.\/postzip($|.exe)/,
 | ||||
|     pack.finalize(); | ||||
| 
 | ||||
|     await new Promise((resolve, reject) => { | ||||
|         target.on('close', resolve); | ||||
|         target.on('error', reject); | ||||
|     }); | ||||
| 
 | ||||
|     return dest; | ||||
| } | ||||
| 
 | ||||
| export async function pack_info(src: string) : Promise<any> { | ||||
|     const appAsarPath = path.join(src, 'resources/app.asar'); | ||||
|     const appPackageJSONPath = path.join(src, 'resources/app/package.json'); | ||||
| 
 | ||||
|     if(await fs.pathExists(appAsarPath)) | ||||
|         return JSON.parse(asar.extractFile(appAsarPath, "package.json")); | ||||
|     else | ||||
|         return await fs.readJson(appPackageJSONPath); | ||||
| } | ||||
| 
 | ||||
| interface InfoEntry { | ||||
|     platform: string; | ||||
|     arch: string; | ||||
|     update: string; | ||||
|     install: string; | ||||
| } | ||||
| 
 | ||||
| export async function write_info(file: string, platform: string, arch: string, update_file: string, install_file: string) { | ||||
|     let infos: InfoEntry[] = fs.existsSync(file) ? await fs.readJson(file) as InfoEntry[] : []; | ||||
|     for(const entry of infos.slice()) { | ||||
|         if(entry.platform == platform && entry.arch == arch) | ||||
|             infos.splice(infos.indexOf(entry),1); | ||||
|     } | ||||
|     infos.push({ | ||||
|         "platform": platform, | ||||
|         "arch": arch, | ||||
|         "update": update_file, | ||||
|         "install": install_file | ||||
|     }); | ||||
|    await fs.writeJson(file, infos); | ||||
| } | ||||
| 
 | ||||
| interface VersionFile { | ||||
|     release: VersionEntry[]; | ||||
|     beta: VersionEntry[]; | ||||
| } | ||||
| 
 | ||||
| interface VersionEntry { | ||||
|     platform: string; | ||||
|     arch: string; | ||||
|     version: Version; | ||||
| } | ||||
| export async function write_version(file: string, platform: string, arch: string, channel: string, version: Version) { | ||||
|     let versions: VersionFile = fs.existsSync(file) ? await fs.readJson(file) as VersionFile : {} as any; | ||||
| 
 | ||||
|     versions[channel] = versions[channel] || []; | ||||
|     let channel_data = versions[channel]; | ||||
| 
 | ||||
|     for(const entry of channel_data.slice()) { | ||||
|         if(entry.platform == platform && entry.arch == arch) | ||||
|             channel_data.splice(channel_data.indexOf(entry), 1); | ||||
|     } | ||||
| 
 | ||||
|     channel_data.push({ | ||||
|         platform: platform, | ||||
|         arch: arch, | ||||
|         version: version | ||||
|     }); | ||||
|     await fs.writeJson(file, versions); | ||||
| } | ||||
| export async function deploy(platform: string, arch: string, channel: string, version: Version, update_file: string, install_file: string, install_suffix: string) { | ||||
|     await new Promise((resolve, reject) => { | ||||
|         const url = (process.env["teaclient_deploy_url"] || "http://clientapi.teaspeak.de/") + "api.php"; | ||||
|         console.log("Requesting " + url); | ||||
|         console.log("Uploading update file " + update_file); | ||||
|         console.log("Uploading install file " + install_file); | ||||
|         console.log("Secret (env key: teaclient_deploy_secret): " + process.env["teaclient_deploy_secret"]); | ||||
|         if(!process.env["teaclient_deploy_secret"]) throw "Missing secret!"; | ||||
| 
 | ||||
| 
 | ||||
|         request.post(url, { | ||||
|             formData: { | ||||
|                 type: "deploy-build", | ||||
|                 secret: process.env["teaclient_deploy_secret"], | ||||
| 
 | ||||
|                 platform: platform, | ||||
|                 arch: arch, | ||||
|                 version: JSON.stringify(version), | ||||
|                 channel: channel, | ||||
| 
 | ||||
|                 update: fs.createReadStream(update_file), | ||||
|                 update_suffix: "tar.gz", | ||||
| 
 | ||||
|                 installer: fs.createReadStream(install_file), | ||||
|                 installer_suffix: install_suffix | ||||
|             } | ||||
|         }, (error, response, body) => { | ||||
|             if(error) { | ||||
|                 console.error("Failed to upload:"); | ||||
|                 console.error(error); | ||||
|                 throw "Failed to upload: " + error; | ||||
|             } | ||||
|             console.log("Response code: " + (response ? response.statusCode : 0)); | ||||
|             let info; | ||||
|             if(response && response.statusCode == 413) { | ||||
|                 info = {msg: "Files too large! Increase limits!"}; | ||||
|             } else { | ||||
|                 try { | ||||
|                     info = JSON.parse(body); | ||||
|                 } catch (error) { | ||||
|                     info = {}; | ||||
|                     console.dir(body); | ||||
|                 } | ||||
|             } | ||||
|             console.dir(info); | ||||
|             if(!info["success"]) throw info["msg"] || "Could not deploy files!"; | ||||
|             resolve(); | ||||
|         }); | ||||
|     }) | ||||
| } | ||||
| @ -1,78 +0,0 @@ | ||||
| const installer = require("electron-installer-debian"); | ||||
| import * as packager from "./package"; | ||||
| import {parse_version, Version} from "../modules/shared/version"; | ||||
| 
 | ||||
| const package_path = "build/TeaClient-linux-x64/"; | ||||
| const filename_update = "TeaClient-linux-x64.tar.gz"; | ||||
| 
 | ||||
| let options = { | ||||
|     src: package_path, | ||||
|     dest: undefined, | ||||
|     dest_file: undefined, | ||||
|     arch: 'amd64', | ||||
| 
 | ||||
|     rename: (directory, name) => { | ||||
|         console.log("Destination directory: " + directory); | ||||
|         console.log("Destination name     : " + name); | ||||
|         options.dest_file = directory + "/" + name; | ||||
|         return directory + "/" + name; | ||||
|     }, | ||||
|     options: { | ||||
|         name: "TeaClient", | ||||
|         productName: "TeaClient", | ||||
|         genericName: "TeaSpeak - Client", | ||||
|         description: "TeaClient by TeaSpeak", | ||||
|         version: undefined, | ||||
|         homepage: "https://teaspeak.de", | ||||
|         maintainer: "WolverinDEV <client@teaspeak.de>", | ||||
| 
 | ||||
|         icon: 'resources/logo.svg', | ||||
|         categories: [ | ||||
|             "Utility" | ||||
|         ], | ||||
|         bin: 'TeaClient' | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| if(process.argv.length < 3) { | ||||
|     console.error("Missing build channel!"); | ||||
|     process.exit(1); | ||||
| } | ||||
| 
 | ||||
| let version: Version; | ||||
| const alive = setInterval(() => {}, 1000); | ||||
| packager.pack_info(package_path).then(package_info => { | ||||
|     options.options.version = (version = parse_version(package_info["version"])).toString(); | ||||
|     options.dest = "build/output/" + process.argv[2] + "/" + options.options.version + "/"; | ||||
|     console.log('Creating package for version ' + options.options.version + ' (this may take a while)'); | ||||
| 
 | ||||
|     //return Promise.resolve();
 | ||||
|     return installer(options); | ||||
| }).then(() => { | ||||
|     if(!options.dest_file) | ||||
|         options.dest_file = options.dest + "TeaClient_" + options.options.version + "_amd64.deb"; | ||||
| 
 | ||||
|     console.log(`Successfully created package at ${options.dest} (${options.dest_file})`); | ||||
|     return packager.pack_update(options.src, options.dest + "/" + filename_update); | ||||
| }).then(() => { | ||||
|     return packager.write_info(options.dest + "info.json", "linux", "x64", filename_update, options.dest_file) | ||||
| }).then(() => { | ||||
|     return packager.write_version("build/output/version.json", "linux", "x64",  process.argv[2], version); | ||||
| }).then(() => { | ||||
|     console.log("Deploying symbol files"); | ||||
|     //FIXME!
 | ||||
| }).then(() => { | ||||
|     //Fixup in case of skip of the packaging
 | ||||
| 
 | ||||
|     console.log("Deploying build"); | ||||
|     return packager.deploy("linux", "x64", process.argv[2], version, options.dest + filename_update, options.dest_file, "deb"); | ||||
| }).then(() => { | ||||
|     console.log("Build version (" + options.options.version + ") created!"); | ||||
|     clearInterval(alive); | ||||
| }) | ||||
| .catch(err => { | ||||
|     console.error("Failed to pack package!"); | ||||
|     console.error(err, err.stack); | ||||
|     process.exit(1) | ||||
| }); | ||||
| 
 | ||||
| @ -1,88 +0,0 @@ | ||||
| import * as packager from "./package"; | ||||
| import * as deployer from "./deploy"; | ||||
| import {parse_version, Version} from "../modules/shared/version"; | ||||
| 
 | ||||
| const fs = require("fs-extra"); | ||||
| const path = require("path"); | ||||
| const util = require('util'); | ||||
| const cproc = require('child_process'); | ||||
| const proc = require('process'); | ||||
| const ejs = require('ejs'); | ||||
| const exec = util.promisify(cproc.exec); | ||||
| const ejs_render = util.promisify(ejs.renderFile); | ||||
| 
 | ||||
| const filename_update = "TeaClient-windows-x64.tar.gz"; | ||||
| const filename_installer = "TeaClient-windows-x64.exe"; | ||||
| const package_path = "build/TeaClient-win32-x64/"; | ||||
| const symbol_pdb_path = "native/build/symbols/"; | ||||
| const symbol_binary_path = package_path + "/resources/natives/"; | ||||
| let dest_path = undefined; | ||||
| let info; | ||||
| let version: Version; | ||||
| async function make_template() : Promise<string> { | ||||
|     const content = await ejs_render("installer/WinInstall.ejs", { | ||||
|         source_dir: path.resolve(package_path) + "/*", | ||||
|         dest_dir: path.resolve(dest_path), | ||||
|         icon_file: path.resolve("resources/logo.ico"), | ||||
|         version: info["version"], | ||||
|         executable_name: filename_installer.substr(0, filename_installer.length - 4) //Remove the .exe
 | ||||
|     }, {}); | ||||
| 
 | ||||
|     await fs.mkdirs(dest_path); | ||||
|     fs.writeFileSync(dest_path + "/" + "installer.iss", content); | ||||
|     return dest_path + "/" + "installer.iss"; | ||||
| } | ||||
| 
 | ||||
| async function make_installer(path: string) { | ||||
|     console.log("Compiling path %s", path); | ||||
|     const { stdout, stderr } = await exec("\"C:\\Program Files (x86)\\Inno Setup 5\\iscc.exe\" " + path, {maxBuffer: 1024 * 1024 * 1024}); //FIXME relative path?
 | ||||
| } | ||||
| if(process.argv.length < 3) { | ||||
|     console.error("Missing build channel!"); | ||||
|     process.exit(1); | ||||
| } | ||||
| 
 | ||||
| packager.pack_info(package_path).then(async _info => { | ||||
|     info = _info; | ||||
|     version = parse_version(_info["version"]); | ||||
|     dest_path = "build/output/" + process.argv[2] + "/" + version.toString() + "/"; | ||||
|     await packager.pack_update(package_path, dest_path + "TeaClient-windows-x64.tar.gz"); | ||||
| }).then(async () => { | ||||
|     await packager.write_info(dest_path + "info.json", "win32", "x64", filename_update, filename_installer) | ||||
| }).then(async () => { | ||||
|     await packager.write_version("build/output/version.json", "win32", "x64",  process.argv[2], version); | ||||
| }).then(async () => await make_template()) | ||||
| .then(async path => await make_installer(path)) | ||||
| .then(async () => { | ||||
|     console.log("Deploying PDB files"); | ||||
|     const files = []; | ||||
|     for(const file of await fs.readdir(symbol_binary_path)) { | ||||
|         if(!file.endsWith(".node")) | ||||
|             continue; | ||||
|         let file_name = path.basename(file); | ||||
|         if(file_name.endsWith(".node")) | ||||
|             file_name = file_name.substr(0, file_name.length - 5); | ||||
|         const binary_path = path.join(symbol_binary_path, file); | ||||
|         const pdb_path = path.join(symbol_pdb_path, file_name + ".pdb"); | ||||
|         if(!fs.existsSync(pdb_path)) { | ||||
|             console.warn("Missing PDB file for binary %s", file); | ||||
|             continue; | ||||
|         } | ||||
|         files.push({ | ||||
|             binary: binary_path, | ||||
|             pdb: pdb_path | ||||
|         }); | ||||
|     } | ||||
|     await deployer.deploy_win_dbg_files(files, version); | ||||
|     console.log("PDB files deployed"); | ||||
| }) | ||||
| .then(async () => { | ||||
|     console.log("Deploying build"); | ||||
|     await packager.deploy("win32", "x64", process.argv[2], version, dest_path + filename_update, dest_path + filename_installer, "exe"); | ||||
| }).then(() => { | ||||
|     console.log("Succeed"); | ||||
|     proc.exit(0); | ||||
| }).catch(error => { | ||||
|     console.log(error); | ||||
|     proc.exit(1); | ||||
| }); | ||||
| @ -1,14 +0,0 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "module": "commonjs", | ||||
|     "target": "es6", | ||||
|     "sourceMap": true, | ||||
|     "moduleResolution": "node" | ||||
|   }, | ||||
|   "include": [ | ||||
|     "./deploy/", | ||||
|     "build.ts", | ||||
|     "package.ts", | ||||
|     "package_linux.ts" | ||||
|   ] | ||||
| } | ||||
| @ -1,14 +0,0 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "module": "commonjs", | ||||
|     "target": "es6", | ||||
|     "sourceMap": true, | ||||
|     "moduleResolution": "node" | ||||
|   }, | ||||
|   "include": [ | ||||
|     "./deploy/", | ||||
|     "build.ts", | ||||
|     "package.ts", | ||||
|     "package_windows.ts" | ||||
|   ] | ||||
| } | ||||
| @ -1,124 +0,0 @@ | ||||
| #!/usr/bin/env bash | ||||
| 
 | ||||
| cd "$(dirname $0)/../" | ||||
| 
 | ||||
| project_name="__build_teaclient" | ||||
| source ../scripts/build_helper.sh | ||||
| 
 | ||||
| function install_npm() { | ||||
|     begin_task "${project_name}_update" "Installing NPM" | ||||
|     npm install --save-dev | ||||
|     check_err_exit ${project_name} "Failed to install nodejs files!" | ||||
|     npm run install-platform | ||||
|     check_err_exit ${project_name} "Failed to install platform depend nodejs files!" | ||||
| 
 | ||||
|     npm update | ||||
|     check_err_exit ${project_name} "Failed to update nodejs files!" | ||||
|     end_task "${project_name}_update" "NPM installed" | ||||
| } | ||||
| 
 | ||||
| function compile_scripts() { | ||||
|     begin_task "${project_name}_tsc_sass" "Compiling TypeScript & SASS" | ||||
|     ./build_declarations.sh | ||||
|     check_err_exit ${project_name} "Failed to build shared ui import declarations!" | ||||
| 
 | ||||
|     npm run compile-tsc -- -p modules/tsconfig_main.json | ||||
|     check_err_exit ${project_name} "Failed to compile typescript main files!" | ||||
|     npm run compile-tsc -- -p modules/tsconfig_renderer.json | ||||
|     check_err_exit ${project_name} "Failed to compile typescript renderer files!" | ||||
| 
 | ||||
|     if [[ ${build_os_type} == "win32" ]]; then | ||||
|         npm run compile-tsc -- -p installer/tsconfig_windows.json | ||||
|         check_err_exit ${project_name} "Failed to compile typescript installer files!" | ||||
|     else | ||||
|         npm run compile-tsc -- -p installer/tsconfig_linux.json | ||||
|         check_err_exit ${project_name} "Failed to compile typescript installer files!" | ||||
|     fi | ||||
| 
 | ||||
|     npm run compile-sass | ||||
|     check_err_exit ${project_name} "Failed to compile sass files!" | ||||
|     end_task "${project_name}_tsc_sass" "TypeScript & SASS compiled" | ||||
|     echo "" | ||||
| } | ||||
| 
 | ||||
| function compile_native() { | ||||
|     begin_task "${project_name}_native" "Compiling native extensions" | ||||
| 
 | ||||
|     local build_path="native/out/${build_os_type}_${build_os_arch}/" | ||||
|     [[ -d ${build_path} ]] && rm -r ${build_path} | ||||
|     mkdir -p ${build_path} | ||||
|     check_err_exit ${project_name} "Failed to create build directory!" | ||||
| 
 | ||||
|     cd ${build_path} | ||||
|     check_err_exit ${project_name} "Failed to enter build directory!" | ||||
| 
 | ||||
|     local _arguments="" | ||||
|     [[ ! -z "$tearoot_cmake_module" ]] && _arguments="${_arguments} -DCMAKE_MODULE_PATH=\"$tearoot_cmake_module\"" | ||||
|     [[ ! -z "$tearoot_cmake_config" ]] && _arguments="${_arguments} -DCMAKE_PLATFORM_INCLUDE=\"$tearoot_cmake_config\"" | ||||
|     [[ ! -z "$traroot_library" ]] && _arguments="${_arguments} -DLIBRARY_PATH=\"$traroot_library\"" | ||||
| 
 | ||||
|     local _generator="" | ||||
|     [[ ${build_os_type} == "win32" ]] && _generator='-G"Visual Studio 15 2017 Win64"' | ||||
| 
 | ||||
|     _command="cmake ../../ ${_generator} -DCMAKE_BUILD_TYPE=RelWithDebInfo ${_arguments}" | ||||
|     echo "Executing cmake command $_command" | ||||
| 
 | ||||
|     eval ${_command} | ||||
|     check_err_exit ${project_name} "Failed create build targets!" | ||||
| 
 | ||||
|     cmake --build `pwd` --target update_installer -- ${CMAKE_MAKE_OPTIONS} | ||||
|     check_err_exit ${project_name} "Failed build teaclient update installer!" | ||||
| 
 | ||||
|     cmake --build `pwd` --target teaclient_connection -- ${CMAKE_MAKE_OPTIONS} | ||||
|     check_err_exit ${project_name} "Failed build teaclient connection!" | ||||
| 
 | ||||
|     cmake --build `pwd` --target teaclient_crash_handler -- ${CMAKE_MAKE_OPTIONS} | ||||
|     check_err_exit ${project_name} "Failed build teaclient crash handler!" | ||||
| 
 | ||||
|     cmake --build `pwd` --target teaclient_ppt -- ${CMAKE_MAKE_OPTIONS} | ||||
|     check_err_exit ${project_name} "Failed build teaclient ppt!" | ||||
| 
 | ||||
|     cmake --build `pwd` --target teaclient_dns -- ${CMAKE_MAKE_OPTIONS} | ||||
|     check_err_exit ${project_name} "Failed to build teaclient dns!" | ||||
| 
 | ||||
|     end_task "${project_name}_native" "Native extensions compiled" | ||||
| } | ||||
| 
 | ||||
| function package_client() { | ||||
|     begin_task "${project_name}_package" "Packaging client" | ||||
|     if [[ ${build_os_type} == "win32" ]]; then | ||||
|         npm run build-windows-64 | ||||
|         check_err_exit ${project_name} "Failed to package client!" | ||||
|     else | ||||
|         npm run build-linux-64 | ||||
|         check_err_exit ${project_name} "Failed to package client!" | ||||
|     fi | ||||
|     end_task "${project_name}_package" "Client package created" | ||||
| } | ||||
| 
 | ||||
| function deploy_client() { | ||||
|     begin_task "${project_name}_package" "Deploying client" | ||||
|     [[ -z ${teaclient_deploy_secret} ]] && { | ||||
|         echo "Missing deploy secret. Dont deploy client!" | ||||
|         return 0 | ||||
|     } | ||||
|     [[ -z ${teaclient_deploy_channel} ]] && { | ||||
|         echo "Missing deploy channel. Dont deploy client!" | ||||
|         return 0 | ||||
|     } | ||||
| 
 | ||||
|     if [[ ${build_os_type} == "win32" ]]; then | ||||
|         npm run package-windows-64 ${teaclient_deploy_channel} | ||||
|         check_err_exit ${project_name} "Failed to deploying client!" | ||||
|     else | ||||
|         npm run package-linux-64 ${teaclient_deploy_channel} | ||||
|         check_err_exit ${project_name} "Failed to deploying client!" | ||||
|     fi | ||||
|     end_task "${project_name}_package" "Client successfully deployed!" | ||||
| } | ||||
| 
 | ||||
| #install_npm | ||||
| #compile_scripts | ||||
| #compile_native | ||||
| #package_client | ||||
| deploy_client | ||||
| @ -1,11 +0,0 @@ | ||||
| https://git.assembla.com/portaudio.git | ||||
| https://github.com/WolverinDEV/libfvad | ||||
| 
 | ||||
| 
 | ||||
| git clone https://git.code.sf.net/p/soxr/code soxr | ||||
| mkdir -p out/linux_x64 | ||||
| cd out/linux_x674 | ||||
| 
 | ||||
| cmake ../../ -DWITH_OPENMP=OFF -DBUILD_TESTS=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_C_FLAGS="-fPIC" | ||||
| make -j 12 | ||||
| sudo make install | ||||
							
								
								
									
										31
									
								
								main.ts
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								main.ts
									
									
									
									
									
								
							| @ -1,31 +0,0 @@ | ||||
| import * as electron from "electron"; | ||||
| import * as os from "os"; | ||||
| 
 | ||||
| { | ||||
|     const app_path = electron.app.getAppPath(); | ||||
|     console.log("Native module path: %s", app_path + "/native/build/" + os.platform() + "_" + os.arch() + "/"); | ||||
| } | ||||
| 
 | ||||
| import * as crash_handler from "./modules/crash_handler"; | ||||
| 
 | ||||
| const is_electron_run = process.argv[0].endsWith("electron") || process.argv[0].endsWith("electron.exe"); | ||||
| const process_arguments = is_electron_run ? process.argv.slice(2) : process.argv.slice(1); | ||||
| if(process_arguments.length > 0 && process_arguments[0] === "crash-handler") { | ||||
|     /* crash handler callback */ | ||||
|     crash_handler.handle_crash_callback(process_arguments.slice(1)); | ||||
| } else { | ||||
|     if(process_arguments.length > 0 && process_arguments[0] == "--main-crash-handler") | ||||
|         crash_handler.initialize_handler("main", is_electron_run); | ||||
|     /* app execute */ | ||||
|     { | ||||
|         const versions = process.versions; | ||||
|         console.log("Versions:"); | ||||
|         console.log("  TeaSpeak Client: " + electron.app.getVersion()); | ||||
| 
 | ||||
|         for (const key of Object.keys(versions)) | ||||
|             console.log("  %s: %s", key, versions[key]); | ||||
|     } | ||||
| 
 | ||||
|     const tea_client = require("./modules/core/main.js"); | ||||
|     tea_client.execute(); | ||||
| } | ||||
| @ -1,42 +0,0 @@ | ||||
| import {BrowserWindow} from "electron"; | ||||
| import * as electron from "electron"; | ||||
| import * as path from "path"; | ||||
| 
 | ||||
| let changelog_window: BrowserWindow; | ||||
| export function open() { | ||||
|     if(changelog_window) { | ||||
|         changelog_window.focus(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     changelog_window = new BrowserWindow({ | ||||
|         show: false | ||||
|     }); | ||||
| 
 | ||||
|     changelog_window.setMenu(null); | ||||
| 
 | ||||
|     let file = ""; | ||||
|     { | ||||
|         const app_path = electron.app.getAppPath(); | ||||
|         if(app_path.endsWith(".asar")) | ||||
|             file = path.join(path.dirname(app_path), "..", "ChangeLog.txt"); | ||||
|         else | ||||
|             file = path.join(app_path, "github", "ChangeLog.txt"); /* We've the source master :D */ | ||||
|     } | ||||
| 
 | ||||
|     changelog_window.loadFile(file); | ||||
|     changelog_window.setTitle("TeaClient ChangeLog"); | ||||
|     changelog_window.on('ready-to-show', () => { | ||||
|         changelog_window.show(); | ||||
|     }); | ||||
|     changelog_window.on('close', () => { | ||||
|         changelog_window = undefined; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function close() { | ||||
|     if(changelog_window) { | ||||
|         changelog_window.close(); | ||||
|         changelog_window = undefined; | ||||
|     } | ||||
| } | ||||
| @ -1,863 +0,0 @@ | ||||
| import * as querystring from "querystring"; | ||||
| import * as request from "request"; | ||||
| import {app, dialog, ipcMain} from "electron"; | ||||
| import * as fs from "fs-extra"; | ||||
| import * as ofs from "original-fs"; | ||||
| import * as os from "os"; | ||||
| import * as tar from "tar-stream"; | ||||
| import * as path from "path"; | ||||
| import * as zlib from "zlib"; | ||||
| import * as child_process from "child_process"; | ||||
| import * as progress from "request-progress"; | ||||
| import * as util from "util"; | ||||
| 
 | ||||
| import {parse_version, Version} from "../../shared/version"; | ||||
| 
 | ||||
| import Timer = NodeJS.Timer; | ||||
| import MessageBoxOptions = Electron.MessageBoxOptions; | ||||
| import {Headers} from "tar-stream"; | ||||
| import {Arguments, process_args} from "../../shared/process-arguments"; | ||||
| import * as electron from "electron"; | ||||
| import {PassThrough} from "stream"; | ||||
| import * as _main_windows from "../main_window"; | ||||
| import ErrnoException = NodeJS.ErrnoException; | ||||
| import {EPERM} from "constants"; | ||||
| import * as winmgr from "../window"; | ||||
| 
 | ||||
| const is_debug = false; | ||||
| export function server_url() : string { | ||||
|     const default_path = is_debug ? "http://localhost/home/TeaSpeak/TeaSpeak/Web-Client/client-api/environment/" : "http://clientapi.teaspeak.de/"; | ||||
|     return process_args.has_value(...Arguments.SERVER_URL) ? process_args.value(...Arguments.SERVER_URL) : default_path; | ||||
| } | ||||
| 
 | ||||
| export interface UpdateVersion { | ||||
|     channel: string; | ||||
|     platform: string, | ||||
|     arch: string; | ||||
|     version: Version; | ||||
| } | ||||
| 
 | ||||
| export interface UpdateData { | ||||
|     versions: UpdateVersion[]; | ||||
|     updater_version: UpdateVersion; | ||||
| } | ||||
| 
 | ||||
| let version_cache: UpdateData = undefined; | ||||
| export async function load_data(allow_cached: boolean = true) : Promise<UpdateData> { | ||||
|     if(version_cache && allow_cached) return Promise.resolve(version_cache); | ||||
| 
 | ||||
|     return new Promise<UpdateData>((resolve, reject) => { | ||||
|         const request_url = server_url() + "/api.php?" + querystring.stringify({ | ||||
|             type: "update-info" | ||||
|         }); | ||||
|         console.log("request: %s", request_url); | ||||
|         request.get(request_url, { | ||||
|             timeout: 2000 | ||||
|         }, (error, response, body) => { | ||||
|             if(!response || response.statusCode != 200) { | ||||
|                 let info; | ||||
|                 try { | ||||
|                     info = JSON.parse(body) || {msg: error}; | ||||
|                 } catch(e) { | ||||
|                     info = {msg: "!-- failed to parse json --!"}; | ||||
|                 } | ||||
|                 setImmediate(reject, "Invalid status code (" + (response || {statusCode: -1}).statusCode + " | " + (info || {msg: "undefined"}).msg + ")"); | ||||
|                 return; | ||||
|             } | ||||
|             const data = JSON.parse(body); | ||||
|             if(!data) { | ||||
|                 setImmediate(reject, "Invalid response"); | ||||
|                 return; | ||||
|             } | ||||
|             if(!data["success"]) { | ||||
|                 setImmediate(reject, "Action failed (" + data["msg"] + ")"); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             let resp: UpdateData = {} as any; | ||||
|             resp.versions = []; | ||||
|             for(const channel of Object.keys(data)) { | ||||
|                 if(channel == "success") continue; | ||||
| 
 | ||||
|                 for(const entry of data[channel]) { | ||||
|                     let version: UpdateVersion = {} as any; | ||||
|                     version.channel = channel; | ||||
|                     version.arch = entry["arch"]; | ||||
|                     version.platform = entry["platform"]; | ||||
|                     version.version = new Version(entry["version"]["major"], entry["version"]["minor"], entry["version"]["patch"], entry["version"]["build"], entry["version"]["timestamp"]); | ||||
|                     if(version.channel == 'updater') | ||||
|                         resp.updater_version = version; | ||||
|                     else | ||||
|                         resp.versions.push(version); | ||||
|                 } | ||||
|             } | ||||
|             resolve(resp); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export async function newest_version(current_version: Version, channel?: string) : Promise<UpdateVersion | undefined> { | ||||
|     if(!app.getAppPath().endsWith(".asar")) { | ||||
|         throw "You cant run an update when you're executing the source code!"; | ||||
|     } | ||||
|     const data = await load_data(); | ||||
|     let had_data = false; | ||||
|     for(const version of data.versions) { | ||||
|         if(version.arch == os.arch() && version.platform == os.platform()) { | ||||
|             if(!channel || version.channel == channel) { | ||||
|                 if(!current_version || version.version.newer_than(current_version)) | ||||
|                     return version; | ||||
|                 else | ||||
|                     had_data = true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(!had_data) | ||||
|         throw "Missing data"; | ||||
|     return undefined; | ||||
| } | ||||
| 
 | ||||
| export async function extract_updater(update_file: string) { | ||||
|     if(!fs.existsSync(update_file)) throw "Missing update file!"; | ||||
| 
 | ||||
| 
 | ||||
|     let parent_path = app.getAppPath(); | ||||
|     if(parent_path.endsWith(".asar")) { | ||||
|         parent_path = path.join(parent_path, "..", ".."); | ||||
|         parent_path = fs.realpathSync(parent_path); | ||||
|     } | ||||
| 
 | ||||
|     let post_path; | ||||
|     if(os.platform() == "linux") | ||||
|         post_path = parent_path + "/update-installer"; | ||||
|     else | ||||
|         post_path = parent_path + "/update-installer.exe"; | ||||
| 
 | ||||
|     const source = fs.createReadStream(update_file); | ||||
|     const extract = tar.extract(); | ||||
|     await new Promise(resolve => { | ||||
|         let updater_found = false; | ||||
|         source.on('end', () => { | ||||
|             if(!updater_found) { | ||||
|                 console.error("Failed to extract the updater (Updater hasn't been found!)"); | ||||
|                 resolve(); //FIXME use reject!
 | ||||
|             } | ||||
|             resolve(); | ||||
|         }); | ||||
| 
 | ||||
|         extract.on('entry', (header: Headers, stream, callback) => { | ||||
|             stream.on('end', callback); | ||||
|             console.log("Got entry " + header.name); | ||||
| 
 | ||||
|             if(header.name == "./update-installer" || header.name == "./update-installer.exe") { | ||||
|                 console.log("Found updater! (" + header.size + ")"); | ||||
|                 console.log("Extracting to %s", post_path); | ||||
|                 const s = fs.createWriteStream(post_path); | ||||
|                 stream.pipe(s).on('finish', event => { | ||||
|                     console.log("Updater extracted and written!"); | ||||
|                     updater_found = true; | ||||
|                     resolve(); | ||||
|                 }); | ||||
|             } else { | ||||
|                 stream.resume(); //Drain the stream
 | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
| 
 | ||||
|         source.pipe(extract); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export async function update_updater() : Promise<void> { | ||||
|     //TODO here
 | ||||
|     return Promise.resolve(); | ||||
| } | ||||
| 
 | ||||
| function data_directory() : string { | ||||
|     return electron.app.getPath('userData'); | ||||
| } | ||||
| 
 | ||||
| function get_update_file(channel: string, version: Version) : string { | ||||
|     let _path = fs.realpathSync(data_directory()); | ||||
| 
 | ||||
|     const name = channel + "_" + version.major + "_" + version.minor + "_" + version.patch + "_" + version.build + ".tar"; | ||||
|     return path.join(_path, "app_versions", name); | ||||
| } | ||||
| 
 | ||||
| export interface ProgressState { | ||||
|     percent: number, // Overall percent (between 0 to 1)
 | ||||
|     speed: number,  // The download speed in bytes/sec
 | ||||
|     size: { | ||||
|         total: number, // The total payload size in bytes
 | ||||
|         transferred: number// The transferred payload size in bytes
 | ||||
|     }, | ||||
|     time: { | ||||
|         elapsed: number,// The total elapsed seconds since the start (3 decimals)
 | ||||
|         remaining: number // The remaining seconds to finish (3 decimals)
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function download_version(channel: string, version: Version, status?: (state: ProgressState) => any) : Promise<string> { | ||||
|     const target_path = get_update_file(channel, version); | ||||
|     console.log("Downloading version %s to %s", version.toString(false), target_path); | ||||
|     if(fs.existsSync(target_path)) { | ||||
|         /* TODO test if this file is valid and can be used */ | ||||
|         try { | ||||
|             await fs.remove(target_path); | ||||
|         } catch(error) { | ||||
|             throw "Failed to remove old file: " + error; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|         await fs.mkdirp(path.dirname(target_path)); | ||||
|     } catch(error) { | ||||
|         throw "Failed to make target directory: " + path.dirname(target_path); | ||||
|     } | ||||
| 
 | ||||
|     const url = server_url() + "/api.php?" + querystring.stringify({ | ||||
|         type: "update-download", | ||||
|         platform: os.platform(), | ||||
|         arch: os.arch(), | ||||
|         version: version.toString(), | ||||
|         channel: channel | ||||
|     }); | ||||
|     console.log("Downloading update from %s. (%s)", server_url(), url); | ||||
|     return new Promise<string>((resolve, reject) => { | ||||
|         let fired = false; | ||||
|         let stream = progress(request.get(url, { | ||||
|             timeout: 2000 | ||||
|         }, (error, response, body) => { | ||||
|             if(!response || response.statusCode != 200) { | ||||
|                 let info; | ||||
|                 try { | ||||
|                     info = JSON.parse(body) | ||||
|                 } catch(e) { | ||||
|                     info = {"msg": "!-- failed to parse json --!"}; | ||||
|                 } | ||||
|                 if(!fired && (fired = true)) | ||||
|                     setImmediate(reject, "Invalid status code (" + (response || {statusCode: -1}).statusCode + "|" + (info || {"msg": "undefined"}).msg + ")"); | ||||
|                 return; | ||||
|             } | ||||
|         })).on('progress', _state => status ? status(_state) : {}).on('error', error => { | ||||
|             console.warn("Encountered error within download pipe. Ignoring error: %o", error); | ||||
|         }).on('end', function () { | ||||
|             console.log("Update downloaded successfully. Waiting for write stream to finish."); | ||||
|             if(status) | ||||
|                 status({ | ||||
|                     percent: 1, | ||||
|                     speed: 0, | ||||
|                     size: { total: 0, transferred: 0}, | ||||
|                     time: { elapsed: 0, remaining: 0} | ||||
|                 }) | ||||
|         }); | ||||
| 
 | ||||
|         console.log("Decompressing update package while streaming!"); | ||||
|         stream = stream.pipe(zlib.createGunzip()); | ||||
|         stream.pipe(fs.createWriteStream(target_path, { | ||||
|             autoClose: true | ||||
|         })).on('finish', () => { | ||||
|             console.log("Write stream has finished. Download successfully."); | ||||
|             if(!fired && (fired = true)) | ||||
|                 setImmediate(resolve, target_path); | ||||
|         }).on('error', error => { | ||||
|             console.log("Write stream encountered an error while downloading update. Error: %o", error); | ||||
|             if(!fired && (fired = true)) | ||||
|                 setImmediate(reject,"failed to write"); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| if(typeof(String.prototype.trim) === "undefined") | ||||
| { | ||||
|     String.prototype.trim = function() | ||||
|     { | ||||
|         return String(this).replace(/^\s+|\s+$/g, ''); | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| export async function test_file_accessibility(update_file: string) : Promise<string[]> { | ||||
|     if(os.platform() === "win32") | ||||
|         return []; /* within windows the update installer request admin privileges if required */ | ||||
| 
 | ||||
|     const original_fs = require('original-fs'); | ||||
|     if(!fs.existsSync(update_file)) | ||||
|         throw "Missing update file (" + update_file + ")"; | ||||
| 
 | ||||
|     let parent_path = app.getAppPath(); | ||||
|     if(parent_path.endsWith(".asar")) { | ||||
|         parent_path = path.join(parent_path, "..", ".."); | ||||
|         parent_path = fs.realpathSync(parent_path); | ||||
|     } | ||||
| 
 | ||||
|     const test_access = async (file: string, mode: number) => { | ||||
|         return await new Promise<NodeJS.ErrnoException>(resolve => original_fs.access(file, mode, resolve)); | ||||
|     }; | ||||
| 
 | ||||
|     let code = await test_access(update_file, original_fs.constants.R_OK); | ||||
|     if(code) | ||||
|         throw "Failed test read for update file. (" + update_file + " results in " + code.code + ")"; | ||||
| 
 | ||||
|     const fstream = original_fs.createReadStream(update_file); | ||||
|     const tar_stream = tar.extract(); | ||||
| 
 | ||||
|     const errors: string[] = []; | ||||
|     const tester = async (header: Headers) => { | ||||
|         const entry_path = path.normalize(path.join(parent_path, header.name)); | ||||
|         if(header.type == "file") { | ||||
|             if(original_fs.existsSync(entry_path)) { | ||||
|                 code = await test_access(entry_path, original_fs.constants.W_OK); | ||||
|                 if(code) | ||||
|                     errors.push("Failed to acquire write permissions for file " + entry_path + " (Code " + code.code + ")"); | ||||
|             } else { | ||||
|                 let directory = path.dirname(entry_path); | ||||
|                 while(directory.length != 0 && !original_fs.existsSync(directory)) | ||||
|                     directory = path.normalize(path.join(directory, "..")); | ||||
| 
 | ||||
|                 code = await test_access(directory, original_fs.constants.W_OK); | ||||
|                 if(code) errors.push("Failed to acquire write permissions for directory " + entry_path + " (Code " + code.code + ". Target directory " + directory + ")"); | ||||
|             } | ||||
|         } else if(header.type == "directory") { | ||||
|             let directory = path.dirname(entry_path); | ||||
|             while(directory.length != 0 && !original_fs.existsSync(directory)) | ||||
|                 directory = path.normalize(path.join(directory, "..")); | ||||
| 
 | ||||
|             code = await test_access(directory, original_fs.constants.W_OK); | ||||
|             if(code) errors.push("Failed to acquire write permissions for directory " + entry_path + " (Code " + code.code + ". Target directory " + directory + ")"); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     tar_stream.on('entry', (header: Headers, stream, next) => { | ||||
|         tester(header).catch(error => { | ||||
|             console.log("Emit out of tar_stream.on('entry' ...)"); | ||||
|             tar_stream.emit('error', error); | ||||
|         }).then(() => { | ||||
|             stream.on('end', next); | ||||
|             stream.resume(); | ||||
|         }); | ||||
|     }); | ||||
| 
 | ||||
|     fstream.pipe(tar_stream); | ||||
|     try { | ||||
|         await new Promise((resolve, reject) => { | ||||
|             tar_stream.on('finish', resolve); | ||||
|             tar_stream.on('error', error => { reject(error); }); | ||||
|         }); | ||||
|     } catch(error) { | ||||
|         throw "Failed to list files within tar: " + error; | ||||
|     } | ||||
| 
 | ||||
|     return errors; | ||||
| } | ||||
| 
 | ||||
| namespace install_config { | ||||
|     export interface LockFile { | ||||
|         filename: string; | ||||
|         timeout: number; | ||||
|         "error-id": string; | ||||
|     } | ||||
|     export interface MoveFile { | ||||
|         source: string; | ||||
|         target: string; | ||||
|         "error-id": string; | ||||
|     } | ||||
|     export interface ConfigFile { | ||||
|         version: number; | ||||
| 
 | ||||
|         backup: boolean; | ||||
|         "backup-directory": string; | ||||
| 
 | ||||
|         "callback_file": string; | ||||
|         "callback_argument_fail": string; | ||||
|         "callback_argument_success": string; | ||||
| 
 | ||||
|         moves: MoveFile[]; | ||||
|         locks: LockFile[]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| async function build_install_config(source_root: string, target_root: string) : Promise<install_config.ConfigFile> { | ||||
|     console.log("Building update install config for target directory: %s. Update source: %o", target_root, source_root); | ||||
|     const result: install_config.ConfigFile = { } as any; | ||||
| 
 | ||||
|     result.version = 1; | ||||
| 
 | ||||
|     result.backup = true; | ||||
| 
 | ||||
|     { | ||||
|         const data = path.parse(source_root); | ||||
|         result["backup-directory"] = path.join(data.dir, data.name + "_backup"); | ||||
|     } | ||||
| 
 | ||||
|     result.callback_file = app.getPath("exe"); | ||||
|     result.callback_argument_fail = "--no-single-instance --update-failed-new="; | ||||
|     result.callback_argument_success = "--no-single-instance --update-succeed-new="; | ||||
| 
 | ||||
|     result.moves = []; | ||||
|     result.locks = [ | ||||
|         { | ||||
|             "error-id": "main-exe-lock", | ||||
|             filename: app.getPath("exe"), | ||||
|             timeout: 5000 | ||||
|         } | ||||
|     ]; | ||||
| 
 | ||||
|     const ignore_list = [ | ||||
|         "update-installer.exe", "update-installer" | ||||
|     ]; | ||||
| 
 | ||||
|     const dir_walker = async (relative_path: string) => { | ||||
|         const source_directory = path.join(source_root, relative_path); | ||||
|         const target_directory = path.join(target_root, relative_path); | ||||
| 
 | ||||
|         let files: string[]; | ||||
|         try { | ||||
|             files = await util.promisify(ofs.readdir)(source_directory); | ||||
|         } catch(error) { | ||||
|             console.warn("Failed to iterate over source directory \"%s\": %o", source_directory, error); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         for(const file of files) { | ||||
|             let _exclude = false; | ||||
|             for(const exclude of ignore_list) { | ||||
|                 if(exclude == file) { | ||||
|                     console.debug("Ignoring file to update (%s/%s)", relative_path, file); | ||||
|                     _exclude = true; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if(_exclude) | ||||
|                 continue; | ||||
| 
 | ||||
|             const source_file = path.join(source_directory, file); | ||||
|             const target_file = path.join(target_directory, file); | ||||
| 
 | ||||
|             //TODO check if file content has changed else ignore?
 | ||||
| 
 | ||||
|             const info = await util.promisify(ofs.stat)(source_file); | ||||
|             if(info.isDirectory()) { | ||||
|                 await dir_walker(path.join(relative_path, file)); | ||||
|             } else { | ||||
|                 /* TODO: ensure its a file! */ | ||||
|                 result.moves.push({ | ||||
|                     "error-id": "move-file-" + result.moves.length, | ||||
|                     source: source_file, | ||||
|                     target: target_file | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     await dir_walker("."); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| export async function execute_update(update_file: string, restart_callback: (callback: () => void) => any) : Promise<void> { | ||||
|     let application_path = app.getAppPath(); | ||||
|     if(application_path.endsWith(".asar")) { | ||||
|         console.log("App path points to ASAR file (Going up to root directory)"); | ||||
|         application_path = await fs.realpath(path.join(application_path, "..", "..")); | ||||
|     } else if(await fs.pathExists(application_path) && (await fs.stat(application_path)).isFile()) | ||||
|         application_path = path.dirname(application_path); | ||||
| 
 | ||||
|     console.log("Located target app path: %s", application_path); | ||||
|     console.log("Using update file: %s", update_file); | ||||
| 
 | ||||
|     const temp_directory = path.join(app.getPath("temp"), "teaclient_update_" + Math.random().toString(36).substring(7)); | ||||
|     { | ||||
|         console.log("Preparing update source directory at %s", temp_directory); | ||||
|         try { | ||||
|             await fs.mkdirp(temp_directory) | ||||
|         } catch(error) { | ||||
|             console.error("failed to create update source directory: %o", error); | ||||
|             throw "failed to create update source directory"; | ||||
|         } | ||||
| 
 | ||||
|         const source = fs.createReadStream(update_file); | ||||
|         const extract = tar.extract(); | ||||
| 
 | ||||
|         extract.on('entry', (header: Headers, stream: PassThrough, callback) => { | ||||
|             const extract = async (header: Headers, stream: PassThrough) => { | ||||
|                 const target_file = path.join(temp_directory, header.name); | ||||
|                 console.debug("Extracting entry %s of type %s to %s", header.name, header.type, target_file); | ||||
| 
 | ||||
|                 if(header.type == "directory") { | ||||
|                     await fs.mkdirp(target_file); | ||||
|                 } else if(header.type == "file") { | ||||
|                     { | ||||
|                         const directory = path.parse(target_file).dir; | ||||
|                         console.debug("Testing for directory: %s", directory); | ||||
|                         if(!(await util.promisify(ofs.exists)(directory)) || !(await util.promisify(ofs.stat)(directory)).isDirectory()) { | ||||
|                             console.log("Creating directory %s", directory); | ||||
|                             try { | ||||
|                                 await fs.mkdirp(directory); | ||||
|                             } catch(error) { | ||||
|                                 console.warn("failed to create directory for file %s", header.type); | ||||
|                             } | ||||
|                         } | ||||
| 
 | ||||
|                     } | ||||
|                     const write_stream = ofs.createWriteStream(target_file); | ||||
|                     try { | ||||
|                         await new Promise((resolve, reject) => { | ||||
|                             stream.pipe(write_stream) | ||||
|                                 .on('error', reject) | ||||
|                                 .on('finish', resolve); | ||||
|                         }); | ||||
|                         return; /* success */ | ||||
|                     } catch(error) { | ||||
|                         console.error("Failed to extract update file %s: %o", header.name, error); | ||||
|                     } | ||||
|                 } else { | ||||
|                     console.debug("Skipping this unknown file type"); | ||||
|                 } | ||||
|                 stream.resume(); /* drain the stream */ | ||||
|             }; | ||||
|             extract(header, stream).catch(error => { | ||||
|                 console.log("Ignoring file %s due to an error: %o", header.name, error); | ||||
|             }).then(() => { | ||||
|                 callback(); | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
| 
 | ||||
|         source.pipe(extract); | ||||
|         try { | ||||
|             await new Promise((resolve, reject) => { | ||||
|                 extract.on('finish', resolve); | ||||
|                 extract.on('error', reject); | ||||
|             }); | ||||
|         } catch(error) { | ||||
|             console.error("Failed to unpack update: %o", error); | ||||
|             throw "update unpacking failed"; | ||||
|         } | ||||
|     } | ||||
|     /* the "new" environment should now be available at 'temp_directory' */ | ||||
|     console.log("Update unpacked successfully. Building update extractor file."); | ||||
| 
 | ||||
|     let install_config; | ||||
|     try { | ||||
|         install_config = await build_install_config(temp_directory, application_path); | ||||
|     } catch(error) { | ||||
|         console.error("Failed to build update installer config: %o", error); | ||||
|         throw "failed to build update installer config"; | ||||
|     } | ||||
| 
 | ||||
|     const log_file = path.join(temp_directory, "update-log.txt"); | ||||
|     const config_file = path.join(temp_directory, "update_install.json"); | ||||
|     console.log("Writing config to %s", config_file); | ||||
|     try { | ||||
|         await fs.writeJSON(config_file, install_config); | ||||
|     } catch(error) { | ||||
|         console.error("Failed to write update install config file: %s", error); | ||||
|         throw "failed to write update install config file"; | ||||
|     } | ||||
| 
 | ||||
|     const update_installer = path.join(application_path, "update-installer" + (os.platform() === "win32" ? ".exe" : "")); | ||||
|     if(!(await fs.pathExists(update_installer))) { | ||||
|         console.error("Missing update installer! Supposed to be at %s", update_installer); | ||||
|         throw "Missing update installer!"; | ||||
|     } else { | ||||
|         console.log("Using update installer located at %s", update_installer); | ||||
|     } | ||||
| 
 | ||||
|     if(os.platform() == "linux") { | ||||
|         console.log("Executing update install on linux"); | ||||
| 
 | ||||
|         //We have to unpack it later
 | ||||
|         const rest_callback = () => { | ||||
|             console.log("Executing command %s with args %o", update_installer, [log_file, config_file]); | ||||
|             try { | ||||
|                 let result = child_process.spawnSync(update_installer, [log_file, config_file]); | ||||
|                 if(result.status != 0) { | ||||
|                     console.error("Failed to execute update installer! Return code: %d", result.status); | ||||
|                     dialog.showMessageBox({ | ||||
|                         buttons: ["update now", "remind me later"], | ||||
|                         title: "Update available", | ||||
|                         message: | ||||
|                             "Failed to execute update installer\n" + | ||||
|                             "Installer exited with code " + result.status | ||||
|                     } as MessageBoxOptions); | ||||
|                 } | ||||
|             } catch(error) { | ||||
|                 console.error("Failed to execute update installer (%o)", error); | ||||
|                 if("errno" in error) { | ||||
|                     const errno = error as ErrnoException; | ||||
|                     if(errno.errno == EPERM) { | ||||
|                         dialog.showMessageBox({ | ||||
|                             buttons: ["quit"], | ||||
|                             title: "Update execute failed", | ||||
|                             message: "Failed to execute update installer. (No permissions)\nPlease execute the client with admin privileges!" | ||||
|                         } as MessageBoxOptions); | ||||
|                         return; | ||||
|                     } | ||||
|                     dialog.showMessageBox({ | ||||
|                         buttons: ["quit"], | ||||
|                         title: "Update execute failed", | ||||
|                         message: "Failed to execute update installer.\nError: " + errno.message | ||||
|                     } as MessageBoxOptions); | ||||
|                     return; | ||||
|                 } | ||||
|                 dialog.showMessageBox({ | ||||
|                     buttons: ["quit"], | ||||
|                     title: "Update execute failed", | ||||
|                     message: "Failed to execute update installer.\nLookup console for more detail" | ||||
|                 } as MessageBoxOptions); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if(electron.app.hasSingleInstanceLock()) | ||||
|                 electron.app.releaseSingleInstanceLock(); | ||||
| 
 | ||||
|             const ids = child_process.execSync("pgrep TeaClient").toString().split(os.EOL).map(e => e.trim()).reverse().join(" "); | ||||
|             console.log("Executing %s", "kill -9 " + ids); | ||||
|             child_process.execSync("kill -9 " + ids); | ||||
|         }; | ||||
|         restart_callback(rest_callback); | ||||
|     } else { | ||||
|         console.log("Executing update install on windows"); | ||||
| 
 | ||||
|         //We have to unpack it later
 | ||||
|         const rest_callback = () => { | ||||
|             let pipe = child_process.spawn(update_installer, [log_file, config_file], { | ||||
|                 detached: true, | ||||
|                 cwd: application_path, | ||||
|                 stdio: 'ignore', | ||||
|             }); | ||||
|             pipe.unref(); | ||||
|             app.quit(); | ||||
|         }; | ||||
|         restart_callback(rest_callback); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function current_version() : Promise<Version> { | ||||
|     if(process_args.has_value(Arguments.UPDATER_LOCAL_VERSION)) | ||||
|         return parse_version(process_args.value(Arguments.UPDATER_LOCAL_VERSION)); | ||||
| 
 | ||||
|     let parent_path = app.getAppPath(); | ||||
|     if(parent_path.endsWith(".asar")) { | ||||
|         parent_path = path.join(parent_path, "..", ".."); | ||||
|         parent_path = fs.realpathSync(parent_path); | ||||
|     } | ||||
|     try { | ||||
|         const info = await fs.readJson(path.join(parent_path, "app_version.json")); | ||||
|         let result = parse_version(info["version"]); | ||||
|         result.timestamp = info["timestamp"]; | ||||
|         return result; | ||||
|     } catch (error) { | ||||
|         console.log("Got no version!"); | ||||
|         return new Version(0, 0, 0, 0, 0); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| async function minawait<T>(object: Promise<T>, time: number) : Promise<T> { | ||||
|     const begin = Date.now(); | ||||
|     const r = await object; | ||||
|     const end = Date.now(); | ||||
|     if(end - begin < time) | ||||
|         await new Promise(resolve => setTimeout(resolve, time + begin - end)); | ||||
|     return r; | ||||
| } | ||||
| 
 | ||||
| export let update_restart_pending = false; | ||||
| export async function execute_graphical(channel: string, ask_install: boolean) : Promise<Boolean> { | ||||
|     const electron = require('electron'); | ||||
| 
 | ||||
|     const ui_debug = process_args.has_flag(Arguments.UPDATER_UI_DEBUG); | ||||
|     const window = new electron.BrowserWindow({ | ||||
|         show: false, | ||||
|         width: ui_debug ? 1200 : 600, | ||||
|         height: ui_debug ? 800 : 400, | ||||
| 
 | ||||
|         webPreferences: { | ||||
|             devTools: true, | ||||
|             nodeIntegration: true, | ||||
|             javascript: true | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     window.loadFile(path.join(path.dirname(module.filename), "ui", "index.html")); | ||||
|     if(ui_debug) { | ||||
|         window.webContents.openDevTools(); | ||||
|     } | ||||
|     await new Promise(resolve => window.on('ready-to-show', resolve)); | ||||
|     window.show(); | ||||
|     await winmgr.apply_bounds('update-installer', window); | ||||
|     winmgr.track_bounds('update-installer', window); | ||||
| 
 | ||||
|     const current_vers = await current_version(); | ||||
|     console.log("Current version: " + current_vers.toString(true)); | ||||
| 
 | ||||
|     console.log("Showed"); | ||||
|     const set_text = text => window.webContents.send('status-update-text', text); | ||||
|     const set_error = text => window.webContents.send('status-error', text); | ||||
|     const set_progress = progress => window.webContents.send('status-update', progress); | ||||
|     const await_exit = () => { return new Promise(resolve => window.on('closed', resolve))}; | ||||
|     const await_version_confirm = version => { | ||||
|         const id = "version-accept-" + Date.now(); | ||||
|         window.webContents.send('status-confirm-update', id, current_vers, version); | ||||
|         return new Promise((resolve, reject) => { | ||||
|             window.on('closed', () => resolve(false)); | ||||
|             ipcMain.once(id, (event, result) => { | ||||
|                 console.log("Got response %o", result); | ||||
|                 resolve(result); | ||||
|             }); | ||||
|         }); | ||||
|     }; | ||||
|     const await_confirm_execute = () => { | ||||
|         const id = "status-confirm-execute-" + Date.now(); | ||||
|         window.webContents.send('status-confirm-execute', id); | ||||
|         return new Promise((resolve, reject) => { | ||||
|             window.on('closed', () => resolve(false)); | ||||
|             ipcMain.once(id, (event, result) => { | ||||
|                 console.log("Got response %o", result); | ||||
|                 resolve(result); | ||||
|             }); | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     set_text("Loading data"); | ||||
|     let version: UpdateVersion; | ||||
|     try { | ||||
|         version = await minawait(newest_version(process_args.has_flag(Arguments.UPDATER_ENFORCE) ? undefined : current_vers, channel), 3000); | ||||
|     } catch (error) { | ||||
|         set_error("Failed to get newest information:<br>" + error); | ||||
|         await await_exit(); | ||||
|         return false; | ||||
|     } | ||||
|     console.log("Got version %o", version); | ||||
| 
 | ||||
|     if(!version) { | ||||
|         set_error("You're already on the newest version!"); | ||||
|         await await_exit(); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if(ask_install) { | ||||
|         try { | ||||
|             const test = await await_version_confirm(version.version); | ||||
|             if(!test) { | ||||
|                 window.close(); | ||||
|                 return false; | ||||
|             } | ||||
|         } catch (error) { | ||||
|             console.dir(error); | ||||
|             window.close(); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     set_text("Updating to version " + version.version.toString() + "<br>Downloading...."); | ||||
|     let update_path: string; | ||||
|     try { | ||||
|         update_path = await download_version(version.channel, version.version, status => { setImmediate(set_progress, status.percent); }); | ||||
|     } catch (error) { | ||||
|         set_error("Failed to download version: <br>" + error); | ||||
|         console.error(error); | ||||
|         await await_exit(); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|         const inaccessible = await test_file_accessibility(update_path); | ||||
|         if(inaccessible.length > 0) { | ||||
|             console.log("Failed to access the following files:"); | ||||
|             for(const fail of inaccessible) | ||||
|                 console.log(" - " + fail); | ||||
| 
 | ||||
|             if(os.platform() == "linux") { | ||||
|                 set_error("Failed to access target files.<br>Please execute this app with administrator (sudo) privileges.<br>Use the following command:<br><p>" + | ||||
|                     "sudo " + path.normalize(app.getAppPath()) + " --update-execute=\"" + path.normalize(update_path) + "\"</p>"); | ||||
|                 await await_exit(); | ||||
|                 return false; | ||||
|             } else if(os.platform() == "win32") { | ||||
|                 /* the updater asks for admin rights anyway :/ */ | ||||
|             } | ||||
|         } | ||||
|     } catch(error) { | ||||
|         set_error("Failed to access target files.<br>You may need to execute the TeaClient as Administrator!<br>Error: " + error); | ||||
|         await await_exit(); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if(!await await_confirm_execute()) { | ||||
|         window.close(); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     set_text("Extracting update installer...<br>Please wait"); | ||||
|     try { | ||||
|         await extract_updater(update_path); | ||||
|     } catch(error) { | ||||
|         console.error("Failed to update the updater! (%o)", error); | ||||
|         set_error("Failed to update the update installer.\nUpdate failed!"); | ||||
|         await await_exit(); | ||||
|         return false; | ||||
|     } | ||||
|     set_text("Executing update...<br>Please wait"); | ||||
| 
 | ||||
|     try { | ||||
|         await execute_update(update_path, callback => { | ||||
|             _main_windows.set_prevent_instant_close(true); | ||||
|             update_restart_pending = true; | ||||
|             window.close(); | ||||
|             callback(); | ||||
|         }); | ||||
|     } catch (error) { | ||||
|         dialog.showErrorBox("Update error", "Failed to execute update!\n" + error); | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| export let update_question_open = false; | ||||
| async function check_update(channel: string) { | ||||
|     let version: UpdateVersion; | ||||
|     try { | ||||
|         version = await newest_version(await current_version(), channel); | ||||
|     } catch(error) { | ||||
|         console.warn("failed check for newer versions!"); | ||||
|         console.error(error); | ||||
|         return; | ||||
|     } | ||||
|     if(version) { | ||||
|         update_question_open = true; | ||||
|         dialog.showMessageBox({ | ||||
|             buttons: ["update now", "remind me later"], | ||||
|             title: "TeaClient: Update available", | ||||
|             message: | ||||
|             "There is an update available!\n" + | ||||
|             "Should we update now?\n" + | ||||
|             "\n" + | ||||
|             "Current version: " + (await current_version()).toString() + "\n" + | ||||
|             "Target version: " + version.version.toString() | ||||
|         } as MessageBoxOptions).then(result => { | ||||
|             if(result.response == 0) { | ||||
|                 execute_graphical(channel, false).then(() => { | ||||
|                     update_question_open = false; | ||||
|                 }); | ||||
|             } else { | ||||
|                 update_question_open = false; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| let update_task: Timer; | ||||
| export function start_auto_update_check() { | ||||
|     if(update_task) return; | ||||
|     update_task = setInterval(check_update, 2 * 60 * 60 * 1000); | ||||
|     setImmediate(check_update); | ||||
| } | ||||
| 
 | ||||
| export function stop_auto_update_check() { | ||||
|     clearInterval(update_task); | ||||
|     update_task = undefined; | ||||
| } | ||||
| 
 | ||||
| export async function selected_channel() : Promise<string> { | ||||
|     return process_args.has_value(Arguments.UPDATER_CHANNEL) ? process_args.value(Arguments.UPDATER_CHANNEL) : "release"; | ||||
| } | ||||
| @ -1,9 +0,0 @@ | ||||
| .page { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .info { | ||||
|   display: block; | ||||
| } | ||||
| 
 | ||||
| /*# sourceMappingURL=index.css.map */ | ||||
| @ -1 +0,0 @@ | ||||
| {"version":3,"sourceRoot":"","sources":["index.scss"],"names":[],"mappings":"AAAA;EACC;;;AAGD;EACC","file":"index.css"} | ||||
| @ -1,36 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|     <head> | ||||
|         <meta charset="UTF-8"> | ||||
|         <title>Updating app</title> | ||||
| 
 | ||||
|         <script type="application/javascript"> | ||||
|             const exports = {}; | ||||
|         </script> | ||||
|         <script src="index.js" type="application/javascript"></script> | ||||
|         <link rel="stylesheet" href="index.css"> | ||||
|     </head> | ||||
|     <body> | ||||
|         <div class="page info"> | ||||
|             <div class="state">Downloading update. Please wait!</div> | ||||
|             <progress class="progress" value="0" max="100"></progress> | ||||
|         </div> | ||||
|         <div class="page error"> | ||||
|             <div class="message"></div> | ||||
|         </div> | ||||
|         <div class="page confirm-restart"> | ||||
|             Download succeeded.<br> | ||||
|             Update installation requires client restart!<br> | ||||
|             Please ensure that <b>all instances</b> are closed!<br> | ||||
|             If not this app would kill them!<br> | ||||
|             <button class="button-execute">Execute update</button> | ||||
|         </div> | ||||
|         <div class="page config-update"> | ||||
|             Update available.<br> | ||||
|             Current version: <a class="current-version"></a><br> | ||||
|             Target version: <a class="target-version"></a><br> | ||||
|             <button class="button-update">update</button> | ||||
|             <button class="button-cancel">cancel</button> | ||||
|         </div> | ||||
|     </body> | ||||
| </html> | ||||
| @ -1,7 +0,0 @@ | ||||
| .page { | ||||
| 	display: none; | ||||
| } | ||||
| 
 | ||||
| .info { | ||||
| 	display: block; | ||||
| } | ||||
| @ -1,51 +0,0 @@ | ||||
| import * as electron from "electron"; | ||||
| import * as $ from "jquery"; | ||||
| import {Version} from "../../../shared/version"; | ||||
| 
 | ||||
| electron.ipcRenderer.on('status-update', (event, progress) => { | ||||
|     if(!$(".info").is(":visible")) { | ||||
|         $(".page").hide(); | ||||
|         $(".info").show() | ||||
|     } | ||||
|     $(".progress").attr("value", progress * 100); | ||||
| }); | ||||
| 
 | ||||
| electron.ipcRenderer.on('status-update-text', (event, text) => { | ||||
|     if(!$(".info").is(":visible")) { | ||||
|         $(".page").hide(); | ||||
|         $(".info").show() | ||||
|     } | ||||
|     $(".state").html(text); | ||||
| }); | ||||
| 
 | ||||
| electron.ipcRenderer.on('status-error', (event, text) => { | ||||
|     console.log("Got error %s", text); | ||||
|     $(".page").hide(); | ||||
|     $(".error").show().html(text); | ||||
| }); | ||||
| electron.ipcRenderer.on('status-confirm-execute', (event, callback_name) => { | ||||
|     $(".page").hide(); | ||||
|     $(".confirm-restart").show(); | ||||
|     $(".button-execute").on('click', event => electron.ipcRenderer.send(callback_name, true)) | ||||
| }); | ||||
| electron.ipcRenderer.on('status-confirm-update', (event, callback_name, current: Version, version: Version) => { | ||||
|     console.dir(callback_name); | ||||
|     console.dir(version); | ||||
| 
 | ||||
|     $(".page").hide(); | ||||
|     $(".config-update").show(); | ||||
| 
 | ||||
|     $(".target-version").text(version.major + "." + version.minor + "." + version.patch + (version.build > 0 ? " (" + version.build + ")" : "")); | ||||
|     $(".current-version").text(current.major + "." + current.minor + "." + current.patch + (current.build > 0 ? " (" + current.build + ")" : "")); | ||||
| 
 | ||||
|     $(".button-update").on('click', event => electron.ipcRenderer.send(callback_name, true)); | ||||
|     $(".button-cancel").on('click', event => electron.ipcRenderer.send(callback_name, false)); | ||||
| }); | ||||
| 
 | ||||
| /* | ||||
|     const set_text = text => window.webContents.send('status-update-text', text); | ||||
|     const set_error = text => window.webContents.send('status-error', text); | ||||
|     const set_confirm_restart = () => window.webContents.send('status-confirm-restart'); | ||||
|     const set_progress = progress => window.webContents.send('status-update', progress); | ||||
|     const await_exit = () => { return new Promise(resolve => window.on('closed', resolve))}; | ||||
|  */ | ||||
| @ -1,209 +0,0 @@ | ||||
| // Quit when all windows are closed.
 | ||||
| import * as electron from "electron"; | ||||
| import * as app_updater from "./app-updater"; | ||||
| 
 | ||||
| import { app } from "electron"; | ||||
| import MessageBoxOptions = electron.MessageBoxOptions; | ||||
| 
 | ||||
| import {process_args, parse_arguments, Arguments} from "../shared/process-arguments"; | ||||
| import {open as open_changelog} from "./app-updater/changelog"; | ||||
| import * as crash_handler from "../crash_handler"; | ||||
| import {open_preview} from "./url-preview"; | ||||
| 
 | ||||
| async function execute_app() { | ||||
|     /* legacy, will be removed soon */ | ||||
|     if(process_args.has_value("update-failed")) { | ||||
|         const result = await electron.dialog.showMessageBox({ | ||||
|             type: "error", | ||||
|             message: "Failed to execute update:\n" + process_args.value("update-failed"), | ||||
|             title: "Update failed!", | ||||
|             buttons: ["retry", "ignore"] | ||||
|         } as MessageBoxOptions); | ||||
|         if(result.response == 0) | ||||
|             if(await app_updater.execute_graphical(await app_updater.selected_channel(), false)) | ||||
|                 return; | ||||
|     } else if(process_args.has_value("update-succeed")) { | ||||
|         const result = await electron.dialog.showMessageBox({ | ||||
|             type: "info", | ||||
|             message: "Update successfully installed!\nShould we launch TeaClient?", | ||||
|             title: "Update succeeded!", | ||||
|             buttons: ["yes", "no"] | ||||
|         } as MessageBoxOptions); | ||||
|         if(result.response != 0) { | ||||
|             electron.app.exit(0); | ||||
|             return; //Not really required here!
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     if(process_args.has_value("update-execute")) { | ||||
|         console.log("Executing update " + process_args.value("update-execute")); | ||||
|         await app_updater.execute_update(process_args.value("update-execute"), callback => { | ||||
|             console.log("Update preconfig successful. Extracting update. (The client should start automatically)"); | ||||
|             app.quit(); | ||||
|             setImmediate(callback); | ||||
|         }); | ||||
|         return; | ||||
|     } else if(process_args.has_value("update-failed-new") || process_args.has_value("update-succeed-new")) { | ||||
|         const success = process_args.has_value("update-succeed-new"); | ||||
|         let data: { | ||||
|             parse_success: boolean; | ||||
|             log_file?: string; | ||||
|             error_id?: string; | ||||
|             error_message?: string; | ||||
|         } = { | ||||
|             parse_success: false | ||||
|         }; | ||||
|         try { | ||||
|             let encoded_data = Buffer.from(process_args.value("update-failed-new") || process_args.value("update-succeed-new"), "base64").toString(); | ||||
|             for(const part of encoded_data.split(";")) { | ||||
|                 const index = part.indexOf(':'); | ||||
|                 if(index == -1) | ||||
|                     data[part] = true; | ||||
|                 else { | ||||
|                     data[part.substr(0, index)] = Buffer.from(part.substr(index + 1), "base64").toString(); | ||||
|                 } | ||||
|             } | ||||
|             data.parse_success = true; | ||||
|         } catch($) { | ||||
|             console.error($); | ||||
|         } | ||||
|         console.log("Update success: %o. Update data: %o", success, data); | ||||
| 
 | ||||
|         let title = ""; | ||||
|         let type = ""; | ||||
|         let message = ""; | ||||
| 
 | ||||
|         const buttons: ({ | ||||
|             key: string, | ||||
|             callback: () => Promise<boolean> | ||||
|         })[] = []; | ||||
| 
 | ||||
|         if(success) { | ||||
|             open_changelog(); | ||||
| 
 | ||||
|             type = "info"; | ||||
|             title = "Update succeeded!"; | ||||
| 
 | ||||
|             message = "Update has been successfully installed!\nWhat do you want to do next?"; | ||||
| 
 | ||||
|             buttons.push({ | ||||
|                 key: "Launch client", | ||||
|                 callback: async () => false | ||||
|             }); | ||||
|             if(data.parse_success && data.log_file) { | ||||
|                 buttons.push({ | ||||
|                     key: "Open update log", | ||||
|                     callback: async () => { | ||||
|                         electron.shell.openItem(data.log_file); | ||||
|                         return true; | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         } else { | ||||
|             type = "error"; | ||||
|             title = "Update failed!"; | ||||
| 
 | ||||
|             message = "Failed to install update."; | ||||
|             if(data.parse_success) { | ||||
|                 message += "\n\n"; | ||||
|                 message += "Error ID:      " + (data.error_id || "undefined") + "\n"; | ||||
|                 message += "Error Message: " + (data.error_message || "undefined") + "\n"; | ||||
|                 message += "Installer log: " + (data.log_file || "undefined"); | ||||
|             } else { | ||||
|                 message += "\nUnknown error! Lookup the console for more details."; | ||||
|             } | ||||
| 
 | ||||
|             buttons.push({ | ||||
|                 key: "Ignore", | ||||
|                 callback: async () => false | ||||
|             }); | ||||
|             buttons.push({ | ||||
|                 key: "Retry update", | ||||
|                 callback: async () => { | ||||
|                     await app_updater.execute_graphical(await app_updater.selected_channel(), false); | ||||
|                     return true; | ||||
|                 } | ||||
|             }); | ||||
|             if(data.parse_success && data.log_file) { | ||||
|                 buttons.push({ | ||||
|                     key: "Open update log", | ||||
|                     callback: async () => { | ||||
|                         electron.shell.openItem(data.log_file); | ||||
|                         return true; | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|         buttons.push({ | ||||
|             key: "Close", | ||||
|             callback: async () => true | ||||
|         }); | ||||
| 
 | ||||
|         const result = await  electron.dialog.showMessageBox({ | ||||
|             type: type, | ||||
|             message: message, | ||||
|             title: title, | ||||
|             buttons: buttons.map(e => e.key) | ||||
|         } as MessageBoxOptions); | ||||
|         if(buttons[result.response].callback) { | ||||
|             if(await buttons[result.response].callback()) | ||||
|                 return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|         { | ||||
|             const version = await app_updater.current_version(); | ||||
|             global["app_version_client"] = version.toString(); | ||||
|         } | ||||
|         const main = require("./main_window"); | ||||
|         main.execute(); | ||||
| 
 | ||||
|         app_updater.start_auto_update_check(); | ||||
|     } catch (error) { | ||||
|         console.dir(error); | ||||
|         const result = electron.dialog.showMessageBox({ | ||||
|             type: "error", | ||||
|             message: "Failed to execute app main!\n" + error, | ||||
|             title: "Main execution failed!", | ||||
|             buttons: ["close"] | ||||
|         } as MessageBoxOptions); | ||||
|         electron.app.exit(1); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function main() { | ||||
|     process.on('uncaughtException', err => { | ||||
|         console.error(err, 'Uncaught Exception thrown'); | ||||
|         console.dir(err); | ||||
|         process.exit(1); | ||||
|     }); | ||||
| 
 | ||||
|     /* | ||||
|     if(false) { | ||||
|         SegfaultHandler = require('segfault-handler'); | ||||
| 
 | ||||
|         SegfaultHandler.registerHandler("crash.log"); // With no argument, SegfaultHandler will generate a generic log file name
 | ||||
|     } | ||||
| 
 | ||||
|     const SegfaultHandler = require('segfault-handler'); | ||||
|     SegfaultHandler.registerHandler("crash.log"); // With no argument, SegfaultHandler will generate a generic log file name
 | ||||
|     */ | ||||
|     if(app) { //We're executed!
 | ||||
|         parse_arguments(); | ||||
|         if(process_args.has_value(Arguments.DISABLE_HARDWARE_ACCELERATION)) | ||||
|             app.disableHardwareAcceleration(); | ||||
|         if(process_args.has_value(Arguments.DUMMY_CRASH_MAIN)) | ||||
|             crash_handler.handler.crash(); | ||||
|         if(!process_args.has_value(Arguments.DEBUG) && !process_args.has_value(Arguments.NO_SINGLE_INSTANCE)) { | ||||
|             if(!app.requestSingleInstanceLock()) { | ||||
|                 console.log("Another instance is already running. Closing this instance"); | ||||
|                 app.exit(0); | ||||
|             } | ||||
|         } | ||||
|         app.on('ready', execute_app); | ||||
|     } | ||||
| } | ||||
| export const execute = main; | ||||
| @ -1,206 +0,0 @@ | ||||
| import {BrowserWindow, Menu, MenuItem, MessageBoxOptions, app, dialog} from "electron"; | ||||
| import * as electron from "electron"; | ||||
| import * as winmgr from "./window"; | ||||
| import * as path from "path"; | ||||
| 
 | ||||
| export let prevent_instant_close: boolean = true; | ||||
| export function set_prevent_instant_close(flag: boolean) { | ||||
|     prevent_instant_close = flag; | ||||
| } | ||||
| 
 | ||||
| export let is_debug: boolean; | ||||
| export let allow_dev_tools: boolean; | ||||
| 
 | ||||
| import {Arguments, parse_arguments, process_args} from "../shared/process-arguments"; | ||||
| import * as updater from "./app-updater"; | ||||
| import * as loader from "./ui-loader"; | ||||
| import * as crash_handler from "../crash_handler"; | ||||
| 
 | ||||
| // Keep a global reference of the window object, if you don't, the window will
 | ||||
| // be closed automatically when the JavaScript object is garbage collected.
 | ||||
| export let main_window: BrowserWindow = null; | ||||
| 
 | ||||
| function spawn_main_window(entry_point: string) { | ||||
|     // Create the browser window.
 | ||||
|     console.log("Spawning main window"); | ||||
|     main_window = new BrowserWindow({ | ||||
|         width: 800, | ||||
|         height: 600, | ||||
| 
 | ||||
|         minHeight: 600, | ||||
|         minWidth: 600, | ||||
| 
 | ||||
|         show: false, | ||||
|         webPreferences: { | ||||
|             webSecurity: false, | ||||
|             nodeIntegrationInWorker: true, | ||||
|             nodeIntegration: true | ||||
|         }, | ||||
|         icon: path.join(__dirname, "..", "..", "resources", "logo.ico") | ||||
|     }); | ||||
| 
 | ||||
|     main_window.webContents.on('devtools-closed', event => { | ||||
|         console.log("Dev tools destroyed!"); | ||||
|     }); | ||||
| 
 | ||||
|     main_window.on('closed', () => { | ||||
|         require("./url-preview").close(); | ||||
|         main_window = null; | ||||
|         prevent_instant_close = false; | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     main_window.loadFile(loader.ui.preloading_page(entry_point)); | ||||
| 
 | ||||
|     main_window.once('ready-to-show', () => { | ||||
|         main_window.show(); | ||||
|         winmgr.apply_bounds('main-window', main_window).then(() => { | ||||
|             winmgr.track_bounds('main-window', main_window); | ||||
| 
 | ||||
|             main_window.focus(); | ||||
|             loader.ui.cleanup(); | ||||
|             if(allow_dev_tools && !main_window.webContents.isDevToolsOpened()) | ||||
|                 main_window.webContents.openDevTools(); | ||||
|             prevent_instant_close = false; /* just to ensure that the client could be unloaded */ | ||||
|         }); | ||||
|     }); | ||||
| 
 | ||||
|     main_window.webContents.on('new-window', (event, url_str, frameName, disposition, options, additionalFeatures) => { | ||||
|         event.preventDefault(); | ||||
|         try { | ||||
|             let url: URL; | ||||
|             try { | ||||
|                 url = new URL(url_str); | ||||
|             } catch(error) { | ||||
|                 throw "failed to parse URL"; | ||||
|             } | ||||
| 
 | ||||
|             { | ||||
|                 let protocol = url.protocol.endsWith(":") ? url.protocol.substring(0, url.protocol.length - 1) : url.protocol; | ||||
|                 if(protocol !== "https" && protocol !== "http") { | ||||
|                     throw "invalid protocol (" + protocol + "). HTTP(S) are only supported!"; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             console.log("Got new window " + frameName); | ||||
|             const url_preview = require("./url-preview"); | ||||
|             url_preview.open_preview(url_str); | ||||
|         } catch(error) { | ||||
|             console.error("Failed to open preview window for URL %s: %o", url_str, error); | ||||
|             dialog.showErrorBox("Failed to open preview", "Failed to open preview URL: " + url_str + "\nError: " + error); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     main_window.webContents.on('crashed', event => { | ||||
|         console.error("UI thread crashed! Closing app!"); | ||||
|         if(!process_args.has_flag(Arguments.DEBUG)) { | ||||
|             main_window.close(); | ||||
|             prevent_instant_close = false; | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function handle_error(message: string) { | ||||
|     console.log("Caught loading error: %s", message); | ||||
|     //"A critical error happened while loading TeaClient!", "A critical error happened while loading TeaClient!<br>" + message
 | ||||
|     dialog.showMessageBox({ | ||||
|         type: "error", | ||||
|         buttons: ["exit"], | ||||
|         title: "A critical error happened while loading TeaClient!", | ||||
|         message: message | ||||
|     }); | ||||
|     loader.ui.cancel(); | ||||
| } | ||||
| 
 | ||||
| function init_listener() { | ||||
|     app.on('quit', () => { | ||||
|         console.debug("Finalizing crash handler"); | ||||
|         crash_handler.finalize_handler(); | ||||
|         console.log("RUNNING quit!"); | ||||
|         loader.cleanup(); | ||||
|         console.log("RUNNING quit 2!"); | ||||
|         loader.ui.cleanup(); | ||||
|         console.log("RUNNING quit done!"); | ||||
|     }); | ||||
| 
 | ||||
|     app.on('window-all-closed', () => { | ||||
|         console.log("RUNNING all win closed!"); | ||||
|         // On macOS it is common for applications and their menu bar
 | ||||
|         // to stay active until the user quits explicitly with Cmd + Q
 | ||||
|         if (process.platform !== 'darwin') { | ||||
|             if(!prevent_instant_close) { | ||||
|                 console.log("All windows have been closed, closing app."); | ||||
|                 app.quit(); | ||||
|             } else { | ||||
|                 console.log("All windows have been closed, but we dont want to quit instantly. Waiting 10 seconds if something happens"); | ||||
|                 setTimeout(() => { | ||||
|                     if(BrowserWindow.getAllWindows().length == 0) { | ||||
|                         console.log("All windows have been closed for over an minute. Exiting app!"); | ||||
|                         app.quit(); | ||||
|                     } | ||||
|                 }, 10 * 1000); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     app.on('activate', () => { | ||||
|         // On macOS it's common to re-create a window in the app when the
 | ||||
|         // dock icon is clicked and there are no other windows open.
 | ||||
|         if (main_window === null) { | ||||
|             //spawn_loading_screen();
 | ||||
|             //createWindow()
 | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { | ||||
|         console.log("Allowing untrusted certificate for %o", url); | ||||
|         event.preventDefault(); | ||||
|         callback(true); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function execute() { | ||||
|     console.log("Main app executed!"); | ||||
| 
 | ||||
|     parse_arguments(); | ||||
|     is_debug = process_args.has_flag(...Arguments.DEBUG); | ||||
|     allow_dev_tools = process_args.has_flag(...Arguments.DEV_TOOLS); | ||||
|     if(is_debug) { | ||||
|         console.log("Enabled debug!"); | ||||
|         console.log("Arguments: %o", process_args); | ||||
|     } | ||||
| 
 | ||||
|     Menu.setApplicationMenu(null); | ||||
|     init_listener(); | ||||
| 
 | ||||
|     console.log("Setting up render backend"); | ||||
|     require("./render-backend"); | ||||
| 
 | ||||
|     console.log("Spawn loading screen"); | ||||
|     loader.ui.execute_loader().then(async (entry_point: string) => { | ||||
|         /* test if the updater may have an update found */ | ||||
|         let awaiting_update_set = false; | ||||
|         while(updater.update_question_open) { | ||||
|             if(!awaiting_update_set) { | ||||
|                 awaiting_update_set = true; | ||||
|                 loader.ui.show_await_update(); | ||||
|                 console.log("Awaiting update stuff to be finished"); | ||||
|             } | ||||
|             await new Promise(resolve => setTimeout(resolve, 100)); | ||||
|         } | ||||
| 
 | ||||
|         if(updater.update_restart_pending) | ||||
|             return undefined; | ||||
| 
 | ||||
|         return entry_point; | ||||
|     }).then((entry_point: string) => { | ||||
|         loader.ui.cleanup(); /* close the window */ | ||||
| 
 | ||||
|         if(entry_point) //has not been canceled
 | ||||
|             spawn_main_window(entry_point); | ||||
|         else { | ||||
|             console.warn("Missing entry point!"); | ||||
|         } | ||||
|     }).catch(handle_error); | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| import "./menu"; | ||||
| 
 | ||||
| import * as electron from "electron"; | ||||
| import ipcMain = electron.ipcMain; | ||||
| import BrowserWindow = electron.BrowserWindow; | ||||
| 
 | ||||
| import {open as open_changelog} from "../app-updater/changelog"; | ||||
| import * as updater from "../app-updater"; | ||||
| 
 | ||||
| ipcMain.on('basic-action', (event, action, ...args: any[]) => { | ||||
|     const window = BrowserWindow.fromWebContents(event.sender); | ||||
| 
 | ||||
|     if(action === "open-changelog") { | ||||
|         open_changelog(); | ||||
|     } else if(action === "check-native-update") { | ||||
|         updater.selected_channel().then(channel => updater.execute_graphical(channel, true)); | ||||
|     } else if(action === "open-dev-tools") { | ||||
|         window.webContents.openDevTools(); | ||||
|     } else if(action === "reload-window") { | ||||
|         window.reload(); | ||||
|     } | ||||
| }); | ||||
| @ -1,34 +0,0 @@ | ||||
| import * as electron from "electron"; | ||||
| import ipcMain = electron.ipcMain; | ||||
| import BrowserWindow = electron.BrowserWindow; | ||||
| 
 | ||||
| ipcMain.on('top-menu', (event, menu_template: electron.MenuItemConstructorOptions[]) => { | ||||
|     const window = BrowserWindow.fromWebContents(event.sender); | ||||
| 
 | ||||
|     const process_template = (item: electron.MenuItemConstructorOptions) => { | ||||
|         if(typeof(item.icon) === "string" && item.icon.startsWith("data:")) | ||||
|             item.icon = electron.nativeImage.createFromDataURL(item.icon); | ||||
| 
 | ||||
|         item.click = () => window.webContents.send('top-menu', item.id); | ||||
|         for(const i of item.submenu as electron.MenuItemConstructorOptions[] || []) { | ||||
|             process_template(i); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     for(const m of menu_template) | ||||
|         process_template(m); | ||||
| 
 | ||||
|     try { | ||||
|         const menu = new electron.Menu(); | ||||
|         for(const m of menu_template) { | ||||
|             try { | ||||
|                 menu.append(new electron.MenuItem(m)); | ||||
|             } catch(error) { | ||||
|                 console.error("Failed to build menu entry: %o\nSource: %o", error, m); | ||||
|             } | ||||
|         } | ||||
|         window.setMenu(menu_template.length == 0 ? undefined : menu); | ||||
|     } catch(error) { | ||||
|         console.error("Failed to set window menu: %o", error); | ||||
|     } | ||||
| }); | ||||
| @ -1,147 +0,0 @@ | ||||
| import * as electron from "electron"; | ||||
| import * as path from "path"; | ||||
| import {screen} from "electron"; | ||||
| 
 | ||||
| import {Arguments, process_args} from "../../shared/process-arguments"; | ||||
| import * as loader from "./loader"; | ||||
| import * as updater from "../app-updater"; | ||||
| import * as winmgr from "../window"; | ||||
| import {main_window} from "../main_window"; | ||||
| 
 | ||||
| export namespace ui { | ||||
|     let gui: electron.BrowserWindow; | ||||
|     let promise: Promise<String>; | ||||
|     let resolve: any; | ||||
|     let reject: any; | ||||
| 
 | ||||
|     export function running() : boolean { | ||||
|         return promise !== undefined; | ||||
|     } | ||||
| 
 | ||||
|     export function cancel() : boolean { | ||||
|         if(resolve) | ||||
|             resolve(); | ||||
| 
 | ||||
|         cleanup(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     export function cleanup() { | ||||
|         if(gui) { | ||||
|             promise = undefined; | ||||
|             resolve = undefined; | ||||
| 
 | ||||
|             gui.destroy(); | ||||
|             gui = undefined; | ||||
| 
 | ||||
|             reject = error => { | ||||
|                 if(error) | ||||
|                     console.error("Received error from loader after it had been closed... Error: %o", error); | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async function load_files() { | ||||
|         const channel = await updater.selected_channel(); | ||||
|         try { | ||||
|             const entry_point = await loader.load_files(channel, (status, index) => { | ||||
|                 if(gui) { | ||||
|                     gui.webContents.send('progress-update', index); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             const resolved = () => { | ||||
|                 resolve(entry_point); | ||||
| 
 | ||||
|                 promise = undefined; | ||||
|                 resolve = undefined; | ||||
|                 reject = error => { | ||||
|                     if(error) | ||||
|                         console.error("Received error from loader after it had been closed... Error: %o", error); | ||||
|                 }; | ||||
|             }; | ||||
|             if(!process_args.has_flag(...Arguments.DISABLE_ANIMATION)) | ||||
|                 setTimeout(resolved, 250); | ||||
|             else | ||||
|                 setImmediate(resolved); | ||||
|         } catch (error) { | ||||
|             throw error; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     export function show_await_update() { | ||||
|         if(gui) | ||||
|             gui.webContents.send('await-update'); | ||||
|     } | ||||
| 
 | ||||
|     function spawn_gui() { | ||||
|         console.log("Spawn window!"); | ||||
|         let dev_tools = false; | ||||
| 
 | ||||
|         const WINDOW_WIDTH = 340 + (dev_tools ? 1000 : 0); | ||||
|         const WINDOW_HEIGHT = 400 + (process.platform == "win32" ? 40 : 0); | ||||
| 
 | ||||
|         let bounds = screen.getPrimaryDisplay().bounds; | ||||
|         let x = bounds.x + (bounds.width - WINDOW_WIDTH) / 2; | ||||
|         let y = bounds.y + (bounds.height - WINDOW_HEIGHT) / 2; | ||||
|         console.log("Bounds: %o; Move loader window to %ox%o", bounds, x, y); | ||||
| 
 | ||||
|         gui = new electron.BrowserWindow({ | ||||
|             width: WINDOW_WIDTH, | ||||
|             height: WINDOW_HEIGHT, | ||||
|             frame: dev_tools, | ||||
|             resizable: dev_tools, | ||||
|             show: false, | ||||
|             autoHideMenuBar: true, | ||||
| 
 | ||||
|             webPreferences: { | ||||
|                 webSecurity: false, | ||||
|                 nodeIntegrationInWorker: false, | ||||
|                 nodeIntegration: true | ||||
|             } | ||||
|         }); | ||||
|         gui.setMenu(null); | ||||
|         gui.loadFile(path.join(path.dirname(module.filename), "ui", "loading_screen.html")); | ||||
|         gui.on('closed', () => { | ||||
|             if(resolve) | ||||
|                 resolve(); | ||||
|             gui = undefined; | ||||
|             cleanup(); | ||||
|         }); | ||||
| 
 | ||||
|         gui.on('ready-to-show', () => { | ||||
|             gui.show(); | ||||
|             gui.setPosition(x, y); | ||||
|             winmgr.apply_bounds('ui-load-window', gui, undefined, { apply_size: false }).then(() => { | ||||
|                 winmgr.track_bounds('ui-load-window', gui); | ||||
| 
 | ||||
|                 const call_loader = () => load_files().catch(reject); | ||||
|                 if(!process_args.has_flag(...Arguments.DISABLE_ANIMATION)) | ||||
|                     setTimeout(call_loader, 1000); | ||||
|                 else | ||||
|                     setImmediate(call_loader); | ||||
| 
 | ||||
|                 if(dev_tools) | ||||
|                     gui.webContents.openDevTools(); | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     export async function execute_loader() : Promise<String> { | ||||
|         return promise = new Promise((_resolve, _reject) => { | ||||
|             resolve = _resolve; | ||||
|             reject = _reject || (error => { | ||||
|                 console.error("Failed to load UI files! Error: %o", error) | ||||
|             }); | ||||
| 
 | ||||
|             spawn_gui(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     export function preloading_page(entry_point: string) : string { | ||||
|         global["browser-root"] = entry_point; /* setup entry point */ | ||||
| 
 | ||||
|         return path.join(path.dirname(module.filename), "ui", "preload_page.html"); | ||||
|     } | ||||
| } | ||||
| @ -1,2 +0,0 @@ | ||||
| export * from "./loader.js"; | ||||
| export * from "./graphical.js"; | ||||
| @ -1,515 +0,0 @@ | ||||
| import {is_debug} from "../main_window"; | ||||
| 
 | ||||
| const request = require('request'); | ||||
| const querystring = require('querystring'); | ||||
| const fs = require('fs-extra'); | ||||
| const os = require('os'); | ||||
| const UUID = require('pure-uuid'); | ||||
| import * as path from "path"; | ||||
| import * as zlib from "zlib"; | ||||
| import * as tar from "tar-stream"; | ||||
| import {Arguments, process_args} from "../../shared/process-arguments"; | ||||
| import {parse_version} from "../../shared/version"; | ||||
| 
 | ||||
| import * as electron from "electron"; | ||||
| import MessageBoxOptions = Electron.MessageBoxOptions; | ||||
| import {current_version, execute_graphical} from "../app-updater"; | ||||
| 
 | ||||
| const TIMEOUT = 10000; | ||||
| let local_path = undefined; | ||||
| 
 | ||||
| interface RemoteURL { | ||||
|     (): string; | ||||
|     cached?: string; | ||||
| } | ||||
| const remote_url: RemoteURL = () => { | ||||
|     if(remote_url.cached) | ||||
|         return remote_url.cached; | ||||
|     const default_path = is_debug ? "http://localhost/home/TeaSpeak/Web-Client/client-api/environment/" : "https://clientapi.teaspeak.de/"; | ||||
|     return remote_url.cached = (process_args.has_value(...Arguments.SERVER_URL) ? process_args.value(...Arguments.SERVER_URL) : default_path); | ||||
| }; | ||||
| 
 | ||||
| function data_directory() : string { | ||||
|     return electron.app.getPath('userData'); | ||||
| } | ||||
| 
 | ||||
| function cache_directory() : string { | ||||
|     return path.join(data_directory(), "cache", "ui"); | ||||
| } | ||||
| 
 | ||||
| function working_directory() : string { | ||||
|     return path.join(data_directory(), "tmp", "ui"); | ||||
| } | ||||
| 
 | ||||
| export interface VersionedFile { | ||||
|     name: string, | ||||
|     hash: string, | ||||
|     path: string, | ||||
|     type: string, | ||||
| 
 | ||||
|     local_url: () => Promise<String> | ||||
| } | ||||
| 
 | ||||
| function generate_tmp() : Promise<String> { | ||||
|     if(local_path) return Promise.resolve(local_path); | ||||
| 
 | ||||
|     const id = new UUID(4).format(); | ||||
|     const directory = path.join(os.tmpdir(), "TeaClient-" + id) + "/"; | ||||
| 
 | ||||
|     return fs.mkdirs(directory).then(() => { | ||||
|         local_path = directory; | ||||
|         global["browser-root"] = local_path; | ||||
|         console.log("Local browser path: %s", local_path); | ||||
|         return Promise.resolve(local_path); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function get_raw_app_files() : Promise<VersionedFile[]> { | ||||
|     return generate_tmp().then(path => new Promise<VersionedFile[]>((resolve, reject) => { | ||||
|             const url = remote_url() + "api.php?" + querystring.stringify({ | ||||
|                 type: "files", | ||||
|             }); | ||||
|             console.debug("Requesting file list from %s", url); | ||||
|             request.get(url, { | ||||
|                 timeout: TIMEOUT | ||||
|             }, (error, response, body: string) => { | ||||
|                 response = response || {statusCode: -1}; | ||||
| 
 | ||||
|                 if(error) { reject(error); return; } | ||||
|                 if(response.statusCode != 200) { setImmediate(reject, "invalid status code " + response.statusCode + " for " + url); return; } | ||||
|                 if(response.headers["info-version"] != 1) { setImmediate(reject, "Invalid response version (" + response.headers["info-version"] + "). Update your app manually!"); return; } | ||||
|                 if(!body) { | ||||
|                     setImmediate(reject, "invalid body. (Missing)"); | ||||
|                     return; | ||||
|                 } | ||||
|                 let result: VersionedFile[] = []; | ||||
| 
 | ||||
|                 body.split("\n").forEach(entry => { | ||||
|                     if(entry.length == 0) return; | ||||
| 
 | ||||
|                     let info = entry.split("\t"); | ||||
|                     if(info[0] == "type") return; | ||||
| 
 | ||||
|                     result.push({ | ||||
|                         type: info[0], | ||||
|                         hash: info[1], | ||||
|                         path: info[2], | ||||
|                         name: info[3] | ||||
|                     } as VersionedFile); | ||||
|                 }); | ||||
|                 setImmediate(resolve, result); | ||||
|             }); | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| function download_raw_app_files() : Promise<VersionedFile[]> { | ||||
|     return get_raw_app_files().then(response => { | ||||
|         for(let file of response) { | ||||
|             const full_path = path.join(local_path, file.path, file.name); | ||||
|             file.local_url = () => fs.mkdirs(path.dirname(full_path)).then(() => new Promise<String>((resolve, reject) => { | ||||
|                 const write_stream = fs.createWriteStream(full_path); | ||||
|                 request.get(remote_url() + "api.php?" + querystring.stringify({ | ||||
|                     type: "file", | ||||
|                     path: file.path, | ||||
|                     name: file.name | ||||
|                 }), { | ||||
|                     timeout: TIMEOUT | ||||
|                 }).on('response', function(response) { | ||||
|                     if(response.statusCode != 200) { | ||||
|                         setImmediate(reject, "invalid status code " + response.statusCode + " for file " + file.name + " (" + file.path + ")"); | ||||
|                         return; | ||||
|                     } | ||||
|                 }).on('complete', event => { | ||||
|                 }).on('error', error => { | ||||
|                     setImmediate(reject, error); | ||||
|                 }).pipe(write_stream) | ||||
|                     .on('finish', event => { | ||||
|                         setImmediate(resolve, file.path + "/" + file.name); | ||||
|                     }); | ||||
|             })); | ||||
|         } | ||||
|         return Promise.resolve(response); | ||||
|     }).catch(error => { | ||||
|         console.log("Failed to get file list: %o", error); | ||||
|         return Promise.reject("Failed to get file list (" + error + ")"); | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
| interface LocalUICache { | ||||
|     fetch_history?: FetchStatus; | ||||
|     versions?: LocalUICacheEntry[]; | ||||
| 
 | ||||
|     remote_index?: UIVersion[] | UIVersion; | ||||
|     remote_index_channel?: string; /* only set if the last status was a channel only*/ | ||||
| 
 | ||||
|     local_index?: UIVersion; | ||||
| } | ||||
| 
 | ||||
| interface FetchStatus { | ||||
|     timestamp: number; | ||||
|     /** | ||||
|      * 0 = success | ||||
|      * 1 = connect fail | ||||
|      * 2 = internal fail | ||||
|      */ | ||||
|     status: number; | ||||
| } | ||||
| 
 | ||||
| interface LocalUICacheEntry { | ||||
|     version: UIVersion; | ||||
|     download_timestamp: number; | ||||
|     tar_file: string; | ||||
|     checksum: string; /* SHA512 */ | ||||
| } | ||||
| 
 | ||||
| export interface UIVersion { | ||||
|     channel: string; | ||||
|     version: string; | ||||
|     git_hash: string; | ||||
|     timestamp: number; | ||||
| 
 | ||||
|     required_client?: string; | ||||
|     filename?: string; | ||||
| 
 | ||||
|     client_shipped?: boolean; | ||||
| } | ||||
| 
 | ||||
| function ui_file_path(version: UIVersion) : string { | ||||
|     if(version.client_shipped) { | ||||
|         const app_path = electron.app.getAppPath(); | ||||
|         if(!app_path.endsWith(".asar")) | ||||
|             return undefined; | ||||
| 
 | ||||
|         return path.join(path.join(path.dirname(app_path), "ui"), version.filename); | ||||
|     } | ||||
| 
 | ||||
|     const file_name = "ui_" + version.channel + "_" + version.version + "_" + version.git_hash + "_" + version.timestamp + ".tar.gz"; | ||||
|     return path.join(cache_directory(), file_name); | ||||
| } | ||||
| 
 | ||||
| let _ui_load_cache: LocalUICache; | ||||
| async function ui_load_cache() : Promise<LocalUICache> { | ||||
|     if(_ui_load_cache) return _ui_load_cache; | ||||
| 
 | ||||
|     const file = path.join(cache_directory(), "data.json"); | ||||
|     if(!fs.existsSync(file)) return {} as LocalUICache; | ||||
| 
 | ||||
|     console.log("Loading UI cache file %s", file); | ||||
|     _ui_load_cache = await fs.readJson(file) as LocalUICache; | ||||
|     return _ui_load_cache; | ||||
| } | ||||
| 
 | ||||
| async function client_shipped_ui() : Promise<UIVersion | undefined> { | ||||
|     const app_path = electron.app.getAppPath(); | ||||
|     if(!app_path.endsWith(".asar")) | ||||
|         return undefined; | ||||
| 
 | ||||
|     const base_path = path.join(path.dirname(app_path), "ui"); | ||||
|     console.debug("Looking for client shipped UI pack at %s", base_path); | ||||
|     if(!(await fs.pathExists(base_path))) | ||||
|         return undefined; | ||||
| 
 | ||||
|     const info: { | ||||
|         channel: string, | ||||
|         version: string, | ||||
|         git_hash: string, | ||||
|         required_client: string, | ||||
|         timestamp: number, | ||||
|         filename: string | ||||
|     } = await fs.readJson(path.join(base_path, "default_ui_info.json")) as any; | ||||
| 
 | ||||
|     return { | ||||
|         channel: info.channel, | ||||
|         client_shipped: true, | ||||
| 
 | ||||
|         filename: info.filename, | ||||
|         git_hash: info.git_hash, | ||||
|         required_client: info.required_client, | ||||
|         timestamp: info.timestamp, | ||||
|         version: info.version, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| async function ui_save_cache(cache: LocalUICache) { | ||||
|     const file = path.join(cache_directory(), "data.json"); | ||||
|     if(!fs.existsSync(path.dirname(file))) | ||||
|         await fs.mkdirs(path.dirname(file)); | ||||
|     await fs.writeJson(file, cache); | ||||
| } | ||||
| 
 | ||||
| async function get_ui_pack(channel?: string) : Promise<UIVersion[] | UIVersion> { | ||||
|     return await new Promise<UIVersion[] | UIVersion>((resolve, reject) => { | ||||
|         const url = remote_url() + "api.php?" + querystring.stringify({ | ||||
|             type: "ui-info" | ||||
|         }); | ||||
|         request.get(url, { | ||||
|             timeout: TIMEOUT | ||||
|         }, (error, response, body: string) => { | ||||
|             try { | ||||
|                 response = response || {statusCode: -1}; | ||||
| 
 | ||||
|                 if(error) { throw error; } | ||||
|                 if(response.statusCode != 200) { throw "invalid status code " + response.statusCode + " for " + url; } | ||||
|                 if(!body) throw "invalid response body"; | ||||
| 
 | ||||
|                 let result: UIVersion[] = []; | ||||
| 
 | ||||
|                 const json = JSON.parse(body) || {success: false, msg: "invalid body"}; | ||||
|                 if(!json["success"]) throw "Failed to get ui info: " + json["msg"]; | ||||
| 
 | ||||
|                 for(const entry of json["versions"]) { | ||||
|                     if(!channel || entry["channel"] == channel) | ||||
|                         result.push({ | ||||
|                             channel: entry["channel"], | ||||
|                             version: entry["version"], | ||||
|                             git_hash: entry["git-ref"], | ||||
|                             timestamp: entry["timestamp"], | ||||
|                             required_client: entry["required_client"] | ||||
|                         }); | ||||
|                 } | ||||
| 
 | ||||
|                 if(result.length == 0 && channel) result.push(undefined); | ||||
|                 const res = channel ? result[0] : result; | ||||
|                 ui_load_cache().then(async cache => { | ||||
|                     cache.fetch_history = cache.fetch_history || {} as any; | ||||
|                     cache.fetch_history.timestamp = Date.now(); | ||||
|                     cache.fetch_history.status = 0; | ||||
|                     cache.remote_index = res as any; | ||||
|                     cache.remote_index_channel = channel; | ||||
|                     await ui_save_cache(cache); | ||||
|                 }).catch(error => { | ||||
|                     console.warn("Failed to save UI cache info: %o", error); | ||||
|                     resolve(res); | ||||
|                 }).then(err => resolve(res)); | ||||
|             } catch(error) { | ||||
|                 reject(error); | ||||
|             } | ||||
|         }); | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
| async function download_ui_pack(version: UIVersion) : Promise<void> { | ||||
|     const directory = cache_directory(); | ||||
|     const file = ui_file_path(version); | ||||
|     await fs.mkdirs(directory); | ||||
| 
 | ||||
|     await new Promise((resolve, reject) => { | ||||
|         request.get(remote_url() + "api.php?" + querystring.stringify({ | ||||
|             type: "ui-download", | ||||
|             "git-ref": version.git_hash, | ||||
|             version: version.version, | ||||
|             timestamp: version.timestamp, | ||||
|             channel: version.channel | ||||
|         }), { | ||||
|             timeout: TIMEOUT | ||||
|         }).on('response', function(response) { | ||||
|             if(response.statusCode != 200) { reject("Failed to download UI files (Status code " + response.statusCode + ")"); } | ||||
|         }).on('error', error => { | ||||
|             reject("Failed to download UI files: " + error); | ||||
|         }).pipe(fs.createWriteStream(file)).on('finish', () => { | ||||
|             ui_load_cache().then(cache => { | ||||
|                 cache.versions.push({ | ||||
|                     checksum: "undefined", | ||||
|                     tar_file: file, | ||||
|                     download_timestamp: Date.now(), | ||||
|                     version: version | ||||
|                 }); | ||||
|                 return ui_save_cache(cache); | ||||
|             }).catch(error => resolve()).then(() => resolve()); | ||||
| 
 | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function ui_pack_exists(version: UIVersion) : boolean { | ||||
|     return fs.existsSync(ui_file_path(version)); | ||||
| } | ||||
| 
 | ||||
| async function unpack_cached(version: UIVersion) : Promise<string> { | ||||
|     const file = ui_file_path(version); | ||||
|     if(!fs.existsSync(file)) throw "missing file"; | ||||
| 
 | ||||
|     const target_dir = path.join(working_directory(), version.channel + "_" + version.timestamp); | ||||
|     if(fs.existsSync(target_dir)) fs.removeSync(target_dir); | ||||
| 
 | ||||
|     await fs.mkdirs(target_dir); | ||||
| 
 | ||||
|     const gunzip = zlib.createGunzip(); | ||||
|     const extract = tar.extract(); | ||||
|     const fpipe = fs.createReadStream(file); | ||||
| 
 | ||||
|     extract.on('entry', function(header: tar.Headers, stream, next) { | ||||
|         if(header.type == 'file') { | ||||
|             const target_file = path.join(target_dir, header.name); | ||||
|             if(!fs.existsSync(path.dirname(target_file))) fs.mkdirsSync(path.dirname(target_file)); | ||||
| 
 | ||||
|             stream.on('end', () => setImmediate(next)); | ||||
|             const wfpipe = fs.createWriteStream(target_file); | ||||
|             stream.pipe(wfpipe); | ||||
|         } else if(header.type == 'directory') { | ||||
|             if(fs.existsSync(path.join(target_dir, header.name))) | ||||
|                 setImmediate(next); | ||||
|             fs.mkdirs(path.join(target_dir, header.name)).catch(error => { | ||||
|                 console.warn("Failed to create unpacking fir " + path.join(target_dir, header.name)); | ||||
|                 console.error(error); | ||||
|             }).then(() => setImmediate(next)); | ||||
|         } else { | ||||
|             console.warn("Invalid ui tar ball entry type (" + header.type + ")"); | ||||
|             return; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     const finish_promise = new Promise(resolve => { | ||||
|         extract.on('finish', resolve); | ||||
|         extract.on('error', event => { | ||||
|             if(!event) return; | ||||
|             throw event; | ||||
|         }); | ||||
|     }); | ||||
| 
 | ||||
|     fpipe.pipe(gunzip).pipe(extract); | ||||
|     await finish_promise; | ||||
| 
 | ||||
|     return target_dir; | ||||
| } | ||||
| 
 | ||||
| export async function cleanup() { | ||||
|     if(await fs.pathExists(local_path)) | ||||
|         await fs.remove(local_path); | ||||
| } | ||||
| 
 | ||||
| export async function load_files(channel: string, static_cb: (message: string, index: number) => any) : Promise<String> { | ||||
|     const type = parseInt(process_args.has_value(Arguments.UPDATER_UI_LOAD_TYPE) ? process_args.value(Arguments.UPDATER_UI_LOAD_TYPE) : "-1"); | ||||
|     if(type == 0 || !is_debug) { | ||||
|         console.log("Loading ui package"); | ||||
| 
 | ||||
|         static_cb("Fetching info", 0); | ||||
|         const cache = await ui_load_cache(); | ||||
|         console.log("Local cache: %o", cache); | ||||
| 
 | ||||
|         let ui_info: UIVersion; | ||||
|         try { | ||||
|             ui_info = await get_ui_pack(channel) as UIVersion; | ||||
|         } catch(error) { | ||||
|             if(error instanceof Error) | ||||
|                 console.error("Failed to fetch ui info: %s. Using cached info!", error.message); | ||||
|             else | ||||
|                 console.error("Failed to fetch ui info: %o. Using cached info!", error); | ||||
|         } | ||||
|         if(!ui_info) { | ||||
|             if(cache && !process_args.has_flag(Arguments.UPDATER_UI_NO_CACHE)) { | ||||
|                 if(Array.isArray(cache.remote_index)) { | ||||
|                     for(const index of cache.remote_index) { | ||||
|                         if(index && index.channel == "release") { | ||||
|                             ui_info = index; | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
|                     //TODO: test channel?
 | ||||
|                     ui_info = cache.remote_index; | ||||
|                 } | ||||
|             } | ||||
|             if(ui_info) { | ||||
|                 console.debug("Found local UI pack."); | ||||
|             } else { | ||||
|                 //Test for the client shipped ui pack
 | ||||
|                 try { | ||||
|                     console.info("Looking for client shipped UI pack."); | ||||
|                     ui_info = await client_shipped_ui(); | ||||
|                     if(!ui_info) | ||||
|                         throw "failed to load info"; | ||||
|                     console.info("Using client shipped UI pack because we've no active internet connection.") | ||||
|                 } catch(error) { | ||||
|                     console.warn("Failed to load client shipped UI pack: %o", error); | ||||
|                     throw "Failed to load UI pack from cache!\nPlease ensure a valid internet connection."; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         static_cb("Searching cache for file", .33); | ||||
|         console.log("Loading UI from data: %o. Target path: %s", ui_info, ui_file_path(ui_info)); | ||||
|         if(ui_info.required_client && !process_args.has_flag(Arguments.DEBUG)) { | ||||
|             const ui_vers = parse_version(ui_info.required_client); | ||||
|             const current_vers = await current_version(); | ||||
|             console.log("Checking required client version (Required: %s, Version: %s)", ui_vers.toString(true), current_vers.toString(true)); | ||||
|             if(ui_vers.newer_than(current_vers) && !current_vers.in_dev()) { | ||||
|                 const local_available = cache && cache.local_index ? ui_pack_exists(cache.local_index) : undefined; | ||||
| 
 | ||||
|                 const result = await electron.dialog.showMessageBox({ | ||||
|                     type: "question", | ||||
|                     message: | ||||
|                         "Local client is outdated.\n" + | ||||
|                         "Newer UI packs (>= " + ui_info.version + ") require client " + ui_info.required_client + "\n" + | ||||
|                         "Do you want to upgrade?", | ||||
|                     title: "Client outdated!", | ||||
|                     buttons: ["yes", local_available ? "ignore and use last possible (" + cache.local_index.version + ")" : "close client"] | ||||
|                 } as MessageBoxOptions); | ||||
|                 if(result.response == 0) { | ||||
|                     await execute_graphical(channel, true); | ||||
|                     throw "client outdated"; | ||||
|                 } else { | ||||
|                     if(!local_available) { | ||||
|                         electron.app.exit(1); | ||||
|                         return; | ||||
|                     } | ||||
| 
 | ||||
|                     ui_info = cache.local_index; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(!ui_pack_exists(ui_info)) { | ||||
|             console.log("Ui version does not locally exists. Downloading new one"); | ||||
|             static_cb("Downloading files", .34); | ||||
|             await download_ui_pack(ui_info); | ||||
|             console.log("Download completed!"); | ||||
|         } | ||||
| 
 | ||||
|         console.log("Unpacking cached ui info"); | ||||
|         static_cb("Unpacking files", .66); | ||||
|         const target_path = await unpack_cached(ui_info); | ||||
|         cache.local_index = ui_info; | ||||
|         await ui_save_cache(cache); | ||||
| 
 | ||||
|         console.log("Unpacked. Target path: %s", target_path); | ||||
|         static_cb("UI loaded", 1); | ||||
| 
 | ||||
|         return path.join(target_path, "index.html"); | ||||
|     } else { | ||||
|         console.log("Loading file by file"); | ||||
| 
 | ||||
|         static_cb("Fetching files", 0); | ||||
|         let files; | ||||
|         try { | ||||
|             files = await download_raw_app_files() | ||||
|         } catch (error) { | ||||
|             throw "Failed to get file list: " + error; | ||||
|         } | ||||
|         console.log("Get raw files:"); | ||||
|         let futures: Promise<void>[] = []; | ||||
|         let finish_count = 0; | ||||
|         static_cb("Downloading files", 0); | ||||
| 
 | ||||
|         for(const file of files) { | ||||
|             console.log("Start downloading %s (%s)", file.name, file.path); | ||||
| 
 | ||||
|             const start = Date.now(); | ||||
|             futures.push(file.local_url().then(data => { | ||||
|                 finish_count++; | ||||
|                 console.log("Downloaded %s (%s) (%ims)", file.name, file.path, Date.now() - start); | ||||
|                 static_cb("Downloading files", finish_count / files.length); | ||||
|             })); | ||||
| 
 | ||||
|             //await new Promise(resolve => setTimeout(resolve, 1000));
 | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             await Promise.all(futures); | ||||
|         } catch (error) { | ||||
|             throw "Failed to download files: " + error; | ||||
|         } | ||||
|         return await generate_tmp() + "index.html"; /* entry point */ | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| Before Width: | Height: | Size: 13 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 8.6 KiB | 
| @ -1,21 +0,0 @@ | ||||
| const icp = require("electron").ipcRenderer; | ||||
| 
 | ||||
| interface Window { | ||||
|     $: JQuery; | ||||
| } | ||||
| (window as any).$ = require("jquery"); | ||||
| 
 | ||||
| icp.on('progress-update', (event, count) => { | ||||
|     console.log("Process update to %f", count); | ||||
| 
 | ||||
|     $(".container-bar .bar").css("width", (count * 100) + "%"); | ||||
| }); | ||||
| 
 | ||||
| icp.on('await-update', (event) => { | ||||
|     console.log("Received update notification"); | ||||
| 
 | ||||
|     $(".container-bar .bar").css("width", "100%"); | ||||
|     $("#loading-text").html("Awaiting client update response<br>(User input required)"); | ||||
| }); | ||||
| 
 | ||||
| export {} | ||||
| @ -1,98 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|     <head> | ||||
|         <meta charset="UTF-8"> | ||||
|         <title>TeaClient</title> | ||||
| 
 | ||||
|         <style type="text/css"> | ||||
|             html, body { | ||||
|                 background: #18BC9C; | ||||
|                 user-select: none; | ||||
|             } | ||||
| 
 | ||||
|             body { | ||||
|                 text-align: center; | ||||
|                 position: absolute; | ||||
| 
 | ||||
|                 top: 0; | ||||
|                 bottom: 0; | ||||
|                 right: 0; | ||||
|                 left: 0; | ||||
| 
 | ||||
|                 margin-left: 18px; | ||||
|                 margin-right: 18px; | ||||
| 
 | ||||
|                 display: flex; | ||||
|                 flex-direction: column; | ||||
|                 justify-content: center; | ||||
| 
 | ||||
|                 -ms-overflow-style: none; | ||||
|                 -webkit-app-region: drag; | ||||
|             } | ||||
| 
 | ||||
|             img { | ||||
|                 position: absolute; | ||||
|                 display: block; | ||||
| 
 | ||||
|                 width: 200px; | ||||
|                 height: 200px; | ||||
|             } | ||||
| 
 | ||||
|             .smoke { | ||||
|                 z-index: 2; | ||||
|             } | ||||
|             .logo { | ||||
|                 z-index: 1; | ||||
|             } | ||||
| 
 | ||||
|             .container-logo { | ||||
|                 align-self: center; | ||||
|                 position: relative; | ||||
|                 display: inline-block; | ||||
| 
 | ||||
|                 width: 200px; | ||||
|                 height: 200px; | ||||
|             } | ||||
| 
 | ||||
|             .container-info a { | ||||
|                 display: inline-block; | ||||
|                 color: #FFFFFF; | ||||
|                 font-family: "Arial",serif; | ||||
|                 font-size: 20px; | ||||
|             } | ||||
| 
 | ||||
|             .container-bar { | ||||
|                 position: relative; | ||||
|                 margin-top: 5px; | ||||
|                 border: white solid 2px; | ||||
|                 height: 18px; | ||||
|             } | ||||
| 
 | ||||
|             .container-bar .bar { | ||||
|                 z-index: 1; | ||||
|                 position: absolute; | ||||
|                 display: block; | ||||
| 
 | ||||
|                 background: whitesmoke; | ||||
|                 border: none; | ||||
|                 width: 0%; | ||||
|                 height: 100%; | ||||
|             } | ||||
|         </style> | ||||
| 
 | ||||
|         <script type="application/ecmascript">const exports = {};</script> | ||||
|         <script type="application/ecmascript" src="loader.js"></script> | ||||
|     </head> | ||||
|     <body> | ||||
|         <div class="container-logo"> | ||||
|             <img class="logo" src="img/logo.svg"> | ||||
|             <img class="smoke" src="img/smoke.png"> | ||||
|         </div> | ||||
|         <div class="container-info"> | ||||
|             <a id="loading-text">Loading... Please wait!</a> | ||||
|             <div class="container-bar"> | ||||
|                 <div class="bar"></div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </body> | ||||
| </html> | ||||
| @ -1,25 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|     <head> | ||||
|         <script type="application/javascript"> | ||||
|             const remote = require('electron').remote; | ||||
|             const fs = require('fs-extra'); | ||||
| 
 | ||||
|             const target_file = remote.getGlobal("browser-root"); | ||||
|             console.log("Navigate to %s", target_file); | ||||
| 
 | ||||
|             if(fs.existsSync(target_file)) | ||||
|                 window.location.href = target_file; | ||||
|             else { | ||||
|                 console.error("Failed to find target file!"); | ||||
|                 if(!remote.getCurrentWebContents().isDevToolsOpened()) | ||||
|                     remote.getCurrentWebContents().openDevTools(); | ||||
|             } | ||||
|         </script> | ||||
|         <title>TeaClient - loading files</title> | ||||
|     </head> | ||||
|     <body> | ||||
|         An unknown error happened!<br> | ||||
|         Please report this! | ||||
|     </body> | ||||
| </html> | ||||
| @ -1,86 +0,0 @@ | ||||
| #nav-body-ctrls { | ||||
|     background-color: #2a2a2a; | ||||
|     padding: 20px; | ||||
|     font-family: arial | ||||
| } | ||||
| 
 | ||||
| #nav-body-tabs { | ||||
|     background: linear-gradient(#2a2a2a 75%, #404040); | ||||
|     height: 36px; | ||||
|     font-family: arial | ||||
| } | ||||
| 
 | ||||
| #nav-body-views { | ||||
|     flex: 1 | ||||
| } | ||||
| 
 | ||||
| .nav-icons { | ||||
|     fill: #fcfcfc !important | ||||
| } | ||||
| 
 | ||||
| .nav-icons:hover { | ||||
|     fill: #c2c2c2 !important | ||||
| } | ||||
| 
 | ||||
| #nav-ctrls-back, #nav-ctrls-forward, #nav-ctrls-reload { | ||||
|     height: 30px; | ||||
|     width: 30px; | ||||
|     margin-right: 10px | ||||
| } | ||||
| 
 | ||||
| #nav-ctrls-url { | ||||
|     box-shadow: 0 0; | ||||
|     border: 0; | ||||
|     border-radius: 2px; | ||||
|     height: 30px !important; | ||||
|     margin-left: 8px; | ||||
|     font-size: 11pt; | ||||
|     outline: none; | ||||
|     padding-left: 10px; | ||||
|     color: #b7b7b7; | ||||
|     background-color: #404040 | ||||
| } | ||||
| 
 | ||||
| #nav-ctrls-url:focus { | ||||
|     color: #fcfcfc; | ||||
|     box-shadow: 0 0 5px #3d3d3d; | ||||
| } | ||||
| 
 | ||||
| #nav-tabs-add { | ||||
|     margin: 5px | ||||
| } | ||||
| 
 | ||||
| .nav-tabs-tab { | ||||
|     border-radius: 2px; | ||||
|     height: 35px | ||||
| } | ||||
| 
 | ||||
| .nav-tabs-tab.active { | ||||
|     background: #404040 | ||||
| } | ||||
| 
 | ||||
| .nav-tabs-favicon { | ||||
|     margin: 6px | ||||
| } | ||||
| 
 | ||||
| .nav-tabs-title { | ||||
|     padding-left: 5px; | ||||
|     font-style: normal; | ||||
|     font-weight: 700; | ||||
|     color: #fcfcfc | ||||
| } | ||||
| 
 | ||||
| .nav-tabs-title:hover { | ||||
|     color: #c2c2c2 | ||||
| } | ||||
| 
 | ||||
| .nav-tabs-close { | ||||
|     width: 20px; | ||||
|     height: 20px; | ||||
|     margin: 6px; | ||||
|     margin-left: 2px | ||||
| } | ||||
| 
 | ||||
| .nav-tabs-close:hover { | ||||
|     fill: #dc143c !important | ||||
| } | ||||
| @ -1,34 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|     <head> | ||||
|         <title>TeaClient - URL preview</title> | ||||
|         <style> | ||||
|             html, | ||||
|             body { | ||||
|                 margin: 0; | ||||
|                 padding: 0; | ||||
|                 width: 100%; | ||||
|                 height: 100%; | ||||
|                 display: flex; | ||||
|                 flex-direction: column; | ||||
|             } | ||||
|         </style> | ||||
|         <link rel="stylesheet" type="text/css" href="./index.css"> | ||||
|     </head> | ||||
| 
 | ||||
|     <body> | ||||
|         <div id="nav-body-ctrls"> | ||||
|             <!-- address --> | ||||
|         </div> | ||||
|         <div id="nav-body-tabs"> | ||||
|             <!-- tabs --> | ||||
|         </div> | ||||
|         <div id="nav-body-views"> | ||||
|             <!-- view --> | ||||
|         </div> | ||||
|         <script> | ||||
|             let exports = {}; | ||||
|         </script> | ||||
|         <script src="./index.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
| @ -1,78 +0,0 @@ | ||||
| import * as electron from "electron"; | ||||
| import * as path from "path"; | ||||
| 
 | ||||
| interface Options { | ||||
|     showBackButton: boolean, | ||||
|     showForwardButton: boolean, | ||||
|     showReloadButton: boolean, | ||||
|     showUrlBar: boolean, | ||||
|     showAddTabButton: boolean, | ||||
|     closableTabs: boolean, | ||||
|     verticalTabs: boolean, | ||||
|     defaultFavicons: boolean, | ||||
|     newTabCallback: (url: string, options: any) => any, | ||||
|     changeTabCallback: () => any, | ||||
|     newTabParams: any | ||||
| } | ||||
| 
 | ||||
| interface NewTabOptions { | ||||
|     id: string, | ||||
|     node: boolean, | ||||
|     readonlyUrl: boolean, | ||||
|     contextMenu: boolean, | ||||
|     webviewAttributes: any, | ||||
|     icon: "clean" | "default" | string, | ||||
|     title: "default", | ||||
|     close: boolean | ||||
| } | ||||
| 
 | ||||
| const enav = new (require('electron-navigation'))({ | ||||
|     closableTabs: true, | ||||
|     showAddTabButton: false, | ||||
|     defaultFavicons: true, | ||||
| 
 | ||||
|     changeTabCallback: new_tab => { | ||||
|         if(new_tab === undefined) | ||||
|             window.close(); | ||||
|     } | ||||
| } as Options); | ||||
| 
 | ||||
| /* Required here: https://github.com/simply-coded/electron-navigation/blob/master/index.js#L364 */ | ||||
| enav.executeJavaScript = () => {}; /* just to suppress an error cause by the API */ | ||||
| 
 | ||||
| let _id_counter = 0; | ||||
| const execute_preview = (url: string) => { | ||||
|     const id = "preview_" + (++_id_counter); | ||||
|     const tab: HTMLElement & { executeJavaScript(js: string) : Promise<any> } = enav.newTab(url, { | ||||
|         id: id, | ||||
|         contextMenu: false, | ||||
|         readonlyUrl: true, | ||||
|         icon: "default", | ||||
|         webviewAttributes: { | ||||
|             'preload': path.join(__dirname, "inject.js") | ||||
|         } | ||||
|     } as NewTabOptions); | ||||
| 
 | ||||
|     /* we only want to preload our script once */ | ||||
|     const show_preview = () => { | ||||
|         tab.removeEventListener("dom-ready", show_preview); | ||||
|         tab.removeAttribute("preload"); | ||||
| 
 | ||||
|         tab.executeJavaScript('__teaclient_preview_notice()').catch((error) => console.log("Failed to show TeaClient overlay! Error: %o", error)); | ||||
|     }; | ||||
| 
 | ||||
|     tab.addEventListener("dom-ready", show_preview); | ||||
| 
 | ||||
|     tab.addEventListener('did-fail-load', (res: any) => { | ||||
|         console.error("Side load failed: %o", res); | ||||
|         if (res.errorCode != -3) { | ||||
|             res.target.executeJavaScript('__teaclient_preview_error("' + res.errorCode + '", "' + encodeURIComponent(res.errorDescription) + '", "' + encodeURIComponent(res.validatedURL) + '")').catch(error => { | ||||
|                 console.warn("Failed to show error page: %o", error); | ||||
|             }); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     tab.addEventListener('close', () => enav.closeTab(id)); | ||||
| }; | ||||
| 
 | ||||
| electron.ipcRenderer.on('preview', (event, url) => execute_preview(url)); | ||||
| @ -1,118 +0,0 @@ | ||||
| declare let __teaclient_preview_notice: () => any; | ||||
| declare let __teaclient_preview_error; | ||||
| 
 | ||||
| const electron = require("electron"); | ||||
| const log_prefix = "[TeaSpeak::Preview] "; | ||||
| 
 | ||||
| const html_overlay = | ||||
| "<div style='position: fixed; top: 0; bottom: 0; left: 0; right: 0; z-index: 99999999999999999999999999;'>" + | ||||
|     "<div style='\n" + | ||||
|                 "font-family: \"Open Sans\"," + | ||||
|                 "sans-serif;\n" + | ||||
|                 "width: 100%;\n" + | ||||
|                 "margin: 0;\n" + | ||||
|                 "height: 40px;\n" + | ||||
|                 "font-size: 17px;\n" + | ||||
|                 "font-weight: 400;\n" + | ||||
|                 "padding: .33em .5em;\n" + | ||||
|                 "color: #5c5e60;\n" + | ||||
|                 "position: fixed;\n" + | ||||
|                 "background-color: white;\n" + | ||||
|                 "box-shadow: 0 1px 3px 2px rgba(0,0,0,0.15);" + | ||||
|                 "display: flex;\n" + | ||||
|                 "flex-direction: row;\n" + | ||||
|                 "justify-content: center;" + | ||||
|                 "align-items: center;'" + | ||||
|     ">" + | ||||
|         "<div style='margin-right: .67em;display: inline-block;line-height: 1.3;text-align: center'>You're in TeaWeb website preview mode. Click <a href='#' class='button-open'>here</a> to open the website in the browser</div>" + | ||||
|     "</div>" + | ||||
|     "<div style='display: table-cell;width: 1.6em;'>" + | ||||
|         "<a style='font-size: 14px;\n" + | ||||
|                     "top: 13px;\n" + | ||||
|                     "right: 25px;\n" + | ||||
|                     "width: 15px;\n" + | ||||
|                     "height: 15px;\n" + | ||||
|                     "opacity: .3;\n" + | ||||
|                     "color: #000;\n" + | ||||
|                     "cursor: pointer;\n" + | ||||
|                     "position: absolute;\n" + | ||||
|                     "text-align: center;\n" + | ||||
|                     "line-height: 15px;\n" + | ||||
|                     "z-index: 1000;\n" + | ||||
|                     "text-decoration: none;'" + | ||||
|         "class='button-close'>" + | ||||
|             "✖" + | ||||
|         "</a>" + | ||||
|     "</div>" + | ||||
| "</div>"; | ||||
| 
 | ||||
| let _close_overlay: () => void; | ||||
| let _inject_overlay = () => { | ||||
|     const element = document.createElement("div"); | ||||
|     element.id = "TeaClient-Overlay-Container"; | ||||
|     document.body.append(element); | ||||
|     element.innerHTML = html_overlay; | ||||
| 
 | ||||
|     { | ||||
|         _close_overlay = () => { | ||||
|             console.trace(log_prefix + "Closing preview notice"); | ||||
|             element.remove(); | ||||
|         }; | ||||
| 
 | ||||
|         const buttons = element.getElementsByClassName("button-close"); | ||||
|         if(buttons.length < 1) { | ||||
|             console.warn(log_prefix + "Failed to find close button for preview notice!"); | ||||
|         } else { | ||||
|             for(const button of buttons) { | ||||
|                 (<HTMLElement>button).onclick = _close_overlay; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     { | ||||
|         const buttons = element.getElementsByClassName("button-open"); | ||||
|         if(buttons.length < 1) { | ||||
|             console.warn(log_prefix + "Failed to find open button for preview notice!"); | ||||
|         } else { | ||||
|             for(const element of buttons) { | ||||
|                 (<HTMLElement>element).onclick = event => { | ||||
|                     console.info(log_prefix + "Opening URL with default browser"); | ||||
|                     electron.remote.shell.openExternal(location.href, { | ||||
|                         activate: true | ||||
|                     }).catch(error => { | ||||
|                         console.warn(log_prefix + "Failed to open URL in browser window: %o", error); | ||||
|                     }).then(() => { | ||||
|                     window.close(); | ||||
|                     }); | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /* Put this into the global scope. But we dont leek some nodejs stuff! */ | ||||
| console.log(log_prefix + "Script loaded waiting to be called!"); | ||||
| __teaclient_preview_notice = () => { | ||||
|     if(_inject_overlay) { | ||||
|         console.log(log_prefix + "TeaClient overlay called. Showing overlay."); | ||||
|         _inject_overlay(); | ||||
|     } else { | ||||
|         console.warn(log_prefix + "TeaClient overlay called, but overlay method undefined. May an load error occured?"); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| const html_error = (error_code, error_desc, url) => | ||||
| "<div style='background-color: whitesmoke; padding: 40px; margin: 20px; font-family: consolas,serif;'>" + | ||||
|     "<h2 align=center>Oops, this page failed to load correctly.</h2>" + | ||||
|     "<p align=center><i>ERROR [ " + error_code + ", " + error_desc + " ]</i></p>" + | ||||
|     '<br/><hr/>' + | ||||
|     '<h4>Try this</h4>' + | ||||
|     '<li type=circle>Check your spelling - <b>"' + url + '".</b></li><br/>' + | ||||
|     '<li type=circle><a href="javascript:location.reload();">Refresh</a> the page.</li><br/>' + | ||||
|     '<li type=circle>Perform a <a href=javascript:location.href="https://www.google.com/search?q=' + url + '">search</a> instead.</li><br/>' + | ||||
| "</div>"; | ||||
| 
 | ||||
| __teaclient_preview_error = (error_code, error_desc, url) => { | ||||
|     document.body.innerHTML = html_error(decodeURIComponent(error_code), decodeURIComponent(error_desc), decodeURIComponent(url)); | ||||
|     _inject_overlay = undefined; | ||||
|     if(_close_overlay) _close_overlay(); | ||||
| }; | ||||
| @ -1,105 +0,0 @@ | ||||
| import * as electron from "electron"; | ||||
| import * as path from "path"; | ||||
| import * as winmgr from "../window"; | ||||
| 
 | ||||
| let global_window: electron.BrowserWindow; | ||||
| let global_window_promise: Promise<void>; | ||||
| 
 | ||||
| export async function close() { | ||||
|     while(global_window_promise) { | ||||
|         try { | ||||
|             await global_window_promise; | ||||
|             break; | ||||
|         } catch(error) {} /* error will be already logged */ | ||||
|     } | ||||
|     if(global_window) { | ||||
|         global_window.close(); | ||||
|         global_window = undefined; | ||||
|         global_window_promise = undefined; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function open_preview(url: string) { | ||||
|     while(global_window_promise) { | ||||
|         try { | ||||
|             await global_window_promise; | ||||
|             break; | ||||
|         } catch(error) {} /* error will be already logged */ | ||||
|     } | ||||
|     if(!global_window) { | ||||
|         global_window_promise = (async () => { | ||||
|             global_window = new electron.BrowserWindow({ | ||||
|                 webPreferences: { | ||||
|                     nodeIntegration: true, | ||||
|                     webviewTag: true | ||||
|                 }, | ||||
|                 center: true, | ||||
|                 show: false, | ||||
|             }); | ||||
|             global_window.setMenuBarVisibility(false); | ||||
|             global_window.setMenu(null); | ||||
|             global_window.loadFile(path.join(__dirname, "html", "index.html")).then(() => { | ||||
|                 //global_window.webContents.openDevTools();
 | ||||
|             }); | ||||
|             global_window.on('close', event => { | ||||
|                 global_window = undefined; | ||||
|             }); | ||||
| 
 | ||||
|             try { | ||||
|                 await winmgr.apply_bounds('url-preview', global_window); | ||||
|                 winmgr.track_bounds('url-preview', global_window); | ||||
| 
 | ||||
|                 await new Promise((resolve, reject) => { | ||||
|                     const timeout = setTimeout(() => reject("timeout"), 5000); | ||||
|                     global_window.on('ready-to-show', () => { | ||||
|                         clearTimeout(timeout); | ||||
|                         resolve(); | ||||
|                     }); | ||||
|                 }); | ||||
|             } catch(error) { | ||||
|                 console.warn("Failed to initialize preview window. Dont show preview! Error: %o", error); | ||||
|                 throw "failed to initialize"; | ||||
|             } | ||||
| 
 | ||||
|             global_window.show(); | ||||
|         })(); | ||||
|         try { | ||||
|             await global_window_promise; | ||||
|         } catch(error) { | ||||
|             console.log("Failed to create preview window! Error: %o", error); | ||||
|             try { | ||||
|                 global_window.close(); | ||||
|             } finally { | ||||
|                 global_window = undefined; | ||||
|             } | ||||
|             global_window_promise = undefined; | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     console.log("Opening URL '%s' as preview.", url); | ||||
|     global_window.webContents.send('preview', url); | ||||
|     if(!global_window.isFocused()) | ||||
|         global_window.focus(); | ||||
| } | ||||
| 
 | ||||
| electron.ipcMain.on('preview-action', (event, args) => { | ||||
|     const sender: electron.WebContents = event.sender; | ||||
|     if(!args || !args.action) { | ||||
|         console.warn("Received preview action without a valid action type!"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if(args.action === "open-url") { | ||||
|         console.log("Opening " +args.url); | ||||
|         electron.shell.openExternal(args.url, { | ||||
|             activate: true | ||||
|         }); | ||||
| 
 | ||||
|         const browser = electron.BrowserWindow.fromWebContents(sender); | ||||
|         if(!browser) | ||||
|             console.warn("Failed to find browser handle"); | ||||
|         else | ||||
|             browser.close(); | ||||
|     } | ||||
| }); | ||||
| @ -1,109 +0,0 @@ | ||||
| import * as electron from "electron"; | ||||
| import * as fs from "fs-extra"; | ||||
| import * as path from "path"; | ||||
| 
 | ||||
| /* We read/write to this file every time again because this file could be used by multible processes */ | ||||
| const data_file: string = path.join(electron.app.getPath('userData'), "window-bounds.json"); | ||||
| 
 | ||||
| import BrowserWindow = Electron.BrowserWindow; | ||||
| import Rectangle = Electron.Rectangle; | ||||
| 
 | ||||
| let _changed_data: {[key: string]:Rectangle} = {}; | ||||
| let _changed_saver: NodeJS.Timer; | ||||
| 
 | ||||
| export async function save_changes() { | ||||
|     clearTimeout(_changed_saver); | ||||
| 
 | ||||
|     try { | ||||
|         const data = (await fs.pathExists(data_file) ? await fs.readJson(data_file) : {}) || {}; | ||||
|         Object.assign(data, _changed_data); | ||||
| 
 | ||||
|         await fs.ensureFile(data_file); | ||||
|         await fs.writeJson(data_file, data); | ||||
|         path_exists = true; | ||||
| 
 | ||||
|         _changed_data = {}; | ||||
|     } catch(error) { | ||||
|         console.warn("Failed to save window bounds: %o", error); | ||||
|     } | ||||
|     console.log("Window bounds have been successfully saved!"); | ||||
| } | ||||
| 
 | ||||
| let path_exists = undefined; | ||||
| export async function get_last_bounds(key: string) : Promise<Rectangle> { | ||||
|     try { | ||||
|         if(typeof(path_exists) === "undefined" ? !(path_exists = await fs.pathExists(data_file)) : !path_exists) | ||||
|             throw "skip!"; | ||||
| 
 | ||||
|         const data = await fs.readJson(data_file) || {}; | ||||
|         if(data[key]) | ||||
|             return data[key]; | ||||
|     } catch(error) { | ||||
|         if(error !== "skip!") | ||||
|             console.warn("Failed to load window bounds for %s: %o", key, error); | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|         height: undefined, | ||||
|         width: undefined, | ||||
|         x: undefined, | ||||
|         y: undefined | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export function track_bounds(key: string, window: BrowserWindow) { | ||||
|     const events = ['move', 'moved', 'resize']; | ||||
| 
 | ||||
|     const update_bounds = () => { | ||||
|         _changed_data[key] = window.getBounds(); | ||||
| 
 | ||||
|         clearTimeout(_changed_saver); | ||||
|         _changed_saver = setTimeout(save_changes, 1000); | ||||
|     }; | ||||
| 
 | ||||
|     for(const event of events) | ||||
|         window.on(event as any, update_bounds); | ||||
| 
 | ||||
|     window.on('closed', () => { | ||||
|         for(const event of events) | ||||
|             window.removeListener(event as any, update_bounds); | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
| export async function apply_bounds(key: string, window: BrowserWindow, bounds?: Rectangle, options?: { apply_size?: boolean; apply_position?: boolean }) { | ||||
|     const screen = electron.screen; | ||||
| 
 | ||||
|     if(!bounds) | ||||
|         bounds = await get_last_bounds(key); | ||||
| 
 | ||||
|     if(!options) | ||||
|         options = {}; | ||||
| 
 | ||||
|     const original_bounds = window.getBounds(); | ||||
| 
 | ||||
|     if(typeof(options.apply_size) !== "boolean" || options.apply_size) { | ||||
|         let height = bounds.height > 0 ? bounds.height : original_bounds.height; | ||||
|         let width = bounds.width > 0 ? bounds.width : original_bounds.width; | ||||
| 
 | ||||
|         if(height != original_bounds.height || width != original_bounds.width) | ||||
|             window.setSize(width, height, true); | ||||
|     } | ||||
|     if(typeof(options.apply_position) !== "boolean" || options.apply_position) { | ||||
|         let x = typeof(bounds.x) === "number" ? bounds.x : original_bounds.x; | ||||
|         let y = typeof(bounds.y) === "number" ? bounds.y : original_bounds.y; | ||||
| 
 | ||||
|         if(x != original_bounds.x || y != original_bounds.y) { | ||||
|             const display = screen.getDisplayNearestPoint({ x: x, y: y }); | ||||
|             if(display) { | ||||
|                 const bounds = display.workArea || display.bounds; | ||||
|                 let flag_invalid = false; | ||||
|                 flag_invalid = flag_invalid || bounds.x > x || (bounds.x + bounds.width) < x; | ||||
|                 flag_invalid = flag_invalid || bounds.y > x || (bounds.y + bounds.height) < y; | ||||
|                 if(!flag_invalid) { | ||||
|                     window.setPosition(x, y, true); | ||||
|                     console.log("Updating position for %s", key); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,103 +0,0 @@ | ||||
| import {app, BrowserWindow, remote} from "electron"; | ||||
| import * as path from "path"; | ||||
| import * as electron from "electron"; | ||||
| import * as os from "os"; | ||||
| 
 | ||||
| export function handle_crash_callback(args: string[]) { | ||||
|     const parameter = {}; | ||||
|     for(const argument of args) { | ||||
|         const colon_index = argument.indexOf('='); | ||||
|         if(colon_index == -1) { | ||||
|             console.warn("Crash callback contains invalid argument! (%s)", argument); | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         parameter[argument.substr(0, colon_index)] = argument.substr(colon_index + 1); | ||||
|     } | ||||
|     console.log("Received crash dump callback. Arguments: %o", parameter); | ||||
| 
 | ||||
|     let error = undefined; | ||||
|     let crash_file = undefined; | ||||
| 
 | ||||
|     if(parameter["success"] == true) { | ||||
|         /* okey we have an crash dump */ | ||||
|         crash_file = parameter["dump_path"]; | ||||
|         if(typeof(crash_file) === "string") { | ||||
|             try { | ||||
|                 crash_file = Buffer.from(crash_file, 'base64').toString(); | ||||
|             } catch(error) { | ||||
|                 console.warn("Failed to decode dump path: %o", error); | ||||
|                 crash_file = undefined; | ||||
|                 error = "failed to decode dump path!"; | ||||
|             } | ||||
|         } | ||||
|     } else if(typeof(parameter["error"]) === "string") { | ||||
|         try { | ||||
|             error = Buffer.from(crash_file, 'base64').toString(); | ||||
|         } catch(error) { | ||||
|             console.warn("Failed to decode error: %o", error); | ||||
|             error = "failed to decode error"; | ||||
|         } | ||||
|     } else { | ||||
|         error = "missing parameters"; | ||||
|     } | ||||
| 
 | ||||
|     app.on('ready', () => { | ||||
|         const crash_window = new BrowserWindow({ | ||||
|             show: false, | ||||
|             width: 1000, | ||||
|             height: 300 + (os.platform() === "win32" ? 50 : 0), | ||||
| 
 | ||||
|             webPreferences: { | ||||
|                 devTools: true, | ||||
|                 nodeIntegration: true, | ||||
|                 javascript: true | ||||
|             } | ||||
|         }); | ||||
|         crash_window.setMenu(null); | ||||
|         crash_window.loadFile(path.join(path.dirname(module.filename), "ui", "index.html")); | ||||
|         crash_window.on('ready-to-show', () => { | ||||
|             if(error) | ||||
|                 crash_window.webContents.send('dump-error', error); | ||||
|             else if(!crash_file) | ||||
|                 crash_window.webContents.send('dump-error', "Missing crash file"); | ||||
|             else | ||||
|                 crash_window.webContents.send('dump-url', crash_file); | ||||
|             crash_window.show(); | ||||
|         }); | ||||
|         app.on('window-all-closed', () => { | ||||
|             process.exit(0); | ||||
|         }); | ||||
| 
 | ||||
|         crash_window.focus(); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| module.paths.push(...(() => { | ||||
|     const app_path = (remote || electron).app.getAppPath(); | ||||
|     const result = []; | ||||
|     result.push(app_path + "/native/build/" + os.platform() + "_" + os.arch() + "/"); | ||||
|     if(app_path.endsWith(".asar")) | ||||
|         result.push(path.join(path.dirname(app_path), "natives")); | ||||
|     return result; | ||||
| })()); | ||||
| 
 | ||||
| export const handler = require( "teaclient_crash_handler"); | ||||
| 
 | ||||
| export function initialize_handler(component_name: string, requires_file: boolean) { | ||||
|     const start_path = requires_file ? (" "  + path.join(__dirname, "..", "..")) : ""; | ||||
|     const success_arguments = process.argv[0] + start_path + " crash-handler success=1 dump_path=%crash_path%"; | ||||
|     const error_arguments = process.argv[0] + start_path + " crash-handler success=0 error=%error_message%"; | ||||
| 
 | ||||
|     console.log("Setting up crash handler. Success callback: %s; Error callback: %s", success_arguments, error_arguments); | ||||
|     handler.setup_crash_handler( | ||||
|         component_name, | ||||
|         path.join((remote || electron).app.getPath('userData'), "crash_dumps"), | ||||
|         success_arguments, | ||||
|         error_arguments | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| export function finalize_handler() { | ||||
|     handler.finalize(); | ||||
| } | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| Before Width: | Height: | Size: 13 KiB | 
| @ -1,42 +0,0 @@ | ||||
| body { | ||||
|   background-color: grey; | ||||
|   min-width: 750px; | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   justify-content: center; | ||||
| } | ||||
| 
 | ||||
| .container .container-header { | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   justify-content: center; | ||||
|   width: fit-content; | ||||
| } | ||||
| .container .container-header > * { | ||||
|   flex-shrink: 0; | ||||
| } | ||||
| .container .container-header img { | ||||
|   vertical-align: middle; | ||||
|   display: inline-block; | ||||
|   width: 128px; | ||||
|   height: 128px; | ||||
| } | ||||
| .container .container-header .text { | ||||
|   margin-left: 20px; | ||||
|   display: inline-block; | ||||
|   text-align: left; | ||||
|   align-self: center; | ||||
| } | ||||
| .container .container-header .text h1 { | ||||
|   color: darkred; | ||||
|   margin-bottom: 0; | ||||
| } | ||||
| .container .container-header .text h2 { | ||||
|   font-size: 1.25em; | ||||
|   margin-top: 0.2em; | ||||
| } | ||||
| .container .error-dump { | ||||
|   color: red; | ||||
| } | ||||
| 
 | ||||
| /*# sourceMappingURL=index.css.map */ | ||||
| @ -1 +0,0 @@ | ||||
| {"version":3,"sourceRoot":"","sources":["index.scss"],"names":[],"mappings":"AAAA;EACC;EACA;EAEA;EACA;EACA;;;AAIA;EACC;EACA;EACA;EACA;;AAEA;EACC;;AAGD;EACC;EACA;EAEA;EACA;;AAGD;EACC;EAEA;EACA;EACA;;AAEA;EACC;EACA;;AAGD;EACC;EACA;;AAKH;EACC","file":"index.css"} | ||||
| @ -1,36 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|     <head> | ||||
|         <meta charset="UTF-8"> | ||||
|         <title>Application crashed!</title> | ||||
|         <link rel="stylesheet" type="text/css" href="index.css"> | ||||
|     </head> | ||||
|     <body> | ||||
|         <div class="container"> | ||||
|             <div class="container-header"> | ||||
|                 <img src="crash_logo.svg"> | ||||
|                 <div class="text"> | ||||
|                     <h1>Ooops, something went incredible wrong!</h1> | ||||
|                     <h2>It seems like your TeaSpeak Client has been crashed.</h2> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="container-body"> | ||||
|                 <p> | ||||
|                     Please report this crash to TeaSpeak and help improving the client!<br> | ||||
|                     Official issue and bug tracker url: <a href="#" onclick="open_issue_tracker(); return false;">https://github.com/TeaSpeak/TeaClient/issues</a><br> | ||||
|                     <b>Attention:</b> Crash reports without a crash dump file will be ignored! | ||||
|                 </p> | ||||
|                 <p class="error-hide"> | ||||
|                     Crash dump file: <a href="#" class="crash-dump-directory">undefined</a> | ||||
|                 </p> | ||||
|                 <p class="error-dump error-show"> | ||||
|                     Failed to create crash dump file: <a class="crash-dump-error"></a> | ||||
|                 </p> | ||||
|             </div> | ||||
|         </div> | ||||
|         <script> | ||||
|             let exports = {}; | ||||
|         </script> | ||||
|         <script src="index.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
| @ -1,51 +0,0 @@ | ||||
| body { | ||||
| 	background-color: grey; | ||||
| 	min-width: 750px; | ||||
| 
 | ||||
| 	display: flex; | ||||
| 	flex-direction: row; | ||||
| 	justify-content: center; | ||||
| } | ||||
| 
 | ||||
| .container { | ||||
| 	.container-header { | ||||
| 		display: flex; | ||||
| 		flex-direction: row; | ||||
| 		justify-content: center; | ||||
| 		width: fit-content; | ||||
| 
 | ||||
| 		> * { | ||||
| 			flex-shrink: 0; | ||||
| 		} | ||||
| 
 | ||||
| 		img { | ||||
| 			vertical-align: middle; | ||||
| 			display: inline-block; | ||||
| 
 | ||||
| 			width: 128px; | ||||
| 			height: 128px; | ||||
| 		} | ||||
| 
 | ||||
| 		.text { | ||||
| 			margin-left: 20px; | ||||
| 
 | ||||
| 			display: inline-block;; | ||||
| 			text-align: left; | ||||
| 			align-self: center; | ||||
| 
 | ||||
| 			h1 { | ||||
| 				color: darkred; | ||||
| 				margin-bottom: 0; | ||||
| 			} | ||||
| 
 | ||||
| 			h2 { | ||||
| 				font-size: 1.25em; | ||||
| 				margin-top: .2em; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	.error-dump { | ||||
| 		color: red; | ||||
| 	} | ||||
| } | ||||
| @ -1,30 +0,0 @@ | ||||
| import { shell, ipcRenderer } from "electron"; | ||||
| 
 | ||||
| function open_issue_tracker() { | ||||
|     shell.openExternal("https://github.com/TeaSpeak/TeaClient/issues"); | ||||
| } | ||||
| 
 | ||||
| function set_dump_error_flag(flag: boolean) { | ||||
|     for(const node of document.getElementsByClassName("error-show") as HTMLCollectionOf<HTMLElement>) | ||||
|         node.style.display = flag ? "block" : "none"; | ||||
| 
 | ||||
|     for(const node of document.getElementsByClassName("error-hide") as HTMLCollectionOf<HTMLElement>) | ||||
|         node.style.display = flag ? "none" : "block"; | ||||
| } | ||||
| 
 | ||||
| function set_dump_url(url: string) { | ||||
|     for(const crash_path_node of document.getElementsByClassName("crash-dump-directory") as HTMLCollectionOf<HTMLElement>) { | ||||
|         crash_path_node.textContent = url; | ||||
|         crash_path_node.onclick = () => shell.showItemInFolder(url); | ||||
|     } | ||||
|     set_dump_error_flag(false); | ||||
| } | ||||
| 
 | ||||
| function set_dump_error(error: string) { | ||||
|     set_dump_error_flag(true); | ||||
|     for(const node of document.getElementsByClassName("crash-dump-error") as HTMLCollectionOf<HTMLElement>) | ||||
|         node.textContent = error; | ||||
| } | ||||
| 
 | ||||
| ipcRenderer.on('dump-url', (event, url) => set_dump_url(url)); | ||||
| ipcRenderer.on('dump-error', (event, error) => set_dump_error(error)); | ||||
| @ -1,82 +0,0 @@ | ||||
| import * as electron from "electron"; | ||||
| import * as path from "path"; | ||||
| import * as fs from "fs-extra"; | ||||
| 
 | ||||
| const APP_DATA = electron.remote.app.getPath("userData"); | ||||
| const SETTINGS_DIR = path.join(APP_DATA, "settings"); | ||||
| 
 | ||||
| let _local_storage: {[key: string]: any} = {}; | ||||
| let _local_storage_save: {[key: string]: boolean} = {}; | ||||
| export async function initialize() { | ||||
|     await fs.mkdirs(SETTINGS_DIR); | ||||
| 
 | ||||
|     const files = await fs.readdir(SETTINGS_DIR); | ||||
|     for(const file of files) { | ||||
|         const key = decodeURIComponent(file); | ||||
|         console.log("Load settings: %s", key); | ||||
| 
 | ||||
|         try { | ||||
|             const data = await fs.readFile(path.join(SETTINGS_DIR, file)); | ||||
|             const decoded = JSON.parse(data.toString() || "{}"); | ||||
| 
 | ||||
|             _local_storage[key] = decoded; | ||||
|         } catch(error) { | ||||
|             console.error("Failed to load settings for %s: %o", key, error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     let _new_storage: Storage = {} as any; | ||||
| 
 | ||||
|     _new_storage.getItem = key => _local_storage[key] || null; | ||||
|     _new_storage.setItem = (key, value) => { | ||||
|         _local_storage[key] = value; | ||||
|         _local_storage_save[key] = true; | ||||
|         save_key(key).catch(error => { | ||||
|             console.warn("Failed to save key: %s => %o", key, error); | ||||
|         }); | ||||
|         (_new_storage as any)["length"] = Object.keys(_local_storage).length; | ||||
|     }; | ||||
| 
 | ||||
|     _new_storage.clear = () => { | ||||
|         _local_storage = {}; | ||||
|         _local_storage_save = {}; | ||||
| 
 | ||||
|         try { | ||||
|             fs.emptyDirSync(SETTINGS_DIR); | ||||
|         } catch(error) { | ||||
|             console.warn("Failed to empty settings dir"); | ||||
|         } | ||||
|         (_new_storage as any)["length"] = 0; | ||||
|     }; | ||||
| 
 | ||||
|     _new_storage.key = index => Object.keys(_local_storage)[index]; | ||||
|     _new_storage.removeItem = key => { | ||||
|         delete _local_storage[key]; | ||||
|         delete_key(key).catch(error => { | ||||
|             console.warn("Failed to delete key on fs: %s => %o", key, error); | ||||
|         }); | ||||
|         (_new_storage as any)["length"] = Object.keys(_local_storage).length; | ||||
|     }; | ||||
| 
 | ||||
|     Object.assign(window.localStorage, _new_storage); | ||||
| } | ||||
| 
 | ||||
| export async function save_all() { | ||||
|     let promises: Promise<void>[] = []; | ||||
|     for(const key of Object.keys(_local_storage)) | ||||
|         promises.push(save_key(key)); | ||||
|     await Promise.all(promises); | ||||
| } | ||||
| 
 | ||||
| export async function save_key(key: string) { | ||||
|     if(!_local_storage_save[key]) | ||||
|         return; | ||||
| 
 | ||||
|     _local_storage_save[key] = false; | ||||
|     await fs.writeJson(path.join(SETTINGS_DIR, encodeURIComponent(key)), _local_storage[key], {spaces: 0}); | ||||
| } | ||||
| 
 | ||||
| export async function delete_key(key: string) { | ||||
|     delete  _local_storage_save[key]; | ||||
|     await fs.remove(path.join(SETTINGS_DIR, encodeURIComponent(key))); | ||||
| } | ||||
| @ -1,121 +0,0 @@ | ||||
| window["require_setup"](module); | ||||
| 
 | ||||
| import {audio as naudio} from "teaclient_connection"; | ||||
| 
 | ||||
| namespace audio.player  { | ||||
|     export interface Device { | ||||
|         device_id: string; | ||||
|         name: string; | ||||
|     } | ||||
| 
 | ||||
|     interface Navigator { | ||||
|         mozGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void; | ||||
|         webkitGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void; | ||||
|     } | ||||
| 
 | ||||
|     let _initialized_callbacks: (() => any)[] = []; | ||||
|     export let _initialized = false; | ||||
|     export let _audioContext: AudioContext; | ||||
|     export let _processor: ScriptProcessorNode; | ||||
|     export let _output_stream: naudio.playback.OwnedAudioOutputStream; | ||||
|     export let _current_device: naudio.AudioDevice; | ||||
| 
 | ||||
|     export function initialized() : boolean { | ||||
|         return _initialized; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     export function context() : AudioContext { | ||||
|         if(!_audioContext) throw "Initialize first!"; | ||||
|         return _audioContext; | ||||
|     } | ||||
| 
 | ||||
|     export function destination() : AudioNode { | ||||
|         if(!_initialized) | ||||
|             throw "Audio player hasn't yet be initialized"; | ||||
|         return _processor || _audioContext.destination; | ||||
|     } | ||||
| 
 | ||||
|     export function on_ready(cb: () => any) { | ||||
|         if(_initialized) | ||||
|             cb(); | ||||
|         else | ||||
|             _initialized_callbacks.push(cb); | ||||
|     } | ||||
| 
 | ||||
|     export function initialize() { | ||||
|         _output_stream = naudio.playback.create_stream(); | ||||
|         _output_stream.set_buffer_max_latency(0.4); | ||||
|         _output_stream.set_buffer_latency(0.02); | ||||
| 
 | ||||
|         _output_stream.callback_overflow = () => { | ||||
|             console.warn("Main audio overflow"); | ||||
|             _output_stream.clear(); | ||||
|         }; | ||||
|         _output_stream.callback_underflow = () => { | ||||
|             console.warn("Main audio underflow"); | ||||
|         }; | ||||
| 
 | ||||
|         _audioContext = new AudioContext(); | ||||
|         _processor = _audioContext.createScriptProcessor(1024 * 8, _output_stream.channels, _output_stream.channels); | ||||
| 
 | ||||
|         _processor.onaudioprocess = function(event) { | ||||
|             const buffer = event.inputBuffer; | ||||
|             //console.log("Received %d channels of %d with a rate of %d", buffer.numberOfChannels, buffer.length, buffer.sampleRate);
 | ||||
|             const target_buffer = new Float32Array(buffer.numberOfChannels * buffer.length); | ||||
| 
 | ||||
|             for(let channel = 0; channel < buffer.numberOfChannels; channel++) { | ||||
|                 const channel_data = buffer.getChannelData(channel); | ||||
|                 target_buffer.set(channel_data, channel * buffer.length); | ||||
|             } | ||||
|             _output_stream.write_data_rated(target_buffer.buffer, false, buffer.sampleRate); | ||||
|         }; | ||||
|         _processor.connect(_audioContext.destination); | ||||
| 
 | ||||
| 
 | ||||
|         _initialized = true; | ||||
|         for(const callback of _initialized_callbacks) | ||||
|             callback(); | ||||
|         _initialized_callbacks = []; | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     export async function available_devices() : Promise<Device[]> { | ||||
|         return naudio.available_devices().filter(e => e.output_supported || e.output_default).map(e => { | ||||
|             return { | ||||
|                 device_id: e.device_id, | ||||
|                 name: e.name | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     export async function set_device(device_id?: string) : Promise<void> { | ||||
|         const dev = naudio.available_devices().filter(e => e.device_id == device_id); | ||||
|         if(dev.length == 0) { | ||||
|             console.warn("Missing audio device with is %s", device_id) | ||||
|             throw "invalid device id"; | ||||
|         } | ||||
| 
 | ||||
|         await naudio.playback.set_device(dev[0].device_index); | ||||
|         _current_device = dev[0]; | ||||
|     } | ||||
| 
 | ||||
|     export function current_device() : Device { | ||||
|         if(_current_device) | ||||
|             return _current_device; | ||||
| 
 | ||||
|         const dev = naudio.available_devices().filter(e => e.output_default); | ||||
|         if(dev.length > 0) | ||||
|             return dev[0]; | ||||
|         return {device_id: "default", name: "default"} as Device; | ||||
|     } | ||||
| 
 | ||||
|     export function get_master_volume() : number { | ||||
|         return naudio.playback.get_master_volume(); | ||||
|     } | ||||
|     export function set_master_volume(volume: number) { | ||||
|         naudio.playback.set_master_volume(volume); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Object.assign(window["audio"] || (window["audio"] = {} as any), audio); | ||||
| @ -1,510 +0,0 @@ | ||||
| window["require_setup"](module); | ||||
| 
 | ||||
| import {audio as naudio} from "teaclient_connection"; | ||||
| //import {audio, tr} from "../imports/imports_shared";
 | ||||
| /// <reference types="./imports/import_shared.d.ts" />
 | ||||
| 
 | ||||
| export namespace _audio.recorder { | ||||
|     import InputDevice = audio.recorder.InputDevice; | ||||
|     import AbstractInput = audio.recorder.AbstractInput; | ||||
| 
 | ||||
|     interface NativeDevice extends InputDevice { | ||||
|         device_index: number; | ||||
|     } | ||||
| 
 | ||||
|     let _device_cache: NativeDevice[] = undefined; | ||||
|     export function devices() : InputDevice[] { | ||||
|         return _device_cache || (_device_cache = naudio.available_devices().filter(e => e.input_supported || e.input_default).map(e => { | ||||
|             return { | ||||
|                 unique_id: e.device_id, | ||||
|                 channels: 2, /* TODO */ | ||||
|                 default_input: e.input_default, | ||||
|                 supported: e.input_supported, | ||||
|                 name: e.name, | ||||
|                 driver: e.driver, | ||||
|                 sample_rate: 44100, /* TODO! */ | ||||
|                 device_index: e.device_index, | ||||
|             } as NativeDevice | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     export function device_refresh_available() : boolean { return false; } | ||||
|     export function refresh_devices() : Promise<void> { throw "not supported yet!"; } | ||||
| 
 | ||||
|     export function create_input() : AbstractInput { | ||||
|         return new NativeInput(); | ||||
|     } | ||||
| 
 | ||||
|     namespace filter { | ||||
|         export abstract class NativeFilter implements audio.recorder.filter.Filter { | ||||
|             type: audio.recorder.filter.Type; | ||||
|             handle: NativeInput; | ||||
|             enabled: boolean = false; | ||||
| 
 | ||||
|             protected constructor(handle, type) { this.handle = handle; this.type = type; } | ||||
| 
 | ||||
|             abstract initialize(); | ||||
|             abstract finalize(); | ||||
| 
 | ||||
| 
 | ||||
|             is_enabled(): boolean { return this.enabled; } | ||||
|         } | ||||
| 
 | ||||
|         export class NThresholdFilter extends NativeFilter implements audio.recorder.filter.ThresholdFilter { | ||||
|             private filter: naudio.record.ThresholdConsumeFilter; | ||||
| 
 | ||||
|             private _margin_frames: number = 6; /* 120ms */ | ||||
|             private _threshold: number = 50; | ||||
|             private _callback_level: any; | ||||
| 
 | ||||
|             private _attack_smooth = 0; | ||||
|             private _release_smooth = 0; | ||||
| 
 | ||||
|             callback_level: (level: number) => any; | ||||
| 
 | ||||
|             constructor(handle) { | ||||
|                 super(handle, audio.recorder.filter.Type.THRESHOLD); | ||||
| 
 | ||||
|                 Object.defineProperty(this, 'callback_level', { | ||||
|                     get(): any { | ||||
|                         return this._callback_level; | ||||
|                     }, set(v: any): void { | ||||
|                         if(v === this._callback_level) | ||||
|                             return; | ||||
| 
 | ||||
|                         this._callback_level = v; | ||||
|                         if(this.filter) | ||||
|                             this.filter.set_analyze_filter(v); | ||||
|                     }, | ||||
|                     enumerable: true, | ||||
|                     configurable: false, | ||||
|                 }) | ||||
|             } | ||||
| 
 | ||||
|             get_margin_frames(): number { | ||||
|                 return this.filter ? this.filter.get_margin_frames() : this._margin_frames; | ||||
|             } | ||||
| 
 | ||||
|             get_threshold(): number { | ||||
|                 return this.filter ? this.filter.get_threshold() : this._threshold; | ||||
|             } | ||||
| 
 | ||||
|             set_margin_frames(value: number) { | ||||
|                 this._margin_frames = value; | ||||
|                 if(this.filter) | ||||
|                     this.filter.set_margin_frames(value); | ||||
|             } | ||||
| 
 | ||||
|             get_attack_smooth(): number { | ||||
|                 return this.filter ? this.filter.get_attack_smooth() : this._attack_smooth; | ||||
|             } | ||||
| 
 | ||||
|             get_release_smooth(): number { | ||||
|                 return this.filter ? this.filter.get_release_smooth() : this._release_smooth; | ||||
|             } | ||||
| 
 | ||||
|             set_attack_smooth(value: number) { | ||||
|                 this._attack_smooth = value; | ||||
|                 if(this.filter) | ||||
|                     this.filter.set_attack_smooth(value); | ||||
|             } | ||||
| 
 | ||||
|             set_release_smooth(value: number) { | ||||
|                 this._release_smooth = value; | ||||
|                 if(this.filter) | ||||
|                     this.filter.set_release_smooth(value); | ||||
|             } | ||||
| 
 | ||||
|             set_threshold(value: number): Promise<void> { | ||||
|                 if(typeof(value) === "string") | ||||
|                     value = parseInt(value); /* yes... this happens */ | ||||
|                 this._threshold = value; | ||||
|                 if(this.filter) | ||||
|                     this.filter.set_threshold(value); | ||||
|                 return Promise.resolve(); | ||||
|             } | ||||
| 
 | ||||
|             finalize() { | ||||
|                 if(this.filter) { | ||||
|                     if(this.handle.consumer) | ||||
|                         this.handle.consumer.unregister_filter(this.filter); | ||||
|                     this.filter = undefined; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             initialize() { | ||||
|                 if(!this.handle.consumer) | ||||
|                     return; | ||||
| 
 | ||||
|                 this.finalize(); | ||||
|                 this.filter = this.handle.consumer.create_filter_threshold(this._threshold); | ||||
|                 if(this._callback_level) | ||||
|                     this.filter.set_analyze_filter(this._callback_level); | ||||
|                 this.filter.set_margin_frames(this._margin_frames); | ||||
|                 this.filter.set_attack_smooth(this._attack_smooth); | ||||
|                 this.filter.set_release_smooth(this._release_smooth); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         export class NStateFilter extends NativeFilter implements audio.recorder.filter.StateFilter { | ||||
|             private filter: naudio.record.StateConsumeFilter; | ||||
|             private active = false; | ||||
| 
 | ||||
|             constructor(handle) { | ||||
|                 super(handle, audio.recorder.filter.Type.STATE); | ||||
|             } | ||||
| 
 | ||||
|             finalize() { | ||||
|                 if(this.filter) { | ||||
|                     if(this.handle.consumer) | ||||
|                         this.handle.consumer.unregister_filter(this.filter); | ||||
|                     this.filter = undefined; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             initialize() { | ||||
|                 if(!this.handle.consumer) | ||||
|                     return; | ||||
| 
 | ||||
|                 this.finalize(); | ||||
|                 this.filter = this.handle.consumer.create_filter_state(); | ||||
|                 this.filter.set_consuming(this.active); | ||||
|             } | ||||
| 
 | ||||
|             is_active(): boolean { | ||||
|                 return this.active; | ||||
|             } | ||||
| 
 | ||||
|             async set_state(state: boolean): Promise<void> { | ||||
|                 if(this.active === state) | ||||
|                     return; | ||||
|                 this.active = state; | ||||
|                 if(this.filter) | ||||
|                     this.filter.set_consuming(state); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         export class NVoiceLevelFilter extends NativeFilter implements audio.recorder.filter.VoiceLevelFilter { | ||||
|             private filter: naudio.record.VADConsumeFilter; | ||||
|             private level = 3; | ||||
|             private _margin_frames = 5; | ||||
| 
 | ||||
|             constructor(handle) { | ||||
|                 super(handle, audio.recorder.filter.Type.VOICE_LEVEL); | ||||
|             } | ||||
| 
 | ||||
|             finalize() { | ||||
|                 if(this.filter) { | ||||
|                     if(this.handle.consumer) | ||||
|                         this.handle.consumer.unregister_filter(this.filter); | ||||
|                     this.filter = undefined; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             initialize() { | ||||
|                 if(!this.handle.consumer) | ||||
|                     return; | ||||
| 
 | ||||
|                 this.finalize(); | ||||
|                 this.filter = this.handle.consumer.create_filter_vad(this.level); | ||||
|                 this.filter.set_margin_frames(this._margin_frames); | ||||
|             } | ||||
| 
 | ||||
|             get_level(): number { | ||||
|                 return this.level; | ||||
|             } | ||||
| 
 | ||||
|             set_level(value: number) { | ||||
|                 if(this.level === value) | ||||
|                     return; | ||||
| 
 | ||||
|                 this.level = value; | ||||
|                 if(this.filter) { | ||||
|                     this.finalize(); | ||||
|                     this.initialize(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             set_margin_frames(value: number) { | ||||
|                 this._margin_frames = value; | ||||
|                 if(this.filter) | ||||
|                     this.filter.set_margin_frames(value); | ||||
|             } | ||||
| 
 | ||||
|             get_margin_frames(): number { | ||||
|                 return this.filter ? this.filter.get_margin_frames() : this._margin_frames; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     export class NativeInput implements AbstractInput { | ||||
|         private handle: naudio.record.AudioRecorder; | ||||
|         consumer: naudio.record.AudioConsumer; | ||||
| 
 | ||||
|         private _current_device: audio.recorder.InputDevice; | ||||
|         private _current_state: audio.recorder.InputState = audio.recorder.InputState.PAUSED; | ||||
| 
 | ||||
|         callback_begin: () => any; | ||||
|         callback_end: () => any; | ||||
| 
 | ||||
|         private filters: filter.NativeFilter[] = []; | ||||
| 
 | ||||
|         constructor() { | ||||
|             this.handle = naudio.record.create_recorder(); | ||||
| 
 | ||||
|             this.consumer = this.handle.create_consumer(); | ||||
|             this.consumer.callback_ended = () => { | ||||
|                 if(this._current_state !== audio.recorder.InputState.RECORDING) | ||||
|                     return; | ||||
| 
 | ||||
|                 this._current_state = audio.recorder.InputState.DRY; | ||||
|                 if(this.callback_end) | ||||
|                     this.callback_end(); | ||||
|             }; | ||||
|             this.consumer.callback_started = () => { | ||||
|                 if(this._current_state !== audio.recorder.InputState.DRY) | ||||
|                     return; | ||||
| 
 | ||||
|                 this._current_state = audio.recorder.InputState.RECORDING; | ||||
|                 if(this.callback_begin) | ||||
|                     this.callback_begin(); | ||||
|             }; | ||||
| 
 | ||||
|             this._current_state = audio.recorder.InputState.PAUSED; | ||||
|         } | ||||
| 
 | ||||
|         /* TODO: some kind of finalize? */ | ||||
|         current_consumer(): audio.recorder.InputConsumer | undefined { | ||||
|             return { | ||||
|                 type: audio.recorder.InputConsumerType.NATIVE | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         async set_consumer(consumer: audio.recorder.InputConsumer): Promise<void> { | ||||
|             if(typeof(consumer) !== "undefined") | ||||
|                 throw "we only support native consumers!"; /* TODO: May create a general wrapper? */ | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         async set_device(_device: audio.recorder.InputDevice | undefined): Promise<void> { | ||||
|             if(_device === this._current_device) | ||||
|                 return; | ||||
| 
 | ||||
|             const device = _device as NativeDevice; /* TODO: test for? */ | ||||
|             this._current_device = _device; | ||||
|             try { | ||||
|                 await new Promise((resolve, reject) => { | ||||
|                     this.handle.set_device(device ? device.device_index : -1, flag => { | ||||
|                         if(typeof(flag) === "boolean" && flag) | ||||
|                             resolve(); | ||||
|                         else | ||||
|                             reject("failed to set device" + (typeof(flag) === "string" ? (": " + flag) : "")); | ||||
|                     }); | ||||
|                 }); | ||||
|                 if(!device) return; | ||||
| 
 | ||||
|                 await new Promise((resolve, reject) => { | ||||
|                     this.handle.start(flag => { | ||||
|                         if(flag) | ||||
|                             resolve(); | ||||
|                         else | ||||
|                             reject("start failed"); | ||||
|                     }); | ||||
|                 }); | ||||
|             } catch(error) { | ||||
|                 console.warn(tr("Failed to start playback on new input device (%o)"), error); | ||||
|                 throw error; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         current_device(): audio.recorder.InputDevice | undefined { | ||||
|             return this._current_device; | ||||
|         } | ||||
| 
 | ||||
|         current_state(): audio.recorder.InputState { | ||||
|             return this._current_state; | ||||
|         } | ||||
| 
 | ||||
|         disable_filter(type: audio.recorder.filter.Type) { | ||||
|             const filter = this.get_filter(type) as filter.NativeFilter; | ||||
|             if(filter.is_enabled()) | ||||
|                 filter.enabled = false; | ||||
|             filter.finalize(); | ||||
|         } | ||||
| 
 | ||||
|         enable_filter(type: audio.recorder.filter.Type) { | ||||
|             const filter = this.get_filter(type) as filter.NativeFilter; | ||||
|             if(!filter.is_enabled()) { | ||||
|                 filter.enabled = true; | ||||
|                 filter.initialize(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         clear_filter() { | ||||
|             for(const filter of this.filters) { | ||||
|                 filter.enabled = false; | ||||
|                 filter.finalize(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         get_filter(type: audio.recorder.filter.Type): audio.recorder.filter.Filter | undefined { | ||||
|             for(const filter of this.filters) | ||||
|                 if(filter.type === type) | ||||
|                     return filter; | ||||
| 
 | ||||
|             let _filter: filter.NativeFilter; | ||||
|             switch (type) { | ||||
|                 case audio.recorder.filter.Type.THRESHOLD: | ||||
|                     _filter = new filter.NThresholdFilter(this); | ||||
|                     break; | ||||
|                 case audio.recorder.filter.Type.STATE: | ||||
|                     _filter = new filter.NStateFilter(this); | ||||
|                     break; | ||||
|                 case audio.recorder.filter.Type.VOICE_LEVEL: | ||||
|                     _filter = new filter.NVoiceLevelFilter(this); | ||||
|                     break; | ||||
|                 default: | ||||
|                     throw "this filter isn't supported!"; | ||||
|             } | ||||
|             this.filters.push(_filter); | ||||
|             return _filter; | ||||
|         } | ||||
| 
 | ||||
|         supports_filter(type: audio.recorder.filter.Type) : boolean { | ||||
|             switch (type) { | ||||
|                 case audio.recorder.filter.Type.THRESHOLD: | ||||
|                 case audio.recorder.filter.Type.STATE: | ||||
|                 case audio.recorder.filter.Type.VOICE_LEVEL: | ||||
|                     return true; | ||||
|                 default: | ||||
|                     return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         async start(): Promise<audio.recorder.InputStartResult> { | ||||
|             try { | ||||
|                 await this.stop(); | ||||
|             } catch(error) { | ||||
|                 console.warn(tr("Failed to stop old record session before start (%o)"), error); | ||||
|             } | ||||
| 
 | ||||
|             this._current_state = audio.recorder.InputState.DRY; | ||||
|             try { | ||||
|                 await new Promise((resolve, reject) => { | ||||
|                     this.handle.start(flag => { | ||||
|                         if(flag) | ||||
|                             resolve(); | ||||
|                         else | ||||
|                             reject("start failed"); | ||||
|                     }); | ||||
|                 }); | ||||
|                 for(const filter of this.filters) | ||||
|                     if(filter.is_enabled()) | ||||
|                         filter.initialize(); | ||||
|                 return audio.recorder.InputStartResult.EOK; | ||||
|             } catch(error) { | ||||
|                 this._current_state = audio.recorder.InputState.PAUSED; | ||||
|                 throw error; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         async stop(): Promise<void> { | ||||
|             this.handle.stop(); | ||||
|             for(const filter of this.filters) | ||||
|                 filter.finalize(); | ||||
|             if(this.callback_end) | ||||
|                 this.callback_end(); | ||||
|             this._current_state = audio.recorder.InputState.PAUSED; | ||||
|         } | ||||
| 
 | ||||
|         get_volume(): number { | ||||
|             return this.handle.get_volume(); | ||||
|         } | ||||
| 
 | ||||
|         set_volume(volume: number) { | ||||
|             this.handle.set_volume(volume); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     export async function create_levelmeter(device: InputDevice) : Promise<audio.recorder.LevelMeter> { | ||||
|         const meter = new NativeLevelmenter(device as any); | ||||
|         await meter.initialize(); | ||||
|         return meter; | ||||
|     } | ||||
| 
 | ||||
|     class NativeLevelmenter implements audio.recorder.LevelMeter { | ||||
|         readonly _device: NativeDevice; | ||||
| 
 | ||||
|         private _callback: (num: number) => any; | ||||
|         private _recorder: naudio.record.AudioRecorder; | ||||
|         private _consumer: naudio.record.AudioConsumer; | ||||
|         private _filter: naudio.record.ThresholdConsumeFilter; | ||||
| 
 | ||||
|         constructor(device: NativeDevice) { | ||||
|             this._device = device; | ||||
|         } | ||||
| 
 | ||||
|         async initialize() { | ||||
|             try { | ||||
|                 this._recorder = naudio.record.create_recorder(); | ||||
|                 this._consumer = this._recorder.create_consumer(); | ||||
| 
 | ||||
|                 this._filter = this._consumer.create_filter_threshold(.5); | ||||
|                 this._filter.set_attack_smooth(.75); | ||||
|                 this._filter.set_release_smooth(.75); | ||||
| 
 | ||||
|                 await new Promise((resolve, reject) => { | ||||
|                     this._recorder.set_device(this._device.device_index, flag => { | ||||
|                         if(typeof(flag) === "boolean" && flag) | ||||
|                             resolve(); | ||||
|                         else | ||||
|                             reject("initialize failed" + (typeof(flag) === "string" ? (": " + flag) : "")); | ||||
|                     }); | ||||
|                 }); | ||||
|                 await new Promise((resolve, reject) => { | ||||
|                     this._recorder.start(flag => { | ||||
|                         if(flag) | ||||
|                             resolve(); | ||||
|                         else | ||||
|                             reject("start failed"); | ||||
|                     }); | ||||
|                 }); | ||||
|             } catch(error) { | ||||
|                 if(typeof(error) === "string") | ||||
|                     throw error; | ||||
|                 console.warn(tr("Failed to initialize levelmeter for device %o: %o"), this._device, error); | ||||
|                 throw "initialize failed (lookup console)"; | ||||
|             } | ||||
| 
 | ||||
|             /* references this variable, needs a destory() call, else memory leak */ | ||||
|             this._filter.set_analyze_filter(value => { | ||||
|                 (this._callback || (() => {}))(value); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         destory() { | ||||
|             if(this._filter) { | ||||
|                 this._filter.set_analyze_filter(undefined); | ||||
|                 this._consumer.unregister_filter(this._filter); | ||||
|             } | ||||
|             if(this._consumer) | ||||
|                 this._recorder.delete_consumer(this._consumer); | ||||
|             this._recorder.stop(); | ||||
|             this._recorder.set_device(-1, () => {}); /* -1 := No device */ | ||||
|             this._recorder = undefined; | ||||
|             this._consumer = undefined; | ||||
|             this._filter = undefined; | ||||
|         } | ||||
| 
 | ||||
|         device(): audio.recorder.InputDevice { | ||||
|             return this._device; | ||||
|         } | ||||
| 
 | ||||
|         set_observer(callback: (value: number) => any) { | ||||
|             this._callback = callback; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Object.assign(window["audio"] || (window["audio"] = {} as any), _audio); | ||||
| _audio.recorder.devices(); /* query devices */ | ||||
| @ -1,185 +0,0 @@ | ||||
| /// <reference path="../imports/imports_shared.d.ts" />
 | ||||
| 
 | ||||
| 
 | ||||
| window["require_setup"](module); | ||||
| import * as native from "teaclient_connection"; | ||||
| 
 | ||||
| namespace _transfer { | ||||
|     class NativeFileDownload implements transfer.DownloadTransfer { | ||||
|         readonly key: transfer.DownloadKey; | ||||
|         private _handle: native.ft.NativeFileTransfer; | ||||
|         private _buffer: Uint8Array; | ||||
| 
 | ||||
|         private _result: Promise<void>; | ||||
|         private _response: Response; | ||||
| 
 | ||||
|         private _result_success: () => any; | ||||
|         private _result_error: (error: any) => any; | ||||
| 
 | ||||
|         constructor(key: transfer.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) | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         get_key(): transfer.DownloadKey { | ||||
|             return this.key; | ||||
|         } | ||||
| 
 | ||||
|         async request_file(): Promise<Response> { | ||||
|             if(this._response) | ||||
|                 return this._response; | ||||
| 
 | ||||
|             try { | ||||
|                 await (this._result || this._start_transfer()); | ||||
|             } catch(error) { | ||||
|                 throw error; | ||||
|             } | ||||
| 
 | ||||
|             if(this._response) | ||||
|                 return this._response; | ||||
| 
 | ||||
|             const buffer = this._buffer.buffer.slice(this._buffer.byteOffset, this._buffer.byteOffset + Math.min(64, this._buffer.byteLength)); | ||||
| 
 | ||||
|             /* may another task has been stepped by and already set the response */ | ||||
|             return this._response || (this._response = new Response(this._buffer, { | ||||
|                 status: 200, | ||||
|                 statusText: "success", | ||||
|                 headers: { | ||||
|                     "X-media-bytes": base64_encode_ab(buffer) | ||||
| 
 | ||||
|                 } | ||||
|             })); | ||||
|         } | ||||
| 
 | ||||
|         _start_transfer() : Promise<void> { | ||||
|             return this._result = new Promise((resolve, reject) => { | ||||
|                 this._result_error = (error) => { | ||||
|                     this._result_error = undefined; | ||||
|                     this._result_success = undefined; | ||||
|                     reject(error); | ||||
|                 }; | ||||
|                 this._result_success = () => { | ||||
|                     this._result_error = undefined; | ||||
|                     this._result_success = undefined; | ||||
|                     resolve(); | ||||
|                 }; | ||||
| 
 | ||||
|                 this._handle.callback_failed = this._result_error; | ||||
|                 this._handle.callback_finished = aborted => { | ||||
|                     if(aborted) | ||||
|                         this._result_error("aborted"); | ||||
|                     else | ||||
|                         this._result_success(); | ||||
|                 }; | ||||
| 
 | ||||
|                 this._handle.start(); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     class NativeFileUpload implements transfer.UploadTransfer { | ||||
|         readonly transfer_key: transfer.UploadKey; | ||||
|         private _handle: native.ft.NativeFileTransfer; | ||||
| 
 | ||||
|         private _result: Promise<void>; | ||||
| 
 | ||||
|         private _result_success: () => any; | ||||
|         private _result_error: (error: any) => any; | ||||
| 
 | ||||
|         constructor(key: transfer.UploadKey) { | ||||
|             this.transfer_key = key; | ||||
|         } | ||||
| 
 | ||||
|         async put_data(data: BlobPart | File) : Promise<void> { | ||||
|             if(this._result) { | ||||
|                 await this._result; | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             let buffer: native.ft.FileTransferSource; | ||||
| 
 | ||||
|             if(data instanceof File) { | ||||
|                 if(data.size != this.transfer_key.total_size) | ||||
|                     throw "invalid size"; | ||||
| 
 | ||||
|                 buffer = native.ft.upload_transfer_object_from_file(data.path, data.name); | ||||
|             } else if(typeof(data) === "string") { | ||||
|                 if(data.length != this.transfer_key.total_size) | ||||
|                     throw "invalid size"; | ||||
| 
 | ||||
|                 buffer = native.ft.upload_transfer_object_from_buffer(str2ab8(data)); | ||||
|             } else { | ||||
|                 let buf = <BufferSource>data; | ||||
|                 if(buf.byteLength != this.transfer_key.total_size) | ||||
|                     throw "invalid size"; | ||||
| 
 | ||||
|                 if(ArrayBuffer.isView(buf)) | ||||
|                     buf = buf.buffer.slice(buf.byteOffset); | ||||
| 
 | ||||
|                 buffer = native.ft.upload_transfer_object_from_buffer(buf); | ||||
|             } | ||||
| 
 | ||||
|             this._handle = native.ft.spawn_connection({ | ||||
|                 client_transfer_id: this.transfer_key.client_transfer_id, | ||||
|                 server_transfer_id: this.transfer_key.server_transfer_id, | ||||
| 
 | ||||
|                 remote_address: this.transfer_key.peer.hosts[0], | ||||
|                 remote_port: this.transfer_key.peer.port, | ||||
| 
 | ||||
|                 transfer_key: this.transfer_key.key, | ||||
| 
 | ||||
|                 object: buffer | ||||
|             }); | ||||
| 
 | ||||
|             await (this._result = new Promise((resolve, reject) => { | ||||
|                 this._result_error = (error) => { | ||||
|                     this._result_error = undefined; | ||||
|                     this._result_success = undefined; | ||||
|                     reject(error); | ||||
|                 }; | ||||
|                 this._result_success = () => { | ||||
|                     this._result_error = undefined; | ||||
|                     this._result_success = undefined; | ||||
|                     resolve(); | ||||
|                 }; | ||||
| 
 | ||||
|                 this._handle.callback_failed = this._result_error; | ||||
|                 this._handle.callback_finished = aborted => { | ||||
|                     if(aborted) | ||||
|                         this._result_error("aborted"); | ||||
|                     else | ||||
|                         this._result_success(); | ||||
|                 }; | ||||
| 
 | ||||
|                 this._handle.start(); | ||||
|             })); | ||||
|         } | ||||
| 
 | ||||
|         get_key(): transfer.UploadKey { | ||||
|             return this.transfer_key; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     export function spawn_download_transfer(key: transfer.DownloadKey) : transfer.DownloadTransfer { | ||||
|         return new NativeFileDownload(key); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     export function spawn_upload_transfer(key: transfer.UploadKey) : transfer.UploadTransfer { | ||||
|         return new NativeFileUpload(key); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Object.assign(window["transfer"] || (window["transfer"] = {} as any), _transfer); | ||||
| @ -1,318 +0,0 @@ | ||||
| /// <reference path="../imports/imports_shared.d.ts" />
 | ||||
| 
 | ||||
| window["require_setup"](module); | ||||
| import { | ||||
|     destroy_server_connection as _destroy_server_connection, | ||||
|     NativeServerConnection, | ||||
|     ServerType, | ||||
|     spawn_server_connection as _spawn_server_connection | ||||
| } from "teaclient_connection"; | ||||
| import {_audio} from "./VoiceConnection"; | ||||
| 
 | ||||
| export namespace _connection { | ||||
|     export namespace native { | ||||
|         import VoiceConnection = _audio.native.VoiceConnection; | ||||
| 
 | ||||
|         class ErrorCommandHandler extends connection.AbstractCommandHandler { | ||||
|                 private _handle: ServerConnection; | ||||
| 
 | ||||
|                 constructor(handle: ServerConnection) { | ||||
|                     super(handle); | ||||
|                     this._handle = handle; | ||||
|                 } | ||||
| 
 | ||||
|                 handle_command(command: connection.ServerCommand): boolean { | ||||
|                     if(command.command === "error") { | ||||
|                         const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"]; | ||||
|                         const data = command.arguments[0]; | ||||
| 
 | ||||
|                         let return_code : string = data["return_code"]; | ||||
|                         if(!return_code) { | ||||
|                             const listener = return_listener["last_command"] || return_listener["_clientinit"]; | ||||
|                             if(typeof(listener) === "function") { | ||||
|                                 console.warn(tr("Received error without return code. Using last command (%o)"), listener); | ||||
|                                 listener(new CommandResult(data)); | ||||
|                                 delete return_listener["last_command"]; | ||||
|                                 delete return_listener["_clientinit"]; | ||||
|                             } else { | ||||
|                                 console.warn(tr("Received error without return code."), data); | ||||
|                             } | ||||
|                             return false; | ||||
|                         } | ||||
| 
 | ||||
|                         if(return_listener[return_code]) { | ||||
|                             return_listener[return_code](new CommandResult(data)); | ||||
|                         } else { | ||||
|                             console.warn(tr("Error received for no handler! (%o)"), data); | ||||
|                         } | ||||
|                         return true; | ||||
|                     } else if(command.command == "initivexpand") { | ||||
|                         if(command.arguments[0]["teaspeak"] == true) { | ||||
|                             console.log("Using TeaSpeak identity type"); | ||||
|                             this._handle.handshake_handler().startHandshake(); | ||||
|                         } | ||||
|                         return true; | ||||
|                     } else if(command.command == "initivexpand2") { | ||||
|                         /* its TeamSpeak or TeaSpeak with experimental 3.1 and not up2date */ | ||||
|                         this._handle["_do_teamspeak"] = true; | ||||
|                     } else if(command.command == "initserver") { | ||||
|                         const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"]; | ||||
| 
 | ||||
|                         if(typeof(return_listener["_clientinit"]) === "function") { | ||||
|                             return_listener["_clientinit"](new CommandResult({id: 0, message: ""})); | ||||
|                             delete return_listener["_clientinit"]; | ||||
|                         } | ||||
| 
 | ||||
|                         if(this._handle.onconnectionstatechanged) | ||||
|                             this._handle.onconnectionstatechanged(ConnectionState.INITIALISING, ConnectionState.CONNECTING); | ||||
|                     } 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 connection.AbstractServerConnection { | ||||
|             private _native_handle: NativeServerConnection; | ||||
|             private _voice_connection: VoiceConnection; | ||||
| 
 | ||||
|             private _do_teamspeak: boolean; | ||||
|             private _return_listener: {[key: string]: (result: CommandResult) => any} = {}; | ||||
| 
 | ||||
|             private _command_handler: NativeConnectionCommandBoss; | ||||
|             private _command_error_handler: ErrorCommandHandler; | ||||
|             private _command_handler_default: connection.ConnectionCommandHandler; | ||||
| 
 | ||||
|             private _remote_address: ServerAddress; | ||||
|             private _handshake_handler: connection.HandshakeHandler; | ||||
| 
 | ||||
|             private _return_code_index: number = 0; | ||||
| 
 | ||||
|             onconnectionstatechanged: connection.ConnectionStateListener; | ||||
| 
 | ||||
|             constructor(props: ConnectionHandler) { | ||||
|                 super(props); | ||||
| 
 | ||||
|                 this._command_handler = new NativeConnectionCommandBoss(this); | ||||
|                 this._command_error_handler = new ErrorCommandHandler(this); | ||||
|                 this._command_handler_default = new connection.ConnectionCommandHandler(this); | ||||
| 
 | ||||
|                 this._command_handler.register_handler(this._command_error_handler); | ||||
|                 this._command_handler.register_handler(this._command_handler_default); | ||||
| 
 | ||||
|                 this._native_handle = _spawn_server_connection(); | ||||
|                 this._native_handle.callback_disconnect = reason => { | ||||
|                     this.client.handleDisconnect(DisconnectReason.CONNECTION_CLOSED, { | ||||
|                         reason: reason, | ||||
|                         event: event | ||||
|                     }); | ||||
|                 }; | ||||
|                 this._native_handle.callback_command = (command, args, switches) => { | ||||
|                     console.log("Received: %o %o %o", command, args, switches); | ||||
|                     //FIXME catch error
 | ||||
| 
 | ||||
|                     this._command_handler.invoke_handle({ | ||||
|                         command: command, | ||||
|                         arguments: args | ||||
|                     }); | ||||
|                 }; | ||||
|                 this._voice_connection = new VoiceConnection(this, this._native_handle._voice_connection); | ||||
| 
 | ||||
|                 this.command_helper.initialize(); | ||||
|                 this._voice_connection.setup(); | ||||
|             } | ||||
| 
 | ||||
|             native_handle() : NativeServerConnection { | ||||
|                 return this._native_handle; | ||||
|             } | ||||
| 
 | ||||
|             finalize() { | ||||
|                 if(this._native_handle) | ||||
|                     _destroy_server_connection(this._native_handle); | ||||
|                 this._native_handle = undefined; | ||||
|             } | ||||
| 
 | ||||
|             connect(address: ServerAddress, handshake: connection.HandshakeHandler, timeout?: number): Promise<void> { | ||||
|                 this._remote_address = address; | ||||
|                 this._handshake_handler = handshake; | ||||
|                 this._do_teamspeak = false; | ||||
|                 handshake.setConnection(this); | ||||
|                 handshake.initialize(); | ||||
| 
 | ||||
|                 return new Promise<void>((resolve, reject) => { | ||||
|                     this._native_handle.connect({ | ||||
|                         remote_host: address.host, | ||||
|                         remote_port: address.port, | ||||
| 
 | ||||
|                         timeout: typeof(timeout) === "number" ? timeout : -1, | ||||
| 
 | ||||
| 
 | ||||
|                         callback: error => { | ||||
|                             if(error != 0) { | ||||
|                                 /* required to notify the handle, just a promise reject does not work */ | ||||
|                                 this.client.handleDisconnect(DisconnectReason.CONNECT_FAILURE, error); | ||||
|                                 reject(this._native_handle.error_message(error)); | ||||
|                                 return; | ||||
|                             } else { | ||||
|                                 resolve(); | ||||
|                             } | ||||
| 
 | ||||
|                             console.log("Remote server type: %o (%s)", this._native_handle.server_type, ServerType[this._native_handle.server_type]); | ||||
|                             if(this._native_handle.server_type == ServerType.TEAMSPEAK || this._do_teamspeak) { | ||||
|                                 console.log("Trying to use TeamSpeak's identity system"); | ||||
|                                 this.handshake_handler().on_teamspeak(); | ||||
|                             } | ||||
|                         }, | ||||
| 
 | ||||
|                         identity_key: (handshake.get_identity_handler() as profiles.identities.TeaSpeakHandshakeHandler).identity.private_key, | ||||
|                         teamspeak: false | ||||
|                     }) | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             remote_address(): ServerAddress { | ||||
|                 return this._remote_address; | ||||
|             } | ||||
| 
 | ||||
|             handshake_handler(): connection.HandshakeHandler { | ||||
|                 return this._handshake_handler; | ||||
|             } | ||||
| 
 | ||||
|             connected(): boolean { | ||||
|                 return typeof(this._native_handle) !== "undefined" && this._native_handle.connected(); | ||||
|             } | ||||
| 
 | ||||
|             disconnect(reason?: string): Promise<void> { | ||||
|                 console.trace("Disconnect: %s",reason); | ||||
|                 return new Promise<void>((resolve, reject) => this._native_handle.disconnect(reason || "", error => { | ||||
|                     if(error == 0) | ||||
|                         resolve(); | ||||
|                     else | ||||
|                         reject(this._native_handle.error_message(error)); | ||||
|                 })); | ||||
|             } | ||||
| 
 | ||||
|             support_voice(): boolean { | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|             voice_connection(): connection.voice.AbstractVoiceConnection { | ||||
|                 return this._voice_connection; | ||||
|             } | ||||
| 
 | ||||
|             command_handler_boss(): connection.AbstractCommandHandlerBoss { | ||||
|                 return this._command_handler; | ||||
|             } | ||||
| 
 | ||||
|             private generate_return_code() : string { | ||||
|                 return (this._return_code_index++).toString(); | ||||
|             } | ||||
| 
 | ||||
|             send_command(command: string, data?: any, _options?: connection.CommandOptions): Promise<CommandResult> { | ||||
|                 if(!this.connected()) { | ||||
|                     console.warn(tr("Tried to send a command without a valid connection.")); | ||||
|                     return Promise.reject(tr("not connected")); | ||||
|                 } | ||||
| 
 | ||||
|                 const options: connection.CommandOptions = {}; | ||||
|                 Object.assign(options, connection.CommandOptionDefaults); | ||||
|                 Object.assign(options, _options); | ||||
| 
 | ||||
|                 data = $.isArray(data) ? data : [data || {}]; | ||||
|                 if(data.length == 0) /* we require min one arg to append return_code */ | ||||
|                     data.push({}); | ||||
| 
 | ||||
|                 let return_code = data[0]["return_code"] !== undefined ? data[0].return_code : this.generate_return_code(); | ||||
|                 data[0]["return_code"] = return_code; | ||||
| 
 | ||||
|                 console.log("Sending %s (%o)", command, data); | ||||
|                 const promise = new Promise<CommandResult>((resolve, reject) => { | ||||
|                     const timeout_id = setTimeout(() => { | ||||
|                         delete this._return_listener[return_code]; | ||||
|                         reject("timeout"); | ||||
|                     }, 5000); | ||||
| 
 | ||||
|                     this._return_listener[return_code] = result => { | ||||
|                         clearTimeout(timeout_id); | ||||
|                         delete this._return_listener[return_code]; | ||||
| 
 | ||||
|                         (result.success ? resolve : reject)(result); | ||||
|                     }; | ||||
| 
 | ||||
|                     if(command == "clientinit") | ||||
|                         this._return_listener["_clientinit"] = this._return_listener[return_code]; /* fix for TS3 (clientinit does not accept a return code) */ | ||||
| 
 | ||||
|                     try { | ||||
|                         this._native_handle.send_command(command, data, options.flagset || []); | ||||
|                     } catch(error) { | ||||
|                         console.warn(tr("Failed to send command: %o"), error); | ||||
|                     } | ||||
|                 }); | ||||
|                 return this._command_handler_default.proxy_command_promise(promise, options); | ||||
|             } | ||||
| 
 | ||||
|             ping(): { native: number; javascript?: number } { | ||||
|                 return { | ||||
|                     native: this._native_handle ? (this._native_handle.current_ping() / 1000) : -2 | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     export class NativeConnectionCommandBoss extends connection.AbstractCommandHandlerBoss { | ||||
|         constructor(connection: connection.AbstractServerConnection) { | ||||
|             super(connection); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /* override the "normal" connection */ | ||||
|     export function spawn_server_connection(handle: ConnectionHandler) : connection.AbstractServerConnection { | ||||
|         console.log("Spawning native connection"); | ||||
|         return new native.ServerConnection(handle); /* will be overridden by the client */ | ||||
|     } | ||||
| 
 | ||||
|     export function destroy_server_connection(handle: connection.AbstractServerConnection) { | ||||
|         if(!(handle instanceof native.ServerConnection)) | ||||
|             throw "invalid handle"; | ||||
|         //TODO: Here!
 | ||||
|         console.log("Call to destroy a server connection"); | ||||
|     } | ||||
| } | ||||
| Object.assign(window["connection"] || (window["connection"] = {} as any), _connection); | ||||
| @ -1,161 +0,0 @@ | ||||
| import {_connection} from "./ServerConnection"; | ||||
| import {_audio as _recorder} from "../audio/AudioRecorder"; | ||||
| 
 | ||||
| import { | ||||
|     NativeVoiceConnection, | ||||
|     NativeVoiceClient | ||||
| } from "teaclient_connection"; | ||||
| 
 | ||||
| export namespace _audio { | ||||
|     export namespace native { | ||||
|         import ServerConnection = _connection.native.ServerConnection; | ||||
| 
 | ||||
|         export class VoiceConnection extends connection.voice.AbstractVoiceConnection { | ||||
|             readonly connection: ServerConnection; | ||||
|             readonly handle: NativeVoiceConnection; | ||||
| 
 | ||||
|             private _audio_source: RecorderProfile; | ||||
| 
 | ||||
|             constructor(connection: ServerConnection, voice: NativeVoiceConnection) { | ||||
|                 super(connection); | ||||
|                 this.connection = connection; | ||||
|                 this.handle = voice; | ||||
|             } | ||||
| 
 | ||||
|             setup() { } | ||||
| 
 | ||||
|             async acquire_voice_recorder(recorder: RecorderProfile | undefined, enforce?: boolean) { | ||||
|                 if(this._audio_source === recorder && !enforce) | ||||
|                     return; | ||||
| 
 | ||||
|                 if(this._audio_source) | ||||
|                     await this._audio_source.unmount(); | ||||
| 
 | ||||
|                 if(recorder) { | ||||
|                     if(!(recorder.input instanceof _recorder.recorder.NativeInput)) | ||||
|                         throw "Recorder input must be an instance of NativeInput!"; | ||||
|                     await recorder.unmount(); | ||||
|                 } | ||||
| 
 | ||||
|                 this.handleVoiceEnded(); | ||||
|                 this._audio_source = recorder; | ||||
| 
 | ||||
|                 if(recorder) { | ||||
|                     recorder.current_handler = this.connection.client; | ||||
| 
 | ||||
|                     recorder.callback_unmount = () => { | ||||
|                         this._audio_source = undefined; | ||||
|                         this.handle.set_audio_source(undefined); | ||||
|                         this.connection.client.update_voice_status(undefined); | ||||
|                     }; | ||||
| 
 | ||||
|                     recorder.callback_start = this.on_voice_started.bind(this); | ||||
|                     recorder.callback_stop = this.handleVoiceEnded.bind(this); | ||||
| 
 | ||||
|                     recorder.callback_support_change = () => { | ||||
|                         this.connection.client.update_voice_status(undefined); | ||||
|                     }; | ||||
| 
 | ||||
|                     this.handle.set_audio_source((recorder.input as _recorder.recorder.NativeInput).consumer); | ||||
|                 } | ||||
|                 this.connection.client.update_voice_status(undefined); | ||||
|             } | ||||
| 
 | ||||
|             voice_playback_support() : boolean { | ||||
|                 return this.connection.connected(); | ||||
|             } | ||||
| 
 | ||||
|             voice_send_support() : boolean { | ||||
|                 return this.connection.connected(); | ||||
|             } | ||||
| 
 | ||||
|             private current_channel_codec() : number { | ||||
|                 const chandler = this.connection.client; | ||||
|                 return (chandler.getClient().currentChannel() || {properties: { channel_codec: 4}}).properties.channel_codec; | ||||
|             } | ||||
| 
 | ||||
|             private handleVoiceEnded() { | ||||
|                 const chandler = this.connection.client; | ||||
|                 chandler.getClient().speaking = false; | ||||
| 
 | ||||
|                 if(!chandler.connected) | ||||
|                     return false; | ||||
| 
 | ||||
|                 if(chandler.client_status.input_muted) | ||||
|                     return false; | ||||
| 
 | ||||
|                 console.log(tr("Local voice ended")); | ||||
|                 //TODO
 | ||||
|             } | ||||
| 
 | ||||
|             private on_voice_started() { | ||||
|                 const chandler = this.connection.client; | ||||
|                 if(chandler.client_status.input_muted) { | ||||
|                     /* evil hack due to the settings :D */ | ||||
|                     log.warn(LogCategory.VOICE, tr("Received local voice started event, even thou we're muted! Do not send any voice.")); | ||||
|                     if(this.handle) { | ||||
|                         this.handle.enable_voice_send(false); | ||||
|                     } | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 log.info(LogCategory.VOICE, tr("Local voice started")); | ||||
|                 this.handle.enable_voice_send(true); | ||||
| 
 | ||||
|                 const ch = chandler.getClient(); | ||||
|                 if(ch) ch.speaking = true; | ||||
|             } | ||||
| 
 | ||||
|             connected(): boolean { | ||||
|                 return true; /* we cant be disconnected at any time! */ | ||||
|             } | ||||
| 
 | ||||
|             voice_recorder(): RecorderProfile { | ||||
|                 return this._audio_source; | ||||
|             } | ||||
| 
 | ||||
|             available_clients(): connection.voice.VoiceClient[] { | ||||
|                 return this.handle.available_clients(); | ||||
|             } | ||||
| 
 | ||||
|             find_client(client_id: number) : connection.voice.VoiceClient | undefined { | ||||
|                 for(const client of this.available_clients()) | ||||
|                     if(client.client_id === client_id) | ||||
|                         return client; | ||||
|                 return undefined; | ||||
|             } | ||||
| 
 | ||||
|             unregister_client(client: connection.voice.VoiceClient): Promise<void> { | ||||
|                 this.handle.unregister_client(client.client_id); | ||||
|                 return Promise.resolve(); | ||||
|             } | ||||
| 
 | ||||
|             register_client(client_id: number): connection.voice.VoiceClient { | ||||
|                 const client = this.handle.register_client(client_id); | ||||
|                 if(!client) | ||||
|                     return client; | ||||
| 
 | ||||
|                 const stream = client.get_stream(); | ||||
|                 stream.set_buffer_latency(0.02); | ||||
|                 stream.set_buffer_max_latency(0.2); | ||||
|                 return client; | ||||
|             } | ||||
| 
 | ||||
|             decoding_supported(codec: number): boolean { | ||||
|                 return this.handle.decoding_supported(codec); | ||||
|             } | ||||
| 
 | ||||
|             encoding_supported(codec: number): boolean { | ||||
|                 return this.handle.encoding_supported(codec); | ||||
|             } | ||||
| 
 | ||||
|             get_encoder_codec(): number { | ||||
|                 return this.handle.get_encoder_codec(); | ||||
|             } | ||||
| 
 | ||||
|             set_encoder_codec(codec: number) { | ||||
|                 return this.handle.set_encoder_codec(codec); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,130 +0,0 @@ | ||||
| import {class_to_image} from "./icon-helper"; | ||||
| 
 | ||||
| window["require_setup"](module); | ||||
| 
 | ||||
| import * as electron from "electron"; | ||||
| const remote = electron.remote; | ||||
| const {Menu, MenuItem} = remote; | ||||
| 
 | ||||
| import {isFunction} from "util"; | ||||
| 
 | ||||
| class ElectronContextMenu implements contextmenu.ContextMenuProvider { | ||||
|     private _close_listeners: (() => any)[] = []; | ||||
|     private _current_menu: electron.Menu; | ||||
| 
 | ||||
|     private _div: JQuery; | ||||
| 
 | ||||
|     despawn_context_menu() { | ||||
|         if(!this._current_menu) | ||||
|             return; | ||||
|         this._current_menu.closePopup(); | ||||
|         this._current_menu = undefined; | ||||
| 
 | ||||
|         for(const listener of this._close_listeners) { | ||||
|             if(listener) { | ||||
|                 listener(); | ||||
|             } | ||||
|         } | ||||
|         this._close_listeners = []; | ||||
|     } | ||||
| 
 | ||||
|     finalize() { | ||||
|         if(this._div) this._div.detach(); | ||||
|         this._div = undefined; | ||||
|     } | ||||
| 
 | ||||
|     initialize() { | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private _entry_id = 0; | ||||
|     private build_menu(entry: contextmenu.MenuEntry) : electron.MenuItem { | ||||
|         if(entry.type == contextmenu.MenuEntryType.CLOSE) { | ||||
|             this._close_listeners.push(entry.callback); | ||||
|             return undefined; | ||||
|         } | ||||
| 
 | ||||
|         const click_callback = () => { | ||||
|             if(entry.callback) | ||||
|                 entry.callback(); | ||||
|             this.despawn_context_menu(); | ||||
|         }; | ||||
|         const _id = "entry_" + (this._entry_id++); | ||||
|         if(entry.type == contextmenu.MenuEntryType.ENTRY) { | ||||
|             return new MenuItem({ | ||||
|                 id: _id, | ||||
|                 label: (isFunction(entry.name) ? (entry.name as (() => string))() : entry.name) as string, | ||||
|                 type: "normal", | ||||
|                 click: click_callback, | ||||
|                 icon: class_to_image(entry.icon_class), | ||||
|                 visible: entry.visible, | ||||
|                 enabled: !entry.disabled | ||||
|             }); | ||||
|         } else if(entry.type == contextmenu.MenuEntryType.HR) { | ||||
|             if(typeof(entry.visible) === "boolean" && !entry.visible) | ||||
|                 return undefined; | ||||
| 
 | ||||
|             return new MenuItem({ | ||||
|                 id: _id, | ||||
|                 type: "separator", | ||||
|                 label: '', | ||||
|                 click: click_callback | ||||
|             }) | ||||
|         } else if(entry.type == contextmenu.MenuEntryType.CHECKBOX) { | ||||
|             return new MenuItem({ | ||||
|                 id: _id, | ||||
|                 label: (isFunction(entry.name) ? (entry.name as (() => string))() : entry.name) as string, | ||||
|                 type: "checkbox", | ||||
|                 checked: !!entry.checkbox_checked, | ||||
|                 click: click_callback, | ||||
|                 icon: class_to_image(entry.icon_class), | ||||
|                 visible: entry.visible, | ||||
|                 enabled: !entry.disabled | ||||
|             }); | ||||
|         } else if (entry.type == contextmenu.MenuEntryType.SUB_MENU) { | ||||
|             const sub_menu = new Menu(); | ||||
|             for(const e of entry.sub_menu) { | ||||
|                 const build = this.build_menu(e); | ||||
|                 if(!build) | ||||
|                     continue; | ||||
|                 sub_menu.append(build); | ||||
|             } | ||||
|             return new MenuItem({ | ||||
|                 id: _id, | ||||
|                 label: (isFunction(entry.name) ? (entry.name as (() => string))() : entry.name) as string, | ||||
|                 type: "submenu", | ||||
|                 submenu: sub_menu, | ||||
|                 click: click_callback, | ||||
|                 icon: class_to_image(entry.icon_class), | ||||
|                 visible: entry.visible, | ||||
|                 enabled: !entry.disabled | ||||
|             }); | ||||
|         } | ||||
|         return undefined; | ||||
|     } | ||||
| 
 | ||||
|     spawn_context_menu(x: number, y: number, ...entries: contextmenu.MenuEntry[]) { | ||||
|         this.despawn_context_menu(); | ||||
| 
 | ||||
|         this._current_menu = new Menu(); | ||||
|         for(const entry of entries) { | ||||
|             const build = this.build_menu(entry); | ||||
|             if(!build) | ||||
|                 continue; | ||||
|             this._current_menu.append(build); | ||||
|         } | ||||
| 
 | ||||
|         this._current_menu.popup({ | ||||
|             window: remote.getCurrentWindow(), | ||||
|             x: x, | ||||
|             y: y, | ||||
|             callback: () => this.despawn_context_menu() | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     html_format_enabled() { return false; } | ||||
| } | ||||
| 
 | ||||
| contextmenu.set_provider(new ElectronContextMenu()); | ||||
| 
 | ||||
| export {}; | ||||
| @ -1,38 +0,0 @@ | ||||
| /// <reference path="../imports/imports_shared.d.ts" />
 | ||||
| 
 | ||||
| window["require_setup"](module); | ||||
| import * as dns_handler from "teaclient_dns"; | ||||
| 
 | ||||
| namespace _dns { | ||||
|     export function supported() { return true; } | ||||
|     export async function resolve_address(address: ServerAddress, _options?: dns.ResolveOptions) : Promise<dns.AddressTarget> { | ||||
|         /* backwards compatibility */ | ||||
|         if(typeof(address) === "string") { | ||||
|             address = { | ||||
|                 host: address, | ||||
|                 port: 9987 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return new Promise<dns.AddressTarget>((resolve, reject) => { | ||||
|             dns_handler.resolve_cr(address.host, address.port, result => { | ||||
|                 if(typeof(result) === "string") | ||||
|                     reject(result); | ||||
|                 else | ||||
|                     resolve({ | ||||
|                         target_ip: result.host, | ||||
|                         target_port: result.port | ||||
|                     }); | ||||
|             }); | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Object.assign(window["dns"] || (window["dns"] = {} as any), _dns); | ||||
| loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { | ||||
|     name: "Native DNS initialized", | ||||
|     function: async () => { | ||||
|         dns_handler.initialize(); | ||||
|     }, | ||||
|     priority: 10 | ||||
| }); | ||||
| @ -1,63 +0,0 @@ | ||||
| import * as electron from "electron"; | ||||
| import NativeImage = electron.NativeImage; | ||||
| 
 | ||||
| let _div: JQuery; | ||||
| let _icon_mash_url: string; | ||||
| let _icon_mask_img: NativeImage; | ||||
| let _cache_klass_map: {[key: string]: NativeImage}; | ||||
| 
 | ||||
| export function class_to_image(klass: string) : NativeImage { | ||||
|     if(!klass || !_icon_mask_img || !_cache_klass_map) | ||||
|         return undefined; | ||||
| 
 | ||||
|     if(_cache_klass_map[klass]) | ||||
|         return _cache_klass_map[klass]; | ||||
| 
 | ||||
|     _div[0].classList.value = 'icon ' + klass; | ||||
|     const data = window.getComputedStyle(_div[0]); | ||||
| 
 | ||||
|     const offset_x = parseInt(data.backgroundPositionX.split(",")[0]); | ||||
|     const offset_y = parseInt(data.backgroundPositionY.split(",")[0]); | ||||
| 
 | ||||
|     //http://localhost/home/TeaSpeak/Web-Client/web/environment/development/img/client_icon_sprite.svg
 | ||||
|     //const hight = element.css('height');
 | ||||
|     //const width = element.css('width');
 | ||||
|     console.log("Offset: x: %o y: %o;", offset_x, offset_y); | ||||
|     return _cache_klass_map[klass] = _icon_mask_img.crop({ | ||||
|         height: 16, | ||||
|         width: 16, | ||||
|         x: offset_x == 0 ? 0 : -offset_x, | ||||
|         y: offset_y == 0 ? 0 : -offset_y | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export async function initialize() { | ||||
|     if(!_div) { | ||||
|         _div = $.spawn("div"); | ||||
|         _div.css('display', 'none'); | ||||
|         _div.appendTo(document.body); | ||||
|     } | ||||
| 
 | ||||
|     const image = new Image(); | ||||
|     image.src = 'img/client_icon_sprite.svg'; | ||||
|     await new Promise((resolve, reject) => { | ||||
|         image.onload = resolve; | ||||
|         image.onerror = reject; | ||||
|     }); | ||||
| 
 | ||||
|     /* TODO: Get a size! */ | ||||
|     const canvas = document.createElement("canvas"); | ||||
|     canvas.width = 1024; | ||||
|     canvas.height = 1024; | ||||
|     canvas.getContext("2d").drawImage(image, 0, 0); | ||||
| 
 | ||||
|     _cache_klass_map = {}; | ||||
|     _icon_mash_url = canvas.toDataURL(); | ||||
|     _icon_mask_img = electron.remote.nativeImage.createFromDataURL(_icon_mash_url); | ||||
| } | ||||
| 
 | ||||
| export function finalize() { | ||||
|     _icon_mask_img = undefined; | ||||
|     _icon_mash_url = undefined; | ||||
|     _cache_klass_map = undefined; | ||||
| } | ||||
							
								
								
									
										4677
									
								
								modules/renderer/imports/.copy_imports_shared.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4677
									
								
								modules/renderer/imports/.copy_imports_shared.d.ts
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,97 +0,0 @@ | ||||
| 
 | ||||
| /* File: /home/wolverindev/TeaSpeak/Web-Client/shared/loader/loader.ts */ | ||||
| declare interface Window { | ||||
|     tr(message: string): string; | ||||
| } | ||||
| declare namespace loader { | ||||
|     export namespace config { | ||||
|         export const loader_groups; | ||||
|         export const verbose; | ||||
|         export const error; | ||||
|     } | ||||
|     export type Task = { | ||||
|         name: string; | ||||
|         priority: number; /* tasks with the same priority will be executed in sync */ | ||||
|         function: () => Promise<void>; | ||||
|     }; | ||||
|     export enum Stage { | ||||
|         /* | ||||
|             loading loader required files (incl this) | ||||
|          */ | ||||
|         INITIALIZING, | ||||
|         /* | ||||
|             setting up the loading process | ||||
|          */ | ||||
|         SETUP, | ||||
|         /* | ||||
|             loading all style sheet files | ||||
|          */ | ||||
|         STYLE, | ||||
|         /* | ||||
|             loading all javascript files | ||||
|          */ | ||||
|         JAVASCRIPT, | ||||
|         /* | ||||
|             loading all template files | ||||
|          */ | ||||
|         TEMPLATES, | ||||
|         /* | ||||
|             initializing static/global stuff | ||||
|          */ | ||||
|         JAVASCRIPT_INITIALIZING, | ||||
|         /* | ||||
|             finalizing load process | ||||
|          */ | ||||
|         FINALIZING, | ||||
|         /* | ||||
|             invoking main task | ||||
|          */ | ||||
|         LOADED, | ||||
|         DONE | ||||
|     } | ||||
|     export function get_cache_version(); | ||||
|     export function finished(); | ||||
|     export function running(); | ||||
|     export function register_task(stage: Stage, task: Task); | ||||
|     export function execute(): Promise<any>; | ||||
|     export function execute_managed(); | ||||
|     export type DependSource = { | ||||
|         url: string; | ||||
|         depends: string[]; | ||||
|     }; | ||||
|     export type SourcePath = string | DependSource | string[]; | ||||
|     export class SyntaxError { | ||||
|         source: any; | ||||
|         constructor(source: any); | ||||
|     } | ||||
|     export function load_script(path: SourcePath): Promise<void>; | ||||
|     export function load_scripts(paths: SourcePath[]): Promise<void>; | ||||
|     export function load_style(path: SourcePath): Promise<void>; | ||||
|     export function load_styles(paths: SourcePath[]): Promise<void>; | ||||
|     export type ErrorHandler = (message: string, detail: string) => void; | ||||
|     export function critical_error(message: string, detail?: string); | ||||
|     export function critical_error_handler(handler?: ErrorHandler, override?: boolean): ErrorHandler; | ||||
| } | ||||
| declare let _fadeout_warned; | ||||
| declare function fadeoutLoader(duration?, minAge?, ignoreAge?); | ||||
| 
 | ||||
| /* File: /home/wolverindev/TeaSpeak/Web-Client/shared/loader/app.ts */ | ||||
| declare interface Window { | ||||
|     $: JQuery; | ||||
| } | ||||
| declare namespace app { | ||||
|     export enum Type { | ||||
|         UNKNOWN, | ||||
|         CLIENT_RELEASE, | ||||
|         CLIENT_DEBUG, | ||||
|         WEB_DEBUG, | ||||
|         WEB_RELEASE | ||||
|     } | ||||
|     export let type: Type; | ||||
|     export function is_web(); | ||||
|     export function ui_version(); | ||||
| } | ||||
| declare const loader_javascript; | ||||
| declare const loader_webassembly; | ||||
| declare const loader_style; | ||||
| declare function load_templates(): Promise<any>; | ||||
							
								
								
									
										2
									
								
								modules/renderer/imports/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								modules/renderer/imports/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,2 +0,0 @@ | ||||
| imports_shared.d.ts | ||||
| imports_shared_loader.d.ts | ||||
| @ -1,259 +0,0 @@ | ||||
| /// <reference path="imports/imports_shared.d.ts" />
 | ||||
| 
 | ||||
| import {Arguments, process_args, parse_arguments} from "../shared/process-arguments"; | ||||
| import {remote} from "electron"; | ||||
| import * as crash_handler from "../crash_handler"; | ||||
| import * as electron from "electron"; | ||||
| import * as path from "path"; | ||||
| import * as os from "os"; | ||||
| 
 | ||||
| /* first of all setup crash handler */ | ||||
| { | ||||
|     const is_electron_run = process.argv[0].endsWith("electron") || process.argv[0].endsWith("electron.exe"); | ||||
|     crash_handler.initialize_handler("renderer", is_electron_run); | ||||
| } | ||||
| 
 | ||||
| interface Window { | ||||
|     $: any; | ||||
|     jQuery: any; | ||||
|     jsrender: any; | ||||
| 
 | ||||
|     impl_display_critical_error: any; | ||||
|     displayCriticalError: any; | ||||
|     teaclient_initialize: any; | ||||
| 
 | ||||
|     open_connected_question: () => Promise<boolean>; | ||||
| } | ||||
| 
 | ||||
| declare const window: Window; | ||||
| 
 | ||||
| export const require_native: NodeRequireFunction = id => require(id); | ||||
| export const initialize = async () => { | ||||
|     /* we use out own jquery resource */ | ||||
|     loader.register_task(loader.Stage.JAVASCRIPT, { | ||||
|         name: "teaclient jquery", | ||||
|         function: jquery_initialize, | ||||
|         priority: 80 | ||||
|     }); | ||||
| 
 | ||||
|     loader.register_task(loader.Stage.JAVASCRIPT, { | ||||
|         name: "teaclient general", | ||||
|         function: load_basic_modules, | ||||
|         priority: 10 | ||||
|     }); | ||||
| 
 | ||||
|     loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { | ||||
|         name: "teaclient javascript init", | ||||
|         function: load_modules, | ||||
|         priority: 50 | ||||
|     }); | ||||
| 
 | ||||
|     loader.register_task(loader.Stage.INITIALIZING, { | ||||
|         name: "teaclient initialize modules", | ||||
|         function: module_loader_setup, | ||||
|         priority: 60 | ||||
|     }); | ||||
| 
 | ||||
|     loader.register_task(loader.Stage.INITIALIZING, { | ||||
|         name: "teaclient initialize persistent storage", | ||||
|         function: async () => { | ||||
|             const storage = require("./PersistentLocalStorage"); | ||||
|             await storage.initialize(); | ||||
|         }, | ||||
|         priority: 90 | ||||
|     }); | ||||
| 
 | ||||
|     loader.register_task(loader.Stage.INITIALIZING, { | ||||
|         name: "teaclient initialize logging", | ||||
|         function: initialize_logging, | ||||
|         priority: 80 | ||||
|     }); | ||||
| 
 | ||||
|     loader.register_task(loader.Stage.INITIALIZING, { | ||||
|         name: "teaclient initialize error", | ||||
|         function: initialize_error_handler, | ||||
|         priority: 100 | ||||
|     }); | ||||
| 
 | ||||
|     loader.register_task(loader.Stage.INITIALIZING, { | ||||
|         name: "teaclient initialize arguments", | ||||
|         function: async () => { | ||||
|             parse_arguments(); | ||||
|             if(process_args.has_value(Arguments.DUMMY_CRASH_RENDERER)) | ||||
|                 crash_handler.handler.crash(); | ||||
|             if(!process_args.has_flag(Arguments.DEBUG)) { | ||||
|                 window.open_connected_question = () => remote.dialog.showMessageBox(remote.getCurrentWindow(), { | ||||
|                     type: 'question', | ||||
|                     buttons: ['Yes', 'No'], | ||||
|                     title: 'Confirm', | ||||
|                     message: 'Are you really sure?\nYou\'re still connected!' | ||||
|                 }).then(result => result.response === 0); | ||||
|             } | ||||
|         }, | ||||
|         priority: 110 | ||||
|     }); | ||||
| 
 | ||||
|     loader.register_task(loader.Stage.INITIALIZING, { | ||||
|         name: 'gdb-waiter', | ||||
|         function: async () => { | ||||
|             if(process_args.has_flag(Arguments.DEV_TOOLS_GDB)) { | ||||
|                 console.log("Process ID: %d", process.pid); | ||||
|                 await new Promise(resolve => { | ||||
|                     console.log("Waiting for continue!"); | ||||
| 
 | ||||
|                     const listener = () => { | ||||
|                         console.log("Continue"); | ||||
|                         document.removeEventListener('click', listener); | ||||
|                         resolve(); | ||||
|                     }; | ||||
|                     document.addEventListener('click', listener); | ||||
|                 }); | ||||
|             } | ||||
|         }, | ||||
|         priority: 100 | ||||
|     }); | ||||
| }; | ||||
| 
 | ||||
| const jquery_initialize = async () => { | ||||
|     window.$ = require("jquery"); | ||||
|     window.jQuery = window.$; | ||||
|     Object.assign(window.$, window.jsrender = require('jsrender')); | ||||
| }; | ||||
| 
 | ||||
| const initialize_logging = async () => { | ||||
|     const logger = require("./logger"); | ||||
|     logger.setup(); | ||||
| }; | ||||
| 
 | ||||
| const initialize_error_handler = async () => { | ||||
|     const _impl = message => { | ||||
|         if(!process_args.has_flag(Arguments.DEBUG)) { | ||||
|             console.error("Displaying critical error: %o", message); | ||||
|             message = message.replace(/<br>/i, "\n"); | ||||
| 
 | ||||
|             const win = remote.getCurrentWindow(); | ||||
|             remote.dialog.showMessageBox({ | ||||
|                 type: "error", | ||||
|                 buttons: ["exit"], | ||||
|                 title: "A critical error happened!", | ||||
|                 message: message | ||||
|             }); | ||||
| 
 | ||||
|             win.close(); | ||||
|         } else { | ||||
|             console.error("Received critical error: %o", message); | ||||
|             console.error("Ignoring error due to the debug mode"); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     if(window.impl_display_critical_error) | ||||
|         window.impl_display_critical_error = _impl; | ||||
|     else | ||||
|         window.displayCriticalError = _impl; | ||||
| }; | ||||
| 
 | ||||
| const module_loader_setup = async () => { | ||||
|     const native_paths = (() => { | ||||
|         const app_path = (remote || electron).app.getAppPath(); | ||||
|         const result = []; | ||||
|         result.push(app_path + "/native/build/" + os.platform() + "_" + os.arch() + "/"); | ||||
|         if(app_path.endsWith(".asar")) | ||||
|             result.push(path.join(path.dirname(app_path), "natives")); | ||||
|         return result; | ||||
|     })(); | ||||
|     window["require_setup"] = _mod => { | ||||
|         if(!_mod || !_mod.paths) return; | ||||
| 
 | ||||
|         _mod.paths.push(...native_paths); | ||||
|         const original_require = _mod.__proto__.require; | ||||
|         if(!_mod.proxied) { | ||||
|             _mod.require = (path: string) => { | ||||
|                 if(path.endsWith("imports/imports_shared")) { | ||||
|                     console.log("Proxy require for %s. Using 'window' as result.", path); | ||||
|                     return window; | ||||
|                 } | ||||
|                 return original_require.apply(_mod, [path]); | ||||
|             }; | ||||
|             _mod.proxied = true; | ||||
|         } | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| const load_basic_modules = async () => { | ||||
|     console.dir(require("./audio/AudioPlayer")); /* setup audio */ | ||||
|     console.dir(require("./audio/AudioRecorder")); /* setup audio */ | ||||
|     require("./logger"); | ||||
| 
 | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| const load_modules = async () => { | ||||
|     window["require_setup"](this); | ||||
| 
 | ||||
|     console.log(module.paths); | ||||
|     console.log("Loading native extensions..."); | ||||
|     try { | ||||
|         try { | ||||
|             require("./version"); | ||||
|         } catch(error) { | ||||
|             console.error("Failed to load version extension"); | ||||
|             console.dir(error); | ||||
|             throw error; | ||||
|         } | ||||
|         try { | ||||
|             const helper = require("./icon-helper"); | ||||
|             await helper.initialize(); | ||||
|         } catch(error) { | ||||
|             console.error("Failed to load the icon helper extension"); | ||||
|             console.dir(error); | ||||
|             throw error; | ||||
|         } | ||||
|         try { | ||||
|             require("./ppt"); | ||||
|         } catch(error) { | ||||
|             console.error("Failed to load ppt"); | ||||
|             console.dir(error); | ||||
|             throw error; | ||||
|         } | ||||
|         try { | ||||
|             require("./connection/ServerConnection"); | ||||
|         } catch(error) { | ||||
|             console.error("Failed to load server connection extension"); | ||||
|             console.dir(error); | ||||
|             throw error; | ||||
|         } | ||||
|         try { | ||||
|             require("./connection/FileTransfer"); | ||||
|         } catch(error) { | ||||
|             console.error("Failed to load file transfer extension"); | ||||
|             console.dir(error); | ||||
|             throw error; | ||||
|         } | ||||
|         try { | ||||
|             require("./dns/dns_resolver"); | ||||
|         } catch(error) { | ||||
|             console.error("Failed to load dns extension"); | ||||
|             console.dir(error); | ||||
|             throw error; | ||||
|         } | ||||
|         try { | ||||
|             require("./menu"); | ||||
|         } catch(error) { | ||||
|             console.error("Failed to load menu extension"); | ||||
|             console.dir(error); | ||||
|             throw error; | ||||
|         } | ||||
|         try { | ||||
|             require("./context-menu"); | ||||
|         } catch(error) { | ||||
|             console.error("Failed to load context menu extension"); | ||||
|             console.dir(error); | ||||
|             throw error; | ||||
|         } | ||||
|     } catch(error){ | ||||
|         console.log(error); | ||||
|         window.displayCriticalError("Failed to load native extensions: " + error); | ||||
|         throw error; | ||||
|     } | ||||
|     console.log("Loaded native extensions"); | ||||
| }; | ||||
| @ -1,79 +0,0 @@ | ||||
| enum LogType { | ||||
|     TRACE, | ||||
|     DEBUG, | ||||
|     INFO, | ||||
|     WARNING, | ||||
|     ERROR | ||||
| } | ||||
| 
 | ||||
| export interface Logger { | ||||
|     trace(message: string, ...args); | ||||
|     debug(message: string, ...args); | ||||
|     info(message: string, ...args); | ||||
|     log(message: string, ...args); | ||||
|     warning(message: string, ...args); | ||||
|     error(message: string, ...args); | ||||
| 
 | ||||
|     dir_error(error: any, message?: string); | ||||
| } | ||||
| 
 | ||||
| let loggers = {}; | ||||
| const original_console: Console = {} as any; | ||||
| 
 | ||||
| export function setup() { | ||||
|     Object.assign(original_console, console); | ||||
|     Object.assign(console, logger("console")); | ||||
| } | ||||
| 
 | ||||
| export function logger(name: string = "console") : Logger { | ||||
|     if(loggers[name]) | ||||
|         return loggers[name]; | ||||
| 
 | ||||
|     return loggers[name] = create_logger(name); | ||||
| } | ||||
| 
 | ||||
| import * as util from "util"; | ||||
| function create_logger(name: string) : Logger { | ||||
|     const log = (type, message: string, ...args) => { | ||||
|         switch (type) { | ||||
|             case LogType.TRACE: | ||||
|                 original_console.trace(message, ...args); | ||||
|                 break; | ||||
|             case LogType.DEBUG: | ||||
|                 original_console.debug(message, ...args); | ||||
|                 break; | ||||
|             case LogType.INFO: | ||||
|                 original_console.info(message, ...args); | ||||
|                 break; | ||||
|             case LogType.WARNING: | ||||
|                 original_console.warn(message, ...args); | ||||
|                 break; | ||||
|             case LogType.ERROR: | ||||
|                 original_console.error(message, ...args); | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         const log_message = util.format(message, ...args); | ||||
|         process.stdout.write(util.format("[%s][%s] %s", name, LogType[type], log_message) + "\n"); | ||||
|     }; | ||||
| 
 | ||||
|     return { | ||||
|         trace: (m, ...a) => log(LogType.TRACE, m, ...a), | ||||
|         debug: (m, ...a) => log(LogType.DEBUG, m, ...a), | ||||
|         info: (m, ...a) => log(LogType.INFO, m, ...a), | ||||
|         log: (m, ...a) => log(LogType.INFO, m, ...a), | ||||
|         warning: (m, ...a) => log(LogType.WARNING, m, ...a), | ||||
|         error: (m, ...a) => log(LogType.ERROR, m, ...a), | ||||
| 
 | ||||
|         dir_error: (e, m) => { | ||||
|             log(LogType.ERROR, "Caught exception: " + m); | ||||
|             log(LogType.ERROR, e); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| (window as any).logger = { | ||||
|     log: function (category, level, message) { | ||||
|         console.log("%d | %d | %s", category, level, message); | ||||
|     } | ||||
| }; | ||||
| @ -1,254 +0,0 @@ | ||||
| import {class_to_image} from "./icon-helper"; | ||||
| 
 | ||||
| window["require_setup"](module); | ||||
| 
 | ||||
| import * as electron from "electron"; | ||||
| //import {top_menu as dtop_menu, Icon} from "./imports/imports_shared";
 | ||||
| 
 | ||||
| /// <reference types="./imports/import_shared.d.ts" />
 | ||||
| import dtop_menu = top_menu; | ||||
| 
 | ||||
| namespace _top_menu { | ||||
|     import ipcRenderer = electron.ipcRenderer; | ||||
|     namespace native { | ||||
|         import ipcRenderer = electron.ipcRenderer; | ||||
|         let _item_index = 1; | ||||
| 
 | ||||
|         abstract class NativeMenuBase { | ||||
|             protected _handle: NativeMenuBar; | ||||
|             protected _click: () => any; | ||||
|             id: string; | ||||
| 
 | ||||
|             protected constructor(handle: NativeMenuBar, id?: string) { | ||||
|                 this._handle = handle; | ||||
|                 this.id = id || ("item_" + (_item_index++)); | ||||
|             } | ||||
| 
 | ||||
|             abstract build() : electron.MenuItemConstructorOptions; | ||||
|             abstract items(): (dtop_menu.MenuItem | dtop_menu.HRItem)[]; | ||||
| 
 | ||||
|             trigger_click() { | ||||
|                 if(this._click) | ||||
|                     this._click(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         class NativeMenuItem extends NativeMenuBase implements dtop_menu.MenuItem { | ||||
|             private _items: (NativeMenuItem | NativeHrItem)[] = []; | ||||
|             private _label: string; | ||||
|             private _enabled: boolean = true; | ||||
|             private _visible: boolean = true; | ||||
| 
 | ||||
|             private _icon_data: string; | ||||
| 
 | ||||
|             constructor(handle: NativeMenuBar) { | ||||
|                 super(handle); | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             append_hr(): dtop_menu.HRItem { | ||||
|                 const item = new NativeHrItem(this._handle); | ||||
|                 this._items.push(item); | ||||
|                 return item; | ||||
|             } | ||||
| 
 | ||||
|             append_item(label: string): dtop_menu.MenuItem { | ||||
|                 const item = new NativeMenuItem(this._handle); | ||||
|                 item.label(label); | ||||
|                 this._items.push(item); | ||||
|                 return item; | ||||
|             } | ||||
| 
 | ||||
|             click(callback: () => any): this { | ||||
|                 this._click = callback; | ||||
|                 return this; | ||||
|             } | ||||
| 
 | ||||
|             delete_item(item: dtop_menu.MenuItem | dtop_menu.HRItem) { | ||||
|                 const i_index = this._items.indexOf(item as any); | ||||
|                 if(i_index < 0) return; | ||||
|                 this._items.splice(i_index, 1); | ||||
|             } | ||||
| 
 | ||||
|             disabled(value?: boolean): boolean { | ||||
|                 if(typeof(value) === "boolean") | ||||
|                     this._enabled = !value; | ||||
|                 return !this._enabled; | ||||
|             } | ||||
| 
 | ||||
|             icon(klass?: string | Promise<Icon> | Icon): string { | ||||
|                 if(typeof(klass) === "string") { | ||||
|                     const buffer = class_to_image(klass); | ||||
|                     if(buffer) | ||||
|                         this._icon_data = buffer.toDataURL(); | ||||
|                 } | ||||
|                 return ""; | ||||
|             } | ||||
| 
 | ||||
|             items(): (dtop_menu.MenuItem | dtop_menu.HRItem)[] { | ||||
|                 return this._items; | ||||
|             } | ||||
| 
 | ||||
|             label(value?: string): string { | ||||
|                 if(typeof(value) === "string") | ||||
|                     this._label = value; | ||||
|                 return this._label; | ||||
|             } | ||||
| 
 | ||||
|             visible(value?: boolean): boolean { | ||||
|                 if(typeof(value) === "boolean") | ||||
|                     this._visible = value; | ||||
|                 return this._visible; | ||||
|             } | ||||
| 
 | ||||
|             build(): Electron.MenuItemConstructorOptions { | ||||
|                 return { | ||||
|                     id: this.id, | ||||
| 
 | ||||
|                     label: this._label || "", | ||||
| 
 | ||||
|                     submenu: this._items.length > 0 ? this._items.map(e => e.build()) : undefined, | ||||
|                     enabled: this._enabled, | ||||
|                     visible: this._visible, | ||||
| 
 | ||||
|                     icon: this._icon_data | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         class NativeHrItem extends NativeMenuBase implements dtop_menu.HRItem { | ||||
|             constructor(handle: NativeMenuBar) { | ||||
|                 super(handle); | ||||
|             } | ||||
| 
 | ||||
|             build(): Electron.MenuItemConstructorOptions { | ||||
|                 return { | ||||
|                     type: 'separator', | ||||
|                     id: this.id | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             items(): (dtop_menu.MenuItem | dtop_menu.HRItem)[] { | ||||
|                 return []; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         function is_similar_deep(a, b) { | ||||
|             if(typeof(a) !== typeof(b)) | ||||
|                 return false; | ||||
|             if(typeof(a) !== "object") | ||||
|                 return a === b; | ||||
| 
 | ||||
|             const aProps = Object.keys(a); | ||||
|             const bProps = Object.keys(b); | ||||
| 
 | ||||
|             if (aProps.length != bProps.length) | ||||
|                 return false; | ||||
| 
 | ||||
|             for (let i = 0; i < aProps.length; i++) { | ||||
|                 const propName = aProps[i]; | ||||
| 
 | ||||
|                 if(!is_similar_deep(a[propName], b[propName])) | ||||
|                     return false; | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         export class NativeMenuBar implements dtop_menu.MenuBarDriver { | ||||
|             private static _instance: NativeMenuBar; | ||||
| 
 | ||||
|             private menu: electron.Menu; | ||||
|             private _items: NativeMenuItem[] = []; | ||||
|             private _current_menu: electron.MenuItemConstructorOptions[]; | ||||
| 
 | ||||
|             public static instance() : NativeMenuBar { | ||||
|                 if(!this._instance) | ||||
|                     this._instance = new NativeMenuBar(); | ||||
|                 return this._instance; | ||||
|             } | ||||
| 
 | ||||
|             append_item(label: string): dtop_menu.MenuItem { | ||||
|                 const item = new NativeMenuItem(this); | ||||
|                 item.label(label); | ||||
|                 this._items.push(item); | ||||
|                 return item; | ||||
|             } | ||||
| 
 | ||||
|             delete_item(item: dtop_menu.MenuItem) { | ||||
|                 const i_index = this._items.indexOf(item as any); | ||||
|                 if(i_index < 0) return; | ||||
|                 this._items.splice(i_index, 1); | ||||
|             } | ||||
| 
 | ||||
|             flush_changes() { | ||||
|                 const target_menu = this.build_menu(); | ||||
|                 if(is_similar_deep(target_menu, this._current_menu)) | ||||
|                     return; | ||||
| 
 | ||||
|                 this._current_menu = target_menu; | ||||
|                 ipcRenderer.send('top-menu', target_menu); | ||||
|             } | ||||
| 
 | ||||
|             private build_menu() : electron.MenuItemConstructorOptions[] { | ||||
|                 return this._items.map(e => e.build()); | ||||
|             } | ||||
| 
 | ||||
|             items(): dtop_menu.MenuItem[] { | ||||
|                 return this._items; | ||||
|             } | ||||
| 
 | ||||
|             initialize() { | ||||
|                 this.menu = new electron.remote.Menu(); | ||||
|                 ipcRenderer.on('top-menu', (event, clicked_item) => { | ||||
|                     console.log("Item %o clicked", clicked_item); | ||||
|                     const check_item = (item: NativeMenuBase) => { | ||||
|                         if(item.id == clicked_item) { | ||||
|                             item.trigger_click(); | ||||
|                             return true; | ||||
|                         } | ||||
|                         for(const child of item.items()) | ||||
|                             if(check_item(child as NativeMenuBase)) | ||||
|                                 return true; | ||||
|                     }; | ||||
| 
 | ||||
|                     for(const item of this._items) | ||||
|                         if(check_item(item)) | ||||
|                             return; | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //Global variable
 | ||||
|     // @ts-ignore
 | ||||
|     top_menu.set_driver(native.NativeMenuBar.instance()); | ||||
| 
 | ||||
| 
 | ||||
|     const call_basic_action = (name: string, ...args: any[]) => ipcRenderer.send('basic-action', name, ...args); | ||||
|     top_menu.native_actions = { | ||||
|         open_change_log() { | ||||
|             call_basic_action("open-changelog"); | ||||
|         }, | ||||
| 
 | ||||
|         check_native_update() { | ||||
|             call_basic_action("check-native-update"); | ||||
|         }, | ||||
| 
 | ||||
|         quit() { | ||||
|             call_basic_action("quit"); | ||||
|         }, | ||||
| 
 | ||||
|         open_dev_tools() { | ||||
|             call_basic_action("open-dev-tools"); | ||||
|         }, | ||||
| 
 | ||||
|         reload_page() { | ||||
|             call_basic_action("reload-window") | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export {}; | ||||
| @ -1,134 +0,0 @@ | ||||
| window["require_setup"](module); | ||||
| 
 | ||||
| import {KeyEvent as NKeyEvent} from "teaclient_ppt"; | ||||
| namespace _ppt { | ||||
|     let key_listener: ((_: ppt.KeyEvent) => any)[] = []; | ||||
| 
 | ||||
|     let native_ppt; | ||||
|     function listener_key(type: ppt.EventType, nevent: NKeyEvent) { | ||||
|         if(nevent.key_code === 'VoidSymbol' || nevent.key_code === 'error') | ||||
|             nevent.key_code = undefined; /* trigger event for state update */ | ||||
| 
 | ||||
|         let event: ppt.KeyEvent = { | ||||
|             type: type, | ||||
| 
 | ||||
|             key: nevent.key_code, | ||||
|             key_code: nevent.key_code, | ||||
| 
 | ||||
|             key_ctrl: nevent.key_ctrl, | ||||
|             key_shift: nevent.key_shift, | ||||
|             key_alt: nevent.key_alt, | ||||
|             key_windows: nevent.key_windows | ||||
|         } as any; | ||||
| 
 | ||||
|         //console.debug("Trigger key event %o", key_event);
 | ||||
| 
 | ||||
|         for (const listener of key_listener) | ||||
|             listener(event); | ||||
|     } | ||||
| 
 | ||||
|     function native_keyhook(event: NKeyEvent) { | ||||
|         //console.log("Native event!: %o", event);
 | ||||
| 
 | ||||
|         if(event.type == 0) | ||||
|             listener_key(ppt.EventType.KEY_PRESS, event); | ||||
|         else if(event.type == 1) | ||||
|             listener_key(ppt.EventType.KEY_RELEASE, event); | ||||
|         else if(event.type == 2) | ||||
|             listener_key(ppt.EventType.KEY_TYPED, event); | ||||
|     } | ||||
| 
 | ||||
|     export async function initialize() : Promise<void> { | ||||
|         native_ppt = require("teaclient_ppt"); | ||||
| 
 | ||||
|         register_key_listener(listener_hook); | ||||
|         native_ppt.RegisterCallback(native_keyhook); | ||||
|     } | ||||
| 
 | ||||
|     export function finalize() { | ||||
|         unregister_key_listener(listener_hook); | ||||
|         native_ppt.UnregisterCallback(native_keyhook); | ||||
|     } | ||||
| 
 | ||||
|     export function register_key_listener(listener: (_: ppt.KeyEvent) => any) { | ||||
|         key_listener.push(listener); | ||||
|     } | ||||
| 
 | ||||
|     export function unregister_key_listener(listener: (_: ppt.KeyEvent) => any) { | ||||
|         key_listener.remove(listener); | ||||
|     } | ||||
| 
 | ||||
|     interface CurrentState { | ||||
|         event: ppt.KeyEvent; | ||||
|         code: string; | ||||
| 
 | ||||
|         special: { [key:number]:boolean }; | ||||
|     } | ||||
| 
 | ||||
|     let current_state: CurrentState = { | ||||
|         special: [] | ||||
|     } as any; | ||||
| 
 | ||||
|     let key_hooks: ppt.KeyHook[] = []; | ||||
|     let key_hooks_active: ppt.KeyHook[] = []; | ||||
| 
 | ||||
|     function listener_hook(event: ppt.KeyEvent) { | ||||
|         if(event.type == ppt.EventType.KEY_TYPED) | ||||
|             return; | ||||
| 
 | ||||
|         let old_hooks = [...key_hooks_active]; | ||||
|         let new_hooks = []; | ||||
| 
 | ||||
|         current_state.special[ppt.SpecialKey.ALT] = event.key_alt; | ||||
|         current_state.special[ppt.SpecialKey.CTRL] = event.key_ctrl; | ||||
|         current_state.special[ppt.SpecialKey.SHIFT] = event.key_shift; | ||||
|         current_state.special[ppt.SpecialKey.WINDOWS] = event.key_windows; | ||||
| 
 | ||||
|         current_state.code = undefined; | ||||
|         current_state.event = undefined; | ||||
| 
 | ||||
|         if(event.type == ppt.EventType.KEY_PRESS) { | ||||
|             current_state.event = event; | ||||
|             current_state.code = event.key_code; | ||||
| 
 | ||||
|             for(const hook of key_hooks) { | ||||
|                 if(hook.key_code && hook.key_code != event.key_code) continue; | ||||
|                 if(hook.key_alt != event.key_alt) continue; | ||||
|                 if(hook.key_ctrl != event.key_ctrl) continue; | ||||
|                 if(hook.key_shift != event.key_shift) continue; | ||||
|                 if(hook.key_windows != event.key_windows) continue; | ||||
| 
 | ||||
|                 new_hooks.push(hook); | ||||
|                 if(!old_hooks.remove(hook) && hook.callback_press) { | ||||
|                     hook.callback_press(); | ||||
|                     console.debug("Trigger key press for %o!", hook); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         //We have a new situation
 | ||||
|         for(const hook of old_hooks) | ||||
|             if(hook.callback_release) { | ||||
|                 hook.callback_release(); | ||||
|                 console.debug("Trigger key release for %o!", hook); | ||||
|             } | ||||
|         key_hooks_active = new_hooks; | ||||
|     } | ||||
| 
 | ||||
|     export function register_key_hook(hook: ppt.KeyHook) { | ||||
|         key_hooks.push(hook); | ||||
|     } | ||||
| 
 | ||||
|     export function unregister_key_hook(hook: ppt.KeyHook) { | ||||
|         key_hooks.remove(hook); | ||||
|         key_hooks_active.remove(hook); | ||||
|     } | ||||
| 
 | ||||
|     export function key_pressed(code: string | ppt.SpecialKey) : boolean { | ||||
|         if(typeof(code) === 'string') | ||||
|             return current_state.code == code; | ||||
|         return current_state.special[code]; | ||||
|     } | ||||
| } | ||||
| Object.assign(window["ppt"] || (window["ppt"] = {} as any), _ppt); | ||||
| console.dir(_ppt); | ||||
| @ -1,21 +0,0 @@ | ||||
| namespace native { | ||||
|     const remote = require('electron').remote; | ||||
|     export async function client_version() : Promise<string> { | ||||
|         const version = remote.getGlobal("app_version_client"); | ||||
|         return version || "?.?.?"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| window["native"] = native; | ||||
| 
 | ||||
| namespace forum { | ||||
|     export interface UserData { | ||||
|         session_id: string; | ||||
|         username: string; | ||||
| 
 | ||||
|         application_data: string; | ||||
|         application_data_sign: string; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| //window["forum"] = forum;
 | ||||
| @ -1,99 +0,0 @@ | ||||
| import * as electron from "electron"; | ||||
| import {app} from "electron"; | ||||
| 
 | ||||
| export class Arguments { | ||||
|     static readonly DEV_TOOLS = ["t", "dev-tools"]; | ||||
|     static readonly DEV_TOOLS_GDB = ["gdb"]; | ||||
|     static readonly DEBUG = ["d", "debug"]; | ||||
|     static readonly DISABLE_ANIMATION = ["a", "disable-animation"]; | ||||
|     static readonly SERVER_URL = ["u", "server-url"]; | ||||
|     static readonly UPDATER_UI_DEBUG = ["updater-debug-ui"]; | ||||
|     static readonly UPDATER_ENFORCE = ["updater-enforce"]; | ||||
|     static readonly UPDATER_CHANNEL = ["updater-channel"]; | ||||
|     static readonly UPDATER_LOCAL_VERSION = ["updater-local-version"]; | ||||
|     static readonly UPDATER_UI_LOAD_TYPE = ["updater-ui-loader_type"]; | ||||
|     static readonly UPDATER_UI_NO_CACHE = ["updater-ui-no-cache"]; | ||||
|     static readonly DISABLE_HARDWARE_ACCELERATION = ["disable-hardware-acceleration"]; | ||||
|     static readonly NO_SINGLE_INSTANCE = ["no-single-instance"]; | ||||
|     static readonly DUMMY_CRASH_MAIN = ["dummy-crash-main"]; | ||||
|     static readonly DUMMY_CRASH_RENDERER = ["dummy-crash-renderer"]; | ||||
| 
 | ||||
|     has_flag: (...keys: (string | string[])[]) => boolean; | ||||
|     has_value: (...keys: (string | string[])[]) => boolean; | ||||
|     value: (...keys: (string | string[])[]) => string; | ||||
| } | ||||
| 
 | ||||
| export interface Window { | ||||
|     process_args: Arguments; | ||||
| } | ||||
| 
 | ||||
| export const process_args: Arguments = {} as Arguments; | ||||
| 
 | ||||
| export function parse_arguments() { | ||||
|     if(!process || !process.type || process.type === 'renderer') { | ||||
|         Object.assign(process_args, electron.remote.getGlobal("process_arguments")); | ||||
|         (window as any).process_args = process_args; | ||||
|     } else { | ||||
|         const is_electron_run = process.argv[0].endsWith("electron") || process.argv[0].endsWith("electron.exe"); | ||||
| 
 | ||||
|         { | ||||
|             const minimist: <T> (args, opts) => T = require("./minimist") as any; | ||||
|             let args = minimist<Arguments>(is_electron_run ? process.argv.slice(2) : process.argv.slice(1), { | ||||
|                 boolean: true, | ||||
|                 stopEarly: true | ||||
|             }) as Arguments; | ||||
|             args.has_flag = (...keys) => { | ||||
|                 for(const key of [].concat(...Array.of(...keys).map(e => Array.isArray(e) ? Array.of(...e) : [e]))) | ||||
|                     if(typeof process_args[key as any as string] === "boolean") | ||||
|                         return process_args[key as any as string]; | ||||
|                 return false; | ||||
|             }; | ||||
| 
 | ||||
|             args.value = (...keys) => { | ||||
|                 for(const key of [].concat(...Array.of(...keys).map(e => Array.isArray(e) ? Array.of(...e) : [e]))) | ||||
|                     if(typeof process_args[key] !== "undefined") | ||||
|                         return process_args[key]; | ||||
|                 return undefined; | ||||
|             }; | ||||
| 
 | ||||
|             args.has_value = (...keys) => { | ||||
|                 return args.value(...keys) !== undefined; | ||||
|             }; | ||||
| 
 | ||||
|             if(args.has_flag(Arguments.DEBUG)) { | ||||
|                 const _has_flag = args.has_flag; | ||||
|                 args.has_flag = (...keys) => { | ||||
|                     const result = _has_flag(...keys); | ||||
|                     console.log("Process argument test for parameter %o results in %o", keys, result); | ||||
|                     return result; | ||||
|                 }; | ||||
| 
 | ||||
|                 const _value = args.value; | ||||
|                 args.value = (...keys) => { | ||||
|                     const result = _value(...keys); | ||||
|                     console.log("Process argument test for parameter %o results in %o", keys, result); | ||||
|                     return result; | ||||
|                 } | ||||
|             } | ||||
|             console.log("Parsed CMD arguments %o as %o", process.argv, args); | ||||
|             Object.assign(process_args, args); | ||||
|             Object.assign(global["process_arguments"] = {}, args); | ||||
|         } | ||||
| 
 | ||||
|         if(process_args.has_flag("help", "h")) { | ||||
|             console.log("TeaClient command line help page"); | ||||
|             console.log(" -h --help => Displays this page"); | ||||
|             console.log(" -d --debug => Enabled the application debug"); | ||||
|             console.log(" -t --dev-tools => Enables dev tools"); | ||||
|             console.log(" -u --server-url => Sets the remote client api server url"); | ||||
|             console.log("    --updater-channel => Set the updater channel"); | ||||
|             console.log(" -a --disable-animation => Disables some cosmetic animations and loadings"); | ||||
|             console.log("    --disable-hardware-acceleration => Disables the hardware acceleration for the UI"); | ||||
|             console.log("    --no-single-instance => Disable multi instance testing"); | ||||
| 
 | ||||
|             //is_debug = process_args.has_flag("debug", "d");
 | ||||
|             //open_dev_tools = process_args.has_flag("dev-tools", "dt");
 | ||||
|             app.exit(0); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,299 +0,0 @@ | ||||
| namespace minimist { | ||||
|     export interface Opts { | ||||
|         /** | ||||
|          * A string or array of strings argument names to always treat as strings | ||||
|          */ | ||||
|         string?: string | string[]; | ||||
| 
 | ||||
|         /** | ||||
|          * A boolean, string or array of strings to always treat as booleans. If true will treat | ||||
|          * all double hyphenated arguments without equals signs as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`) | ||||
|          */ | ||||
|         boolean?: boolean | string | string[]; | ||||
| 
 | ||||
|         /** | ||||
|          * An object mapping string names to strings or arrays of string argument names to use as aliases | ||||
|          */ | ||||
|         alias?: { [key: string]: string | string[] }; | ||||
| 
 | ||||
|         /** | ||||
|          * An object mapping string argument names to default values | ||||
|          */ | ||||
|         default?: { [key: string]: any }; | ||||
| 
 | ||||
|         /** | ||||
|          * When true, populate argv._ with everything after the first non-option | ||||
|          */ | ||||
|         stopEarly?: boolean; | ||||
| 
 | ||||
|         /** | ||||
|          * A function which is invoked with a command line parameter not defined in the opts | ||||
|          * configuration object. If the function returns false, the unknown option is not added to argv | ||||
|          */ | ||||
|         unknown?: (arg: string) => boolean; | ||||
| 
 | ||||
|         /** | ||||
|          * When true, populate argv._ with everything before the -- and argv['--'] with everything after the --. | ||||
|          * Note that with -- set, parsing for arguments still stops after the `--`. | ||||
|          */ | ||||
|         '--'?: boolean; | ||||
|     } | ||||
| 
 | ||||
|     export interface ParsedArgs { | ||||
|         [arg: string]: any; | ||||
| 
 | ||||
|         /** | ||||
|          * If opts['--'] is true, populated with everything after the -- | ||||
|          */ | ||||
|         '--'?: string[]; | ||||
| 
 | ||||
|         /** | ||||
|          * Contains all the arguments that didn't have an option associated with them | ||||
|          */ | ||||
|         _: string[]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const parse = (args: string[], options: minimist.Opts) => { | ||||
|     options = options || {}; | ||||
| 
 | ||||
|     var flags = { bools : {}, strings : {}, unknownFn: null, allBools: false }; | ||||
| 
 | ||||
|     if (typeof options['unknown'] === 'function') { | ||||
|         flags.unknownFn = options['unknown']; | ||||
|     } | ||||
| 
 | ||||
|     if (typeof options['boolean'] === 'boolean' && options['boolean']) { | ||||
|         flags.allBools = true; | ||||
|     } else { | ||||
|         [].concat(options['boolean']).filter(Boolean).forEach(function (key) { | ||||
|             flags.bools[key] = true; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     var aliases = {}; | ||||
|     Object.keys(options.alias || {}).forEach(function (key) { | ||||
|         aliases[key] = [].concat(options.alias[key]); | ||||
|         aliases[key].forEach(function (x) { | ||||
|             aliases[x] = [key].concat(aliases[key].filter(function (y) { | ||||
|                 return x !== y; | ||||
|             })); | ||||
|         }); | ||||
|     }); | ||||
| 
 | ||||
|     [].concat(options.string).filter(Boolean).forEach(function (key) { | ||||
|         flags.strings[key] = true; | ||||
|         if (aliases[key]) { | ||||
|             flags.strings[aliases[key]] = true; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     var defaults = options['default'] || {}; | ||||
| 
 | ||||
|     var argv = { _ : [] }; | ||||
|     Object.keys(flags.bools).forEach(function (key) { | ||||
|         setArg(key, defaults[key] === undefined ? false : defaults[key]); | ||||
|     }); | ||||
| 
 | ||||
|     var notFlags = []; | ||||
| 
 | ||||
|     if (args.indexOf('--') !== -1) { | ||||
|         notFlags = args.slice(args.indexOf('--')+1); | ||||
|         args = args.slice(0, args.indexOf('--')); | ||||
|     } | ||||
| 
 | ||||
|     function argDefined(key, arg) { | ||||
|         return (flags.allBools && /^--[^=]+$/.test(arg)) || | ||||
|             flags.strings[key] || flags.bools[key] || aliases[key]; | ||||
|     } | ||||
| 
 | ||||
|     function setArg (key, val, arg?) { | ||||
|         if (arg && flags.unknownFn && !argDefined(key, arg)) { | ||||
|             if (flags.unknownFn(arg) === false) return; | ||||
|         } | ||||
| 
 | ||||
|         var value = !flags.strings[key] && isNumber(val) | ||||
|             ? Number(val) : val | ||||
|         ; | ||||
|         setKey(argv, key.split('.'), value); | ||||
| 
 | ||||
|         (aliases[key] || []).forEach(function (x) { | ||||
|             setKey(argv, x.split('.'), value); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     function setKey (obj, keys, value) { | ||||
|         var o = obj; | ||||
|         keys.slice(0,-1).forEach(function (key) { | ||||
|             if (o[key] === undefined) o[key] = {}; | ||||
|             o = o[key]; | ||||
|         }); | ||||
| 
 | ||||
|         var key = keys[keys.length - 1]; | ||||
|         if (o[key] === undefined || flags.bools[key] || typeof o[key] === 'boolean') { | ||||
|             o[key] = value; | ||||
|         } | ||||
|         else if (Array.isArray(o[key])) { | ||||
|             o[key].push(value); | ||||
|         } | ||||
|         else { | ||||
|             o[key] = [ o[key], value ]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function aliasIsBoolean(key) { | ||||
|         return aliases[key].some(function (x) { | ||||
|             return flags.bools[x]; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     for (var i = 0; i < args.length; i++) { | ||||
|         var arg = args[i]; | ||||
| 
 | ||||
|         if (/^--.+=/.test(arg)) { | ||||
|             // Using [\s\S] instead of . because js doesn't support the
 | ||||
|             // 'dotall' regex modifier. See:
 | ||||
|             // http://stackoverflow.com/a/1068308/13216
 | ||||
|             var m = arg.match(/^--([^=]+)=([\s\S]*)$/); | ||||
|             var key = m[1]; | ||||
|             var value: any = m[2]; | ||||
|             if (flags.bools[key]) { | ||||
|                 value = value !== 'false'; | ||||
|             } | ||||
|             setArg(key, value, arg); | ||||
|         } | ||||
|         /* | ||||
|         else if (/^--no-.+/.test(arg)) { | ||||
|             var key = arg.match(/^--no-(.+)/)[1]; | ||||
|             setArg(key, false, arg); | ||||
|         } | ||||
|         */ | ||||
|         else if (/^--.+/.test(arg)) { | ||||
|             var key = arg.match(/^--(.+)/)[1]; | ||||
|             var next = args[i + 1]; | ||||
|             if (next !== undefined && !/^-/.test(next) | ||||
|                 && !flags.bools[key] | ||||
|                 && !flags.allBools | ||||
|                 && (aliases[key] ? !aliasIsBoolean(key) : true)) { | ||||
|                 setArg(key, next, arg); | ||||
|                 i++; | ||||
|             } | ||||
|             else if (/^(true|false)$/.test(next)) { | ||||
|                 setArg(key, next === 'true', arg); | ||||
|                 i++; | ||||
|             } | ||||
|             else { | ||||
|                 setArg(key, flags.strings[key] ? '' : true, arg); | ||||
|             } | ||||
|         } | ||||
|         else if (/^-[^-]+/.test(arg)) { | ||||
|             var letters = arg.slice(1,-1).split(''); | ||||
| 
 | ||||
|             var broken = false; | ||||
|             for (var j = 0; j < letters.length; j++) { | ||||
|                 var next = arg.slice(j+2); | ||||
| 
 | ||||
|                 if (next === '-') { | ||||
|                     setArg(letters[j], next, arg) | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 if (/[A-Za-z]/.test(letters[j]) && /=/.test(next)) { | ||||
|                     setArg(letters[j], next.split('=')[1], arg); | ||||
|                     broken = true; | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 if (/[A-Za-z]/.test(letters[j]) | ||||
|                     && /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) { | ||||
|                     setArg(letters[j], next, arg); | ||||
|                     broken = true; | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 if (letters[j+1] && letters[j+1].match(/\W/)) { | ||||
|                     setArg(letters[j], arg.slice(j+2), arg); | ||||
|                     broken = true; | ||||
|                     break; | ||||
|                 } | ||||
|                 else { | ||||
|                     setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             var key = arg.slice(-1)[0]; | ||||
|             if (!broken && key !== '-') { | ||||
|                 if (args[i+1] && !/^(-|--)[^-]/.test(args[i+1]) | ||||
|                     && !flags.bools[key] | ||||
|                     && (aliases[key] ? !aliasIsBoolean(key) : true)) { | ||||
|                     setArg(key, args[i+1], arg); | ||||
|                     i++; | ||||
|                 } | ||||
|                 else if (args[i+1] && /true|false/.test(args[i+1])) { | ||||
|                     setArg(key, args[i+1] === 'true', arg); | ||||
|                     i++; | ||||
|                 } | ||||
|                 else { | ||||
|                     setArg(key, flags.strings[key] ? '' : true, arg); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             if (!flags.unknownFn || flags.unknownFn(arg) !== false) { | ||||
|                 argv._.push( | ||||
|                     flags.strings['_'] || !isNumber(arg) ? arg : Number(arg) | ||||
|                 ); | ||||
|             } | ||||
|             if (options.stopEarly) { | ||||
|                 argv._.push.apply(argv._, args.slice(i + 1)); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     Object.keys(defaults).forEach(function (key) { | ||||
|         if (!hasKey(argv, key.split('.'))) { | ||||
|             setKey(argv, key.split('.'), defaults[key]); | ||||
| 
 | ||||
|             (aliases[key] || []).forEach(function (x) { | ||||
|                 setKey(argv, x.split('.'), defaults[key]); | ||||
|             }); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     if (options['--']) { | ||||
|         argv['--'] = new Array(); | ||||
|         notFlags.forEach(function(key) { | ||||
|             argv['--'].push(key); | ||||
|         }); | ||||
|     } | ||||
|     else { | ||||
|         notFlags.forEach(function(key) { | ||||
|             argv._.push(key); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     return argv; | ||||
| }; | ||||
| 
 | ||||
| function hasKey (obj, keys) { | ||||
|     var o = obj; | ||||
|     keys.slice(0,-1).forEach(function (key) { | ||||
|         o = (o[key] || {}); | ||||
|     }); | ||||
| 
 | ||||
|     var key = keys[keys.length - 1]; | ||||
|     return key in o; | ||||
| } | ||||
| 
 | ||||
| function isNumber (x) { | ||||
|     if (typeof x === 'number') return true; | ||||
|     if (/^0x[0-9a-f]+$/i.test(x)) return true; | ||||
|     return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function minimist<T>(args?: string[], opts?: minimist.Opts): T & minimist.ParsedArgs { | ||||
|     return parse(args || [], opts) as any; | ||||
| } | ||||
| export = minimist; | ||||
| @ -1,81 +0,0 @@ | ||||
| export class Version { | ||||
|     major: number = 0; | ||||
|     minor: number = 0; | ||||
|     patch: number = 0; | ||||
|     build: number = 0; | ||||
|     timestamp: number = 0; | ||||
| 
 | ||||
|     constructor(major: number, minor: number, patch: number, build: number, timestamp: number) { | ||||
|         this.major = major; | ||||
|         this.minor = minor; | ||||
|         this.patch = patch; | ||||
|         this.build = build; | ||||
|     } | ||||
| 
 | ||||
|     toString(timestamp: boolean = false) { | ||||
|         let result = ""; | ||||
|         result += this.major + "."; | ||||
|         result += this.minor + "."; | ||||
|         result += this.patch; | ||||
|         if(this.build > 0) | ||||
|             result += "-" + this.build; | ||||
|         if(timestamp && this.timestamp > 0) | ||||
|             result += " [" + this.timestamp + "]"; | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     equals(other: Version) : boolean { | ||||
|         if(other == this) return true; | ||||
|         if(typeof(other) != typeof(this)) return false; | ||||
| 
 | ||||
|         if(other.major != this.major) return false; | ||||
|         if(other.minor != this.minor) return false; | ||||
|         if(other.patch != this.patch) return false; | ||||
|         if(other.build != this.build) return false; | ||||
|         if(other.timestamp != this.timestamp) return false; | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     newer_than(other: Version) : boolean { | ||||
|         if(other.major > this.major) return false; | ||||
|         else if(other.major < this.major) return true; | ||||
| 
 | ||||
|         if(other.minor > this.minor) return false; | ||||
|         else if(other.minor < this.minor) return true; | ||||
| 
 | ||||
|         else if(other.patch < this.patch) return true; | ||||
|         if(other.patch > this.patch) return false; | ||||
| 
 | ||||
|         if(other.build > this.build) return false; | ||||
|         else if(other.build < this.build) return true; | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     in_dev() : boolean { | ||||
|         return this.build == 0 && this.major == 0 && this.minor == 0 && this.patch == 0 && this.timestamp == 0; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| //1.0.0-2 [1000]
 | ||||
| export function parse_version(version: string) : Version { | ||||
|     let result: Version = new Version(0, 0, 0, 0, 0); | ||||
| 
 | ||||
|     const roots = version.split(" "); | ||||
|     { | ||||
|         const parts = roots[0].split("-"); | ||||
|         const numbers = parts[0].split("."); | ||||
| 
 | ||||
|         if(numbers.length > 0) result.major = parseInt(numbers[0]); | ||||
|         if(numbers.length > 1) result.minor = parseInt(numbers[1]); | ||||
|         if(numbers.length > 2) result.patch = parseInt(numbers[2]); | ||||
|         if(parts.length > 1) result.build = parseInt(parts[1]); | ||||
|     } | ||||
|     if(roots.length > 1 && ((roots[1] = roots[1].trim()).startsWith("[") && roots[1].endsWith("]"))) { | ||||
|         result.timestamp = parseInt(roots[1].substr(1, roots[1].length - 2)); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| @ -1,19 +0,0 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "module": "commonjs", | ||||
|     "target": "es6", | ||||
|     "sourceMap": true, | ||||
|     "moduleResolution": "node" | ||||
|   }, | ||||
|   "include": [ | ||||
|     "./core", | ||||
|     "./crash_handler", | ||||
|     "./shared", | ||||
|     "../main.ts" | ||||
|   ], | ||||
|   "exclude": [ | ||||
|     "node_modules", | ||||
|     "declarations", | ||||
|     "app/dummy-declarations/*.d.ts" | ||||
|   ] | ||||
| } | ||||
| @ -1,17 +0,0 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "module": "commonjs", | ||||
|     "target": "es6", | ||||
|     "sourceMap": true, | ||||
|     "moduleResolution": "node" | ||||
|   }, | ||||
|   "include": [ | ||||
|     "./renderer", | ||||
|     "./crash_handler", | ||||
|     "./shared/*.ts", | ||||
|     "../native/*/exports/" | ||||
|   ], | ||||
|   "exclude": [ | ||||
|     "./renderer/imports/.copy_*.d.ts" | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										3
									
								
								native/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								native/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +0,0 @@ | ||||
| build/ | ||||
| cmake-build-* | ||||
| out/* | ||||
| @ -1,176 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.1) | ||||
| 
 | ||||
| project(TeaClient-Natives VERSION 1.0.0) | ||||
| set(CMAKE_CXX_STANDARD 17) | ||||
| set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||||
| set(CMAKE_CXX_EXTENSIONS OFF) | ||||
| set(CMAKE_VERBOSE_MAKEFILE ON) | ||||
| 
 | ||||
| if (CMAKE_INCLUDE_FILE AND NOT CMAKE_INCLUDE_FILE STREQUAL "") | ||||
| 	message("Include file ${CMAKE_INCLUDE_FILE}") | ||||
| 	include("${CMAKE_INCLUDE_FILE}") | ||||
| endif () | ||||
| 
 | ||||
| if(CMAKE_PLATFORM_INCLUDE AND NOT CMAKE_PLATFORM_INCLUDE STREQUAL "") | ||||
| 	message("Include file ${CMAKE_PLATFORM_INCLUDE}") | ||||
| 	include("${CMAKE_PLATFORM_INCLUDE}") | ||||
| endif() | ||||
| message("Library path: ${LIBRARY_PATH}") | ||||
| message("Module path: ${CMAKE_MODULE_PATH}") | ||||
| 
 | ||||
| #Setup NodeJS | ||||
| function(setup_nodejs) | ||||
| 	set(NodeJS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") | ||||
| 	set(NODEJS_URL "https://atom.io/download/atom-shell") | ||||
| 	set(NODEJS_VERSION "v6.0.7") | ||||
| 	#set(NODEJS_VERSION "v8.0.0") | ||||
| 
 | ||||
| 	#set(NODEJS_URL "https://nodejs.org/download/release/") | ||||
| 	#set(NODEJS_VERSION "v12.7.0") | ||||
| 
 | ||||
| 	find_package(NodeJS REQUIRED) | ||||
| 
 | ||||
| 	set(NODEJS_NAN_DIR "node_modules/nan") | ||||
| 	nodejs_init() | ||||
| 
 | ||||
| 	#Fix nan include headers | ||||
| 	set(NAN_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/../node_modules/nan") | ||||
| 	if(NOT EXISTS "${NAN_INCLUDE_DIR}") | ||||
| 		message(FATAL_ERROR "Failed to find nan headers.") | ||||
| 	endif() | ||||
| 	list(APPEND NODEJS_INCLUDE_DIRS ${NAN_INCLUDE_DIR}) | ||||
| 
 | ||||
| 	set(EXE_DIRECTORY "${CMAKE_SOURCE_DIR}/build/exe/" PARENT_SCOPE) | ||||
| 	if (MSVC) | ||||
| 		set(NODE_LIB_DIRECTORY "${CMAKE_SOURCE_DIR}/build/win32_x64/" PARENT_SCOPE) | ||||
|         set(NODE_PDB_DIRECTORY "${CMAKE_SOURCE_DIR}/build/symbols/" PARENT_SCOPE) | ||||
| 	else() | ||||
| 		set(NODE_LIB_DIRECTORY "${CMAKE_SOURCE_DIR}/build/linux_x64/" PARENT_SCOPE) | ||||
| 	endif() | ||||
| 
 | ||||
| 	#Set some more variables | ||||
| 	set(NODEJS_INCLUDE_DIRS "${NODEJS_INCLUDE_DIRS}" PARENT_SCOPE) | ||||
| 	set(NODEJS_INIT ${NODEJS_INIT} PARENT_SCOPE) | ||||
| 
 | ||||
| 	include_directories("dist/ext_nan") | ||||
| 
 | ||||
| 
 | ||||
| 	function(add_nodejs_module NAME) | ||||
| 		message("Registering module ${NAME}") | ||||
| 		_add_nodejs_module(${NAME} ${ARGN}) | ||||
| 		target_compile_features(${NAME} PUBLIC cxx_std_17) | ||||
| 		set_target_properties(${NAME} | ||||
| 				PROPERTIES | ||||
| 				LIBRARY_OUTPUT_DIRECTORY "${NODE_LIB_DIRECTORY}/" | ||||
| 		) | ||||
| 		target_include_directories(${NAME} PUBLIC ${NODEJS_INCLUDE_DIRS}) | ||||
| 		set_target_properties(${NAME} PROPERTIES CXX_STANDARD 17) #Needs to be overridden after _add_nodejs_module sets it to 11 | ||||
| 
 | ||||
| 		if(MSVC) | ||||
| 			add_custom_command(TARGET ${NAME} POST_BUILD | ||||
| 					COMMAND ${CMAKE_COMMAND} -E | ||||
| 					copy "$<TARGET_FILE:${NAME}>" "${NODE_LIB_DIRECTORY}/${NAME}.node" | ||||
| 			) | ||||
|             add_custom_command(TARGET ${NAME} POST_BUILD | ||||
|                     COMMAND ${CMAKE_COMMAND} -E | ||||
|                     copy "$<TARGET_PDB_FILE:${NAME}>" "${NODE_PDB_DIRECTORY}/${NAME}.pdb" | ||||
|             ) | ||||
| 		else() | ||||
| 			set_target_properties(${NAME} | ||||
| 					PROPERTIES | ||||
| 					LIBRARY_OUTPUT_DIRECTORY "${NODE_LIB_DIRECTORY}/" | ||||
| 			) | ||||
| 		endif() | ||||
| 	endfunction() | ||||
| 
 | ||||
| 	#Forward parameters to global scope | ||||
| 	set(NODEJS_VERSION ${NODEJS_VERSION} PARENT_SCOPE) | ||||
| 	set(NODEJS_SOURCES ${NODEJS_SOURCES} PARENT_SCOPE) | ||||
| 	set(NODEJS_INCLUDE_DIRS ${NODEJS_INCLUDE_DIRS} PARENT_SCOPE) | ||||
| 	set(NODEJS_LIBRARIES ${NODEJS_LIBRARIES} PARENT_SCOPE) | ||||
| 	set(NODEJS_LINK_FLAGS ${NODEJS_LINK_FLAGS} PARENT_SCOPE) | ||||
| 	set(NODEJS_DEFINITIONS ${NODEJS_DEFINITIONS} PARENT_SCOPE) | ||||
| 
 | ||||
| 	# Prevents this function from executing more than once | ||||
| 	set(NODEJS_INIT TRUE PARENT_SCOPE) | ||||
| endfunction() | ||||
| 
 | ||||
| #Setup the compiler (Cant be done within a function!) | ||||
| if (MSVC) | ||||
| 	set(CompilerFlags | ||||
| 			CMAKE_C_FLAGS_DEBUG | ||||
| 			CMAKE_C_FLAGS_MINSIZEREL | ||||
| 			CMAKE_C_FLAGS_RELEASE | ||||
| 			CMAKE_C_FLAGS_RELWITHDEBINFO | ||||
| 			CMAKE_CXX_FLAGS_DEBUG | ||||
| 			CMAKE_CXX_FLAGS_MINSIZEREL | ||||
| 			CMAKE_CXX_FLAGS_RELEASE | ||||
| 			CMAKE_CXX_FLAGS_RELWITHDEBINFO | ||||
| 	) | ||||
| 	foreach(CompilerFlag ${CompilerFlags}) | ||||
| 		string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") | ||||
| 	endforeach() | ||||
| 	add_compile_options("/EHsc") #We require exception handling | ||||
| else() | ||||
| 	#This is a bad thing here! | ||||
| 	function(resolve_library VARIABLE FALLBACK PATHS) | ||||
| 		set( _PATHS ${PATHS} ${ARGN} ) # Merge them together | ||||
| 
 | ||||
| 		foreach(PATH IN ITEMS ${_PATHS}) | ||||
| 			message(STATUS "Try to use path ${PATH} for ${VARIABLE}") | ||||
| 			if(EXISTS ${PATH}) | ||||
| 				message(STATUS "Setting ${VARIABLE} to ${PATH}") | ||||
| 				set(${VARIABLE} ${PATH} PARENT_SCOPE) | ||||
| 				return() | ||||
| 			endif() | ||||
| 		endforeach() | ||||
| 
 | ||||
| 		if(FALLBACK) | ||||
| 			message(WARNING "Failed to resolve library path for ${VARIABLE}. Using default ${VARIABLE}") | ||||
| 		else() | ||||
| 			message(FATAL_ERROR "Failed to find requited library. Variable: ${VARIABLE} Paths: ${_PATHS}") | ||||
| 		endif() | ||||
| 	endfunction() | ||||
| 
 | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -static-libgcc -static-libstdc++") | ||||
| 	set(CMAKE_SHARED_LIBRARY_CXX_FLAGS "") # Disable -fPIC (We dont want any relonking with build in electron libraries | ||||
| endif() | ||||
| 
 | ||||
| setup_nodejs() | ||||
| if(NOT NODEJS_INCLUDE_DIRS OR NODEJS_INCLUDE_DIRS STREQUAL "") | ||||
| 	message(FATAL_ERROR "Failed to find node headers") | ||||
| else() | ||||
| 	message("Including NodeJS headers: ${NODEJS_INCLUDE_DIRS}") | ||||
| endif() | ||||
| include_directories(${NODEJS_INCLUDE_DIRS}) | ||||
| 
 | ||||
| function(build_update_installer) | ||||
| 	add_subdirectory(updater) | ||||
| endfunction() | ||||
| build_update_installer() | ||||
| 
 | ||||
| 
 | ||||
| function(build_codec) | ||||
| 	add_subdirectory(codec) | ||||
| endfunction() | ||||
| #build_codec() | ||||
| 
 | ||||
| function(build_ppt) | ||||
| 	add_subdirectory(ppt) | ||||
| endfunction() | ||||
| build_ppt() | ||||
| 
 | ||||
| function(build_connection) | ||||
| 	add_subdirectory(serverconnection) | ||||
| endfunction() | ||||
| build_connection() | ||||
| 
 | ||||
| function(build_crash_handler) | ||||
| 	add_subdirectory(crash_handler) | ||||
| endfunction() | ||||
| build_crash_handler() | ||||
| 
 | ||||
| function(build_dns) | ||||
| 	add_subdirectory(dns) | ||||
| endfunction() | ||||
| build_dns() | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										14
									
								
								native/codec/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								native/codec/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,14 +0,0 @@ | ||||
| # Default ignored files | ||||
| /.idea/shelf/ | ||||
| /.idea/workspace.xml | ||||
| 
 | ||||
| # Datasource local storage ignored files | ||||
| /.idea/dataSources/ | ||||
| dataSources.local.xml | ||||
| 
 | ||||
| # Editor-based HTTP Client requests | ||||
| /.idea/httpRequests/ | ||||
| rest-client.private.env.json | ||||
| http-client.private.env.json | ||||
| 
 | ||||
| cmake-build-*/ | ||||
| @ -1,60 +0,0 @@ | ||||
| set(MODULE_NAME "teaclient_codec") | ||||
| set(CMAKE_MODULE_DIR "${CMAKE_SOURCE_DIR}/codec") | ||||
| 
 | ||||
| set(SOURCE_FILES | ||||
| 		binding.cc | ||||
| 		codec/NativeCodec.cpp | ||||
| 		codec/OpusCodec.cpp | ||||
| 		codec/SpeexCodec.cpp | ||||
| 		codec/CeltCodec.cpp | ||||
| ) | ||||
| 
 | ||||
| add_nodejs_module(${MODULE_NAME} ${SOURCE_FILES}) | ||||
| 
 | ||||
| if(MSVC) | ||||
| 	set(OPUS_LIBRARY_PATH "${CMAKE_MODULE_DIR}/libraries/generated/opus/lib/opus.lib") | ||||
| 	set(SPEEX_LIBRARY_PATH "${CMAKE_MODULE_DIR}/libraries/generated/speex/lib/libspeex.lib") | ||||
| else() | ||||
| 	set(OPUS_LIBRARY_PATH "${CMAKE_MODULE_DIR}/libraries/generated/opus/lib/libopus.a") | ||||
| 	set(SPEEX_LIBRARY_PATH "${CMAKE_MODULE_DIR}/libraries/generated/speex/lib/libspeex.a") | ||||
| 	set(CELT_LIBRARY_PATH "${CMAKE_MODULE_DIR}/libraries/generated/celt/lib/libcelt0.a") | ||||
| endif() | ||||
| 
 | ||||
| #Detect opus | ||||
| if(EXISTS "${CMAKE_MODULE_DIR}/libraries/generated/opus/include" AND EXISTS ${OPUS_LIBRARY_PATH}) | ||||
| 	set(HAVE_OPUS ON) | ||||
| 
 | ||||
| 	add_definitions(-DHAVE_OPUS) | ||||
| 	include_directories(${CMAKE_MODULE_DIR}/libraries/generated/opus/include) | ||||
| 	target_link_libraries(${MODULE_NAME} ${OPUS_LIBRARY_PATH}) | ||||
| else() | ||||
| 	message(WARNING "Missing opus libraries. Building without opus support!\n" "Build opus with the build script given within the libraries foulder") | ||||
| endif() | ||||
| 
 | ||||
| #Detect speex | ||||
| if(EXISTS "${CMAKE_MODULE_DIR}/libraries/generated/speex/include" AND EXISTS ${SPEEX_LIBRARY_PATH}) | ||||
| 	set(HAVE_SPEEX ON) | ||||
| 
 | ||||
| 	add_definitions(-DHAVE_SPEEX) | ||||
| 	include_directories(${CMAKE_MODULE_DIR}/libraries/generated/speex/include) | ||||
| 	target_link_libraries(${MODULE_NAME} ${SPEEX_LIBRARY_PATH}) | ||||
| else() | ||||
| 	message(WARNING "Missing speex libraries. Building without speex support!\n" "Build speex with the build script given within the libraries foulder") | ||||
| endif() | ||||
| 
 | ||||
| #Detect celt | ||||
| set(BUILD_CELT OFF) | ||||
| if(EXISTS "${CMAKE_MODULE_DIR}/libraries/generated/celt/include" AND EXISTS "${CELT_LIBRARY_PATH}" AND BUILD_CELT) | ||||
| 	set(HAVE_CELT ON) | ||||
| 
 | ||||
| 	add_definitions(-DHAVE_CELT) | ||||
| 	include_directories(${CMAKE_MODULE_DIR}/libraries/generated/celt/include) | ||||
| 
 | ||||
| 	target_link_libraries(${MODULE_NAME} ${CELT_LIBRARY_PATH}) | ||||
| else() | ||||
| 	message(WARNING "Missing celt libraries. Building without celt support!\n" "Build celt with the build script given within the libraries foulder") | ||||
| endif() | ||||
| 
 | ||||
| if(HAVE_OPUS AND HAVE_CELT) | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-no-whole-archive,--allow-multiple-definition") | ||||
| endif() | ||||
| @ -1,31 +0,0 @@ | ||||
| #include <nan.h> | ||||
| #include <node.h> | ||||
| #include <v8.h> | ||||
| #include <iostream> | ||||
| #include "codec/NativeCodec.h" | ||||
| 
 | ||||
| using namespace std; | ||||
| 
 | ||||
| 
 | ||||
| tc::WorkerPool* tc::codec_workers = nullptr; | ||||
| 
 | ||||
| NAN_METHOD(finalize) { | ||||
| 	auto pool = tc::codec_workers; | ||||
| 	if(!pool) return; | ||||
| 
 | ||||
| 	tc::codec_workers = nullptr; | ||||
| 
 | ||||
| 	pool->finalize(); | ||||
| 	delete pool; | ||||
| } | ||||
| 
 | ||||
| NAN_MODULE_INIT(init) { | ||||
| 	tc::codec_workers = new tc::WorkerPool(); | ||||
| 	tc::codec_workers->initialize(); | ||||
| 
 | ||||
| 	Nan::Set(target, Nan::New<v8::String>("new_instance").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(tc::NativeCodec::NewInstance)).ToLocalChecked()); | ||||
| 	tc::NativeCodec::Init(target); | ||||
| 	tc::NativeCodec::CodecType::Init(target); | ||||
| } | ||||
| 
 | ||||
| NODE_MODULE(MODULE_NAME, init) | ||||
| @ -1,203 +0,0 @@ | ||||
| #include <iostream> | ||||
| #include <chrono> | ||||
| #include <thread> | ||||
| #include <memory> | ||||
| #include "CeltCodec.h" | ||||
| #include "NativeCodec.h" | ||||
| #include "NanException.h" | ||||
| #include "NanEventCallback.h" | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace std::chrono; | ||||
| using namespace tc; | ||||
| using namespace v8; | ||||
| using namespace Nan; | ||||
| 
 | ||||
| bool CeltCodec::supported() { | ||||
| #ifdef HAVE_CELT | ||||
| 	return true; | ||||
| #endif | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| #ifdef HAVE_CELT | ||||
| 
 | ||||
| CeltCodec::CeltCodec(NativeCodec::CodecType::value type) : NativeCodec(type) { | ||||
| 	cout << "Allocate celt instance" << endl; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| CeltCodec::~CeltCodec() { | ||||
| 	cout << "Free celt instance" << endl; | ||||
| 
 | ||||
| 	if(this->decoder || this->encoder) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "please finalize before releasing!"); | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(CeltCodec::initialize) { | ||||
| 	lock_guard lock(this->coder_lock); | ||||
| 
 | ||||
| 	//(≥20kHz; sample rates from 8 kHz to 48 kHz)
 | ||||
| 	int error = 0; | ||||
| 
 | ||||
| 	int sample_rate = 48000; | ||||
| 	this->encoder = celt_encoder_create(sample_rate, this->channels, &error); | ||||
| 	if(!this->encoder || error) { | ||||
| 		cout << this->encoder << " - " << error << endl; | ||||
| 		if(this->encoder) | ||||
| 			celt_encoder_destroy(this->encoder); | ||||
| 		this->encoder = nullptr; | ||||
| 
 | ||||
| 		NAN_THROW_EXCEPTION(Error, ("Failed to create encoder (" + to_string(error) + ")").c_str()); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	this->decoder = celt_decoder_create(sample_rate, this->channels, &error); | ||||
| 	if(!this->decoder || error) { | ||||
| 		if(this->decoder) | ||||
| 			celt_decoder_destroy(this->decoder); | ||||
| 		this->decoder = nullptr; | ||||
| 		if(this->encoder) | ||||
| 			celt_encoder_destroy(this->encoder); | ||||
| 
 | ||||
| 		this->encoder = nullptr; | ||||
| 		NAN_THROW_EXCEPTION(Error, ("Failed to create decoder (" + to_string(error) + ")").c_str()); | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(CeltCodec::finalize) { | ||||
| 	lock_guard lock(this->coder_lock); | ||||
| 
 | ||||
| 	if(this->encoder) { | ||||
| 		celt_encoder_destroy(this->encoder); | ||||
| 		this->encoder = nullptr; | ||||
| 	} | ||||
| 
 | ||||
| 	if(this->decoder) { | ||||
| 		celt_decoder_destroy(this->decoder); | ||||
| 		this->decoder = nullptr; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| NAN_METHOD(CeltCodec::encode) { | ||||
| 	Nan::HandleScope scope; | ||||
| 
 | ||||
| 	if(!info[0]->IsArrayBuffer()) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "First argument isn't an array buffer!"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	auto js_buffer = info[0].As<ArrayBuffer>()->GetContents(); | ||||
| 	auto buffer = make_unique<Chunk>(max(js_buffer.ByteLength(), 256UL)); | ||||
| 	buffer->length = js_buffer.ByteLength(); | ||||
| 	memcpy(buffer->memory, js_buffer.Data(), js_buffer.ByteLength()); | ||||
| 
 | ||||
| 	auto callback_success = make_unique<Nan::Callback>(info[1].As<Function>()); | ||||
| 	auto callback_error = make_unique<Nan::Callback>(info[2].As<Function>()); | ||||
| 
 | ||||
| 	auto codec = make_unique<v8::Persistent<Object>>(info.GetIsolate(), info.Holder()); | ||||
| 	auto callback = Nan::async_callback([callback_success = move(callback_success), callback_error = move(callback_error), codec = move(codec)](std::unique_ptr<Chunk> result, std::string error) { | ||||
| 		Nan::HandleScope scope; | ||||
| 		if(result) { | ||||
| 			auto _buffer = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), result->length); | ||||
| 			memcpy(_buffer->GetContents().Data(), result->memory, result->length); | ||||
| 
 | ||||
| 			Local<Value> argv[] = { _buffer }; //_buffer
 | ||||
| 			callback_success->Call(1, argv); | ||||
| 		} else  { | ||||
| 			Local<Value> argv[] = { Nan::New<String>(error).ToLocalChecked() }; //error
 | ||||
| 			callback_error->Call(1, argv); | ||||
| 		} | ||||
| 		codec->Reset(); | ||||
| 	}).option_destroyed_execute(true); | ||||
| 
 | ||||
| 	auto frame_size = buffer->length / sizeof(float) / this->channels; | ||||
| 	if(frame_size != 64 && frame_size != 128 && frame_size != 256 && frame_size != 512) { | ||||
| 		NAN_THROW_EXCEPTION(Error, ("Invalid frame size (" + to_string(frame_size) + "). Only allow 64, 128, 256, 512bytes").c_str()); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	tc::codec_workers->enqueue_task( | ||||
| 			[this, callback, buffer = move(buffer)]() mutable { | ||||
| 				{ | ||||
| 					lock_guard lock(this->coder_lock); | ||||
| 					if(!this->encoder) { | ||||
| 						callback(nullptr, "Please initialize first!"); | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					auto nbytes = celt_encode_float(this->encoder, (float*) buffer->memory, buffer->length / sizeof(float) / this->channels, (u_char*) buffer->memory, buffer->allocated_length); | ||||
| 					if(nbytes < 0) { | ||||
| 						callback(nullptr, "Invalid encode return code (" + to_string(nbytes) + ")"); | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					buffer->length = nbytes; | ||||
| 				} | ||||
| 				callback(move(buffer), ""); | ||||
| 			} | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(CeltCodec::decode) { | ||||
| 	Nan::HandleScope scope; | ||||
| 
 | ||||
| 	if(!info[0]->IsArrayBuffer()) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "First argument isn't an array buffer!"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	auto js_buffer = info[0].As<ArrayBuffer>()->GetContents(); | ||||
| 	auto buffer = make_unique<Chunk>(max(js_buffer.ByteLength(), this->max_frame_size * this->channels * sizeof(float))); | ||||
| 	buffer->length = js_buffer.ByteLength(); | ||||
| 	memcpy(buffer->memory, js_buffer.Data(), js_buffer.ByteLength()); | ||||
| 
 | ||||
| 	auto callback_success = make_unique<Nan::Callback>(info[1].As<Function>()); | ||||
| 	auto callback_error = make_unique<Nan::Callback>(info[2].As<Function>()); | ||||
| 
 | ||||
| 	auto codec = make_unique<v8::Persistent<Object>>(info.GetIsolate(), info.Holder()); | ||||
| 	auto callback = Nan::async_callback([callback_success = move(callback_success), callback_error = move(callback_error), codec = move(codec)](std::unique_ptr<Chunk> result, std::string error) { | ||||
| 		Nan::HandleScope scope; | ||||
| 		if(result) { | ||||
| 			auto _buffer = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), result->length); | ||||
| 			memcpy(_buffer->GetContents().Data(), result->memory, result->length); | ||||
| 			Local<Value> argv[] = { _buffer }; | ||||
| 			callback_success->Call(1, argv); | ||||
| 		} else  { | ||||
| 			Local<Value> argv[] = { Nan::New<String>(error).ToLocalChecked() }; | ||||
| 			callback_error->Call(1, argv); | ||||
| 		} | ||||
| 		codec->Reset(); | ||||
| 	}).option_destroyed_execute(true); | ||||
| 
 | ||||
| 	tc::codec_workers->enqueue_task( | ||||
| 			[this, callback, buffer = move(buffer)]() mutable { | ||||
| 				int result; | ||||
| 
 | ||||
| 				{ | ||||
| 					lock_guard lock(this->coder_lock); | ||||
| 					if(!this->decoder) { | ||||
| 						callback(nullptr, "Please initialize first!"); | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					auto code = celt_decode_float(this->decoder, (u_char*) buffer->memory, buffer->length, (float*) buffer->memory, buffer->allocated_length / sizeof(float) / this->channels); | ||||
| 					if(code < 0) { | ||||
| 						callback(nullptr, "Invalid decode return code (" + to_string(code) + ")"); | ||||
| 						return; | ||||
| 					} | ||||
| 					cout << code << endl; | ||||
| 
 | ||||
| 					buffer->length = this->channels * this->max_frame_size * sizeof(float); | ||||
| 				} | ||||
| 
 | ||||
| 				callback(move(buffer), ""); | ||||
| 			} | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -1,34 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "NativeCodec.h" | ||||
| 
 | ||||
| #ifdef HAVE_CELT | ||||
| 	#include <celt/celt.h> | ||||
| #endif | ||||
| 
 | ||||
| namespace tc { | ||||
| 	class NativeCodec; | ||||
| 
 | ||||
| 
 | ||||
| 	class CeltCodec : public NativeCodec  { | ||||
| 		public: | ||||
| 			static bool supported(); | ||||
| 
 | ||||
| #ifdef HAVE_CELT | ||||
| 			explicit CeltCodec(CodecType::value type); | ||||
| 			virtual ~CeltCodec(); | ||||
| 
 | ||||
| 			virtual NAN_METHOD(initialize); | ||||
| 			virtual NAN_METHOD(finalize); | ||||
| 			virtual NAN_METHOD(encode); | ||||
| 			virtual NAN_METHOD(decode); | ||||
| 		private: | ||||
| 			std::mutex coder_lock; | ||||
| 			int max_frame_size = 512; | ||||
| 			int channels = 1; /* fixed */ | ||||
| 
 | ||||
| 			CELTEncoder* encoder = nullptr; | ||||
| 			CELTDecoder* decoder = nullptr; | ||||
| #endif | ||||
| 	}; | ||||
| } | ||||
| @ -1,205 +0,0 @@ | ||||
| #include <chrono> | ||||
| #include <thread> | ||||
| #include <memory> | ||||
| #include "NativeCodec.h" | ||||
| #include "OpusCodec.h" | ||||
| #include "SpeexCodec.h" | ||||
| #include "CeltCodec.h" | ||||
| #include "NanException.h" | ||||
| #include <iostream> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace std::chrono; | ||||
| using namespace tc; | ||||
| using namespace v8; | ||||
| using namespace Nan; | ||||
| 
 | ||||
| #define DEFINE_ENUM_ENTRY(name, value) \ | ||||
| 	Nan::ForceSet(types, Nan::New<v8::String>(name).ToLocalChecked(), Nan::New<v8::Number>(value), static_cast<PropertyAttribute>(ReadOnly|DontDelete)); \ | ||||
| 	//Nan::ForceSet(types, Nan::New<v8::Number>(value), Nan::New<v8::String>(name).ToLocalChecked(), static_cast<PropertyAttribute>(ReadOnly|DontDelete));
 | ||||
| 
 | ||||
| NAN_MODULE_INIT(NativeCodec::CodecType::Init) { | ||||
| 	auto types = Nan::New<v8::Object>(); | ||||
| 
 | ||||
| 	DEFINE_ENUM_ENTRY("OPUS_VOICE", CodecType::OPUS_VOICE); | ||||
| 	DEFINE_ENUM_ENTRY("OPUS_MUSIC", CodecType::OPUS_MUSIC); | ||||
| 	DEFINE_ENUM_ENTRY("SPEEX_NARROWBAND", CodecType::SPEEX_NARROWBAND); | ||||
| 	DEFINE_ENUM_ENTRY("SPEEX_WIDEBAND", CodecType::SPEEX_WIDEBAND); | ||||
| 	DEFINE_ENUM_ENTRY("SPEEX_ULTRA_WIDEBAND", CodecType::SPEEX_ULTRA_WIDEBAND); | ||||
| 	DEFINE_ENUM_ENTRY("CELT_MONO", CodecType::CELT_MONO); | ||||
| 
 | ||||
| 	Nan::Set(target, Nan::New<v8::String>("CodecTypes").ToLocalChecked(), types); | ||||
| 	Nan::Set(target, Nan::New<v8::String>("codec_supported").ToLocalChecked(), Nan::New<v8::Function>(NativeCodec::CodecType::supported)); | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(NativeCodec::CodecType::supported) { | ||||
| 	if(!info[0]->IsNumber()) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "Argument 0 shall be a number!"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	auto type = (CodecType::value) Nan::To<int>(info[0]).FromJust(); | ||||
| 	if(type == CodecType::OPUS_MUSIC || type == CodecType::OPUS_VOICE) { | ||||
| 		info.GetReturnValue().Set(OpusCodec::supported()); | ||||
| 	} else if(type == CodecType::SPEEX_NARROWBAND || type == CodecType::SPEEX_WIDEBAND || type == CodecType::SPEEX_ULTRA_WIDEBAND) { | ||||
| 		info.GetReturnValue().Set(SpeexCodec::supported()); | ||||
| 	} else if(type == CodecType::CELT_MONO) { | ||||
| 		info.GetReturnValue().Set(CeltCodec::supported()); | ||||
| 	} else { | ||||
| 		NAN_THROW_EXCEPTION(Error, "Invalid type!"); | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| NAN_MODULE_INIT(NativeCodec::Init) { | ||||
| 	auto klass = New<FunctionTemplate>(NewInstance); | ||||
| 	klass->SetClassName(Nan::New("NativeCodec").ToLocalChecked()); | ||||
| 	klass->InstanceTemplate()->SetInternalFieldCount(1); | ||||
| 
 | ||||
| 	Nan::SetPrototypeMethod(klass, "decode", NativeCodec::function_decode); | ||||
| 	Nan::SetPrototypeMethod(klass, "encode", NativeCodec::function_encode); | ||||
| 	Nan::SetPrototypeMethod(klass, "initialize", NativeCodec::function_initialize); | ||||
| 	Nan::SetPrototypeMethod(klass, "finalize", NativeCodec::function_finalize); | ||||
| 
 | ||||
| 	constructor().Reset(Nan::GetFunction(klass).ToLocalChecked()); | ||||
| 	//Nan::Set(target, Nan::New("NativeCodec").ToLocalChecked(), Nan::GetFunction(klass).ToLocalChecked());
 | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(NativeCodec::NewInstance) { | ||||
| 	if (info.IsConstructCall()) { | ||||
| 		if(!info[0]->IsNumber()) { | ||||
| 			NAN_THROW_EXCEPTION(Error, "Argument 0 shall be a number!"); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		auto type = (CodecType::value) Nan::To<int>(info[0]).FromJust(); | ||||
| 		std::unique_ptr<NativeCodec> instance; | ||||
| 		if(type == CodecType::OPUS_MUSIC || type == CodecType::OPUS_VOICE) { | ||||
| 			#ifdef HAVE_OPUS | ||||
| 			instance = make_unique<OpusCodec>(type); | ||||
| 			#endif | ||||
| 		} else if(type == CodecType::SPEEX_NARROWBAND || type == CodecType::SPEEX_WIDEBAND || type == CodecType::SPEEX_ULTRA_WIDEBAND) { | ||||
| 			#ifdef HAVE_SPEEX | ||||
| 			instance = make_unique<SpeexCodec>(type); | ||||
| 			#endif | ||||
| 		} else if(type == CodecType::CELT_MONO) { | ||||
| 			#ifdef HAVE_CELT | ||||
| 			instance = make_unique<CeltCodec>(type); | ||||
| 			#endif | ||||
| 		} else { | ||||
| 			NAN_THROW_EXCEPTION(Error, "Invalid type!"); | ||||
| 			return; | ||||
| 		} | ||||
| 		if(!instance) { | ||||
| 			NAN_THROW_EXCEPTION(Error, "Target codec isn't supported!"); | ||||
| 			return; | ||||
| 		} | ||||
| 		instance.release()->Wrap(info.This()); | ||||
| 
 | ||||
| 		Nan::Set(info.This(), New<String>("type").ToLocalChecked(), New<Number>(type)); | ||||
| 		info.GetReturnValue().Set(info.This()); | ||||
| 	} else { | ||||
| 		const int argc = 1; | ||||
| 		v8::Local<v8::Value> argv[argc] = {info[0]}; | ||||
| 		v8::Local<v8::Function> cons = Nan::New(constructor()); | ||||
| 
 | ||||
| 		Nan::TryCatch try_catch; | ||||
| 		auto result = Nan::NewInstance(cons, argc, argv); | ||||
| 		if(try_catch.HasCaught()) { | ||||
| 			try_catch.ReThrow(); | ||||
| 			return; | ||||
| 		} | ||||
| 		info.GetReturnValue().Set(result.ToLocalChecked()); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(NativeCodec::function_decode) { | ||||
| 	if(info.Length() != 3) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "Invalid argument count!"); | ||||
| 		return; | ||||
| 	} | ||||
| 	if(!info[0]->IsArrayBuffer() || !info[1]->IsFunction() || !info[2]->IsFunction()) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "Invalid argument types!"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	auto codec = ObjectWrap::Unwrap<NativeCodec>(info.Holder()); | ||||
| 	codec->decode(info); | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(NativeCodec::function_encode) { | ||||
| 	if(info.Length() != 3) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "Invalid argument count!"); | ||||
| 		return; | ||||
| 	} | ||||
| 	if(!info[0]->IsArrayBuffer() || !info[1]->IsFunction() || !info[2]->IsFunction()) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "Invalid argument types!"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	auto codec = ObjectWrap::Unwrap<NativeCodec>(info.Holder()); | ||||
| 	codec->encode(info); | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(NativeCodec::function_initialize) { | ||||
| 	auto codec = ObjectWrap::Unwrap<NativeCodec>(info.Holder()); | ||||
| 	codec->initialize(info); | ||||
| } | ||||
| NAN_METHOD(NativeCodec::function_finalize) { | ||||
| 	auto codec = ObjectWrap::Unwrap<NativeCodec>(info.Holder()); | ||||
| 	codec->finalize(info); | ||||
| } | ||||
| 
 | ||||
| NativeCodec::NativeCodec(tc::NativeCodec::CodecType::value type) : type(type) {} | ||||
| NativeCodec::~NativeCodec() {} | ||||
| 
 | ||||
| 
 | ||||
| WorkerPool::WorkerPool() {} | ||||
| WorkerPool::~WorkerPool() {} | ||||
| 
 | ||||
| void WorkerPool::initialize() { | ||||
| 	assert(!this->_running); | ||||
| 
 | ||||
| 	this->_running = true; | ||||
| 	this->worker = thread([&]{ | ||||
| 		while(this->_running) { | ||||
| 			function<void()> worker; | ||||
| 			{ | ||||
| 				unique_lock lock(this->worker_lock); | ||||
| 				this->worker_wait.wait_for(lock, minutes(1), [&]{ return !this->_running || !this->tasks.empty(); }); | ||||
| 				if(!this->_running) break; | ||||
| 				if(this->tasks.empty()) continue; | ||||
| 
 | ||||
| 				worker = move(this->tasks.front()); | ||||
| 				this->tasks.pop_front(); | ||||
| 			} | ||||
| 
 | ||||
| 			try { | ||||
| 				worker(); | ||||
| 			} catch(std::exception& ex) { | ||||
| 				cerr << "failed to invoke opus worker! message: " << ex.what() << endl; | ||||
| 			} catch (...) { | ||||
| 				cerr << "failed to invoke opus worker!" << endl; | ||||
| 			} | ||||
| 		} | ||||
| 	}); | ||||
| #ifndef WIN32 | ||||
| 	auto worker_handle = this->worker.native_handle(); | ||||
| 	pthread_setname_np(worker_handle, "Codec Worker"); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void WorkerPool::finalize() { | ||||
| 	this->_running = false; | ||||
| 	this->worker_wait.notify_all(); | ||||
| 	this->tasks.clear(); | ||||
| 
 | ||||
| 	if(this->worker.joinable()) | ||||
| 		this->worker.join(); | ||||
| } | ||||
| 
 | ||||
| void WorkerPool::enqueue_task(std::function<void()> task) { | ||||
| 	lock_guard lock(this->worker_lock); | ||||
| 	this->tasks.push_back(std::move(task)); | ||||
| 	this->worker_wait.notify_one(); | ||||
| } | ||||
| @ -1,95 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <nan.h> | ||||
| #include <mutex> | ||||
| #include <functional> | ||||
| #include <thread> | ||||
| #include <condition_variable> | ||||
| 
 | ||||
| namespace tc { | ||||
| 	struct WorkerPool { | ||||
| 		public: | ||||
| 			WorkerPool(); | ||||
| 			virtual ~WorkerPool(); | ||||
| 
 | ||||
| 			void initialize(); | ||||
| 			void finalize(); | ||||
| 
 | ||||
| 			void enqueue_task(std::function<void()> /* function */); | ||||
| 
 | ||||
| 			template <typename T> | ||||
| 			void enqueue_task(T&& closure) { | ||||
| 				auto handle = std::make_shared<T>(std::forward<T>(closure)); | ||||
| 				this->enqueue_task(std::function<void()>([handle]{ (*handle)(); })); | ||||
| 			} | ||||
| 
 | ||||
| 		private: | ||||
| 			bool _running = false; | ||||
| 
 | ||||
| 			std::thread worker; | ||||
| 			std::mutex worker_lock; | ||||
| 			std::condition_variable worker_wait; | ||||
| 
 | ||||
| 			std::deque<std::function<void()>> tasks; | ||||
| 	}; | ||||
| 	extern WorkerPool* codec_workers; | ||||
| 
 | ||||
| 	struct Chunk { | ||||
| 		char* memory; | ||||
| 		size_t length; | ||||
| 		size_t allocated_length; | ||||
| 
 | ||||
| 		Chunk(size_t length) { | ||||
| 			memory = (char*) malloc(length); | ||||
| 			this->allocated_length = length; | ||||
| 			this->length = 0; | ||||
| 		} | ||||
| 
 | ||||
| 		~Chunk() { | ||||
| 			free(memory); | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	class NativeCodec : public Nan::ObjectWrap { | ||||
| 		public: | ||||
| 			struct CodecType { | ||||
| 				enum value { | ||||
| 					SPEEX_NARROWBAND, | ||||
| 					SPEEX_WIDEBAND, | ||||
| 					SPEEX_ULTRA_WIDEBAND, | ||||
| 					CELT_MONO, | ||||
| 					OPUS_VOICE, | ||||
| 					OPUS_MUSIC | ||||
| 				}; | ||||
| 
 | ||||
| 				static NAN_MODULE_INIT(Init); | ||||
| 				static NAN_METHOD(supported); | ||||
| 			}; | ||||
| 
 | ||||
| 			static NAN_MODULE_INIT(Init); | ||||
| 			static NAN_METHOD(NewInstance); | ||||
| 
 | ||||
| 			static inline Nan::Persistent<v8::Function> & constructor() { | ||||
| 				static Nan::Persistent<v8::Function> my_constructor; | ||||
| 				return my_constructor; | ||||
| 			} | ||||
| 
 | ||||
| 			explicit NativeCodec(CodecType::value type); | ||||
| 			virtual ~NativeCodec(); | ||||
| 
 | ||||
| 			virtual NAN_METHOD(initialize) = 0; | ||||
| 			virtual NAN_METHOD(finalize) = 0; | ||||
| 
 | ||||
| 			virtual NAN_METHOD(encode) = 0; | ||||
| 			virtual NAN_METHOD(decode) = 0; | ||||
| 
 | ||||
| 			static NAN_METHOD(function_encode); | ||||
| 			static NAN_METHOD(function_decode); | ||||
| 			static NAN_METHOD(function_initialize); | ||||
| 			static NAN_METHOD(function_finalize); | ||||
| 
 | ||||
| 		protected: | ||||
| 			CodecType::value type; | ||||
| 
 | ||||
| 	}; | ||||
| } | ||||
| @ -1,204 +0,0 @@ | ||||
| #include <iostream> | ||||
| #include <chrono> | ||||
| #include <thread> | ||||
| #include <memory> | ||||
| #include "OpusCodec.h" | ||||
| #include "NativeCodec.h" | ||||
| #include "NanException.h" | ||||
| #include "NanEventCallback.h" | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace std::chrono; | ||||
| using namespace tc; | ||||
| using namespace v8; | ||||
| using namespace Nan; | ||||
| 
 | ||||
| bool OpusCodec::supported() { | ||||
| #ifdef HAVE_OPUS | ||||
| 	return true; | ||||
| #endif | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| #ifdef HAVE_OPUS | ||||
| OpusCodec::OpusCodec(NativeCodec::CodecType::value type) : NativeCodec(type) { | ||||
| 	cout << "New opus instance" << endl; | ||||
| 
 | ||||
| 	this->sampling_rate = 48000; | ||||
| 	this->frames = 960; | ||||
| 	if(type == CodecType::OPUS_MUSIC) { | ||||
| 		this->channels = 2; | ||||
| 	} else { | ||||
| 		this->channels = 1; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| OpusCodec::~OpusCodec() { | ||||
| 	cout << "Free opus instance" << endl; | ||||
| 
 | ||||
| 	if(this->decoder || this->encoder) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "please finalize before releasing!"); | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(OpusCodec::initialize) { | ||||
| 	int error = 0; | ||||
| 	lock_guard lock(this->coder_lock); | ||||
| 
 | ||||
| 	this->encoder = opus_encoder_create(this->sampling_rate, this->channels, this->type == CodecType::OPUS_MUSIC ? OPUS_APPLICATION_AUDIO : OPUS_APPLICATION_VOIP, &error); | ||||
| 	if(!this->encoder || error) { | ||||
| 		NAN_THROW_EXCEPTION(Error, ("Failed to create encoder (" + to_string(error) + ")").c_str()); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	this->decoder = opus_decoder_create(this->sampling_rate, this->channels, &error); | ||||
| 	if(!this->encoder || error) { | ||||
| 		opus_encoder_destroy(this->encoder); | ||||
| 		this->encoder = nullptr; | ||||
| 
 | ||||
| 		NAN_THROW_EXCEPTION(Error, ("Failed to create decoder (" + to_string(error) + ")").c_str()); | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(OpusCodec::finalize) { | ||||
| 	lock_guard lock(this->coder_lock); | ||||
| 
 | ||||
| 	opus_encoder_destroy(this->encoder); | ||||
| 	this->encoder = nullptr; | ||||
| 
 | ||||
| 	opus_decoder_destroy(this->decoder); | ||||
| 	this->decoder = nullptr; | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(OpusCodec::encode) { | ||||
| 	Nan::HandleScope scope; | ||||
| 
 | ||||
| 	if(info.Length() != 3 || !info[0]->IsArrayBuffer()) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "Invalid arguments"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	auto js_buffer = info[0].As<ArrayBuffer>()->GetContents(); | ||||
| 	auto buffer = make_unique<Chunk>(max(js_buffer.ByteLength(), 256UL)); | ||||
| 	buffer->length = js_buffer.ByteLength(); | ||||
| 	memcpy(buffer->memory, js_buffer.Data(), js_buffer.ByteLength()); | ||||
| 
 | ||||
| 	auto callback_success = make_unique<Nan::Callback>(info[1].As<Function>()); | ||||
| 	auto callback_error = make_unique<Nan::Callback>(info[2].As<Function>()); | ||||
| 
 | ||||
| 	auto codec = make_unique<v8::Persistent<Object>>(info.GetIsolate(), info.Holder()); | ||||
| 	auto callback = Nan::async_callback([callback_success = move(callback_success), callback_error = move(callback_error), codec = move(codec)](std::unique_ptr<Chunk> result, std::string error) { | ||||
| 		if(result) { | ||||
| 			auto _buffer = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), result->length); | ||||
| 			memcpy(_buffer->GetContents().Data(), result->memory, result->length); | ||||
| 
 | ||||
| 			Local<Value> argv[] = { _buffer }; //_buffer
 | ||||
| 			callback_success->Call(1, argv); | ||||
| 		} else  { | ||||
| 			Local<Value> argv[] = { Nan::New<String>(error).ToLocalChecked() }; //error
 | ||||
| 			callback_error->Call(1, argv); | ||||
| 		} | ||||
| 		codec->Reset(); | ||||
| 	}).option_destroyed_execute(true); | ||||
| 
 | ||||
| 	tc::codec_workers->enqueue_task( | ||||
| 			[this, callback, buffer = move(buffer)]() mutable { | ||||
| 				int result; | ||||
| 				{ | ||||
| 					lock_guard lock(this->coder_lock); | ||||
| 					if(!this->encoder) { | ||||
| 						callback(nullptr, "Please initialize first!"); | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					if(this->channels == 1) { | ||||
| 						result = opus_encode_float(this->encoder, (float*) buffer->memory, (int) (buffer->length / sizeof(float) / this->channels), (u_char*) buffer->memory, (opus_int32) buffer->allocated_length); | ||||
| 					} else { | ||||
| 						auto samples = buffer->length / sizeof(float) / this->channels; | ||||
| 						float* local_buffer = new float[samples * this->channels]; | ||||
| 						for(size_t channel = 0; channel < this->channels; channel++) | ||||
| 							for(size_t sample = 0; sample < samples; sample++) | ||||
| 								local_buffer[sample * this->channels + channel] = ((float*) buffer->memory)[channel * samples + sample]; | ||||
| 
 | ||||
| 						result = opus_encode_float(this->encoder, local_buffer, samples, (u_char*) buffer->memory, (opus_int32) buffer->allocated_length); | ||||
| 						delete[] local_buffer; | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				if(result <= 0) { | ||||
| 					callback(nullptr, "Invalid return code (" + to_string(result) + ")"); | ||||
| 				} else { | ||||
| 					buffer->length = result; | ||||
| 					callback(move(buffer), ""); | ||||
| 				} | ||||
| 			} | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(OpusCodec::decode) { | ||||
| 	Nan::HandleScope scope; | ||||
| 
 | ||||
| 	if(!info[0]->IsArrayBuffer()) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "First argument isn't an array buffer!"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	auto js_buffer = info[0].As<ArrayBuffer>()->GetContents(); | ||||
| 	auto buffer = make_unique<Chunk>(max(js_buffer.ByteLength(), this->channels * this->frames * sizeof(float))); | ||||
| 	buffer->length = js_buffer.ByteLength(); | ||||
| 	memcpy(buffer->memory, js_buffer.Data(), js_buffer.ByteLength()); | ||||
| 
 | ||||
| 	auto callback_success = make_unique<Nan::Callback>(info[1].As<Function>()); | ||||
| 	auto callback_error = make_unique<Nan::Callback>(info[2].As<Function>()); | ||||
| 
 | ||||
| 	auto codec = make_unique<v8::Persistent<Object>>(info.GetIsolate(), info.Holder()); | ||||
| 	auto callback = Nan::async_callback([callback_success = move(callback_success), callback_error = move(callback_error), codec = move(codec)](std::unique_ptr<Chunk> result, std::string error) { | ||||
| 		Nan::HandleScope scope; | ||||
| 		if(result) { | ||||
| 			auto _buffer = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), result->length); | ||||
| 			memcpy(_buffer->GetContents().Data(), result->memory, result->length); | ||||
| 			Local<Value> argv[] = { _buffer }; | ||||
| 			callback_success->Call(1, argv); | ||||
| 		} else  { | ||||
| 			Local<Value> argv[] = { Nan::New<String>(error).ToLocalChecked() }; | ||||
| 			callback_error->Call(1, argv); | ||||
| 		} | ||||
| 		codec->Reset(); | ||||
| 	}).option_destroyed_execute(true); | ||||
| 
 | ||||
| 	tc::codec_workers->enqueue_task( | ||||
| 			[this, buffer = move(buffer), callback]() mutable { | ||||
| 				int result; | ||||
| 
 | ||||
| 				{ | ||||
| 					lock_guard lock(this->coder_lock); | ||||
| 					if(!this->decoder) { | ||||
| 						callback(nullptr, "Please initialize first!"); | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					if(this->channels == 1) { | ||||
| 						result = opus_decode_float(this->decoder, (u_char*) buffer->memory, (opus_int32) buffer->length, (float*) buffer->memory, this->frames, 0); | ||||
| 					} else { | ||||
| 						float* local_buffer = new float[this->frames * this->channels]; | ||||
| 						result = opus_decode_float(this->decoder, (u_char*) buffer->memory, (opus_int32) buffer->length, (float*) local_buffer, this->frames, 0); | ||||
| 
 | ||||
| 						for(size_t channel = 0; channel < this->channels; channel++) | ||||
| 							for(size_t sample = 0; sample < result; sample++) | ||||
| 								((float*) buffer->memory)[channel * this->frames + sample] = local_buffer[sample * this->channels + channel]; | ||||
| 						delete[] local_buffer; | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				if(result <= 0) { | ||||
| 					callback(nullptr, "Invalid return code (" + to_string(result) + ")"); | ||||
| 				} else { | ||||
| 					buffer->length = result * sizeof(float) * this->channels; | ||||
| 					callback(move(buffer), ""); | ||||
| 				} | ||||
| 			} | ||||
| 	); | ||||
| } | ||||
| #endif | ||||
| @ -1,33 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "NativeCodec.h" | ||||
| 
 | ||||
| #ifdef HAVE_OPUS | ||||
| 	#include <opus/opus.h> | ||||
| #endif | ||||
| 
 | ||||
| namespace tc { | ||||
| 	class NativeCodec; | ||||
| 
 | ||||
| 	class OpusCodec : public NativeCodec  { | ||||
| 		public: | ||||
| 			static bool supported(); | ||||
| #ifdef HAVE_OPUS | ||||
| 			explicit OpusCodec(CodecType::value type); | ||||
| 			virtual ~OpusCodec(); | ||||
| 
 | ||||
| 			virtual NAN_METHOD(initialize); | ||||
| 			virtual NAN_METHOD(finalize); | ||||
| 			virtual NAN_METHOD(encode); | ||||
| 			virtual NAN_METHOD(decode); | ||||
| 		private: | ||||
| 			uint16_t sampling_rate = 0; | ||||
| 			uint16_t frames = 0; | ||||
| 			uint32_t channels = 0; | ||||
| 
 | ||||
| 			std::mutex coder_lock; | ||||
| 			OpusDecoder* decoder = nullptr; | ||||
| 			OpusEncoder* encoder = nullptr; | ||||
| #endif | ||||
| 	}; | ||||
| } | ||||
| @ -1,234 +0,0 @@ | ||||
| #include <iostream> | ||||
| #include <chrono> | ||||
| #include <thread> | ||||
| #include <memory> | ||||
| #include "SpeexCodec.h" | ||||
| #include "NativeCodec.h" | ||||
| #include "NanException.h" | ||||
| #include "NanEventCallback.h" | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace std::chrono; | ||||
| using namespace tc; | ||||
| using namespace v8; | ||||
| using namespace Nan; | ||||
| 
 | ||||
| bool SpeexCodec::supported() { | ||||
| #ifdef HAVE_SPEEX | ||||
| 	return true; | ||||
| #endif | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| #ifdef HAVE_SPEEX | ||||
| 
 | ||||
| SpeexCodec::SpeexCodec(NativeCodec::CodecType::value type) : NativeCodec(type) { | ||||
| 	cout << "Allocate speex instance" << endl; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| SpeexCodec::~SpeexCodec() { | ||||
| 	cout << "Free speex instance" << endl; | ||||
| 
 | ||||
| 	if(this->decoder || this->encoder) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "please finalize before releasing!"); | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(SpeexCodec::initialize) { | ||||
| 	lock_guard lock(this->coder_lock); | ||||
| 
 | ||||
| 	const SpeexMode* speex_mode = nullptr; | ||||
| 
 | ||||
| 	if(this->type == CodecType::SPEEX_NARROWBAND) | ||||
| 		speex_mode = &speex_nb_mode; | ||||
| 	else if(this->type == CodecType::SPEEX_WIDEBAND) | ||||
| 		speex_mode = &speex_wb_mode; | ||||
| 	else if(this->type == CodecType::SPEEX_ULTRA_WIDEBAND) | ||||
| 		speex_mode = &speex_uwb_mode; | ||||
| 
 | ||||
| 	assert(speex_mode); | ||||
| 	{ | ||||
| 
 | ||||
| 		this->encoder = speex_encoder_init(speex_mode); | ||||
| 		if(!this->encoder) { | ||||
| 			NAN_THROW_EXCEPTION(Error, "Failed to create encoder"); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		/*Set the quality to 8 (15 kbps)*/ | ||||
| 		int tmp = 8; //FIXME configurable
 | ||||
| 		speex_encoder_ctl(this->encoder, SPEEX_SET_QUALITY, &tmp); | ||||
| 
 | ||||
| 		speex_encoder_ctl(this->encoder, SPEEX_GET_FRAME_SIZE, &this->frame_size); | ||||
| 	} | ||||
| 	{ | ||||
| 		this->decoder = speex_decoder_init(speex_mode); | ||||
| 		if(!this->decoder) { | ||||
| 			speex_encoder_destroy(this->encoder); | ||||
| 			this->encoder = nullptr; | ||||
| 
 | ||||
| 			NAN_THROW_EXCEPTION(Error, "Failed to create decoder"); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		int tmp = 1; //TODO What is this?
 | ||||
| 		speex_decoder_ctl(this->decoder, SPEEX_SET_ENH, &tmp); | ||||
| 
 | ||||
| 
 | ||||
| 		int tmp_frame_size; | ||||
| 		speex_encoder_ctl(this->encoder, SPEEX_GET_FRAME_SIZE, &tmp_frame_size); | ||||
| 		if(tmp_frame_size != this->frame_size) { | ||||
| 			NAN_THROW_EXCEPTION(Error, "Decoder and encoder have different frame sizes!"); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	speex_bits_init(&this->encoder_bits); | ||||
| 	speex_bits_init(&this->decoder_bits); | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(SpeexCodec::finalize) { | ||||
| 	lock_guard lock(this->coder_lock); | ||||
| 
 | ||||
| 	if(this->encoder) { | ||||
| 		speex_encoder_destroy(this->encoder); | ||||
| 		this->encoder = nullptr; | ||||
| 
 | ||||
| 		speex_bits_destroy(&this->encoder_bits); | ||||
| 	} | ||||
| 
 | ||||
| 	if(this->decoder) { | ||||
| 		speex_decoder_destroy(this->decoder); | ||||
| 		this->decoder = nullptr; | ||||
| 
 | ||||
| 		speex_bits_destroy(&this->decoder_bits); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| NAN_METHOD(SpeexCodec::encode) { | ||||
| 	Nan::HandleScope scope; | ||||
| 
 | ||||
| 	if(!info[0]->IsArrayBuffer()) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "First argument isn't an array buffer!"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	auto js_buffer = info[0].As<ArrayBuffer>()->GetContents(); | ||||
| 	auto buffer = make_unique<Chunk>(max(js_buffer.ByteLength(), 256UL)); | ||||
| 	buffer->length = js_buffer.ByteLength(); | ||||
| 	memcpy(buffer->memory, js_buffer.Data(), js_buffer.ByteLength()); | ||||
| 
 | ||||
| 	auto callback_success = make_unique<Nan::Callback>(info[1].As<Function>()); | ||||
| 	auto callback_error = make_unique<Nan::Callback>(info[2].As<Function>()); | ||||
| 
 | ||||
| 	auto codec = make_unique<v8::Persistent<Object>>(info.GetIsolate(), info.Holder()); | ||||
| 	auto callback = Nan::async_callback([callback_success = move(callback_success), callback_error = move(callback_error), codec = move(codec)](std::unique_ptr<Chunk> result, std::string error) { | ||||
| 		Nan::HandleScope scope; | ||||
| 		if(result) { | ||||
| 			auto _buffer = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), result->length); | ||||
| 			memcpy(_buffer->GetContents().Data(), result->memory, result->length); | ||||
| 
 | ||||
| 			Local<Value> argv[] = { _buffer }; //_buffer
 | ||||
| 			callback_success->Call(1, argv); | ||||
| 		} else  { | ||||
| 			Local<Value> argv[] = { Nan::New<String>(error).ToLocalChecked() }; //error
 | ||||
| 			callback_error->Call(1, argv); | ||||
| 		} | ||||
| 		codec->Reset(); | ||||
| 	}).option_destroyed_execute(true); | ||||
| 
 | ||||
| 	tc::codec_workers->enqueue_task( | ||||
| 			[this, callback, buffer = move(buffer)]() mutable { | ||||
| 				{ | ||||
| 					lock_guard lock(this->coder_lock); | ||||
| 					if(!this->encoder) { | ||||
| 						callback(nullptr, "Please initialize first!"); | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					if(buffer->length < this->frame_size * sizeof(float)) { | ||||
| 						callback(nullptr, "Input buffer to short! Received " + to_string(buffer->length) + ", Expected " + to_string(this->frame_size * sizeof(float))); | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					for(size_t frame = 0; frame < this->frame_size; frame++) | ||||
| 						((float*) buffer->memory)[frame] *= 8000; //We want a range between 0 and 8000
 | ||||
| 
 | ||||
| 					speex_bits_reset(&this->encoder_bits); | ||||
| 					speex_encode(this->encoder, (float*) buffer->memory, &this->encoder_bits); | ||||
| 					auto nbytes = speex_bits_write(&this->encoder_bits, buffer->memory, buffer->allocated_length); | ||||
| 
 | ||||
| 					if(nbytes < 0) { | ||||
| 						callback(nullptr, "Invalid write?"); | ||||
| 						return; | ||||
| 					} | ||||
| 					buffer->length = nbytes; | ||||
| 				} | ||||
| 				callback(move(buffer), ""); | ||||
| 			} | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| NAN_METHOD(SpeexCodec::decode) { | ||||
| 	Nan::HandleScope scope; | ||||
| 
 | ||||
| 	if(!info[0]->IsArrayBuffer()) { | ||||
| 		NAN_THROW_EXCEPTION(Error, "First argument isn't an array buffer!"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	auto js_buffer = info[0].As<ArrayBuffer>()->GetContents(); | ||||
| 	auto buffer = make_unique<Chunk>(max(js_buffer.ByteLength(), this->frame_size * sizeof(float))); | ||||
| 	buffer->length = js_buffer.ByteLength(); | ||||
| 	memcpy(buffer->memory, js_buffer.Data(), js_buffer.ByteLength()); | ||||
| 
 | ||||
| 	auto callback_success = make_unique<Nan::Callback>(info[1].As<Function>()); | ||||
| 	auto callback_error = make_unique<Nan::Callback>(info[2].As<Function>()); | ||||
| 
 | ||||
| 	auto codec = make_unique<v8::Persistent<Object>>(info.GetIsolate(), info.Holder()); | ||||
| 	auto callback = Nan::async_callback([callback_success = move(callback_success), callback_error = move(callback_error), codec = move(codec)](std::unique_ptr<Chunk> result, std::string error) { | ||||
| 		Nan::HandleScope scope; | ||||
| 		if(result) { | ||||
| 			auto _buffer = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), result->length); | ||||
| 			memcpy(_buffer->GetContents().Data(), result->memory, result->length); | ||||
| 			Local<Value> argv[] = { _buffer }; | ||||
| 			callback_success->Call(1, argv); | ||||
| 		} else  { | ||||
| 			Local<Value> argv[] = { Nan::New<String>(error).ToLocalChecked() }; | ||||
| 			callback_error->Call(1, argv); | ||||
| 		} | ||||
| 		codec->Reset(); | ||||
| 	}).option_destroyed_execute(true); | ||||
| 
 | ||||
| 	tc::codec_workers->enqueue_task( | ||||
| 			[this, callback, buffer = move(buffer)]() mutable { | ||||
| 				int result; | ||||
| 
 | ||||
| 				{ | ||||
| 					lock_guard lock(this->coder_lock); | ||||
| 					if(!this->decoder) { | ||||
| 						callback(nullptr, "Please initialize first!"); | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					speex_bits_reset(&this->decoder_bits); | ||||
| 					speex_bits_read_from(&this->decoder_bits, buffer->memory, buffer->length); | ||||
| 					auto state = speex_decode(this->decoder, &this->decoder_bits, (float*) buffer->memory); | ||||
| 					if(state != 0) { | ||||
| 						callback(nullptr, "decode failed (" + to_string(state) + ")"); | ||||
| 						return; | ||||
| 					} | ||||
| 					buffer->length = this->frame_size * sizeof(float); | ||||
| 					for(size_t frame = 0; frame < this->frame_size; frame++) | ||||
| 						((float*) buffer->memory)[frame] /= 8000; //We want a range between 0 and 1
 | ||||
| 				} | ||||
| 
 | ||||
| 				callback(move(buffer), ""); | ||||
| 			} | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -1,36 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "NativeCodec.h" | ||||
| 
 | ||||
| #ifdef HAVE_SPEEX | ||||
| 	#include <speex/speex.h> | ||||
| #endif | ||||
| 
 | ||||
| namespace tc { | ||||
| 	class NativeCodec; | ||||
| 
 | ||||
| 
 | ||||
| 	class SpeexCodec : public NativeCodec  { | ||||
| 		public: | ||||
| 			static bool supported(); | ||||
| 
 | ||||
| #ifdef HAVE_SPEEX | ||||
| 			explicit SpeexCodec(CodecType::value type); | ||||
| 			virtual ~SpeexCodec(); | ||||
| 
 | ||||
| 			virtual NAN_METHOD(initialize); | ||||
| 			virtual NAN_METHOD(finalize); | ||||
| 			virtual NAN_METHOD(encode); | ||||
| 			virtual NAN_METHOD(decode); | ||||
| 		private: | ||||
| 			int frame_size = 0; | ||||
| 
 | ||||
| 			//TODO are two bits really necessary?
 | ||||
| 			std::mutex coder_lock; | ||||
| 			SpeexBits encoder_bits; | ||||
| 			void* encoder = nullptr; | ||||
| 			SpeexBits decoder_bits; | ||||
| 			void* decoder = nullptr; | ||||
| #endif | ||||
| 	}; | ||||
| } | ||||
							
								
								
									
										2
									
								
								native/codec/libraries/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								native/codec/libraries/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,2 +0,0 @@ | ||||
| generated/ | ||||
| opus/build | ||||
| @ -1,94 +0,0 @@ | ||||
| #!/bin/bash | ||||
| 
 | ||||
| cd $(dirname "$0") | ||||
| install_directory="$(pwd)/generated/celt/" | ||||
| 
 | ||||
| machine="$(uname -s)" | ||||
| case "${machine}" in | ||||
|     Linux*)     machine=Linux;; | ||||
| #    Darwin*)    machine=Mac;; | ||||
|     MINGW*)     machine=MinGW;; | ||||
|     *)          machine="UNKNOWN:${machine}" | ||||
| esac | ||||
| 
 | ||||
| if [[ ${machine} == "UNKNOWN"* ]]; then | ||||
|     echo "Unknown platform ${machine}" | ||||
|     exit 1 | ||||
| fi | ||||
| 
 | ||||
| cd celt | ||||
| 
 | ||||
| if [[ ${machine} == "Linux" ]]; then | ||||
|     if [[ ! -e configure ]]; then | ||||
|         echo "Generating configure file" | ||||
|         ./autogen.sh | ||||
|         if [[ $? -ne 0 ]]; then | ||||
|             echo "Failed to generate configure file" | ||||
|             exit 1 | ||||
|         fi | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| windows_build="win32/VS2015/" | ||||
| windows_build_type="x64" | ||||
| if [[ ( ! -d build ) && ( ! -d "${windows_build}/${windows_build_type}" ) ]] || [[ "$1" == "rebuild" ]]; then | ||||
|     if [[ ${machine} == "Linux" ]]; then | ||||
| 		if [[ -e build ]]; then | ||||
| 			rm -r build | ||||
| 		fi | ||||
| 		mkdir build && cd build | ||||
| 
 | ||||
|         export CFLAGS="-fPIC" | ||||
|         ../configure --prefix="${install_directory}" --with-pic | ||||
|         if [[ $? -ne 0 ]]; then | ||||
|             echo "Failed to configure project!" | ||||
|             exit 1 | ||||
|         fi | ||||
| 
 | ||||
|         cd .. | ||||
|     elif [[ ${machine} == "MinGW" ]]; then | ||||
|     	#Only cleanup last shit | ||||
|         if [[ -e "${windows_build}/${windows_build_type}" ]]; then | ||||
|         	rm -r "${windows_build}/${windows_build_type}" | ||||
|         fi | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| if [[ -e "${install_directory}" ]]; then | ||||
|     echo "deleting old install directory!" | ||||
|     rm -r "${install_directory}" | ||||
|     echo "rm -r '${install_directory}'" | ||||
| fi | ||||
| 
 | ||||
| if [[ ${machine} == "MinGW" ]]; then | ||||
| 	saved_pwd=$(pwd) | ||||
| 	cd "${windows_build}" | ||||
| 	MSBuild.exe -p:Platform=x64 -property:Configuration=Release opus.vcxproj | ||||
| 	if [[ $? -ne 0 ]]; then | ||||
| 		echo "Failed to build celt!" | ||||
| 		exit 1 | ||||
| 	fi | ||||
| 	cd ${saved_pwd} | ||||
| 
 | ||||
| 	mkdir -p "${install_directory}/include/celt" | ||||
| 	mkdir -p "${install_directory}/lib/" | ||||
| 
 | ||||
| 	cp -r include/* "${install_directory}/include/celt/" | ||||
| 	cp -r ${windows_build}/${windows_build_type}/Release/*.lib "${install_directory}/lib/" | ||||
| elif [[ ${machine} == "Linux" ]]; then | ||||
|     cd build | ||||
| 
 | ||||
|     make -j 12 | ||||
|     if [[ $? -ne 0 ]]; then | ||||
|         echo "Failed to build celt!" | ||||
|         exit 1 | ||||
|     fi | ||||
| 
 | ||||
|     make install | ||||
|     if [[ $? -ne 0 ]]; then | ||||
|         echo "Failed to install celt!" | ||||
|         exit 1 | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| echo "Celt build successfully" | ||||
| @ -1,98 +0,0 @@ | ||||
| #!/bin/bash | ||||
| 
 | ||||
| cd $(dirname "$0") | ||||
| install_directory="$(pwd)/generated/opus/" | ||||
| 
 | ||||
| machine="$(uname -s)" | ||||
| case "${machine}" in | ||||
|     Linux*)     machine=Linux;; | ||||
| #    Darwin*)    machine=Mac;; | ||||
|     MINGW*)     machine=MinGW;; | ||||
|     *)          machine="UNKNOWN:${machine}" | ||||
| esac | ||||
| 
 | ||||
| if [[ ${machine} == "UNKNOWN"* ]]; then | ||||
|     echo "Unknown platform ${machine}" | ||||
|     exit 1 | ||||
| fi | ||||
| 
 | ||||
| cd opus | ||||
| #if [ ! -e CMakeLists.txt ]; then | ||||
| #    echo "Linking CMakeLists" | ||||
| #    ln -s ../cmake/opus/CMakeLists.txt . | ||||
| #fi | ||||
| 
 | ||||
| if [[ ${machine} == "Linux" ]]; then | ||||
|     if [[ ! -e configure ]]; then | ||||
|         echo "Generating configure file" | ||||
|         ./autogen.sh | ||||
|         if [[ $? -ne 0 ]]; then | ||||
|             echo "Failed to generate configure file" | ||||
|             exit 1 | ||||
|         fi | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| windows_build="win32/VS2015/" | ||||
| windows_build_type="x64" | ||||
| if [[ ( ! -d build ) && ( ! -d "${windows_build}/${windows_build_type}" ) ]] || [[ "$1" == "rebuild" ]]; then | ||||
|     if [[ ${machine} == "Linux" ]]; then | ||||
| 		if [[ -e build ]]; then | ||||
| 			rm -r build | ||||
| 		fi | ||||
| 		mkdir build && cd build | ||||
| 
 | ||||
|         export CFLAGS="-fPIC" | ||||
|         ../configure --prefix="${install_directory}" --with-pic | ||||
|         if [[ $? -ne 0 ]]; then | ||||
|             echo "Failed to configure project!" | ||||
|             exit 1 | ||||
|         fi | ||||
| 
 | ||||
|         cd .. | ||||
|     elif [[ ${machine} == "MinGW" ]]; then | ||||
|     	#Only cleanup last shit | ||||
|         if [[ -e "${windows_build}/${windows_build_type}" ]]; then | ||||
|         	rm -r "${windows_build}/${windows_build_type}" | ||||
|         fi | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| if [[ -e "${install_directory}" ]]; then | ||||
|     echo "deleting old install directory!" | ||||
|     rm -r "${install_directory}" | ||||
|     echo "rm -r '${install_directory}'" | ||||
| fi | ||||
| 
 | ||||
| if [[ ${machine} == "MinGW" ]]; then | ||||
| 	saved_pwd=$(pwd) | ||||
| 	cd "${windows_build}" | ||||
| 	MSBuild.exe -p:Platform=x64 -property:Configuration=Release opus.vcxproj | ||||
| 	if [[ $? -ne 0 ]]; then | ||||
| 		echo "Failed to build opus!" | ||||
| 		exit 1 | ||||
| 	fi | ||||
| 	cd ${saved_pwd} | ||||
| 
 | ||||
| 	mkdir -p "${install_directory}/include/opus" | ||||
| 	mkdir -p "${install_directory}/lib/" | ||||
| 
 | ||||
| 	cp -r include/* "${install_directory}/include/opus/" | ||||
| 	cp -r ${windows_build}/${windows_build_type}/Release/*.lib "${install_directory}/lib/" | ||||
| elif [[ ${machine} == "Linux" ]]; then | ||||
|     cd build | ||||
| 
 | ||||
|     make -j 12 | ||||
|     if [[ $? -ne 0 ]]; then | ||||
|         echo "Failed to build opus!" | ||||
|         exit 1 | ||||
|     fi | ||||
| 
 | ||||
|     make install | ||||
|     if [[ $? -ne 0 ]]; then | ||||
|         echo "Failed to install opus!" | ||||
|         exit 1 | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| echo "Opus build successfully" | ||||
| @ -1,97 +0,0 @@ | ||||
| #!/bin/bash | ||||
| 
 | ||||
| cd $(dirname "$0") | ||||
| install_directory="$(pwd)/generated/speex/" | ||||
| 
 | ||||
| machine="$(uname -s)" | ||||
| case "${machine}" in | ||||
|     Linux*)     machine=Linux;; | ||||
| #    Darwin*)    machine=Mac;; | ||||
|     MINGW*)     machine=MinGW;; | ||||
|     *)          machine="UNKNOWN:${machine}" | ||||
| esac | ||||
| 
 | ||||
| if [[ ${machine} == "UNKNOWN"* ]]; then | ||||
|     echo "Unknown platform ${machine}" | ||||
|     exit 1 | ||||
| fi | ||||
| 
 | ||||
| cd speex | ||||
| if [[ ${machine} == "Linux" ]]; then | ||||
|     if [[ ! -e configure ]]; then | ||||
|         echo "Generating configure file" | ||||
|         ./autogen.sh | ||||
|         if [[ $? -ne 0 ]]; then | ||||
|             echo "Failed to generate configure file" | ||||
|             exit 1 | ||||
|         fi | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| windows_build="win32/VS2015/" | ||||
| windows_build_type="x64" | ||||
| if [[ ( ! -d build ) && ( ! -d "${windows_build}/${windows_build_type}" ) ]] || [[ "$1" == "rebuild" ]]; then | ||||
|     if [[ ${machine} == "Linux" ]]; then | ||||
| 		if [[ -e build ]]; then | ||||
| 			rm -r build | ||||
| 		fi | ||||
| 		mkdir build && cd build | ||||
| 
 | ||||
|         export CFLAGS="-fPIC" | ||||
|         ../configure --prefix="${install_directory}" --with-pic | ||||
|         if [[ $? -ne 0 ]]; then | ||||
|             echo "Failed to configure project!" | ||||
|             exit 1 | ||||
|         fi | ||||
| 
 | ||||
|         cd .. | ||||
|     elif [[ ${machine} == "MinGW" ]]; then | ||||
|         #Only cleanup last shit | ||||
|         if [[ -e "${windows_build}" ]]; then | ||||
|         	rm -r "${windows_build}" | ||||
|         fi | ||||
| 
 | ||||
|         mkdir -p ${windows_build} | ||||
|         cp -r ../template/speex_VS2015/* ${windows_build}/ | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| if [[ -e "${install_directory}" ]]; then | ||||
|     echo "deleting old install directory!" | ||||
|     rm -r "${install_directory}" | ||||
|     echo "rm -r '${install_directory}'" | ||||
| fi | ||||
| 
 | ||||
| if [[ ${machine} == "MinGW" ]]; then | ||||
| 	saved_pwd=$(pwd) | ||||
| 	cd "${windows_build}" | ||||
| 	MSBuild.exe -p:Platform=x64 -property:Configuration=Release libspeex/libspeex.vcxproj | ||||
| 
 | ||||
| 	if [[ $? -ne 0 ]]; then | ||||
| 		echo "Failed to build speex!" | ||||
| 		exit 1 | ||||
| 	fi | ||||
| 	cd ${saved_pwd} | ||||
| 
 | ||||
| 	mkdir -p "${install_directory}/include/speex" | ||||
| 	mkdir -p "${install_directory}/lib/" | ||||
| 
 | ||||
| 	cp -r include/speex/*.h "${install_directory}/include/speex/" | ||||
| 	cp -r ${windows_build}/libspeex/${windows_build_type}/Release/*.lib "${install_directory}/lib/" | ||||
| elif [[ ${machine} == "Linux" ]]; then | ||||
|     cd build | ||||
| 
 | ||||
|     make -j 12 | ||||
|     if [[ $? -ne 0 ]]; then | ||||
|         echo "Failed to build speex!" | ||||
|         exit 1 | ||||
|     fi | ||||
| 
 | ||||
|     make install | ||||
|     if [[ $? -ne 0 ]]; then | ||||
|         echo "Failed to install speex!" | ||||
|         exit 1 | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| echo "Speex build successfully" | ||||
							
								
								
									
										33
									
								
								native/codec/libraries/celt/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								native/codec/libraries/celt/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,33 +0,0 @@ | ||||
| Makefile | ||||
| Makefile.in | ||||
| aclocal.m4 | ||||
| autom4te.cache | ||||
| *.kdevelop.pcs | ||||
| *.kdevses | ||||
| config.guess | ||||
| config.h | ||||
| config.h.in | ||||
| config.log | ||||
| config.status | ||||
| config.sub | ||||
| configure | ||||
| depcomp | ||||
| install-sh | ||||
| .deps | ||||
| .libs | ||||
| *.la | ||||
| testcelt | ||||
| libtool | ||||
| ltmain.sh | ||||
| missing | ||||
| stamp-h1 | ||||
| *.sw | ||||
| *.o | ||||
| *.lo | ||||
| *~ | ||||
| tests/*test | ||||
| tools/celtdec | ||||
| tools/celtenc | ||||
| celt.pc | ||||
| libcelt.spec | ||||
| libcelt/dump_modes | ||||
| @ -1,25 +0,0 @@ | ||||
| Copyright 2001-2009 Jean-Marc Valin, Timothy B. Terriberry, | ||||
|                     CSIRO, and other contributors | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions | ||||
| are met: | ||||
| 
 | ||||
| - Redistributions of source code must retain the above copyright | ||||
| notice, this list of conditions and the following disclaimer. | ||||
| 
 | ||||
| - Redistributions in binary form must reproduce the above copyright | ||||
| notice, this list of conditions and the following disclaimer in the | ||||
| documentation and/or other materials provided with the distribution. | ||||
| 
 | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR | ||||
| CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | ||||
| EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | ||||
| PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||||
| PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||||
| NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| @ -1,283 +0,0 @@ | ||||
| # Doxyfile 1.5.3 | ||||
| 
 | ||||
| #--------------------------------------------------------------------------- | ||||
| # Project related configuration options | ||||
| #--------------------------------------------------------------------------- | ||||
| DOXYFILE_ENCODING      = UTF-8 | ||||
| PROJECT_NAME           = CELT | ||||
| PROJECT_NUMBER         = 0.11.4 | ||||
| OUTPUT_DIRECTORY       = doc/API | ||||
| CREATE_SUBDIRS         = NO | ||||
| OUTPUT_LANGUAGE        = English | ||||
| BRIEF_MEMBER_DESC      = YES | ||||
| REPEAT_BRIEF           = YES | ||||
| ABBREVIATE_BRIEF       = "The $name class " \ | ||||
|                          "The $name widget " \ | ||||
|                          "The $name file " \ | ||||
|                          is \ | ||||
|                          provides \ | ||||
|                          specifies \ | ||||
|                          contains \ | ||||
|                          represents \ | ||||
|                          a \ | ||||
|                          an \ | ||||
|                          the | ||||
| ALWAYS_DETAILED_SEC    = NO | ||||
| INLINE_INHERITED_MEMB  = NO | ||||
| FULL_PATH_NAMES        = YES | ||||
| STRIP_FROM_PATH        =  | ||||
| STRIP_FROM_INC_PATH    =  | ||||
| SHORT_NAMES            = NO | ||||
| JAVADOC_AUTOBRIEF      = NO | ||||
| QT_AUTOBRIEF           = NO | ||||
| MULTILINE_CPP_IS_BRIEF = NO | ||||
| DETAILS_AT_TOP         = NO | ||||
| INHERIT_DOCS           = YES | ||||
| SEPARATE_MEMBER_PAGES  = NO | ||||
| TAB_SIZE               = 8 | ||||
| ALIASES                =  | ||||
| OPTIMIZE_OUTPUT_FOR_C  = YES | ||||
| OPTIMIZE_OUTPUT_JAVA   = NO | ||||
| BUILTIN_STL_SUPPORT    = NO | ||||
| CPP_CLI_SUPPORT        = NO | ||||
| DISTRIBUTE_GROUP_DOC   = NO | ||||
| SUBGROUPING            = YES | ||||
| #--------------------------------------------------------------------------- | ||||
| # Build related configuration options | ||||
| #--------------------------------------------------------------------------- | ||||
| EXTRACT_ALL            = NO | ||||
| EXTRACT_PRIVATE        = NO | ||||
| EXTRACT_STATIC         = NO | ||||
| EXTRACT_LOCAL_CLASSES  = YES | ||||
| EXTRACT_LOCAL_METHODS  = NO | ||||
| EXTRACT_ANON_NSPACES   = NO | ||||
| HIDE_UNDOC_MEMBERS     = YES | ||||
| HIDE_UNDOC_CLASSES     = YES | ||||
| HIDE_FRIEND_COMPOUNDS  = NO | ||||
| HIDE_IN_BODY_DOCS      = NO | ||||
| INTERNAL_DOCS          = NO | ||||
| CASE_SENSE_NAMES       = YES | ||||
| HIDE_SCOPE_NAMES       = NO | ||||
| SHOW_INCLUDE_FILES     = YES | ||||
| INLINE_INFO            = YES | ||||
| SORT_MEMBER_DOCS       = YES | ||||
| SORT_BRIEF_DOCS        = NO | ||||
| SORT_BY_SCOPE_NAME     = NO | ||||
| GENERATE_TODOLIST      = YES | ||||
| GENERATE_TESTLIST      = YES | ||||
| GENERATE_BUGLIST       = YES | ||||
| GENERATE_DEPRECATEDLIST= YES | ||||
| ENABLED_SECTIONS       =  | ||||
| MAX_INITIALIZER_LINES  = 30 | ||||
| SHOW_USED_FILES        = YES | ||||
| SHOW_DIRECTORIES       = NO | ||||
| FILE_VERSION_FILTER    =  | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to warning and progress messages | ||||
| #--------------------------------------------------------------------------- | ||||
| QUIET                  = NO | ||||
| WARNINGS               = YES | ||||
| WARN_IF_UNDOCUMENTED   = YES | ||||
| WARN_IF_DOC_ERROR      = YES | ||||
| WARN_NO_PARAMDOC       = NO | ||||
| WARN_FORMAT            = "$file:$line: $text " | ||||
| WARN_LOGFILE           =  | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the input files | ||||
| #--------------------------------------------------------------------------- | ||||
| INPUT                  = libcelt/celt.h \ | ||||
|                          libcelt/celt_types.h \ | ||||
|                          libcelt/celt_header.h | ||||
| INPUT_ENCODING         = UTF-8 | ||||
| FILE_PATTERNS          = *.c \ | ||||
|                          *.cc \ | ||||
|                          *.cxx \ | ||||
|                          *.cpp \ | ||||
|                          *.c++ \ | ||||
|                          *.d \ | ||||
|                          *.java \ | ||||
|                          *.ii \ | ||||
|                          *.ixx \ | ||||
|                          *.ipp \ | ||||
|                          *.i++ \ | ||||
|                          *.inl \ | ||||
|                          *.h \ | ||||
|                          *.hh \ | ||||
|                          *.hxx \ | ||||
|                          *.hpp \ | ||||
|                          *.h++ \ | ||||
|                          *.idl \ | ||||
|                          *.odl \ | ||||
|                          *.cs \ | ||||
|                          *.php \ | ||||
|                          *.php3 \ | ||||
|                          *.inc \ | ||||
|                          *.m \ | ||||
|                          *.mm \ | ||||
|                          *.dox \ | ||||
|                          *.py \ | ||||
|                          *.C \ | ||||
|                          *.CC \ | ||||
|                          *.C++ \ | ||||
|                          *.II \ | ||||
|                          *.I++ \ | ||||
|                          *.H \ | ||||
|                          *.HH \ | ||||
|                          *.H++ \ | ||||
|                          *.CS \ | ||||
|                          *.PHP \ | ||||
|                          *.PHP3 \ | ||||
|                          *.M \ | ||||
|                          *.MM \ | ||||
|                          *.PY | ||||
| RECURSIVE              = NO | ||||
| EXCLUDE                =  | ||||
| EXCLUDE_SYMLINKS       = NO | ||||
| EXCLUDE_PATTERNS       = *.c | ||||
| EXCLUDE_SYMBOLS        =  | ||||
| EXAMPLE_PATH           =  | ||||
| EXAMPLE_PATTERNS       = * | ||||
| EXAMPLE_RECURSIVE      = NO | ||||
| IMAGE_PATH             =  | ||||
| INPUT_FILTER           =  | ||||
| FILTER_PATTERNS        =  | ||||
| FILTER_SOURCE_FILES    = NO | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to source browsing | ||||
| #--------------------------------------------------------------------------- | ||||
| SOURCE_BROWSER         = YES | ||||
| INLINE_SOURCES         = NO | ||||
| STRIP_CODE_COMMENTS    = YES | ||||
| REFERENCED_BY_RELATION = YES | ||||
| REFERENCES_RELATION    = YES | ||||
| REFERENCES_LINK_SOURCE = YES | ||||
| USE_HTAGS              = NO | ||||
| VERBATIM_HEADERS       = YES | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the alphabetical class index | ||||
| #--------------------------------------------------------------------------- | ||||
| ALPHABETICAL_INDEX     = NO | ||||
| COLS_IN_ALPHA_INDEX    = 5 | ||||
| IGNORE_PREFIX          =  | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the HTML output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_HTML          = YES | ||||
| HTML_OUTPUT            = html | ||||
| HTML_FILE_EXTENSION    = .html | ||||
| HTML_HEADER            =  | ||||
| HTML_FOOTER            =  | ||||
| HTML_STYLESHEET        =  | ||||
| HTML_ALIGN_MEMBERS     = YES | ||||
| GENERATE_HTMLHELP      = NO | ||||
| HTML_DYNAMIC_SECTIONS  = NO | ||||
| CHM_FILE               =  | ||||
| HHC_LOCATION           =  | ||||
| GENERATE_CHI           = NO | ||||
| BINARY_TOC             = NO | ||||
| TOC_EXPAND             = NO | ||||
| DISABLE_INDEX          = NO | ||||
| ENUM_VALUES_PER_LINE   = 4 | ||||
| GENERATE_TREEVIEW      = NO | ||||
| TREEVIEW_WIDTH         = 250 | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the LaTeX output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_LATEX         = YES | ||||
| LATEX_OUTPUT           = latex | ||||
| LATEX_CMD_NAME         = latex | ||||
| MAKEINDEX_CMD_NAME     = makeindex | ||||
| COMPACT_LATEX          = NO | ||||
| PAPER_TYPE             = a4wide | ||||
| EXTRA_PACKAGES         =  | ||||
| LATEX_HEADER           =  | ||||
| PDF_HYPERLINKS         = YES | ||||
| USE_PDFLATEX           = YES | ||||
| LATEX_BATCHMODE        = NO | ||||
| LATEX_HIDE_INDICES     = NO | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the RTF output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_RTF           = NO | ||||
| RTF_OUTPUT             = rtf | ||||
| COMPACT_RTF            = NO | ||||
| RTF_HYPERLINKS         = NO | ||||
| RTF_STYLESHEET_FILE    =  | ||||
| RTF_EXTENSIONS_FILE    =  | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the man page output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_MAN           = YES | ||||
| MAN_OUTPUT             = man | ||||
| MAN_EXTENSION          = .3 | ||||
| MAN_LINKS              = NO | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the XML output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_XML           = NO | ||||
| XML_OUTPUT             = xml | ||||
| XML_SCHEMA             =  | ||||
| XML_DTD                =  | ||||
| XML_PROGRAMLISTING     = YES | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options for the AutoGen Definitions output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_AUTOGEN_DEF   = NO | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the Perl module output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_PERLMOD       = NO | ||||
| PERLMOD_LATEX          = NO | ||||
| PERLMOD_PRETTY         = YES | ||||
| PERLMOD_MAKEVAR_PREFIX =  | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration options related to the preprocessor    | ||||
| #--------------------------------------------------------------------------- | ||||
| ENABLE_PREPROCESSING   = YES | ||||
| MACRO_EXPANSION        = NO | ||||
| EXPAND_ONLY_PREDEF     = NO | ||||
| SEARCH_INCLUDES        = YES | ||||
| INCLUDE_PATH           =  | ||||
| INCLUDE_FILE_PATTERNS  =  | ||||
| PREDEFINED             =  | ||||
| EXPAND_AS_DEFINED      =  | ||||
| SKIP_FUNCTION_MACROS   = YES | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration::additions related to external references    | ||||
| #--------------------------------------------------------------------------- | ||||
| TAGFILES               =  | ||||
| GENERATE_TAGFILE       =  | ||||
| ALLEXTERNALS           = NO | ||||
| EXTERNAL_GROUPS        = YES | ||||
| PERL_PATH              = /usr/bin/perl | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration options related to the dot tool    | ||||
| #--------------------------------------------------------------------------- | ||||
| CLASS_DIAGRAMS         = NO | ||||
| MSCGEN_PATH            =  | ||||
| HIDE_UNDOC_RELATIONS   = YES | ||||
| HAVE_DOT               = YES | ||||
| CLASS_GRAPH            = YES | ||||
| COLLABORATION_GRAPH    = YES | ||||
| GROUP_GRAPHS           = YES | ||||
| UML_LOOK               = NO | ||||
| TEMPLATE_RELATIONS     = NO | ||||
| INCLUDE_GRAPH          = NO | ||||
| INCLUDED_BY_GRAPH      = NO | ||||
| CALL_GRAPH             = NO | ||||
| CALLER_GRAPH           = NO | ||||
| GRAPHICAL_HIERARCHY    = YES | ||||
| DIRECTORY_GRAPH        = YES | ||||
| DOT_IMAGE_FORMAT       = png | ||||
| DOT_PATH               =  | ||||
| DOTFILE_DIRS           =  | ||||
| DOT_GRAPH_MAX_NODES    = 50 | ||||
| MAX_DOT_GRAPH_DEPTH    = 1000 | ||||
| DOT_TRANSPARENT        = NO | ||||
| DOT_MULTI_TARGETS      = NO | ||||
| GENERATE_LEGEND        = YES | ||||
| DOT_CLEANUP            = YES | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration::additions related to the search engine    | ||||
| #--------------------------------------------------------------------------- | ||||
| SEARCHENGINE           = NO | ||||
| @ -1,281 +0,0 @@ | ||||
| # Doxyfile 1.5.3 | ||||
| 
 | ||||
| #--------------------------------------------------------------------------- | ||||
| # Project related configuration options | ||||
| #--------------------------------------------------------------------------- | ||||
| DOXYFILE_ENCODING      = UTF-8 | ||||
| PROJECT_NAME           = CELT | ||||
| PROJECT_NUMBER         = 0.11.4 | ||||
| OUTPUT_DIRECTORY       = doc/devel | ||||
| CREATE_SUBDIRS         = NO | ||||
| OUTPUT_LANGUAGE        = English | ||||
| BRIEF_MEMBER_DESC      = YES | ||||
| REPEAT_BRIEF           = YES | ||||
| ABBREVIATE_BRIEF       = "The $name class  " \ | ||||
|                          "The $name widget  " \ | ||||
|                          "The $name file  " \ | ||||
|                          is \ | ||||
|                          provides \ | ||||
|                          specifies \ | ||||
|                          contains \ | ||||
|                          represents \ | ||||
|                          a \ | ||||
|                          an \ | ||||
|                          the | ||||
| ALWAYS_DETAILED_SEC    = NO | ||||
| INLINE_INHERITED_MEMB  = NO | ||||
| FULL_PATH_NAMES        = YES | ||||
| STRIP_FROM_PATH        =  | ||||
| STRIP_FROM_INC_PATH    =  | ||||
| SHORT_NAMES            = NO | ||||
| JAVADOC_AUTOBRIEF      = NO | ||||
| QT_AUTOBRIEF           = NO | ||||
| MULTILINE_CPP_IS_BRIEF = NO | ||||
| DETAILS_AT_TOP         = NO | ||||
| INHERIT_DOCS           = YES | ||||
| SEPARATE_MEMBER_PAGES  = NO | ||||
| TAB_SIZE               = 8 | ||||
| ALIASES                =  | ||||
| OPTIMIZE_OUTPUT_FOR_C  = YES | ||||
| OPTIMIZE_OUTPUT_JAVA   = NO | ||||
| BUILTIN_STL_SUPPORT    = NO | ||||
| CPP_CLI_SUPPORT        = NO | ||||
| DISTRIBUTE_GROUP_DOC   = NO | ||||
| SUBGROUPING            = YES | ||||
| #--------------------------------------------------------------------------- | ||||
| # Build related configuration options | ||||
| #--------------------------------------------------------------------------- | ||||
| EXTRACT_ALL            = YES | ||||
| EXTRACT_PRIVATE        = NO | ||||
| EXTRACT_STATIC         = NO | ||||
| EXTRACT_LOCAL_CLASSES  = YES | ||||
| EXTRACT_LOCAL_METHODS  = NO | ||||
| EXTRACT_ANON_NSPACES   = NO | ||||
| HIDE_UNDOC_MEMBERS     = YES | ||||
| HIDE_UNDOC_CLASSES     = YES | ||||
| HIDE_FRIEND_COMPOUNDS  = NO | ||||
| HIDE_IN_BODY_DOCS      = NO | ||||
| INTERNAL_DOCS          = NO | ||||
| CASE_SENSE_NAMES       = YES | ||||
| HIDE_SCOPE_NAMES       = NO | ||||
| SHOW_INCLUDE_FILES     = YES | ||||
| INLINE_INFO            = YES | ||||
| SORT_MEMBER_DOCS       = YES | ||||
| SORT_BRIEF_DOCS        = NO | ||||
| SORT_BY_SCOPE_NAME     = NO | ||||
| GENERATE_TODOLIST      = YES | ||||
| GENERATE_TESTLIST      = YES | ||||
| GENERATE_BUGLIST       = YES | ||||
| GENERATE_DEPRECATEDLIST= YES | ||||
| ENABLED_SECTIONS       =  | ||||
| MAX_INITIALIZER_LINES  = 30 | ||||
| SHOW_USED_FILES        = YES | ||||
| SHOW_DIRECTORIES       = NO | ||||
| FILE_VERSION_FILTER    =  | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to warning and progress messages | ||||
| #--------------------------------------------------------------------------- | ||||
| QUIET                  = NO | ||||
| WARNINGS               = YES | ||||
| WARN_IF_UNDOCUMENTED   = YES | ||||
| WARN_IF_DOC_ERROR      = YES | ||||
| WARN_NO_PARAMDOC       = NO | ||||
| WARN_FORMAT            = "$file:$line: $text  " | ||||
| WARN_LOGFILE           =  | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the input files | ||||
| #--------------------------------------------------------------------------- | ||||
| INPUT                  = libcelt | ||||
| INPUT_ENCODING         = UTF-8 | ||||
| FILE_PATTERNS          = *.c \ | ||||
|                          *.cc \ | ||||
|                          *.cxx \ | ||||
|                          *.cpp \ | ||||
|                          *.c++ \ | ||||
|                          *.d \ | ||||
|                          *.java \ | ||||
|                          *.ii \ | ||||
|                          *.ixx \ | ||||
|                          *.ipp \ | ||||
|                          *.i++ \ | ||||
|                          *.inl \ | ||||
|                          *.h \ | ||||
|                          *.hh \ | ||||
|                          *.hxx \ | ||||
|                          *.hpp \ | ||||
|                          *.h++ \ | ||||
|                          *.idl \ | ||||
|                          *.odl \ | ||||
|                          *.cs \ | ||||
|                          *.php \ | ||||
|                          *.php3 \ | ||||
|                          *.inc \ | ||||
|                          *.m \ | ||||
|                          *.mm \ | ||||
|                          *.dox \ | ||||
|                          *.py \ | ||||
|                          *.C \ | ||||
|                          *.CC \ | ||||
|                          *.C++ \ | ||||
|                          *.II \ | ||||
|                          *.I++ \ | ||||
|                          *.H \ | ||||
|                          *.HH \ | ||||
|                          *.H++ \ | ||||
|                          *.CS \ | ||||
|                          *.PHP \ | ||||
|                          *.PHP3 \ | ||||
|                          *.M \ | ||||
|                          *.MM \ | ||||
|                          *.PY | ||||
| RECURSIVE              = NO | ||||
| EXCLUDE                =  | ||||
| EXCLUDE_SYMLINKS       = NO | ||||
| EXCLUDE_PATTERNS       =  | ||||
| EXCLUDE_SYMBOLS        =  | ||||
| EXAMPLE_PATH           =  | ||||
| EXAMPLE_PATTERNS       = * | ||||
| EXAMPLE_RECURSIVE      = NO | ||||
| IMAGE_PATH             =  | ||||
| INPUT_FILTER           =  | ||||
| FILTER_PATTERNS        =  | ||||
| FILTER_SOURCE_FILES    = NO | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to source browsing | ||||
| #--------------------------------------------------------------------------- | ||||
| SOURCE_BROWSER         = YES | ||||
| INLINE_SOURCES         = NO | ||||
| STRIP_CODE_COMMENTS    = YES | ||||
| REFERENCED_BY_RELATION = YES | ||||
| REFERENCES_RELATION    = YES | ||||
| REFERENCES_LINK_SOURCE = YES | ||||
| USE_HTAGS              = NO | ||||
| VERBATIM_HEADERS       = YES | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the alphabetical class index | ||||
| #--------------------------------------------------------------------------- | ||||
| ALPHABETICAL_INDEX     = NO | ||||
| COLS_IN_ALPHA_INDEX    = 5 | ||||
| IGNORE_PREFIX          =  | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the HTML output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_HTML          = YES | ||||
| HTML_OUTPUT            = html | ||||
| HTML_FILE_EXTENSION    = .html | ||||
| HTML_HEADER            =  | ||||
| HTML_FOOTER            =  | ||||
| HTML_STYLESHEET        =  | ||||
| HTML_ALIGN_MEMBERS     = YES | ||||
| GENERATE_HTMLHELP      = NO | ||||
| HTML_DYNAMIC_SECTIONS  = NO | ||||
| CHM_FILE               =  | ||||
| HHC_LOCATION           =  | ||||
| GENERATE_CHI           = NO | ||||
| BINARY_TOC             = NO | ||||
| TOC_EXPAND             = NO | ||||
| DISABLE_INDEX          = NO | ||||
| ENUM_VALUES_PER_LINE   = 4 | ||||
| GENERATE_TREEVIEW      = NO | ||||
| TREEVIEW_WIDTH         = 250 | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the LaTeX output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_LATEX         = YES | ||||
| LATEX_OUTPUT           = latex | ||||
| LATEX_CMD_NAME         = latex | ||||
| MAKEINDEX_CMD_NAME     = makeindex | ||||
| COMPACT_LATEX          = NO | ||||
| PAPER_TYPE             = a4wide | ||||
| EXTRA_PACKAGES         =  | ||||
| LATEX_HEADER           =  | ||||
| PDF_HYPERLINKS         = YES | ||||
| USE_PDFLATEX           = YES | ||||
| LATEX_BATCHMODE        = NO | ||||
| LATEX_HIDE_INDICES     = NO | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the RTF output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_RTF           = NO | ||||
| RTF_OUTPUT             = rtf | ||||
| COMPACT_RTF            = NO | ||||
| RTF_HYPERLINKS         = NO | ||||
| RTF_STYLESHEET_FILE    =  | ||||
| RTF_EXTENSIONS_FILE    =  | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the man page output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_MAN           = YES | ||||
| MAN_OUTPUT             = man | ||||
| MAN_EXTENSION          = .3 | ||||
| MAN_LINKS              = NO | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the XML output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_XML           = NO | ||||
| XML_OUTPUT             = xml | ||||
| XML_SCHEMA             =  | ||||
| XML_DTD                =  | ||||
| XML_PROGRAMLISTING     = YES | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options for the AutoGen Definitions output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_AUTOGEN_DEF   = NO | ||||
| #--------------------------------------------------------------------------- | ||||
| # configuration options related to the Perl module output | ||||
| #--------------------------------------------------------------------------- | ||||
| GENERATE_PERLMOD       = NO | ||||
| PERLMOD_LATEX          = NO | ||||
| PERLMOD_PRETTY         = YES | ||||
| PERLMOD_MAKEVAR_PREFIX =  | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration options related to the preprocessor    | ||||
| #--------------------------------------------------------------------------- | ||||
| ENABLE_PREPROCESSING   = YES | ||||
| MACRO_EXPANSION        = NO | ||||
| EXPAND_ONLY_PREDEF     = NO | ||||
| SEARCH_INCLUDES        = YES | ||||
| INCLUDE_PATH           =  | ||||
| INCLUDE_FILE_PATTERNS  =  | ||||
| PREDEFINED             =  | ||||
| EXPAND_AS_DEFINED      =  | ||||
| SKIP_FUNCTION_MACROS   = YES | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration::additions related to external references    | ||||
| #--------------------------------------------------------------------------- | ||||
| TAGFILES               =  | ||||
| GENERATE_TAGFILE       =  | ||||
| ALLEXTERNALS           = NO | ||||
| EXTERNAL_GROUPS        = YES | ||||
| PERL_PATH              = /usr/bin/perl | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration options related to the dot tool    | ||||
| #--------------------------------------------------------------------------- | ||||
| CLASS_DIAGRAMS         = NO | ||||
| MSCGEN_PATH            =  | ||||
| HIDE_UNDOC_RELATIONS   = YES | ||||
| HAVE_DOT               = YES | ||||
| CLASS_GRAPH            = YES | ||||
| COLLABORATION_GRAPH    = YES | ||||
| GROUP_GRAPHS           = YES | ||||
| UML_LOOK               = NO | ||||
| TEMPLATE_RELATIONS     = NO | ||||
| INCLUDE_GRAPH          = NO | ||||
| INCLUDED_BY_GRAPH      = NO | ||||
| CALL_GRAPH             = NO | ||||
| CALLER_GRAPH           = NO | ||||
| GRAPHICAL_HIERARCHY    = YES | ||||
| DIRECTORY_GRAPH        = YES | ||||
| DOT_IMAGE_FORMAT       = png | ||||
| DOT_PATH               =  | ||||
| DOTFILE_DIRS           =  | ||||
| DOT_GRAPH_MAX_NODES    = 50 | ||||
| MAX_DOT_GRAPH_DEPTH    = 1000 | ||||
| DOT_TRANSPARENT        = NO | ||||
| DOT_MULTI_TARGETS      = NO | ||||
| GENERATE_LEGEND        = YES | ||||
| DOT_CLEANUP            = YES | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration::additions related to the search engine    | ||||
| #--------------------------------------------------------------------------- | ||||
| SEARCHENGINE           = NO | ||||
| @ -1,5 +0,0 @@ | ||||
| To compile: | ||||
| 
 | ||||
| ./configure | ||||
| make | ||||
| 
 | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user