Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/HTML5/HTML5Platform.Automation.cs
Chris Babcock dc647b9547 Copying //UE4/Dev-Mobile to //UE4/Main (Source: //UE4/Dev-Mobile @ 3600060)
#rb none
#lockdown nick.penwarden

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3292215 on 2017/02/08 by Nick.Shin

	HTML5 emscripten: wasm and wbegl2 support

	- emscripten toolchain

	#jira UEPLAT-1437  Switch [to] web assembly
	#rb none

Change 3293994 on 2017/02/09 by Nick.Shin

	HTML5 emscripten: wasm and webgl2 support

	- OSX toolchain

	#jira UEPLAT-1437  Switch [to] web assembly
	#rb none

Change 3317951 on 2017/02/22 by Nick.Shin

	HTML5 emscripten: wasm & webgl2 support - RC1

	- emscripten toolchain

	WARNING: emscripten/incoming/source/include/libc/bit (the file) might need to be deleted first

	#jira UEMOB-263  Switch [to] web assembly
	#jira UEMOB-201  Support ES3 / WebGL2 in HTML5
	#rb none

Change 3318669 on 2017/02/23 by Nick.Shin

	HTML5 emscripten: wasm & webgl2 support - RC1

	- OSX toolchain

	#jira UEMOB-263  Switch [to] web assembly
	#jira UEMOB-201  Support ES3 / WebGL2 in HTML5
	#rb none

Change 3462146 on 2017/05/26 by Nick.Shin

	HTML5 - merge from Release-4.16 to Dev-Mobile

	#jira none
	#rb none
	#rnx

Change 3504996 on 2017/06/22 by Cosmin.Sulea

	UEMOB-362 - Add per-texture and per-format compression quality override settings
	#rb Dmitriy.Dyomin
	#jira UEMOB-362
	#codereview Dmitriy.Dyomin
	#codereview Jack.Porter

Change 3505056 on 2017/06/22 by Cosmin.Sulea

	Back out changelist 3504996 - due to errors generated in xboxOne, PS4 and Switch versions
	#rb none

Change 3508049 on 2017/06/23 by Nick.Shin

	HTML5 toolchain notes corrections

	#jira none
	#rb none
	#rnx

Change 3508663 on 2017/06/24 by Nick.Shin

	HTML5LaunchHelper.exe on linux - redo

	- it seems that i need to also check-in the exe and pdb file instead of having CIS make and checking-in them itself...
	- modified c# program to output a version number to help track which version of HTML5LaunchHelper is running...

	#jira UE-45302  HTML5LaunchHelper.exe hosts the files in the current working directory on Linux
	#rnx
	#rb none

Change 3509210 on 2017/06/26 by Dmitriy.Dyomin

	 ExposureScale will be applied during tonemap pass when MobileHDR is on
	#rb jack.porter
	#codereview Allan.Bentham

Change 3511058 on 2017/06/27 by Cosmin.Sulea

	UEMOB-362 - Add per-texture and per-format compression quality override settings - resubmitted
	#rb Dmitriy.Dyomin
	#jira UEMOB-362
	#codereview Dmitriy.Dyomin

Change 3511069 on 2017/06/27 by Jack.Porter

	PS4, XboxOne and Switch fixes for changes to ITextureFormat interface
	#rb Dmitriy.Dyomin
	#jira UEMOB-362

Change 3513028 on 2017/06/28 by Jack.Porter

	Merging //UE4/Dev-Main to Dev-Mobile (//UE4/Dev-Mobile)
	#rb None

Change 3517409 on 2017/06/30 by Jack.Porter

	Merging //UE4/Dev-Main to Dev-Mobile (//UE4/Dev-Mobile)
	#rb None

Change 3517730 on 2017/06/30 by Cosmin.Sulea

	UEMOB-328 - Improve handling of iOS signing key on remote Mac system keychain when using remote toolchain
	#rb Jack.Porter
	#jira UEMOB-328
	#codereview: peter.sauerbrei

Change 3517757 on 2017/06/30 by Cosmin.Sulea

	UE-46245 - Building with remote toolchain does not use Project Setting for iOS signing identity which can cause signing errors
	#rb Jack.Porter
	#jira UE-46245
	#codereview: peter.sauerbrei

Change 3518149 on 2017/06/30 by Adrian.Chelu

	UE-43035 Tilt axis for X and Z are not consistent between Android and iOS devices
	#rb Jack.Porter
	#jira UE-46245
	#codereview: Chris Babcock <chris.babcock@epicgames.com>

Change 3524242 on 2017/07/06 by Nick.Shin

	HTML5 - refraction shader

	note: this CL also contains fixes to webgl2 [float4 vs half2] and a [% vs Mod()] material custom function changes to some TM-ShaderModels shaders
	specifically: fixes to and similar with: DitherTemporalAA

	#jria UE-46434  No Refraction in QA Game TM-Shadermodels HTML5
	#rb none
	#rn
	#codereview jack.porter dmitriy.dyomin

Change 3535295 on 2017/07/13 by Allan.Bentham

	#jira UEMOB-390
	Add Android cpu stats.
	add 'stat AndroidCPU' to android's console spinner UI.
	increase GetCPUState's core count support to 16.
	#jira UE-45888
	Use cvar value to limit android cpu stat update rate.
	#rb none

Change 3535306 on 2017/07/13 by Allan.Bentham

	Add missing pragma once
	#rb none

Change 3537047 on 2017/07/13 by Ben.Marsh

	Fixing case of iOS directories, pt1

	#rb none

Change 3537051 on 2017/07/13 by Ben.Marsh

	Fixing case of iOS directories, pt2

	#rb none

Change 3537373 on 2017/07/14 by Allan.Bentham

	Add scope level android egl error verification.
	work around minor issue with invalid egl config property.
	#rb chris.babcock

Change 3541735 on 2017/07/18 by Allan.Bentham

	Add 'sustained performance mode' support for API 24+ devices.
	#jira UEMOB-386
	#rb chris.babcock

Change 3543001 on 2017/07/18 by Sorin.Gradinaru

	#jira UE-45766 Improved Virtual Keyboard cannot receive non-English characters.

	- for Android, add an native EditBox above the virtual keyboard to receive the text and pass it to the object from the slate

	#rb Chris.Babcock

Change 3554399 on 2017/07/25 by Nick.Shin

	STATS disabled for non multi-threaded platforms

	#jira UE-47485  ( Pri:1 - 4.18 )  Crash running Stat Command test in TM-Core on Firefox
	#rnx
	#rb none

Change 3554402 on 2017/07/25 by Nick.Shin

	STATS TaskGraph disabled for non multi-threaded platforms

	#jira UE-47486  ( Pri:1 - 4.18 )  QAGame hard locks on Firefox when triggering Task Graph Benchmark test
	#rb none
	#rnx

Change 3556957 on 2017/07/26 by Nick.Shin

	HTML5 - WASM enabled by default - part 1 -- commenting out asmjs stuff

	begin sunsetting ASM.JS

	note to self: CL#3462146 "backout" asmjs

	#jira UEMOB-416  WASM enabled by default
	#rnx
	#rb none

Change 3557654 on 2017/07/26 by Nick.Shin

	HTML5 - WASM enabled by default - part 2 -- remove asmjs code

	sunsetting ASM.JS

	note to self: CL#3462146 "backout" asmjs

	#jira UEMOB-416  WASM enabled by default
	#rn
	#rb none

Change 3557910 on 2017/07/27 by Jack.Porter

	Support Client configuration when packaging in the editor
	#jira UE-39973
	#rb Dmitriy.Dyomin

Change 3557917 on 2017/07/27 by Jack.Porter

	Missing file from CL 3557910
	#rb trivial

Change 3559642 on 2017/07/27 by Nick.Shin

	STATS TaskGraph disabled for non multi-threaded platforms

	- both "LockFree stress test" and "task graph benchmark" are disabled - no multi-threading for WASM exist yet (note: ASM.JS has been sunsetted)

	- stat command crash "fixed" - but, font size are totally broken - i can look at this (much) later...

	- new bug: physx will crash on "gc and level load stress test" -- please bug this as a new jira

	#jira UE-47486  ( Pri:1 - 4.18 )  QAGame hard locks on Firefox when triggering Task Graph Benchmark test
	#rb none
	#rnx

Change 3565656 on 2017/07/31 by Dmitriy.Dyomin

	Added a way to lock level position in Word Composition
	#jira UE-47713
	#rb none

Change 3565757 on 2017/08/01 by Dmitriy.Dyomin

	compile fix
	#rb none

Change 3567446 on 2017/08/01 by Chris.Babcock

	Allow addElement and addElements to only insert once with once="true" attribute in UPL
	#jira UE-47951
	#ue4
	#android
	#rb Peter.Sauerbrei

Change 3567592 on 2017/08/01 by Chris.Babcock

	Use absolute path for repositories for Gradle
	#jira UE-47952
	#ue4
	#android
	#rb Tim.Lincoln

Change 3568690 on 2017/08/02 by Chris.Babcock

	Removed warnings for once attribute in UPL
	#ue4
	#android
	#rb none

Change 3569975 on 2017/08/02 by Chris.Babcock

	Add <baseBuildGradleAdditions> to UPL to allow additions to the root-level build.gradle
	#jira UE-47995
	#ue4
	#android
	#rb Tim.Lincoln

Change 3570117 on 2017/08/02 by Chris.Babcock

	Add <setBoolFromPropertyContains> to UPL
	- sets bool to true if string list in ini matches contains attribute
	#jira UE-47996
	#ue4
	#android
	#rb Jack.Porter

Change 3571552 on 2017/08/03 by Chris.Babcock

	Removed unneeded settings.gradle file (generated)
	#jira UE-48041
	#ue4
	#android
	#rb none

Change 3572224 on 2017/08/04 by Dmitriy.Dyomin

	Better selection tracking in world composition
	#rb none

Change 3573662 on 2017/08/04 by Nick.Shin

	HTML5 remove PreLoadMap "feature" (was only available/used with HTML5)

	- asyncronous loads are not allowed during UEngine::LoadMap()
	- the files/code will be repurposed for pakfile CHUNK support

	#jira UEMOB-425  HTML5 streaming content investigation (part 1 of 2)
	#rn
	#rb none

Change 3574471 on 2017/08/07 by Dmitriy.Dyomin

	Export ULevelStreamingKismet::LoadLevelInstance function
	#rb none

Change 3576262 on 2017/08/08 by Dmitriy.Dyomin

	Fixed: widget clipping issues in world composition
	#rb none

Change 3576845 on 2017/08/08 by Nick.Shin

	set HTML5LaunchHelper application's icon to UE4.ico

	#jira UE-19225 HTML5LaunchHelper application does not have an unreal icon
	#rb none
	#rnx

Change 3578313 on 2017/08/09 by Dmitriy.Dyomin

	Added: an RHI call to invalidate/clear cached state, RHIInvalidateCachedState
	#jira UEMOB-435
	#rb jack.porter

Change 3578364 on 2017/08/09 by Dmitriy.Dyomin

	Vertex Fog is disabled on mobile by default. If scene uses vertex fog - Mobile preview and device will show on screen message: PROJECT HAS VERTEX FOG ON MOBILE DISABLED
	This saves about 90 instructions in VS and a few in PS
	#jira UEMOB-166
	#rb jack.porter

Change 3578703 on 2017/08/09 by Nick.Shin

	set HTML5LaunchHelper application's icon to UE4.ico

	forgot to check in exe and pdb file

	#jira UE-19225 HTML5LaunchHelper application does not have an unreal icon
	#rb none
	#rnx

Change 3578961 on 2017/08/09 by Peter.Sauerbrei

	deprecate IOS 8 as the minimum OS supported.
	#jira UEMOB-429
	#rb chris.babcock

Change 3579319 on 2017/08/09 by Peter.Sauerbrei

	fixes for compile errors with Xcode 9 beta 4
	#rb none

Change 3579356 on 2017/08/09 by Peter.Sauerbrei

	modified minimum IOS to build with
	#rb chris.babcock

Change 3579687 on 2017/08/09 by Chris.Babcock

	Fix GoogleVR Gradle packaging
	#jira UE-48239
	#ue4
	#android
	#rb none

Change 3579921 on 2017/08/10 by Dmitriy.Dyomin

	GitHub 3670 : More zoom levels for World Composition (300)
	#contributedby: user37337
	#jira UE-45977
	#3670
	#rb none

Change 3580576 on 2017/08/10 by Peter.Sauerbrei

	detection of iPad Pro 10.5 and IPad Pro 12.9 (2nd Gen)
	#rb chris.babcock

Change 3580611 on 2017/08/10 by Chris.Babcock

	Set online provider back to GooglePlay and remove forcing IAP permission (contributed by umerov1999)
	#jira UE-48185
	#PR #3876
	#ue4
	#android
	#rb Peter.Sauerbrei

Change 3582166 on 2017/08/11 by Nick.Shin

	nuke PLATFORM_HTML5_WIN32


	PLATFORM_HTML5_WIN32 code removal tested successfully with (force rebuild and repackaging):

	* Win64 server (WindowsServer)
	* Win64 client (WindowsNoEditor)
	* HTML5 client

	all playing together via websocket net driver (i've attached a screen shot of this in jira)


	code changes touches: physics, audio and main build files


	#jira UEMOB-433  Remove Win32 SDL "HTML5 Simulator" code
	#rb ben.marsh
	#rnx
	#codereview josh.adams
	#fyi ori.cohen, aaron.mclera

Change 3582474 on 2017/08/11 by Chris.Babcock

	Don't use V2 signing for Gear VR APKs
	#jira UE-48354
	#ue4
	#android
	#rb Peter.Sauerbrei

Change 3582614 on 2017/08/11 by Chris.Babcock

	Filter out unneeded architectures from APK for Gradle builds
	#jira UE-48355
	#ue4
	#android
	#rb Peter.Sauerbrei

Change 3582923 on 2017/08/11 by Nick.Shin

	backport release 4.17 to dev-mobile

	#jira none
	#rb none
	#rnx

Change 3582924 on 2017/08/11 by Nick.Shin

	FNetworkFileServerHttp - error gracefully when port is already in use

	#jira UE-46409  [CrashReport] Assertion on Mac: Could not create a libwebsocket - FNetworkFileServerHttp::Init()
	#rnx
	#rb none

Change 3582925 on 2017/08/11 by Nick.Shin

	HTML5 - turn off pak file compression in favor of gzip packages

	#jira UE-46729  HTML5 - on shipping builds - turn off pak file compression in favor of gzip packages
	#rn
	#rb none

Change 3583943 on 2017/08/14 by Cosmin.Sulea

	UEMOB-363 - second iteration - Project wide texture quality control by texture group
	#rb Dmitriy Dyomin
	#jira UEMOB-363

Change 3583967 on 2017/08/14 by Cosmin.Sulea

	Back out changelist 3583943
	#rb none

Change 3584121 on 2017/08/14 by Peter.Sauerbrei

	fix for mac compile failure
	#rb none

Change 3587877 on 2017/08/15 by Peter.Sauerbrei

	josh's suggested fix is not working for Xcode 8.3, so brute forcing for now
	#rb none

Change 3588612 on 2017/08/15 by Peter.Sauerbrei

	Xcode 9 project compatbility updates
	#rb chris.babcock
	#codereview michael.trepka

Change 3589223 on 2017/08/15 by Dmitriy.Dyomin

	Fixed: bNavigationAutoUpdateEnabled was not always working when reopeinig the map
	Fixed: Navigation Build was not clearing some mesh tiles when bNavigationAutoUpdateEnabled is enabled
	Fixed: Streaming out a level in editor was not always updating NavMesh debug draw
	#rb lukasz.furman

Change 3589900 on 2017/08/16 by Dmitriy.Dyomin

	Support vulkan validation layers on Android, only in Debug and Development configuration (requires r.Vulkan.EnableValidation=1)
	#codereview chris.babcock, rolando.caloca
	#rb none

Change 3590592 on 2017/08/16 by Nick.Shin

	HTML5 emscripten 1.37.19 OSX

	#jira UE-47813
	#rb none
	#rn HTML5 emscripten 1.37.19 OSX

Change 3590597 on 2017/08/16 by Nick.Shin

	HTML5 emscripten 1.37.19 Linux

	#jira UE-47813
	#rb none
	#rn HTML5 emscripten 1.37.19 Linux

Change 3590624 on 2017/08/16 by Nick.Shin

	HTML5 emscripten 1.37.19 toolchain

	#jira UE-47813
	#rb none
	#rn HTML5 emscripten 1.37.19 toolchain

Change 3591720 on 2017/08/16 by Chris.Babcock

	Enable Gradle by default and add button to accept Android SDK license to project settings
	#jira UE-48519
	#ue4
	#android
	#rb Tim.Lincoln
	#fyi Peter.Sauerbrei

Change 3591998 on 2017/08/16 by Chris.Babcock

	Fix nonunity build
	#ue4
	#android
	#rb none

Change 3592407 on 2017/08/17 by Nick.Shin

	HTML5 emscripten 1.37.19 Win64

	#jira UE-47813
	#rb none
	#rn HTML5 emscripten 1.37.19 Win64

Change 3592479 on 2017/08/17 by Nick.Shin

	HTML5 3rd Party Libs - compiled with emscripten 1.37.19

	#jira UE-47813
	#rb none
	#rn HTML5 3rd Party Libs - compiled with emscripten 1.37.19 toolchain

Change 3592480 on 2017/08/17 by Nick.Shin

	HTML5 emscripten 1.37.19 toolchain Epic edits

	as well as setting UE4 HTML c# scripts to use new toolchain

	#jira UE-47813
	#rb none
	#rn HTML5 emscripten 1.37.19 toolchain Epic edits

Change 3592481 on 2017/08/17 by Nick.Shin

	HTML5 remove old emscripten toolchain

	#jira UE-47813
	#rb none
	#rn HTML5 remove old emscripten toolchain

Change 3592485 on 2017/08/17 by Nick.Shin

	HTML5 undo CanUseXGE - this might be breaking CIS for HTML5 builds...

	#jira UE-47813
	#rb none
	#rnx

Change 3592549 on 2017/08/17 by Dmitriy.Dyomin

	Added GetDiskTotalAndFreeSpace for IOS and Android
	#jira UE-46479
	#codereview chris.babcock, peter.sauerbrei
	#rb none

Change 3594045 on 2017/08/17 by Peter.Sauerbrei

	comment about potential failure case in the remote tool chain
	#rb none

Change 3594342 on 2017/08/17 by Peter.Sauerbrei

	Merging

	//UE4/Main/...

	to //UE4/Dev-Mobile/...

	#rb none

Change 3594920 on 2017/08/17 by Peter.Sauerbrei

	fix for non-unity builds (accidentally merged something incorrectly)
	#rb none

Change 3595347 on 2017/08/17 by Chris.Babcock

	merge fixes for Android
	#ue4
	#android
	#rb Peter.Sauerbrei
	#lockdown Peter.Sauerbrei

Change 3595752 on 2017/08/17 by Chris.Babcock

	Update Facebook plugin to support Gradle
	#jira UE-48569
	#ue4
	#android
	#fyi Josh.Markiewicz
	#rb none
	#lockdown Peter.Sauerbrei

Change 3595849 on 2017/08/17 by Chris.Babcock

	Fix issue with libovrplatformloader.so for non armv7 targets
	#jira UE-48533
	#ue4
	#android
	#rb none
	#lockdown Peter.Sauerbrei

Change 3596419 on 2017/08/18 by Peter.Sauerbrei

	fix for Mac Editor build failure
	#rb none

Change 3597023 on 2017/08/18 by Peter.Sauerbrei

	fix for game editor build failure
	#rb none

Change 3597032 on 2017/08/18 by Peter.Sauerbrei

	fix for app bundle id in Info-Editor.plist
	#rb none

Change 3597034 on 2017/08/18 by Peter.Sauerbrei

	put back the info.plist, found the real problem
	#rb none

Change 3597197 on 2017/08/18 by Peter.Sauerbrei

	pull Info.plist from the build products
	#rb none

[CL 3600450 by Chris Babcock in Main branch]
2017-08-21 15:05:19 -04:00

801 lines
30 KiB
C#

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using AutomationTool;
using UnrealBuildTool;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
public class HTML5Platform : Platform
{
// ini configurations
static bool Compressed = false;
static bool targetWebGL2 = true;
static bool enableIndexedDB = false; // experimental for now...
public HTML5Platform()
: base(UnrealTargetPlatform.HTML5)
{
}
public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL)
{
Log("Package {0}", Params.RawProjectPath);
Log("Setting Emscripten SDK for packaging..");
HTML5SDKInfo.SetupEmscriptenTemp();
HTML5SDKInfo.SetUpEmscriptenConfigFile();
string PackagePath = Path.Combine(Path.GetDirectoryName(Params.RawProjectPath.FullName), "Binaries", "HTML5");
if (!Directory.Exists(PackagePath))
{
Directory.CreateDirectory(PackagePath);
}
// ini configurations
var ConfigCache = UnrealBuildTool.ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(Params.RawProjectPath), UnrealTargetPlatform.HTML5);
bool targetWebGL1 = false; // inverted checked - this will be going away soon...
if ( ConfigCache.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "TargetWebGL1", out targetWebGL1) )
{
targetWebGL2 = !targetWebGL1;
}
// Debug and Development builds are not uncompressed to:
// - speed up iteration times
// - ensure (IndexedDB) data are not cached/used
// Shipping builds "can be":
// - compressed
// - (IndexedDB) cached
if (Params.ClientConfigsToBuild[0].ToString() == "Shipping")
{
ConfigCache.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "Compressed", out Compressed);
ConfigCache.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "EnableIndexedDB", out enableIndexedDB);
}
Log("HTML5Platform.Automation: TargetWebGL2 = " + targetWebGL2 );
Log("HTML5Platform.Automation: Compressed = " + Compressed );
Log("HTML5Platform.Automation: EnableIndexedDB = " + enableIndexedDB );
string FinalDataLocation = Path.Combine(PackagePath, Params.ShortProjectName) + ".data";
if (HTMLPakAutomation.CanCreateMapPaks(Params))
{
HTMLPakAutomation PakAutomation = new HTMLPakAutomation(Params, SC);
// Create Necessary Paks.
PakAutomation.CreateEnginePak();
PakAutomation.CreateGamePak();
PakAutomation.CreateContentDirectoryPak();
// Create Emscripten Package from Necessary Paks. - This will be the VFS.
PakAutomation.CreateEmscriptenDataPackage(PackagePath, FinalDataLocation);
// Create All Map Paks which will be downloaded on the fly.
PakAutomation.CreateMapPak();
// Create Delta Paks if setup.
List<string> Paks = new List<string>();
ConfigCache.GetArray("/Script/HTML5PlatformEditor.HTML5TargetSettings", "LevelTransitions", out Paks);
if (Paks != null)
{
foreach (var Pak in Paks)
{
var Matched = Regex.Matches(Pak, "\"[^\"]+\"", RegexOptions.IgnoreCase);
string MapFrom = Path.GetFileNameWithoutExtension(Matched[0].ToString().Replace("\"", ""));
string MapTo = Path.GetFileNameWithoutExtension(Matched[1].ToString().Replace("\"", ""));
PakAutomation.CreateDeltaMapPaks(MapFrom, MapTo);
}
}
}
else
{
// we need to operate in the root
string PythonPath = HTML5SDKInfo.Python();
string PackagerPath = HTML5SDKInfo.EmscriptenPackager();
using (new ScopedEnvVar("EM_CONFIG", HTML5SDKInfo.DOT_EMSCRIPTEN))
{
using (new PushedDirectory(Path.Combine(Params.BaseStageDirectory, "HTML5")))
{
string CmdLine = string.Format("\"{0}\" \"{1}\" --preload . --js-output=\"{1}.js\" --no-heap-copy", PackagerPath, FinalDataLocation);
RunAndLog(CmdEnv, PythonPath, CmdLine);
}
}
}
// copy the "Executable" to the package directory
string GameBasename = Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename);
if (Params.ClientConfigsToBuild[0].ToString() != "Development")
{
GameBasename += "-HTML5-" + Params.ClientConfigsToBuild[0].ToString();
}
// no extension
string GameBasepath = Path.GetDirectoryName(Params.ProjectGameExeFilename);
string FullGameBasePath = Path.Combine(GameBasepath, GameBasename);
string FullPackageGameBasePath = Path.Combine(PackagePath, GameBasename);
// with extension
string GameExe = GameBasename + ".js";
string FullGameExePath = Path.Combine(GameBasepath, GameExe);
string FullPackageGameExePath = Path.Combine(PackagePath, GameExe);
// ensure the ue4game binary exists, if applicable
if (!SC.IsCodeBasedProject && !FileExists_NoExceptions(FullGameExePath))
{
Log("Failed to find game application " + FullGameExePath);
throw new AutomationException(ExitCode.Error_MissingExecutable, "Stage Failed. Could not find application {0}. You may need to build the UE4 project with your target configuration and platform.", FullGameExePath);
}
if (FullGameExePath != FullPackageGameExePath) // TODO: remove this check
{
File.Copy(FullGameExePath + ".symbols", FullPackageGameExePath + ".symbols", true);
File.Copy(FullGameBasePath + ".wasm", FullPackageGameBasePath + ".wasm", true);
File.Copy(FullGameExePath, FullPackageGameExePath, true);
}
File.SetAttributes(FullPackageGameExePath + ".symbols", FileAttributes.Normal);
File.SetAttributes(FullPackageGameBasePath + ".wasm", FileAttributes.Normal);
File.SetAttributes(FullPackageGameExePath, FileAttributes.Normal);
// put the HTML file to the package directory
string TemplateFile = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5/GameX.html.template");
string OutputFile = Path.Combine(PackagePath,
(Params.ClientConfigsToBuild[0].ToString() != "Development" ?
(Params.ShortProjectName + "-HTML5-" + Params.ClientConfigsToBuild[0].ToString()) :
Params.ShortProjectName)) + ".html";
GenerateFileFromTemplate(TemplateFile,
OutputFile,
Params.ShortProjectName,
Params.ClientConfigsToBuild[0].ToString(),
Params.StageCommandline,
!Params.IsCodeBasedProject,
HTML5SDKInfo.HeapSize(ConfigCache, Params.ClientConfigsToBuild[0].ToString()));
string MacBashTemplateFile = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5/RunMacHTML5LaunchHelper.command.template");
string MacBashOutputFile = Path.Combine(PackagePath, "RunMacHTML5LaunchHelper.command");
string MonoPath = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/BatchFiles/Mac/SetupMono.sh");
GenerateMacCommandFromTemplate(MacBashTemplateFile, MacBashOutputFile, MonoPath);
string htaccessTemplate = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5/htaccess.template");
string htaccesspath = Path.Combine(PackagePath, ".htaccess");
if ( File.Exists(htaccesspath) )
{
FileAttributes attributes = File.GetAttributes(htaccesspath);
if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
attributes &= ~FileAttributes.ReadOnly;
File.SetAttributes(htaccesspath, attributes);
}
}
File.Copy(htaccessTemplate, htaccesspath, true);
string JSDir = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5");
string OutDir = PackagePath;
// Gather utlity .js files and combine into one file
string[] UtilityJavaScriptFiles = Directory.GetFiles(JSDir, "*.js");
string DestinationFile = OutDir + "/Utility.js";
File.Delete(DestinationFile);
foreach( var UtilityFile in UtilityJavaScriptFiles)
{
string Data = File.ReadAllText(UtilityFile);
File.AppendAllText(DestinationFile, Data);
}
if (Compressed)
{
Log("Build configuration is " + Params.ClientConfigsToBuild[0].ToString() + ", so (gzip) compressing files for web servers.");
// Compress all files. These are independent tasks which can be threaded.
List<Task> CompressionTasks = new List<Task>();
// data file
CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FinalDataLocation, FinalDataLocation + "gz")));
// data file .js driver.
CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FinalDataLocation + ".js" , FinalDataLocation + ".jsgz")));
// main game code
CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameBasePath + ".wasm", FullPackageGameBasePath + ".wasmgz")));
// main js.
CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameExePath, FullPackageGameExePath + "gz")));
// symbols file.
CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameExePath + ".symbols", FullPackageGameExePath + ".symbolsgz")));
// Utility
CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(OutDir + "/Utility.js", OutDir + "/Utility.jsgz")));
Task.WaitAll(CompressionTasks.ToArray());
}
else
{
Log("Build configuration is " + Params.ClientConfigsToBuild[0].ToString() + ", so not compressing. Build Shipping configuration to compress files to save space.");
// nuke old compressed files to prevent using stale files
File.Delete(FinalDataLocation + "gz");
File.Delete(FinalDataLocation + ".jsgz");
File.Delete(FullPackageGameExePath + "gz");
File.Delete(FullPackageGameBasePath + ".wasmgz");
File.Delete(FullPackageGameExePath + ".symbolsgz");
File.Delete(OutDir + "/Utility.jsgz");
}
File.Copy(CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/HTML5LaunchHelper.exe"),CombinePaths(OutDir, "HTML5LaunchHelper.exe"),true);
// Task.WaitAll(CompressionTasks);
PrintRunTime();
}
void CompressFile(string Source, string Destination)
{
Log(" Compressing " + Source);
bool DeleteSource = false;
if( Source == Destination )
{
string CopyOrig = Source + ".Copy";
File.Copy(Source, CopyOrig);
Source = CopyOrig;
DeleteSource = true;
}
using (System.IO.Stream input = System.IO.File.OpenRead(Source))
{
using (var raw = System.IO.File.Create(Destination))
{
using (Stream compressor = new Ionic.Zlib.GZipStream(raw, Ionic.Zlib.CompressionMode.Compress,Ionic.Zlib.CompressionLevel.BestCompression))
{
byte[] buffer = new byte[2048];
int SizeRead = 0;
while ((SizeRead = input.Read(buffer, 0, buffer.Length)) != 0)
{
compressor.Write(buffer, 0, SizeRead);
}
}
}
}
if (DeleteSource)
{
File.Delete(Source);
}
}
public override bool RequiresPackageToDeploy
{
get { return true; }
}
protected void GenerateFileFromTemplate(string InTemplateFile, string InOutputFile, string InGameName, string InGameConfiguration, string InArguments, bool IsContentOnly, int HeapSize)
{
StringBuilder outputContents = new StringBuilder();
using (StreamReader reader = new StreamReader(InTemplateFile))
{
string LineStr = null;
while (reader.Peek() != -1)
{
LineStr = reader.ReadLine();
if (LineStr.Contains("%TIMESTAMP%"))
{
string TimeStamp = DateTime.UtcNow.ToString("yyyyMMddHHmm");
LineStr = LineStr.Replace("%TIMESTAMP%", TimeStamp);
}
if (LineStr.Contains("%GAME%"))
{
LineStr = LineStr.Replace("%GAME%", InGameName);
}
if (LineStr.Contains("%SERVE_COMPRESSED%"))
{
LineStr = LineStr.Replace("%SERVE_COMPRESSED%", Compressed ? "true" : "false");
}
if (LineStr.Contains("%DISABLE_INDEXEDDB%"))
{
LineStr = LineStr.Replace("%DISABLE_INDEXEDDB%",
enableIndexedDB ? "" : "enableReadFromIndexedDB = false;\nenableWriteToIndexedDB = false;");
}
if (LineStr.Contains("%HEAPSIZE%"))
{
LineStr = LineStr.Replace("%HEAPSIZE%", HeapSize.ToString() + " * 1024 * 1024");
}
if (LineStr.Contains("%CONFIG%"))
{
string TempGameName = InGameName;
if (IsContentOnly)
TempGameName = "UE4Game";
LineStr = LineStr.Replace("%CONFIG%", (InGameConfiguration != "Development" ? (TempGameName + "-HTML5-" + InGameConfiguration) : TempGameName));
}
if (LineStr.Contains("%UE4CMDLINE%"))
{
InArguments = InArguments.Replace ("\"", "");
string[] Arguments = InArguments.Split(' ');
string ArgumentString = IsContentOnly ? "'../../../" + InGameName + "/" + InGameName + ".uproject '," : "";
for (int i = 0; i < Arguments.Length - 1; ++i)
{
ArgumentString += "'" + Arguments[i] + "'" + ",' ',";
}
if (Arguments.Length > 0)
{
ArgumentString += "'" + Arguments[Arguments.Length - 1] + "'";
}
LineStr = LineStr.Replace("%UE4CMDLINE%", ArgumentString);
}
if (!targetWebGL2 && LineStr.Contains("const explicitlyUseWebGL1"))
{
LineStr = "const explicitlyUseWebGL1 = true;";
}
outputContents.AppendLine(LineStr);
}
}
if (outputContents.Length > 0)
{
// Save the file
try
{
Directory.CreateDirectory(Path.GetDirectoryName(InOutputFile));
File.WriteAllText(InOutputFile, outputContents.ToString(), Encoding.UTF8);
}
catch (Exception)
{
// Unable to write to the project file.
}
}
}
protected void GenerateMacCommandFromTemplate(string InTemplateFile, string InOutputFile, string InMonoPath)
{
StringBuilder outputContents = new StringBuilder();
using (StreamReader reader = new StreamReader(InTemplateFile))
{
string InMonoPathParent = Path.GetDirectoryName(InMonoPath);
string LineStr = null;
while (reader.Peek() != -1)
{
LineStr = reader.ReadLine();
if (LineStr.Contains("${unreal_mono_pkg_path}"))
{
LineStr = LineStr.Replace("${unreal_mono_pkg_path}", InMonoPath);
}
if (LineStr.Contains("${unreal_mono_pkg_path_base}"))
{
LineStr = LineStr.Replace("${unreal_mono_pkg_path_base}", InMonoPathParent);
}
outputContents.Append(LineStr + '\n');
}
}
if (outputContents.Length > 0)
{
// Save the file. We Copy the template file to keep any permissions set to it.
try
{
Directory.CreateDirectory(Path.GetDirectoryName(InOutputFile));
if (File.Exists(InOutputFile))
{
File.SetAttributes(InOutputFile, File.GetAttributes(InOutputFile) & ~FileAttributes.ReadOnly);
File.Delete(InOutputFile);
}
File.Copy(InTemplateFile, InOutputFile);
File.SetAttributes(InOutputFile, File.GetAttributes(InOutputFile) & ~FileAttributes.ReadOnly);
using (var CmdFile = File.Open(InOutputFile, FileMode.OpenOrCreate | FileMode.Truncate))
{
Byte[] BytesToWrite = new UTF8Encoding(true).GetBytes(outputContents.ToString());
CmdFile.Write(BytesToWrite, 0, BytesToWrite.Length);
}
}
catch (Exception)
{
// Unable to write to the project file.
}
}
}
public override void GetFilesToDeployOrStage(ProjectParams Params, DeploymentContext SC)
{
}
public override void GetFilesToArchive(ProjectParams Params, DeploymentContext SC)
{
if (SC.StageTargetConfigurations.Count != 1)
{
throw new AutomationException("iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations", SC.StageTargetConfigurations.Count);
}
string PackagePath = Path.Combine(Path.GetDirectoryName(Params.RawProjectPath.FullName), "Binaries", "HTML5");
string ProjectDataName = Params.ShortProjectName + ".data";
// copy the "Executable" to the archive directory
string GameBasename = Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename);
if (Params.ClientConfigsToBuild[0].ToString() != "Development")
{
GameBasename += "-HTML5-" + Params.ClientConfigsToBuild[0].ToString();
}
string GameExe = GameBasename + ".js";
// put the HTML file to the package directory
string OutputFilename = (Params.ClientConfigsToBuild[0].ToString() != "Development" ?
(Params.ShortProjectName + "-HTML5-" + Params.ClientConfigsToBuild[0].ToString()) :
Params.ShortProjectName) + ".html";
// data file
SC.ArchiveFiles(PackagePath, ProjectDataName);
// data file js driver
SC.ArchiveFiles(PackagePath, ProjectDataName + ".js");
// main game code
SC.ArchiveFiles(PackagePath, GameBasename + ".wasm");
// main js file
SC.ArchiveFiles(PackagePath, GameExe);
// symbols file
SC.ArchiveFiles(PackagePath, GameExe + ".symbols");
// utilities
SC.ArchiveFiles(PackagePath, "Utility.js");
// landing page.
SC.ArchiveFiles(PackagePath, OutputFilename);
// Archive HTML5 Server and a Readme.
SC.ArchiveFiles(CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/"), "HTML5LaunchHelper.exe");
SC.ArchiveFiles(CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5/"), "Readme.txt");
SC.ArchiveFiles(PackagePath, "RunMacHTML5LaunchHelper.command");
SC.ArchiveFiles(PackagePath, ".htaccess");
if (Compressed)
{
SC.ArchiveFiles(PackagePath, ProjectDataName + "gz");
SC.ArchiveFiles(PackagePath, ProjectDataName + ".jsgz");
SC.ArchiveFiles(PackagePath, GameExe + "gz");
SC.ArchiveFiles(PackagePath, GameBasename + ".wasmgz");
SC.ArchiveFiles(PackagePath, GameExe + ".symbolsgz");
SC.ArchiveFiles(PackagePath, "Utility.jsgz");
}
else
{
// nuke old compressed files to prevent using stale files
File.Delete(ProjectDataName + "gz");
File.Delete(ProjectDataName + ".jsgz");
File.Delete(GameExe + "gz");
File.Delete(GameBasename + ".wasmgz");
File.Delete(GameExe + ".symbolsgz");
File.Delete("Utility.jsgz");
}
if (HTMLPakAutomation.CanCreateMapPaks(Params))
{
// find all paks.
string[] Files = Directory.GetFiles(Path.Combine(PackagePath, Params.ShortProjectName), "*", SearchOption.AllDirectories);
foreach(string PakFile in Files)
{
SC.ArchivedFiles.Add(PakFile, Path.GetFileName(PakFile));
}
}
UploadToS3(SC, OutputFilename);
}
public override IProcessResult RunClient(ERunOptions ClientRunFlags, string ClientApp, string ClientCmdLine, ProjectParams Params)
{
// look for browser
string BrowserPath = Params.Devices[0].Replace("HTML5@", "");
// open the webpage
Int32 ServerPort = 8000; // HTML5LaunchHelper default
var ConfigCache = UnrealBuildTool.ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(Params.RawProjectPath), UnrealTargetPlatform.HTML5);
ConfigCache.GetInt32("/Script/HTML5PlatformEditor.HTML5TargetSettings", "DeployServerPort", out ServerPort); // LaunchOn via Editor or FrontEnd
string WorkingDirectory = Path.GetDirectoryName(ClientApp);
string url = Path.GetFileName(ClientApp) +".html";
// WARNING: splitting the following situation
// if cookonthefly is used: tell the browser to use the PROXY at DEFAULT_HTTP_FILE_SERVING_PORT
// leave the normal HTML5LaunchHelper port (ServerPort) alone -- otherwise it will collide with the PROXY port
if (ClientCmdLine.Contains("filehostip"))
{
url += "?cookonthefly=true";
Int32 ProxyPort = 41898; // DEFAULT_HTTP_FILE_SERVING_PORT
url = String.Format("http://localhost:{0}/{1}", ProxyPort, url);
}
else
{
url = String.Format("http://localhost:{0}/{1}", ServerPort, url);
}
// Check HTML5LaunchHelper source for command line args
var LowerBrowserPath = BrowserPath.ToLower();
var ProfileDirectory = Path.Combine(Utils.GetUserSettingDirectory().FullName, "UE4_HTML5", "user");
string BrowserCommandline = url;
if (LowerBrowserPath.Contains("chrome"))
{
ProfileDirectory = Path.Combine(ProfileDirectory, "chrome");
// removing [--enable-logging] otherwise, chrome breaks with a bunch of the following errors:
// > ERROR:process_info.cc(631)] range at 0x7848406c00000000, size 0x1a4 fully unreadable
// leaving this note here for future reference: UE-45078
BrowserCommandline += " " + String.Format("--user-data-dir=\\\"{0}\\\" --no-first-run", ProfileDirectory);
}
else if (LowerBrowserPath.Contains("firefox"))
{
ProfileDirectory = Path.Combine(ProfileDirectory, "firefox");
BrowserCommandline += " " + String.Format("-no-remote -profile \\\"{0}\\\"", ProfileDirectory);
}
if (UnrealBuildTool.BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Linux)
{
// TODO: test on other platforms to remove this if() check
if (!Directory.Exists(ProfileDirectory))
{
Directory.CreateDirectory(ProfileDirectory);
}
}
string LauncherArguments = string.Format(" -Browser=\"{0}\" + -BrowserCommandLine=\"{1}\" -ServerPort=\"{2}\" -ServerRoot=\"{3}\" ",
new object[] { BrowserPath, BrowserCommandline, ServerPort, WorkingDirectory });
var LaunchHelperPath = CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/HTML5LaunchHelper.exe");
IProcessResult BrowserProcess = Run(LaunchHelperPath, LauncherArguments, null, ClientRunFlags | ERunOptions.NoWaitForExit);
return BrowserProcess;
}
public override string GetCookPlatform(bool bDedicatedServer, bool bIsClientOnly)
{
return "HTML5";
}
public override string GetCookExtraCommandLine(ProjectParams Params)
{
return HTMLPakAutomation.CanCreateMapPaks(Params) ? " -GenerateDependenciesForMaps " : "";
}
public override PakType RequiresPak(ProjectParams Params)
{
return HTMLPakAutomation.CanCreateMapPaks(Params) ? PakType.Never : PakType.Always;
}
public override string GetPlatformPakCommandLine()
{
return Compressed ? " -compress" : "";
}
public override bool DeployLowerCaseFilenames()
{
return false;
}
public override string LocalPathToTargetPath(string LocalPath, string LocalRoot)
{
return LocalPath;//.Replace("\\", "/").Replace(LocalRoot, "../../..");
}
public override bool IsSupported { get { return true; } }
public override List<string> GetDebugFileExtentions()
{
return new List<string> { ".pdb" };
}
#region Hooks
public override void PreBuildAgenda(UE4Build Build, UE4Build.BuildAgenda Agenda)
{
}
public override List<FileReference> GetExecutableNames(DeploymentContext SC)
{
List<FileReference> ExecutableNames = new List<FileReference>();
ExecutableNames.Add(FileReference.Combine(SC.ProjectRoot, "Binaries", "HTML5", SC.ShortProjectName));
return ExecutableNames;
}
#endregion
#region AMAZON S3
public void UploadToS3(DeploymentContext SC, string OutputFilename)
{
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(SC.RawProjectPath), SC.StageTargetPlatform.PlatformType);
bool Upload = false;
string Region = "";
string KeyId = "";
string AccessKey = "";
string BucketName = "";
string FolderName = "";
if (! Ini.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "UploadToS3", out Upload) || ! Upload )
{
return;
}
bool AmazonIdentity = Ini.GetString("/Script/HTML5PlatformEditor.HTML5TargetSettings", "S3Region", out Region) &&
Ini.GetString("/Script/HTML5PlatformEditor.HTML5TargetSettings", "S3KeyID", out KeyId) &&
Ini.GetString("/Script/HTML5PlatformEditor.HTML5TargetSettings", "S3SecretAccessKey", out AccessKey) &&
Ini.GetString("/Script/HTML5PlatformEditor.HTML5TargetSettings", "S3BucketName", out BucketName);
if ( !AmazonIdentity )
{
Log("Amazon S3 Incorrectly configured");
return;
}
Ini.GetString("/Script/HTML5PlatformEditor.HTML5TargetSettings", "S3FolderName", out FolderName);
if ( FolderName == "" )
{
FolderName = SC.ShortProjectName;
}
else
{
// strip any before and after folder "/"
FolderName = Regex.Replace(Regex.Replace(FolderName, "^/+", "" ), "/+$", "");
}
List<Task> UploadTasks = new List<Task>();
long msTimeOut = 0;
foreach (KeyValuePair<string, string> Entry in SC.ArchivedFiles)
{
FileInfo Info = new FileInfo(Entry.Key);
UploadTasks.Add(UploadToS3Worker(Info, Region, KeyId, AccessKey, BucketName, FolderName));
if ( msTimeOut < Info.Length )
{
msTimeOut = Info.Length;
}
}
msTimeOut /= 100; // [miliseconds] give 10 secs for each ~MB ( (10s * 1000ms) / ( 1024KB * 1024MB * 1000ms ) )
if ( msTimeOut < (100*1000) ) // HttpClient: default timeout is 100 sec
{
msTimeOut = 100*1000;
}
Log("Upload Timeout set to: " + (msTimeOut/1000) + "secs");
Task.WaitAll(UploadTasks.ToArray(), (int)msTimeOut); // set timeout [miliseconds]
string URL = "https://" + BucketName + ".s3.amazonaws.com/" + FolderName + "/" + OutputFilename;
Log("Your project's shareable link is: " + URL);
Log("Upload Tasks finished.");
}
private static IDictionary<string, string> MimeTypeMapping = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
{
// the following will default to: "appication/octet-stream"
// .data .datagz
{ ".wasm", "application/wasm" },
{ ".wasmgz", "application/wasm" },
{ ".htaccess", "text/plain"},
{ ".html", "text/html"},
{ ".js", "application/x-javascript" },
{ ".jsgz", "application/x-javascript" },
{ ".symbols", "text/plain"},
{ ".symbolsgz", "text/plain"},
{ ".txt", "text/plain"}
};
static async Task UploadToS3Worker(FileInfo Info, string Region, string KeyId, string AccessKey, string BucketName, string FolderName)
{
// --------------------------------------------------
// "AWS Signature Version 4"
// http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
// --------------------------------------------------
Log(" Uploading " + Info.Name);
// --------------------------------------------------
// http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
string TimeStamp = DateTime.UtcNow.ToString("yyyyMMddTHHmmssZ");
string DateExpire = DateTime.UtcNow.AddDays(1).ToString("yyyy-MM-dd");
string AWSDate = DateTime.UtcNow.AddDays(1).ToString("yyyyMMdd");
string MimeType = (MimeTypeMapping.ContainsKey(Info.Extension))
? MimeTypeMapping[Info.Extension]
: "application/octet-stream";
string MimePath = MimeType.Split('/')[0];
string AWSCredential = KeyId + "/" + AWSDate + "/" + Region + "/s3/aws4_request";
// --------------------------------------------------
string policy = "{ \"expiration\": \"" + DateExpire + "T12:00:00.000Z\"," +
" \"conditions\": [" +
" { \"bucket\": \"" + BucketName + "\" }," +
" [ \"starts-with\", \"$key\", \"" + FolderName + "/\" ]," +
" { \"acl\": \"public-read\" }," +
" [ \"starts-with\", \"$content-type\", \"" + MimePath + "/\" ],";
if (Info.Extension.EndsWith("gz"))
{
policy += " [ \"starts-with\", \"$content-encoding\", \"gzip\" ],";
}
policy += " { \"x-amz-credential\": \"" + AWSCredential + "\" }," +
" { \"x-amz-algorithm\": \"AWS4-HMAC-SHA256\" }," +
" { \"x-amz-date\": \"" + TimeStamp + "\" }" +
" ]" +
"}";
string policyBase64 = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(policy), Base64FormattingOptions.InsertLineBreaks);
// --------------------------------------------------
// http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html
var kha = KeyedHashAlgorithm.Create("HmacSHA256");
kha.Key = Encoding.UTF8.GetBytes(("AWS4" + AccessKey).ToCharArray()); // kSecret
byte[] sig = kha.ComputeHash(Encoding.UTF8.GetBytes(AWSDate));
kha.Key = sig; // kDate
sig = kha.ComputeHash(Encoding.UTF8.GetBytes(Region));
kha.Key = sig; // kRegion
sig = kha.ComputeHash(Encoding.UTF8.GetBytes("s3"));
kha.Key = sig; // kService
sig = kha.ComputeHash(Encoding.UTF8.GetBytes("aws4_request"));
kha.Key = sig; // kSigning
sig = kha.ComputeHash(Encoding.UTF8.GetBytes(policyBase64));
string signature = BitConverter.ToString(sig).Replace("-", "").ToLower(); // for Authorization
// debugging...
//Console.WriteLine("policy: [" + policy + "]");
//Console.WriteLine("policyBase64: [" + policyBase64 + "]");
//Console.WriteLine("signature: [" + signature + "]");
// --------------------------------------------------
HttpClient httpClient = new HttpClient();
var formData = new MultipartFormDataContent();
formData.Add(new StringContent(FolderName + "/" + Info.Name), "key");
formData.Add(new StringContent("public-read"), "acl");
formData.Add(new StringContent(AWSCredential), "X-Amz-Credential");
formData.Add(new StringContent("AWS4-HMAC-SHA256"), "X-Amz-Algorithm");
formData.Add(new StringContent(signature), "X-Amz-Signature");
formData.Add(new StringContent(TimeStamp), "X-Amz-Date");
formData.Add(new StringContent(policyBase64), "Policy");
formData.Add(new StringContent(MimeType), "Content-Type");
if ( Info.Extension.EndsWith("gz") )
{
formData.Add(new StringContent("gzip"), "Content-Encoding");
}
// debugging...
//Console.WriteLine("key: [" + FolderName + "/" + Info.Name + "]");
//Console.WriteLine("AWSCredential: [" + AWSCredential + "]");
//Console.WriteLine("TimeStamp: [" + TimeStamp + "]");
//Console.WriteLine("MimeType: [" + MimeType + "]");
// the file ----------------------------------------
var fileContent = new ByteArrayContent(System.IO.File.ReadAllBytes(Info.FullName));
fileContent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(MimeType);
formData.Add(fileContent, "file", Info.Name);
int adjustTimeout = (int)(Info.Length / (100*1000)); // [seconds] give 10 secs for each ~MB ( (10s * 1000ms) / ( 1024KB * 1024MB * 1000ms ) )
if ( adjustTimeout > 100 ) // default timeout is 100 sec
{
httpClient.Timeout = TimeSpan.FromSeconds(adjustTimeout); // increase timeout
}
//Console.WriteLine("httpClient Timeout: [" + httpClient.Timeout + "]" );
// upload ----------------------------------------
string URL = "https://" + BucketName + ".s3.amazonaws.com/";
var response = await httpClient.PostAsync(URL, formData);
if (response.IsSuccessStatusCode)
{
Log("Upload done: " + Info.Name);
}
else
{
var contents = response.Content.ReadAsStringAsync();
var reason = Regex.Replace(
/* grab inner block */ Regex.Replace(contents.Result, "<[^>]+>\n<[^>]+>([^<]+)</[^>]+>", "$1")
/* strip tags */ , "<([^>]+)>([^<]+)</[^>]+>", "$1 - $2\n");
//Console.WriteLine("Fail to Upload: " + Info.Name + " Header - " + response.ToString());
Console.WriteLine("Fail to Upload: " + Info.Name + "\nResponse - " + reason);
throw new Exception("FAILED TO UPLOAD: " + Info.Name);
}
}
#endregion
}