438 lines
14 KiB
C#
438 lines
14 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright (c) The OpenRA Developers and Contributors
|
|
* 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.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using OpenRA.Mods.Common.Installer;
|
|
using OpenRA.Primitives;
|
|
using OpenRA.Widgets;
|
|
|
|
namespace OpenRA.Mods.Common.Widgets.Logic
|
|
{
|
|
public class InstallFromSourceLogic : ChromeLogic
|
|
{
|
|
[FluentReference]
|
|
const string DetectingSources = "label-detecting-sources";
|
|
|
|
[FluentReference]
|
|
const string CheckingSources = "label-checking-sources";
|
|
|
|
[FluentReference("title")]
|
|
const string SearchingSourceFor = "label-searching-source-for";
|
|
|
|
[FluentReference]
|
|
const string ContentPackageInstallation = "label-content-package-installation";
|
|
|
|
[FluentReference]
|
|
const string GameSources = "label-game-sources";
|
|
|
|
[FluentReference]
|
|
const string DigitalInstalls = "label-digital-installs";
|
|
|
|
[FluentReference]
|
|
const string GameContentNotFound = "label-game-content-not-found";
|
|
|
|
[FluentReference]
|
|
const string AlternativeContentSources = "label-alternative-content-sources";
|
|
|
|
[FluentReference]
|
|
const string InstallingContent = "label-installing-content";
|
|
|
|
[FluentReference("filename")]
|
|
public const string CopyingFilename = "label-copying-filename";
|
|
|
|
[FluentReference("filename", "progress")]
|
|
public const string CopyingFilenameProgress = "label-copying-filename-progress";
|
|
|
|
[FluentReference]
|
|
const string InstallationFailed = "label-installation-failed";
|
|
|
|
[FluentReference]
|
|
const string CheckInstallLog = "label-check-install-log";
|
|
|
|
[FluentReference("filename")]
|
|
public const string Extracting = "label-extracting-filename";
|
|
|
|
[FluentReference("filename", "progress")]
|
|
public const string ExtractingProgress = "label-extracting-filename-progress";
|
|
|
|
[FluentReference]
|
|
public const string Continue = "button-continue";
|
|
|
|
[FluentReference]
|
|
const string Cancel = "button-cancel";
|
|
|
|
[FluentReference]
|
|
const string Retry = "button-retry";
|
|
|
|
[FluentReference]
|
|
const string Back = "button-back";
|
|
|
|
// Hide percentage indicators for files smaller than 25 MB
|
|
public const int ShowPercentageThreshold = 26214400;
|
|
|
|
enum Mode { Progress, Message, List }
|
|
|
|
readonly ModData modData;
|
|
readonly ModContent content;
|
|
readonly Dictionary<string, ModContent.ModSource> sources;
|
|
|
|
readonly Widget panel;
|
|
readonly LabelWidget titleLabel;
|
|
readonly ButtonWidget primaryButton;
|
|
readonly ButtonWidget secondaryButton;
|
|
|
|
// Progress panel
|
|
readonly Widget progressContainer;
|
|
readonly ProgressBarWidget progressBar;
|
|
readonly LabelWidget progressLabel;
|
|
|
|
// Message panel
|
|
readonly Widget messageContainer;
|
|
readonly LabelWidget messageLabel;
|
|
|
|
// List Panel
|
|
readonly Widget listContainer;
|
|
readonly ScrollPanelWidget listPanel;
|
|
readonly Widget listHeaderTemplate;
|
|
readonly LabelWidget labelListTemplate;
|
|
readonly ContainerWidget checkboxListTemplate;
|
|
readonly LabelWidget listLabel;
|
|
|
|
ModContent.ModPackage[] availablePackages;
|
|
IDictionary<string, bool> selectedPackages;
|
|
|
|
Mode visible = Mode.Progress;
|
|
|
|
[ObjectCreator.UseCtor]
|
|
public InstallFromSourceLogic(
|
|
Widget widget, ModData modData, ModContent content, Dictionary<string, ModContent.ModSource> sources)
|
|
{
|
|
this.modData = modData;
|
|
this.content = content;
|
|
this.sources = sources;
|
|
|
|
Log.AddChannel("install", "install.log");
|
|
|
|
panel = widget.Get("SOURCE_INSTALL_PANEL");
|
|
|
|
titleLabel = panel.Get<LabelWidget>("TITLE");
|
|
|
|
primaryButton = panel.Get<ButtonWidget>("PRIMARY_BUTTON");
|
|
secondaryButton = panel.Get<ButtonWidget>("SECONDARY_BUTTON");
|
|
|
|
// Progress view
|
|
progressContainer = panel.Get("PROGRESS");
|
|
progressContainer.IsVisible = () => visible == Mode.Progress;
|
|
progressBar = panel.Get<ProgressBarWidget>("PROGRESS_BAR");
|
|
progressLabel = panel.Get<LabelWidget>("PROGRESS_MESSAGE");
|
|
progressLabel.IsVisible = () => visible == Mode.Progress;
|
|
|
|
// Message view
|
|
messageContainer = panel.Get("MESSAGE");
|
|
messageContainer.IsVisible = () => visible == Mode.Message;
|
|
messageLabel = messageContainer.Get<LabelWidget>("MESSAGE_MESSAGE");
|
|
|
|
// List view
|
|
listContainer = panel.Get("LIST");
|
|
listContainer.IsVisible = () => visible == Mode.List;
|
|
|
|
listPanel = listContainer.Get<ScrollPanelWidget>("LIST_PANEL");
|
|
listHeaderTemplate = listPanel.Get("LIST_HEADER_TEMPLATE");
|
|
labelListTemplate = listPanel.Get<LabelWidget>("LABEL_LIST_TEMPLATE");
|
|
checkboxListTemplate = listPanel.Get<ContainerWidget>("CHECKBOX_LIST_TEMPLATE");
|
|
listPanel.RemoveChildren();
|
|
|
|
listLabel = listContainer.Get<LabelWidget>("LIST_MESSAGE");
|
|
|
|
DetectContentSources();
|
|
}
|
|
|
|
void DetectContentSources()
|
|
{
|
|
var message = FluentProvider.GetMessage(DetectingSources);
|
|
ShowProgressbar(FluentProvider.GetMessage(CheckingSources), () => message);
|
|
ShowBackRetry(DetectContentSources);
|
|
|
|
new Task(() =>
|
|
{
|
|
foreach (var kv in sources)
|
|
{
|
|
message = FluentProvider.GetMessage(SearchingSourceFor, "title", kv.Value.Title);
|
|
|
|
var sourceResolver = modData.ObjectCreator.CreateObject<ISourceResolver>($"{kv.Value.Type.Value}SourceResolver");
|
|
|
|
var path = sourceResolver.FindSourcePath(kv.Value);
|
|
if (path != null)
|
|
{
|
|
Log.Write("install", $"Using installer `{kv.Key}: {kv.Value.Title}` of type `{kv.Value.Type.Value}`:");
|
|
|
|
availablePackages = content.Packages.Values
|
|
.Where(p => p.Sources.Contains(kv.Key) && !p.IsInstalled())
|
|
.ToArray();
|
|
|
|
selectedPackages = availablePackages.ToDictionary(x => x.Identifier, y => y.Required);
|
|
|
|
// Ignore source if content is already installed
|
|
if (availablePackages.Length != 0)
|
|
{
|
|
Game.RunAfterTick(() =>
|
|
{
|
|
ShowList(kv.Value, FluentProvider.GetMessage(ContentPackageInstallation));
|
|
ShowContinueCancel(() => InstallFromSource(path, kv.Value));
|
|
});
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
var missingSources = content.Packages.Values
|
|
.Where(p => !p.IsInstalled())
|
|
.SelectMany(p => p.Sources)
|
|
.Select(d => sources[d]);
|
|
|
|
var gameSources = new HashSet<string>();
|
|
var digitalInstalls = new HashSet<string>();
|
|
|
|
foreach (var source in missingSources)
|
|
{
|
|
var sourceResolver = modData.ObjectCreator.CreateObject<ISourceResolver>($"{source.Type.Value}SourceResolver");
|
|
|
|
var availability = sourceResolver.GetAvailability();
|
|
|
|
if (availability == Availability.GameSource)
|
|
gameSources.Add(source.Title);
|
|
else if (availability == Availability.DigitalInstall)
|
|
digitalInstalls.Add(source.Title);
|
|
}
|
|
|
|
var options = new Dictionary<string, IEnumerable<string>>();
|
|
|
|
if (gameSources.Count != 0)
|
|
options.Add(FluentProvider.GetMessage(GameSources), gameSources);
|
|
|
|
if (digitalInstalls.Count != 0)
|
|
options.Add(FluentProvider.GetMessage(DigitalInstalls), digitalInstalls);
|
|
|
|
Game.RunAfterTick(() =>
|
|
{
|
|
ShowList(FluentProvider.GetMessage(GameContentNotFound), FluentProvider.GetMessage(AlternativeContentSources), options);
|
|
ShowBackRetry(DetectContentSources);
|
|
});
|
|
}).Start();
|
|
}
|
|
|
|
void InstallFromSource(string path, ModContent.ModSource modSource)
|
|
{
|
|
var message = "";
|
|
ShowProgressbar(FluentProvider.GetMessage(InstallingContent), () => message);
|
|
ShowDisabledCancel();
|
|
|
|
new Task(() =>
|
|
{
|
|
var extracted = new List<string>();
|
|
|
|
try
|
|
{
|
|
void RunSourceActions(MiniYamlNode contentPackageYaml)
|
|
{
|
|
var sourceActionListYaml = contentPackageYaml.Value.NodeWithKeyOrDefault("Actions");
|
|
if (sourceActionListYaml == null)
|
|
return;
|
|
|
|
foreach (var sourceActionNode in sourceActionListYaml.Value.Nodes)
|
|
{
|
|
var key = sourceActionNode.Key;
|
|
var split = key.IndexOf('@');
|
|
if (split != -1)
|
|
key = key[..split];
|
|
var sourceAction = modData.ObjectCreator.CreateObject<ISourceAction>($"{key}SourceAction");
|
|
sourceAction.RunActionOnSource(sourceActionNode.Value, path, modData, extracted, m => message = m);
|
|
}
|
|
}
|
|
|
|
var beforeInstall = modSource.Install.FirstOrDefault(x => x.Key == "BeforeInstall");
|
|
if (beforeInstall != null)
|
|
RunSourceActions(beforeInstall);
|
|
|
|
foreach (var packageInstallationNode in modSource.Install.Where(
|
|
x => x.Key == "ContentPackage" || x.Key.StartsWith("ContentPackage@", StringComparison.Ordinal)))
|
|
{
|
|
var packageName = packageInstallationNode.Value.NodeWithKeyOrDefault("Name")?.Value.Value;
|
|
if (!string.IsNullOrEmpty(packageName) && selectedPackages.TryGetValue(packageName, out var required) && required)
|
|
RunSourceActions(packageInstallationNode);
|
|
}
|
|
|
|
var afterInstall = modSource.Install.FirstOrDefault(x => x.Key == "AfterInstall");
|
|
if (afterInstall != null)
|
|
RunSourceActions(afterInstall);
|
|
|
|
Game.RunAfterTick(Ui.CloseWindow);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Write("install", e.ToString());
|
|
|
|
foreach (var f in extracted)
|
|
{
|
|
Log.Write("install", "Deleting " + f);
|
|
File.Delete(f);
|
|
}
|
|
|
|
Game.RunAfterTick(() =>
|
|
{
|
|
ShowMessage(FluentProvider.GetMessage(InstallationFailed), FluentProvider.GetMessage(CheckInstallLog));
|
|
ShowBackRetry(() => InstallFromSource(path, modSource));
|
|
});
|
|
}
|
|
}).Start();
|
|
}
|
|
|
|
void ShowMessage(string title, string message)
|
|
{
|
|
visible = Mode.Message;
|
|
titleLabel.GetText = () => title;
|
|
messageLabel.GetText = () => message;
|
|
|
|
primaryButton.Bounds.Y += messageContainer.Bounds.Height - panel.Bounds.Height;
|
|
secondaryButton.Bounds.Y += messageContainer.Bounds.Height - panel.Bounds.Height;
|
|
panel.Bounds.Y -= (messageContainer.Bounds.Height - panel.Bounds.Height) / 2;
|
|
panel.Bounds.Height = messageContainer.Bounds.Height;
|
|
}
|
|
|
|
void ShowProgressbar(string title, Func<string> getMessage)
|
|
{
|
|
visible = Mode.Progress;
|
|
titleLabel.GetText = () => title;
|
|
progressBar.IsIndeterminate = () => true;
|
|
|
|
var font = Game.Renderer.Fonts[progressLabel.Font];
|
|
var status = new CachedTransform<string, string>(s => WidgetUtils.TruncateText(s, progressLabel.Bounds.Width, font));
|
|
progressLabel.GetText = () => status.Update(getMessage());
|
|
|
|
primaryButton.Bounds.Y += progressContainer.Bounds.Height - panel.Bounds.Height;
|
|
secondaryButton.Bounds.Y += progressContainer.Bounds.Height - panel.Bounds.Height;
|
|
panel.Bounds.Y -= (progressContainer.Bounds.Height - panel.Bounds.Height) / 2;
|
|
panel.Bounds.Height = progressContainer.Bounds.Height;
|
|
}
|
|
|
|
void ShowList(ModContent.ModSource source, string message)
|
|
{
|
|
visible = Mode.List;
|
|
var titleText = source.Title;
|
|
titleLabel.GetText = () => titleText;
|
|
listLabel.GetText = () => message;
|
|
|
|
listPanel.RemoveChildren();
|
|
foreach (var package in availablePackages)
|
|
{
|
|
var containerWidget = (ContainerWidget)checkboxListTemplate.Clone();
|
|
var checkboxWidget = containerWidget.Get<CheckboxWidget>("PACKAGE_CHECKBOX");
|
|
var title = FluentProvider.GetMessage(package.Title);
|
|
checkboxWidget.GetText = () => title;
|
|
checkboxWidget.IsDisabled = () => package.Required;
|
|
checkboxWidget.IsChecked = () => selectedPackages[package.Identifier];
|
|
checkboxWidget.OnClick = () => selectedPackages[package.Identifier] = !selectedPackages[package.Identifier];
|
|
|
|
var contentPackageNode = source.Install.FirstOrDefault(x =>
|
|
x.Value.NodeWithKeyOrDefault("Name")?.Value.Value == package.Identifier);
|
|
|
|
var tooltipText = contentPackageNode?.Value.NodeWithKeyOrDefault(nameof(ModContent.ModSource.TooltipText))?.Value.Value;
|
|
var tooltipIcon = containerWidget.Get<ImageWidget>("PACKAGE_INFO");
|
|
tooltipIcon.IsVisible = () => !string.IsNullOrWhiteSpace(tooltipText);
|
|
tooltipIcon.GetTooltipText = () => tooltipText;
|
|
|
|
listPanel.AddChild(containerWidget);
|
|
}
|
|
|
|
primaryButton.Bounds.Y += listContainer.Bounds.Height - panel.Bounds.Height;
|
|
secondaryButton.Bounds.Y += listContainer.Bounds.Height - panel.Bounds.Height;
|
|
panel.Bounds.Y -= (listContainer.Bounds.Height - panel.Bounds.Height) / 2;
|
|
panel.Bounds.Height = listContainer.Bounds.Height;
|
|
}
|
|
|
|
void ShowList(string title, string message, Dictionary<string, IEnumerable<string>> groups)
|
|
{
|
|
visible = Mode.List;
|
|
titleLabel.GetText = () => title;
|
|
listLabel.GetText = () => message;
|
|
|
|
listPanel.RemoveChildren();
|
|
|
|
foreach (var kv in groups)
|
|
{
|
|
if (kv.Value.Any())
|
|
{
|
|
var groupTitle = kv.Key;
|
|
var headerWidget = listHeaderTemplate.Clone();
|
|
var headerTitleWidget = headerWidget.Get<LabelWidget>("LABEL");
|
|
headerTitleWidget.GetText = () => groupTitle;
|
|
listPanel.AddChild(headerWidget);
|
|
}
|
|
|
|
foreach (var i in kv.Value)
|
|
{
|
|
var item = i;
|
|
var labelWidget = (LabelWidget)labelListTemplate.Clone();
|
|
labelWidget.GetText = () => item;
|
|
listPanel.AddChild(labelWidget);
|
|
}
|
|
}
|
|
|
|
primaryButton.Bounds.Y += listContainer.Bounds.Height - panel.Bounds.Height;
|
|
secondaryButton.Bounds.Y += listContainer.Bounds.Height - panel.Bounds.Height;
|
|
panel.Bounds.Y -= (listContainer.Bounds.Height - panel.Bounds.Height) / 2;
|
|
panel.Bounds.Height = listContainer.Bounds.Height;
|
|
}
|
|
|
|
void ShowContinueCancel(Action continueAction)
|
|
{
|
|
primaryButton.OnClick = continueAction;
|
|
var primaryButtonText = FluentProvider.GetMessage(Continue);
|
|
primaryButton.GetText = () => primaryButtonText;
|
|
primaryButton.Visible = true;
|
|
|
|
secondaryButton.OnClick = Ui.CloseWindow;
|
|
var secondaryButtonText = FluentProvider.GetMessage(Cancel);
|
|
secondaryButton.GetText = () => secondaryButtonText;
|
|
secondaryButton.Visible = true;
|
|
secondaryButton.Disabled = false;
|
|
Game.RunAfterTick(Ui.ResetTooltips);
|
|
}
|
|
|
|
void ShowBackRetry(Action retryAction)
|
|
{
|
|
primaryButton.OnClick = retryAction;
|
|
var primaryButtonText = FluentProvider.GetMessage(Retry);
|
|
primaryButton.GetText = () => primaryButtonText;
|
|
primaryButton.Visible = true;
|
|
|
|
secondaryButton.OnClick = Ui.CloseWindow;
|
|
var secondaryButtonText = FluentProvider.GetMessage(Back);
|
|
secondaryButton.GetText = () => secondaryButtonText;
|
|
secondaryButton.Visible = true;
|
|
secondaryButton.Disabled = false;
|
|
Game.RunAfterTick(Ui.ResetTooltips);
|
|
}
|
|
|
|
void ShowDisabledCancel()
|
|
{
|
|
primaryButton.Visible = false;
|
|
secondaryButton.Disabled = true;
|
|
Game.RunAfterTick(Ui.ResetTooltips);
|
|
}
|
|
}
|
|
}
|