mirror of
https://github.com/modernw/App-Installer-For-Windows-8.x-Reset.git
synced 2026-04-11 17:57:19 +10:00
271 lines
7.1 KiB
C#
271 lines
7.1 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Reflection;
|
|
using System.Globalization;
|
|
using System.Runtime.InteropServices;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace DataUtils
|
|
{
|
|
[ComVisible (false)]
|
|
public sealed class DownloadHelper
|
|
{
|
|
private static readonly object s_downloadLock = new object (); // mimic g_download_cs
|
|
|
|
// Public static entry (starts background thread)
|
|
public static void DownloadFile (string httpUrl, string savePath, object onProgress, object onComplete, object onError)
|
|
{
|
|
if (string.IsNullOrEmpty (httpUrl)) throw new ArgumentNullException ("httpUrl");
|
|
if (string.IsNullOrEmpty (savePath)) throw new ArgumentNullException ("savePath");
|
|
|
|
var helper = new DownloadHelper (httpUrl, savePath, onProgress, onComplete, onError);
|
|
Thread th = new Thread (helper.Worker);
|
|
th.IsBackground = true;
|
|
th.Start ();
|
|
}
|
|
|
|
// Instance members
|
|
private readonly string _url;
|
|
private readonly string _savePath;
|
|
private readonly object _cbProgress;
|
|
private readonly object _cbComplete;
|
|
private readonly object _cbError;
|
|
|
|
private DownloadHelper (string url, string savePath, object cbProgress, object cbComplete, object cbError)
|
|
{
|
|
_url = url;
|
|
_savePath = savePath;
|
|
_cbProgress = cbProgress;
|
|
_cbComplete = cbComplete;
|
|
_cbError = cbError;
|
|
}
|
|
|
|
private void Worker ()
|
|
{
|
|
// Single download at a time (mimic CreateScopedLock)
|
|
lock (s_downloadLock)
|
|
{
|
|
HttpWebRequest request = null;
|
|
HttpWebResponse response = null;
|
|
Stream responseStream = null;
|
|
FileStream fileStream = null;
|
|
|
|
try
|
|
{
|
|
request = (HttpWebRequest)WebRequest.Create (_url);
|
|
request.Method = "GET";
|
|
request.AllowAutoRedirect = true;
|
|
request.UserAgent = "Mozilla/5.0 (Windows NT 6.2; Win32; x86) AppInstallerUpdater/1.0";
|
|
request.Timeout = 60000; // 60s connect timeout
|
|
request.ReadWriteTimeout = 60000;
|
|
|
|
response = (HttpWebResponse)request.GetResponse ();
|
|
long contentLength = -1;
|
|
try { contentLength = response.ContentLength; } catch { contentLength = -1; }
|
|
|
|
responseStream = response.GetResponseStream ();
|
|
|
|
// create directory if needed
|
|
string dir = Path.GetDirectoryName (_savePath);
|
|
if (!string.IsNullOrEmpty (dir) && !Directory.Exists (dir))
|
|
Directory.CreateDirectory (dir);
|
|
|
|
fileStream = new FileStream (_savePath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
|
|
|
byte [] buffer = new byte [8192];
|
|
int bytesRead;
|
|
long received = 0;
|
|
|
|
var lastCheck = DateTime.UtcNow;
|
|
long lastBytes = 0;
|
|
const double reportIntervalSeconds = 0.5;
|
|
|
|
while ((bytesRead = SafeRead (responseStream, buffer, 0, buffer.Length)) > 0)
|
|
{
|
|
fileStream.Write (buffer, 0, bytesRead);
|
|
received += bytesRead;
|
|
|
|
var now = DateTime.UtcNow;
|
|
double interval = (now - lastCheck).TotalSeconds;
|
|
long speed = -1;
|
|
if (interval >= reportIntervalSeconds)
|
|
{
|
|
long bytesInInterval = received - lastBytes;
|
|
if (interval > 0)
|
|
speed = (long)(bytesInInterval / interval); // B/s
|
|
lastCheck = now;
|
|
lastBytes = received;
|
|
}
|
|
|
|
ReportProgress (received, contentLength >= 0 ? contentLength : 0, speed);
|
|
}
|
|
|
|
// flush and close file
|
|
fileStream.Flush ();
|
|
|
|
ReportComplete (_savePath, received);
|
|
}
|
|
catch (WebException wex)
|
|
{
|
|
string reason = BuildWebExceptionReason (wex);
|
|
ReportError (_savePath, reason);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ReportError (_savePath, ex.Message ?? ex.ToString ());
|
|
}
|
|
finally
|
|
{
|
|
try { if (responseStream != null) responseStream.Close (); } catch { }
|
|
try { if (response != null) response.Close (); } catch { }
|
|
try { if (fileStream != null) fileStream.Close (); } catch { }
|
|
}
|
|
}
|
|
}
|
|
|
|
// Safe read wrapper to handle potential stream interruptions
|
|
private static int SafeRead (Stream s, byte [] buffer, int offset, int count)
|
|
{
|
|
try
|
|
{
|
|
return s.Read (buffer, offset, count);
|
|
}
|
|
catch
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Build a user-friendly reason text from WebException (includes status / inner messages)
|
|
private static string BuildWebExceptionReason (WebException wex)
|
|
{
|
|
try
|
|
{
|
|
StringBuilder sb = new StringBuilder ();
|
|
sb.Append ("WebException: ");
|
|
sb.Append (wex.Status.ToString ());
|
|
if (wex.Response != null)
|
|
{
|
|
try
|
|
{
|
|
var resp = (HttpWebResponse)wex.Response;
|
|
sb.AppendFormat (CultureInfo.InvariantCulture, " (HTTP {0})", (int)resp.StatusCode);
|
|
}
|
|
catch { }
|
|
}
|
|
if (!string.IsNullOrEmpty (wex.Message))
|
|
{
|
|
sb.Append (" - ");
|
|
sb.Append (wex.Message);
|
|
}
|
|
return sb.ToString ();
|
|
}
|
|
catch
|
|
{
|
|
return wex.Message ?? "Unknown WebException";
|
|
}
|
|
}
|
|
|
|
// ---------- Reporting helpers (use Newtonsoft.Json) ----------
|
|
|
|
private void ReportProgress (long received, long total, long speed)
|
|
{
|
|
if (_cbProgress == null) return;
|
|
|
|
double progress = 0.0;
|
|
if (total > 0) progress = received / (double)total * 100.0;
|
|
|
|
var payload = new
|
|
{
|
|
received = received,
|
|
total = total,
|
|
speed = FormatSpeed (speed),
|
|
progress = progress
|
|
};
|
|
|
|
string json = JsonConvert.SerializeObject (payload);
|
|
CallJS (_cbProgress, json);
|
|
}
|
|
|
|
private void ReportComplete (string file, long size)
|
|
{
|
|
if (_cbComplete == null) return;
|
|
|
|
var payload = new
|
|
{
|
|
file = file ?? string.Empty,
|
|
status = "ok",
|
|
size = size
|
|
};
|
|
|
|
string json = JsonConvert.SerializeObject (payload);
|
|
CallJS (_cbComplete, json);
|
|
}
|
|
|
|
private void ReportError (string file, string reason)
|
|
{
|
|
if (_cbError == null) return;
|
|
|
|
var payload = new
|
|
{
|
|
file = file ?? string.Empty,
|
|
status = "failed",
|
|
reason = reason ?? string.Empty
|
|
};
|
|
|
|
string json = JsonConvert.SerializeObject (payload);
|
|
CallJS (_cbError, json);
|
|
}
|
|
|
|
// Call JS callback object: invoke its "call" method like original code: jsFunc.call(1, arg)
|
|
private void CallJS (object jsFunc, string arg)
|
|
{
|
|
if (jsFunc == null) return;
|
|
try
|
|
{
|
|
// Use reflection to invoke `call` method with (thisArg, arg)
|
|
jsFunc.GetType ().InvokeMember (
|
|
"call",
|
|
BindingFlags.InvokeMethod,
|
|
null,
|
|
jsFunc,
|
|
new object [] { 1, arg });
|
|
}
|
|
catch
|
|
{
|
|
// ignore errors in callback invocation
|
|
}
|
|
}
|
|
|
|
// Format speed like original: B/s, KB/s, MB/s, …
|
|
private string FormatSpeed (long speed)
|
|
{
|
|
if (speed < 0) return "--/s";
|
|
string [] units = new string [] { "B/s", "KB/s", "MB/s", "GB/s", "TB/s" };
|
|
double s = (double)speed;
|
|
int idx = 0;
|
|
while (s >= 1024.0 && idx < units.Length - 1)
|
|
{
|
|
s /= 1024.0;
|
|
idx++;
|
|
}
|
|
return string.Format (CultureInfo.InvariantCulture, "{0:0.##} {1}", s, units [idx]);
|
|
}
|
|
}
|
|
|
|
// Simple COM-visible wrapper class for JS/COM consumers
|
|
[ComVisible (true)]
|
|
[ClassInterface (ClassInterfaceType.AutoDual)]
|
|
public class _I_Download
|
|
{
|
|
// Keep method order and names similar to your C++/CLI _I_Download.WorkAsync
|
|
public void WorkAsync (string httpurl, string saveFilePath, object onComplete, object onError, object onProgress)
|
|
{
|
|
DownloadHelper.DownloadFile (httpurl, saveFilePath, onProgress, onComplete, onError);
|
|
}
|
|
}
|
|
}
|