Skip to content

Commit

Permalink
Split download into chunks for maximum download speed
Browse files Browse the repository at this point in the history
And animate the progress bar for launch task
  • Loading branch information
TrinityDevelopers committed Apr 20, 2020
1 parent f962e1d commit 02bdb10
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 21 deletions.
1 change: 1 addition & 0 deletions ZenovaLauncher/Pages/PlayPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
Background="{DynamicResource LauncherControlTransientLowBackgroundBrush}"
Minimum="0"
Maximum="{Binding LaunchInfo.ProgressMax, Mode=OneWay}"
local:ProgressBarSmoother.AnimateTime="{Binding LaunchInfo.AnimateTime, Mode=OneWay}"
local:ProgressBarSmoother.SmoothValue="{Binding LaunchInfo.ProgressCurrent, Mode=OneWay}"
IsIndeterminate="{Binding LaunchInfo.IsProgressIndeterminate}" />
<TextBlock
Expand Down
1 change: 1 addition & 0 deletions ZenovaLauncher/Pages/ProfilesPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@
Background="{DynamicResource LauncherControlTransientLowBackgroundBrush}"
Minimum="0"
Maximum="{Binding LaunchInfo.ProgressMax, Mode=OneWay}"
local:ProgressBarSmoother.AnimateTime="{Binding LaunchInfo.AnimateTime, Mode=OneWay}"
local:ProgressBarSmoother.SmoothValue="{Binding LaunchInfo.ProgressCurrent, Mode=OneWay}"
IsIndeterminate="{Binding LaunchInfo.IsProgressIndeterminate}" />
<TextBlock
Expand Down
58 changes: 46 additions & 12 deletions ZenovaLauncher/Profiles/ProfileLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public void LaunchProfile(Profile p)
if (!p.Version.IsInstalled)
installStatus = await Download(p);
LaunchInfo.Status = LaunchStatus.InitializingLaunch;
if (installStatus)
await Launch(p);
Expand All @@ -79,6 +80,7 @@ private async Task Launch(Profile p)

try
{
LaunchInfo.Status = LaunchStatus.Launching;
var pkg = await AppDiagnosticInfo.RequestInfoForPackageAsync(MINECRAFT_PACKAGE_FAMILY);
if (pkg.Count > 0)
await pkg[0].LaunchAsync();
Expand All @@ -92,11 +94,14 @@ private async Task Launch(Profile p)
}
}

private async Task DeploymentProgressWrapper(IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> t)
private async Task DeploymentProgressWrapper(IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> t, LaunchStatus status)
{
TaskCompletionSource<int> src = new TaskCompletionSource<int>();
t.Progress += (v, p) =>
{
if (LaunchInfo.Status != status)
LaunchInfo.Status = status;
LaunchInfo.LaunchCurrent = p.percentage;
Debug.WriteLine("Deployment progress: " + p.state + " " + p.percentage + "%");
};
t.Completed += (v, p) =>
Expand Down Expand Up @@ -179,19 +184,19 @@ private async Task ReRegisterPackage(string gameDir)
if (!pkg.IsDevelopmentMode)
{
BackupMinecraftDataForRemoval();
await DeploymentProgressWrapper(new PackageManager().RemovePackageAsync(pkg.Id.FullName, 0));
await DeploymentProgressWrapper(new PackageManager().RemovePackageAsync(pkg.Id.FullName, 0), LaunchStatus.LaunchRemovePackage);
}
else
{
Debug.WriteLine("Package is in development mode");
await DeploymentProgressWrapper(new PackageManager().RemovePackageAsync(pkg.Id.FullName, RemovalOptions.PreserveApplicationData));
await DeploymentProgressWrapper(new PackageManager().RemovePackageAsync(pkg.Id.FullName, RemovalOptions.PreserveApplicationData), LaunchStatus.LaunchRemovePackage);
}
Debug.WriteLine("Removal of package done: " + pkg.Id.FullName);
break;
}
Debug.WriteLine("Registering package");
string manifestPath = Path.Combine(gameDir, "AppxManifest.xml");
await DeploymentProgressWrapper(new PackageManager().RegisterPackageAsync(new Uri(manifestPath), null, DeploymentOptions.DevelopmentMode));
await DeploymentProgressWrapper(new PackageManager().RegisterPackageAsync(new Uri(manifestPath), null, DeploymentOptions.DevelopmentMode), LaunchStatus.LaunchRegisterPackage);
Debug.WriteLine("App re-register done!");
RestoreMinecraftDataFromReinstall();
}
Expand Down Expand Up @@ -229,7 +234,7 @@ await downloader.Download(v.UUID, "1", dlPath, (current, total) =>
if (total.HasValue)
LaunchInfo.DownloadSize = total.Value;
}
LaunchInfo.DownloadedBytes = current;
LaunchInfo.DownloadedBytes += current;
}, cancelSource.Token);
Debug.WriteLine("Download complete");
}
Expand Down Expand Up @@ -281,16 +286,17 @@ public class ProfileLaunchInfo : NotifyPropertyChangedBase
private long _zipProcessed;
private long _zipTotal;
private string _zipCurrentItem;
private long _launchCurrent;

public LaunchStatus Status
{
get { return _status; }
set { _status = value; OnPropertyChanged("Status"); OnPropertyChanged("IsProgressIndeterminate"); OnPropertyChanged("DisplayStatus"); }
set { _status = value; OnPropertyChanged("Status"); OnPropertyChanged("IsProgressIndeterminate"); OnPropertyChanged("DisplayStatus"); OnPropertyChanged("AnimateTime"); OnPropertyChanged("ProgressMax"); }
}

public bool IsProgressIndeterminate
{
get { return Status == LaunchStatus.InitializingDownload || Status == LaunchStatus.InitializingExtraction; }
get { return Status == LaunchStatus.InitializingDownload || Status == LaunchStatus.InitializingExtraction || Status == LaunchStatus.InitializingLaunch || Status == LaunchStatus.Launching; }
}

public long DownloadedBytes
Expand Down Expand Up @@ -323,6 +329,14 @@ public string ZipCurrentItem
set { _zipCurrentItem = value; OnPropertyChanged("ZipCurrentItem"); OnPropertyChanged("DisplayStatus"); }
}

public long LaunchCurrent
{
get { return _launchCurrent; }
set { _launchCurrent = value; OnPropertyChanged("LaunchCurrent"); OnPropertyChanged("DisplayStatus"); OnPropertyChanged("ProgressCurrent"); }
}

public long LaunchTotal => 100 * 2;

public long ProgressCurrent
{
get
Expand All @@ -331,6 +345,10 @@ public long ProgressCurrent
return ZipProcessed;
if (Status == LaunchStatus.Downloading)
return DownloadedBytes;
if (Status == LaunchStatus.LaunchRemovePackage)
return LaunchCurrent;
if (Status == LaunchStatus.LaunchRegisterPackage)
return 100 + LaunchCurrent;
return 0;
}
}
Expand All @@ -343,6 +361,8 @@ public long ProgressMax
return ZipTotal;
if (Status == LaunchStatus.Downloading)
return DownloadSize;
if (Status == LaunchStatus.LaunchRegisterPackage || Status == LaunchStatus.LaunchRemovePackage)
return LaunchTotal;
return 1;
}
}
Expand All @@ -351,15 +371,25 @@ public string DisplayStatus
{
get
{
if (Status == LaunchStatus.InitializingDownload)
return "Preparing...";
if (Status == LaunchStatus.Downloading)
return "Downloading " + ((double)DownloadedBytes / 1024 / 1024).ToString("N2") + " MB / " + ((double)DownloadSize / 1024 / 1024).ToString("N2") + " MB";
if (Status == LaunchStatus.InitializingExtraction)
return "Extracting...";
return "Extracting";
if (Status == LaunchStatus.Extracting)
return "Extracting " + ZipCurrentItem;
return "";
if (Status == LaunchStatus.Launching)
return "Finalizing";
return "Preparing";
}
}

public TimeSpan AnimateTime
{
get
{
if (Status == LaunchStatus.LaunchRemovePackage || Status == LaunchStatus.LaunchRegisterPackage || Status == LaunchStatus.Launching)
return new TimeSpan(0, 0, 0, 0, 500);
return new TimeSpan(0, 0, 0, 0, 100);
}
}

Expand All @@ -371,7 +401,11 @@ public enum LaunchStatus
InitializingDownload,
Downloading,
InitializingExtraction,
Extracting
Extracting,
InitializingLaunch,
LaunchRemovePackage,
LaunchRegisterPackage,
Launching
}
}
}
74 changes: 65 additions & 9 deletions ZenovaLauncher/Profiles/VersionDownloader.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -36,26 +40,78 @@ private async Task<XDocument> PostXmlAsync(string url, XDocument data)
}
}

private async Task DownloadFile(string url, string to, DownloadProgress progress, CancellationToken cancellationToken)
private async Task DownloadFileChunk(string url, DownloadProgress progress, long transferred, long? totalSize, CancellationToken cancellationToken, Tuple<long, long> readRange, int index, ConcurrentDictionary<int, String> tempFilesDictionary)
{
using (var resp = await _client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
httpRequestMessage.Headers.Range = new RangeHeaderValue(readRange.Item1, readRange.Item2);
using (var resp = await _client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
string tempFilePath = Path.GetTempFileName();
Debug.WriteLine("DownloadChunk" + index + ": " + tempFilePath);
using (var fileStream = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write, FileShare.Write))
using (var inStream = await resp.Content.ReadAsStreamAsync())
using (var outStream = new FileStream(to, FileMode.Create))
{
long? totalSize = resp.Content.Headers.ContentLength;
progress(0, totalSize);
long transferred = 0;
byte[] buf = new byte[1024 * 1024];
while (true)
{
int n = await inStream.ReadAsync(buf, 0, buf.Length, cancellationToken);
if (n == 0)
break;
await outStream.WriteAsync(buf, 0, n, cancellationToken);
transferred += n;
progress(transferred, totalSize);
await fileStream.WriteAsync(buf, 0, n, cancellationToken);
progress(n, totalSize);
}
tempFilesDictionary.TryAdd((int)index, tempFilePath);
}
}
}

private async Task DownloadFile(string url, string to, DownloadProgress progress, CancellationToken cancellationToken, int parallelDownloads = 0)
{
if (parallelDownloads <= 0)
parallelDownloads = Environment.ProcessorCount;
long? totalSize;
long transferred = 0;
using (var resp = await _client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
totalSize = resp.Content.Headers.ContentLength;
Debug.WriteLine("TotalSize of Download: " + totalSize);
progress(0, totalSize);
}
using (var outStream = new FileStream(to, FileMode.Create))
{
ConcurrentDictionary<int, String> tempFilesDictionary = new ConcurrentDictionary<int, String>();

List<Tuple<long, long>> readRanges = new List<Tuple<long, long>>();
for (int chunk = 0; chunk < parallelDownloads - 1; chunk++)
{
var range = new Tuple<long, long>
(
chunk * (totalSize.GetValueOrDefault() / parallelDownloads),
((chunk + 1) * (totalSize.GetValueOrDefault() / parallelDownloads)) - 1
);
readRanges.Add(range);
}

readRanges.Add(new Tuple<long, long>
(
readRanges.Any() ? readRanges.Last().Item2 + 1 : 0,
totalSize.GetValueOrDefault() - 1
));

int index = 0;
var DownloadTasks = readRanges.Select(readRange =>
{
var task = DownloadFileChunk(url, progress, transferred, totalSize, cancellationToken, readRange, index, tempFilesDictionary);
index++;
return task;
});
await Task.WhenAll(DownloadTasks);

foreach (var tempFile in tempFilesDictionary.OrderBy(b => b.Key))
{
byte[] tempFileBytes = File.ReadAllBytes(tempFile.Value);
await outStream.WriteAsync(tempFileBytes, 0, tempFileBytes.Length, cancellationToken);
File.Delete(tempFile.Value);
}
}
}
Expand Down

0 comments on commit 02bdb10

Please sign in to comment.