When I downloaded the assets for Red Alert through the Quick Install I noticed the progress bar proceed and display a recognizable message: `Downloading from … 1.47/12 MB (12%)`. This was fine.
When I downloaded the assets for one of the other games, maybe Dune 2000, there was obviously no total download size available. I was an unexpected message: `Downloading from … 1.47/NaN (NaN%)`
The code handling network progress events seems to be aware of the possibility that no full download size exists but it doesn't update the message. In this path I'm proposing that we display a separate messaging indicating that we don't know how much more we have to download for these cases.
Of the alternative ways to implement this I chose to move the reassignment of `getStatusText` into the conditional structures to preserve the existing choice. The message was qualitatively different and so I felt it worthwhile to create entirely different closures vs. doing something like this…
```cs
getStatusText = () => ( Double.isNaN( dataTotal ) ? "Downloading {1} of unknown amount" : "Downloading {1}/{2}" ).F( … );
```
241 lines
6.6 KiB
C#
241 lines
6.6 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
|
|
* This file is part of OpenRA, which is free software. It is made
|
|
* available to you under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation, either version 3 of
|
|
* the License, or (at your option) any later version. For more
|
|
* information, see COPYING.
|
|
*/
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Text;
|
|
using OpenRA.Primitives;
|
|
using OpenRA.Support;
|
|
using OpenRA.Widgets;
|
|
|
|
namespace OpenRA.Mods.Common.Widgets.Logic
|
|
{
|
|
public class DownloadPackageLogic : ChromeLogic
|
|
{
|
|
static readonly string[] SizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
|
|
readonly ModContent.ModDownload download;
|
|
readonly Action onSuccess;
|
|
|
|
readonly Widget panel;
|
|
readonly ProgressBarWidget progressBar;
|
|
|
|
Func<string> getStatusText = () => "";
|
|
string downloadHost;
|
|
|
|
[ObjectCreator.UseCtor]
|
|
public DownloadPackageLogic(Widget widget, ModContent.ModDownload download, Action onSuccess)
|
|
{
|
|
this.download = download;
|
|
this.onSuccess = onSuccess;
|
|
|
|
Log.AddChannel("install", "install.log");
|
|
|
|
panel = widget.Get("PACKAGE_DOWNLOAD_PANEL");
|
|
progressBar = panel.Get<ProgressBarWidget>("PROGRESS_BAR");
|
|
|
|
var statusLabel = panel.Get<LabelWidget>("STATUS_LABEL");
|
|
var statusFont = Game.Renderer.Fonts[statusLabel.Font];
|
|
var status = new CachedTransform<string, string>(s => WidgetUtils.TruncateText(s, statusLabel.Bounds.Width, statusFont));
|
|
statusLabel.GetText = () => status.Update(getStatusText());
|
|
|
|
var text = "Downloading {0}".F(download.Title);
|
|
panel.Get<LabelWidget>("TITLE").Text = text;
|
|
|
|
ShowDownloadDialog();
|
|
}
|
|
|
|
void ShowDownloadDialog()
|
|
{
|
|
getStatusText = () => "Fetching list of mirrors...";
|
|
progressBar.Indeterminate = true;
|
|
|
|
var retryButton = panel.Get<ButtonWidget>("RETRY_BUTTON");
|
|
retryButton.IsVisible = () => false;
|
|
|
|
var cancelButton = panel.Get<ButtonWidget>("CANCEL_BUTTON");
|
|
|
|
var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
|
|
|
Action deleteTempFile = () =>
|
|
{
|
|
Log.Write("install", "Deleting temporary file " + file);
|
|
File.Delete(file);
|
|
};
|
|
|
|
Action<DownloadProgressChangedEventArgs> onDownloadProgress = i =>
|
|
{
|
|
var dataReceived = 0.0f;
|
|
var dataTotal = 0.0f;
|
|
var mag = 0;
|
|
var dataSuffix = "";
|
|
|
|
if (i.TotalBytesToReceive < 0)
|
|
{
|
|
dataTotal = float.NaN;
|
|
dataReceived = i.BytesReceived;
|
|
dataSuffix = SizeSuffixes[0];
|
|
|
|
getStatusText = () => "Downloading from {2} {0:0.00} {1} (unknown size)".F(dataReceived,
|
|
dataSuffix,
|
|
downloadHost ?? "unknown host");
|
|
}
|
|
else
|
|
{
|
|
mag = (int)Math.Log(i.TotalBytesToReceive, 1024);
|
|
dataTotal = i.TotalBytesToReceive / (float)(1L << (mag * 10));
|
|
dataReceived = i.BytesReceived / (float)(1L << (mag * 10));
|
|
dataSuffix = SizeSuffixes[mag];
|
|
|
|
getStatusText = () => "Downloading from {4} {1:0.00}/{2:0.00} {3} ({0}%)".F(i.ProgressPercentage,
|
|
dataReceived, dataTotal, dataSuffix,
|
|
downloadHost ?? "unknown host");
|
|
}
|
|
|
|
progressBar.Indeterminate = false;
|
|
progressBar.Percentage = i.ProgressPercentage;
|
|
};
|
|
|
|
Action<string> onExtractProgress = s => Game.RunAfterTick(() => getStatusText = () => s);
|
|
|
|
Action<string> onError = s => Game.RunAfterTick(() =>
|
|
{
|
|
Log.Write("install", "Download failed: " + s);
|
|
|
|
progressBar.Indeterminate = false;
|
|
progressBar.Percentage = 100;
|
|
getStatusText = () => "Error: " + s;
|
|
retryButton.IsVisible = () => true;
|
|
cancelButton.OnClick = Ui.CloseWindow;
|
|
});
|
|
|
|
Action<AsyncCompletedEventArgs> onDownloadComplete = i =>
|
|
{
|
|
if (i.Cancelled)
|
|
{
|
|
deleteTempFile();
|
|
Game.RunAfterTick(Ui.CloseWindow);
|
|
return;
|
|
}
|
|
|
|
if (i.Error != null)
|
|
{
|
|
deleteTempFile();
|
|
onError(Download.FormatErrorMessage(i.Error));
|
|
return;
|
|
}
|
|
|
|
// Automatically extract
|
|
getStatusText = () => "Extracting...";
|
|
progressBar.Indeterminate = true;
|
|
|
|
var extracted = new List<string>();
|
|
try
|
|
{
|
|
using (var stream = File.OpenRead(file))
|
|
using (var z = ZipFileHelper.Create(stream))
|
|
{
|
|
foreach (var kv in download.Extract)
|
|
{
|
|
var entry = z.GetEntry(kv.Value);
|
|
if (entry == null || !entry.IsFile)
|
|
continue;
|
|
|
|
onExtractProgress("Extracting " + entry.Name);
|
|
Log.Write("install", "Extracting " + entry.Name);
|
|
var targetPath = Platform.ResolvePath(kv.Key);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
|
|
extracted.Add(targetPath);
|
|
|
|
using (var zz = z.GetInputStream(entry))
|
|
using (var f = File.Create(targetPath))
|
|
zz.CopyTo(f);
|
|
}
|
|
|
|
z.Close();
|
|
}
|
|
|
|
Game.RunAfterTick(() => { Ui.CloseWindow(); onSuccess(); });
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Write("install", "Archive extraction failed: " + e.ToString());
|
|
|
|
foreach (var f in extracted)
|
|
{
|
|
Log.Write("install", "Deleting " + f);
|
|
File.Delete(f);
|
|
}
|
|
|
|
onError("Archive extraction failed");
|
|
}
|
|
finally
|
|
{
|
|
deleteTempFile();
|
|
}
|
|
};
|
|
|
|
Action<string> downloadUrl = url =>
|
|
{
|
|
Log.Write("install", "Downloading " + url);
|
|
|
|
downloadHost = new Uri(url).Host;
|
|
var dl = new Download(url, file, onDownloadProgress, onDownloadComplete);
|
|
cancelButton.OnClick = dl.CancelAsync;
|
|
retryButton.OnClick = ShowDownloadDialog;
|
|
};
|
|
|
|
if (download.MirrorList != null)
|
|
{
|
|
Log.Write("install", "Fetching mirrors from " + download.MirrorList);
|
|
|
|
Action<DownloadDataCompletedEventArgs> onFetchMirrorsComplete = i =>
|
|
{
|
|
progressBar.Indeterminate = true;
|
|
|
|
if (i.Cancelled)
|
|
{
|
|
Game.RunAfterTick(Ui.CloseWindow);
|
|
return;
|
|
}
|
|
|
|
if (i.Error != null)
|
|
{
|
|
onError(Download.FormatErrorMessage(i.Error));
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var data = Encoding.UTF8.GetString(i.Result);
|
|
var mirrorList = data.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
|
downloadUrl(mirrorList.Random(new MersenneTwister()));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Write("install", "Mirror selection failed with error:");
|
|
Log.Write("install", e.ToString());
|
|
onError("Online mirror is not available. Please install from an original disc.");
|
|
}
|
|
};
|
|
|
|
var updateMirrors = new Download(download.MirrorList, onDownloadProgress, onFetchMirrorsComplete);
|
|
cancelButton.OnClick = updateMirrors.CancelAsync;
|
|
retryButton.OnClick = ShowDownloadDialog;
|
|
}
|
|
else
|
|
downloadUrl(download.URL);
|
|
}
|
|
}
|
|
}
|