From a6ce91861e1f18ed8d81a74629be455941adcf25 Mon Sep 17 00:00:00 2001 From: Hugh Sanderson Date: Sat, 21 Sep 2024 22:58:44 +0800 Subject: [PATCH] Add URLLoader.close --- acadnme/src/Acadnme.hx | 15 +++ include.nmml | 1 + project/include/URL.h | 1 + project/src/common/CURL.cpp | 26 +++-- project/src/common/ExternalInterface.cpp | 13 +++ project/src/emscripten/CurlFetch.cpp | 1 + samples/AcadnmeBoot/src/AcadnmeBoot.hx | 60 ++++++++++- src/nme/net/HttpLoader.hx | 129 ++++++++++++++--------- src/nme/net/URLLoader.hx | 30 +++++- src/nme/net/URLRequest.hx | 2 + 10 files changed, 216 insertions(+), 62 deletions(-) diff --git a/acadnme/src/Acadnme.hx b/acadnme/src/Acadnme.hx index f0f7f1625..14fed8394 100644 --- a/acadnme/src/Acadnme.hx +++ b/acadnme/src/Acadnme.hx @@ -7,6 +7,7 @@ import nme.ui.Scale; import nme.utils.Float32Array; import nme.utils.UInt8Array; import nme.events.KeyboardEvent; +import nme.utils.ByteArray; import AllNme; import Sys; @@ -238,6 +239,13 @@ class Acadnme extends Sprite implements IScriptHandler } } + public function runBytes(bytes:ByteArray) + { + clearBoot(); + trace("Run bytes " + bytes.length); + nme.script.Nme.runBytes(bytes); + } + public function runBoot() { if (nme.Assets.hasBytes("AcadnmeBoot.nme")) @@ -262,6 +270,13 @@ class Acadnme extends Sprite implements IScriptHandler instance.run(inScript); } + @:keep // Used by boot + public static function runScriptBytes(inScript:ByteArray) + { + instance.runBytes(inScript); + } + + @:keep // Used by boot public static function getEngines() : Array< {name:String, version:String} > { diff --git a/include.nmml b/include.nmml index a55e3f8e1..356d2e278 100644 --- a/include.nmml +++ b/include.nmml @@ -17,6 +17,7 @@ +
diff --git a/project/include/URL.h b/project/include/URL.h index 250de2a45..1a09fdc5e 100644 --- a/project/include/URL.h +++ b/project/include/URL.h @@ -51,6 +51,7 @@ class URLLoader : public Object virtual ~URLLoader() { }; virtual URLState getState()=0; + virtual void close()=0; virtual int bytesLoaded()=0; virtual int bytesTotal()=0; virtual int getHttpCode()=0; diff --git a/project/src/common/CURL.cpp b/project/src/common/CURL.cpp index 9d7c9a11f..8d7238ce9 100644 --- a/project/src/common/CURL.cpp +++ b/project/src/common/CURL.cpp @@ -204,6 +204,10 @@ class CURLLoader : public URLLoader return ((CURLLoader *)userdata)->ReadFunc(ptr,size,nmemb); } + void close() + { + } + void SetPutBuffer(const unsigned char *inBuffer, size_t inLen) { mPutBuffer = new unsigned char[inLen]; @@ -225,18 +229,28 @@ class CURLLoader : public URLLoader processMultiMessages(); } - ~CURLLoader() + void close() { delete [] mPutBuffer; - curl_easy_cleanup(mHandle); - sLoaders--; - if (sLoaders==0) + mPutBuffer = nullptr; + if (mHandle) { - curl_multi_cleanup(sCurlM); - sCurlM = 0; + curl_easy_cleanup(mHandle); + mHandle = nullptr; + sLoaders--; + if (sLoaders==0) + { + curl_multi_cleanup(sCurlM); + sCurlM = 0; + } } } + ~CURLLoader() + { + close(); + } + size_t onData( void *inBuffer, size_t inItemSize, size_t inItems) { size_t size = inItemSize*inItems; diff --git a/project/src/common/ExternalInterface.cpp b/project/src/common/ExternalInterface.cpp index 54c31987c..ba59b22db 100644 --- a/project/src/common/ExternalInterface.cpp +++ b/project/src/common/ExternalInterface.cpp @@ -5570,6 +5570,19 @@ value nme_curl_create(value inURLRequest) } DEFINE_PRIM(nme_curl_create,1); +value nme_curl_close(value inLoader) +{ + #ifdef NME_CURL + URLLoader *loader; + if (AbstractToObject(inLoader,loader)) + { + loader->close(); + } + #endif + return alloc_null(); +} +DEFINE_PRIM(nme_curl_close,1); + value nme_curl_process_loaders() { diff --git a/project/src/emscripten/CurlFetch.cpp b/project/src/emscripten/CurlFetch.cpp index eca041c2d..7ff46a592 100755 --- a/project/src/emscripten/CurlFetch.cpp +++ b/project/src/emscripten/CurlFetch.cpp @@ -134,6 +134,7 @@ class CurlFetch : public URLLoader close(); } + void onSuccess() { if (fetch) diff --git a/samples/AcadnmeBoot/src/AcadnmeBoot.hx b/samples/AcadnmeBoot/src/AcadnmeBoot.hx index ac940dafb..d7fe3cb8d 100644 --- a/samples/AcadnmeBoot/src/AcadnmeBoot.hx +++ b/samples/AcadnmeBoot/src/AcadnmeBoot.hx @@ -17,8 +17,10 @@ import gm2d.skin.Shape; import gm2d.skin.Skin; import gm2d.svg.Svg; import gm2d.svg.SvgRenderer; +import gm2d.Game; import sys.FileSystem; -import nme.net.SharedObject; +import nme.net.*; +import nme.events.*; import sys.io.File; using StringTools; @@ -118,9 +120,65 @@ class AcadnmeBoot extends Screen implements IBoot if (isWeb) { var q = nme.Lib.getWebpageParam("prog"); + if (q!=null && q!="") + downloadAndRun(q); } } + function downloadAndRun(url:String) + { + var loader = new URLLoader(); + loader.dataFormat = URLLoaderDataFormat.BINARY; + + var status:String = null; + var progress = ProgressDialog.create("Download", url, status, 100.0, () -> { + if (loader!=null) + { + loader = null; + } + } ); + progress.show(true,false); + loader.addEventListener( Event.COMPLETE, (_) -> { + Game.closeDialog(); + if (loader!=null) + { + var bytes:ByteArray = loader.data; + if (bytes==null) + warn("Error Loading Data","No data."); + else + Acadnme.runScriptBytes(bytes); + } + }); + var lastPct = 0; + loader.addEventListener(ProgressEvent.PROGRESS, (p) -> { + var pct = Std.int( 100 * p.bytesLoaded / Math.max(1,p.bytesTotal) ); + if (pct!=lastPct) + { + lastPct = pct; + progress.update(pct); + } + } ); + loader.addEventListener(IOErrorEvent.IO_ERROR, (e) -> { + Game.closeDialog(); + warn("Error Loading Data", "Error: " + e.text ); + } ); + + var req = new URLRequest(url); + req.preferHaxeHttp = true; + loader.load(req); + } + + public function warn(title:String, message:String) + { + var panel = new Panel(title); + //Sys.println("Warning:" + message); + panel.addLabel(message); + panel.addTextButton("Ok", Game.closeDialog ); + var dlg = new gm2d.ui.Dialog(panel.getPane()); + Game.doShowDialog(dlg,true); + } + + function onEnable(inValue:Bool) { serverEnabled = inValue; diff --git a/src/nme/net/HttpLoader.hx b/src/nme/net/HttpLoader.hx index 81fdaefd5..ddc819cc3 100644 --- a/src/nme/net/HttpLoader.hx +++ b/src/nme/net/HttpLoader.hx @@ -29,11 +29,13 @@ private class OutputWatcher extends haxe.io.BytesOutput override public function prepare(nbytes:Int) { super.prepare(nbytes); + loader.checkClosed(); loader.onBytesTotal(nbytes); } override function writeBytes(buf:haxe.io.Bytes, pos:Int, len:Int):Int { + loader.checkClosed(); var result = super.writeBytes(buf, pos, len); loader.onBytesLoaded(b.length); return result; @@ -54,6 +56,7 @@ class HttpLoader var errorMessage:String; var code:Int; var cookies:Array; + var closed:Bool; var byteData:ByteArray; var stringData:String; @@ -62,6 +65,9 @@ class HttpLoader public var bytesTotal(default,null):Int; public var state(default,null):Int; var http:Http; + #if !js + var output:OutputWatcher; + #end public function new(inLoader:URLLoader, inRequest:URLRequest) { @@ -71,6 +77,7 @@ class HttpLoader bytesTotal = 0; state = URLLoader.urlLoading; code = 0; + closed = false; http = new Http(inRequest.url); http.onError = onError; @@ -86,81 +93,103 @@ class HttpLoader if (isPost) http.setPostBytes(urlRequest.nmeBytes); - #if !js + #if wasm + run(); + #elseif !js runAsync(run); #end } - #if !js - public function run() + public function checkClosed() { - var output = new OutputWatcher(this); + if (closed) + throw "closed by client"; + } - var isPost = urlRequest.method==URLRequestMethod.POST; - http.customRequest(isPost, output); + public function close() + { + state = URLLoader.urlClosed; + closed = true; + } - if (state!=URLLoader.urlError) + #if !js + public function run() + { + try { - var bytes = output.getBytes(); + var output = new OutputWatcher(this); - bytesLoaded = bytesTotal = bytes.length; + var isPost = urlRequest.method==URLRequestMethod.POST; + http.customRequest(isPost, output); - var encoding = http.responseHeaders.get("Content-Encoding"); - if (encoding=="gzip") + if (state!=URLLoader.urlError) { - var decoded = false; - try + var bytes = output.getBytes(); + + bytesLoaded = bytesTotal = bytes.length; + + var encoding = http.responseHeaders.get("Content-Encoding"); + if (encoding=="gzip") { - if (bytes.length>10 && bytes.get(0)==0x1f && bytes.get(1)==0x8b) + var decoded = false; + try { - var u = new haxe.zip.Uncompress(15|32); - var tmp = haxe.io.Bytes.alloc(1<<16); - u.setFlushMode(haxe.zip.FlushMode.SYNC); - var b = new haxe.io.BytesBuffer(); - var pos = 0; - while (true) { - var r = u.execute(bytes, pos, tmp, 0); - b.addBytes(tmp, 0, r.write); - pos += r.read; - if (r.done) - break; + if (bytes.length>10 && bytes.get(0)==0x1f && bytes.get(1)==0x8b) + { + var u = new haxe.zip.Uncompress(15|32); + var tmp = haxe.io.Bytes.alloc(1<<16); + u.setFlushMode(haxe.zip.FlushMode.SYNC); + var b = new haxe.io.BytesBuffer(); + var pos = 0; + while (true) { + var r = u.execute(bytes, pos, tmp, 0); + b.addBytes(tmp, 0, r.write); + pos += r.read; + if (r.done) + break; + } + u.close(); + bytes = b.getBytes(); + decoded = bytes!=null; } - u.close(); - bytes = b.getBytes(); - decoded = bytes!=null; } + catch(e:Dynamic) + { + trace(e); + } + + if (!decoded) + onError("Bad GZip data"); } - catch(e:Dynamic) + + if (urlLoader.dataFormat== URLLoaderDataFormat.BINARY) { - trace(e); + byteData = ByteArray.fromBytes(bytes); + } + else + { + #if neko + stringData = neko.Lib.stringReference(bytes); + #else + #if haxe4 + stringData = bytes.getString(0, bytes.length, UTF8); + #else + stringData = bytes.getString(0, bytes.length); + #end + #end } - if (!decoded) - onError("Bad GZip data"); - } - - if (urlLoader.dataFormat== URLLoaderDataFormat.BINARY) - { - byteData = ByteArray.fromBytes(bytes); + state = URLLoader.urlComplete; } else { - #if neko - stringData = neko.Lib.stringReference(bytes); - #else - #if haxe4 - stringData = bytes.getString(0, bytes.length, UTF8); - #else - stringData = bytes.getString(0, bytes.length); - #end - #end + //trace(" -> error"); } - - state = URLLoader.urlComplete; } - else + catch(e:Dynamic) { - //trace(" -> error"); + if (!closed) + onError(""+e); } } #end diff --git a/src/nme/net/URLLoader.hx b/src/nme/net/URLLoader.hx index d6188043d..282d89015 100644 --- a/src/nme/net/URLLoader.hx +++ b/src/nme/net/URLLoader.hx @@ -51,6 +51,7 @@ class URLLoader extends EventDispatcher public static inline var urlLoading = 2; public static inline var urlComplete = 3; public static inline var urlError = 4; + public static inline var urlClosed = 5; private var state:Int; public var nmeOnComplete:Dynamic -> Bool; @@ -69,6 +70,18 @@ class URLLoader extends EventDispatcher load(request); } + public function close():Void + { + activeLoaders.remove(this); + if (nmeHandle!=null) + nme_curl_close(nmeHandle); + #if !no_haxe_http + if (httpLoader!=null) + httpLoader.close(); + #end + disposeHandler(); + } + override public function toString() { #if no_haxe_http @@ -78,10 +91,6 @@ class URLLoader extends EventDispatcher #end } - public function close() - { - } - public static function hasActive() { return !activeLoaders.isEmpty(); @@ -130,7 +139,17 @@ class URLLoader extends EventDispatcher { request.nmePrepare(); - nmeHandle = nme_curl_create(request); + #if no_haxe_http + var tryCurl = true; + #else + var tryCurl = !request.preferHaxeHttp; + #end + + if (tryCurl) + { + nmeHandle = nme_curl_create(request); + } + #if !no_haxe_http if (nmeHandle==null) { @@ -380,6 +399,7 @@ class URLLoader extends EventDispatcher private static var nme_curl_get_data = Loader.load("nme_curl_get_data", 1); private static var nme_curl_get_cookies = Loader.load("nme_curl_get_cookies", 1); private static var nme_curl_get_headers = Loader.load("nme_curl_get_headers", 1); + private static var nme_curl_close = Loader.load("nme_curl_close", 1); private static var nme_curl_initialize = Loader.load("nme_curl_initialize", 1); } diff --git a/src/nme/net/URLRequest.hx b/src/nme/net/URLRequest.hx index 909e5d87e..31525e3c6 100644 --- a/src/nme/net/URLRequest.hx +++ b/src/nme/net/URLRequest.hx @@ -26,6 +26,8 @@ class URLRequest public var credentials:String; public var followRedirects:Bool; public var allowFile:Bool; + // Uses internal version, which can side-step CORS prevention + public var preferHaxeHttp:Bool; /** @private */ public var __bytes:ByteArray; /** @private */ public var nmeBytes(get, set):ByteArray;