Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 46 additions & 76 deletions src/Files.App/ServicesImplementation/SideloadUpdateService.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,46 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Windows.ApplicationModel;
using Windows.Management.Deployment;
using Windows.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Files.Backend.Services;
using Files.Shared;
using Windows.ApplicationModel;
using Windows.Management.Deployment;
using Windows.Storage;

namespace Files.App.ServicesImplementation
{
public class SideloadUpdateService : ObservableObject, IUpdateService
public sealed class SideloadUpdateService : ObservableObject, IUpdateService, IDisposable
{
private const string SideloadStable = "https://cdn.files.community/files/stable/Files.Package.appinstaller";
private const string SideloadPreview = "https://cdn.files.community/files/preview/Files.Package.appinstaller";
private const string SIDELOAD_STABLE = "https://cdn.files.community/files/stable/Files.Package.appinstaller";
private const string SIDELOAD_PREVIEW = "https://cdn.files.community/files/preview/Files.Package.appinstaller";

private bool _isUpdateAvailable;
private bool _isUpdating;
private int _downloadPercentage;

private readonly HttpClient _client = new(new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(1) });

private readonly Dictionary<string, string> _sideloadVersion = new()
{
{ "Files", SideloadStable },
{ "FilesPreview", SideloadPreview }
{ "Files", SIDELOAD_STABLE },
{ "FilesPreview", SIDELOAD_PREVIEW }
};

private const string TemporaryUpdatePackageName = "UpdatePackage.msix";
private const string TEMPORARY_UPDATE_PACKAGE_NAME = "UpdatePackage.msix";

private ILogger Logger { get; } = Ioc.Default.GetService<ILogger>();
private ILogger? Logger { get; } = Ioc.Default.GetService<ILogger>();

private string PackageName { get; } = Package.Current.Id.Name;

private Version PackageVersion { get; } = new(Package.Current.Id.Version.Major,
Package.Current.Id.Version.Minor, Package.Current.Id.Version.Build, Package.Current.Id.Version.Revision);

private Uri DownloadUri { get; set; }
private Uri? DownloadUri { get; set; }

public bool IsUpdateAvailable
{
Expand All @@ -53,12 +54,6 @@ public bool IsUpdating
private set => SetProperty(ref _isUpdating, value);
}

public int DownloadPercentage
{
get => _downloadPercentage;
private set => SetProperty(ref _downloadPercentage, value);
}

public async Task DownloadUpdates()
{
await ApplyPackageUpdate();
Expand All @@ -71,114 +66,87 @@ public Task DownloadMandatoryUpdates()

public async Task CheckForUpdates()
{
Logger.Info($"SIDELOAD: Checking for updates...");
await CheckForRemoteUpdate(_sideloadVersion[PackageName]);
}

private async Task CheckForRemoteUpdate(string uri)
{
if (string.IsNullOrEmpty(uri))
{
throw new ArgumentNullException(nameof(uri));
}

try
{
using var client = new WebClient();
using var stream = await client.OpenReadTaskAsync(uri);
Logger?.Info($"SIDELOAD: Checking for updates...");

await using var stream = await _client.GetStreamAsync(_sideloadVersion[PackageName]);

// Deserialize AppInstaller.
XmlSerializer xml = new XmlSerializer(typeof(AppInstaller));
var appInstaller = (AppInstaller)xml.Deserialize(stream);
var appInstaller = (AppInstaller?)xml.Deserialize(stream);

if (appInstaller == null)
{
throw new ArgumentNullException(nameof(appInstaller));
}

var remoteVersion = new Version(appInstaller.Version);

Logger.Info($"SIDELOAD: Current Package Name: {PackageName}");
Logger.Info($"SIDELOAD: Remote Package Name: {appInstaller.MainBundle.Name}");
Logger.Info($"SIDELOAD: Current Version: {PackageVersion}");
Logger.Info($"SIDELOAD: Remote Version: {remoteVersion}");
Logger?.Info($"SIDELOAD: Current Package Name: {PackageName}");
Logger?.Info($"SIDELOAD: Remote Package Name: {appInstaller.MainBundle.Name}");
Logger?.Info($"SIDELOAD: Current Version: {PackageVersion}");
Logger?.Info($"SIDELOAD: Remote Version: {remoteVersion}");

// Check details and version number.
if (appInstaller.MainBundle.Name.Equals(PackageName) && remoteVersion.CompareTo(PackageVersion) > 0)
{
Logger.Info("SIDELOAD: Update found.");
Logger.Info("SIDELOAD: Starting background download.");
Logger?.Info("SIDELOAD: Update found.");
Logger?.Info("SIDELOAD: Starting background download.");
DownloadUri = new Uri(appInstaller.MainBundle.Uri);
await StartBackgroundDownload();
}
else
{
Logger.Warn("SIDELOAD: Update not found.");
Logger?.Warn("SIDELOAD: Update not found.");
IsUpdateAvailable = false;
}
}
catch (Exception e)
{
Logger.Error(e, e.Message);
Logger?.Error(e, e.Message);
}
}

private async Task StartBackgroundDownload()
{
try
{
using var client = new WebClient();
client.DownloadFileCompleted += BackgroundDownloadCompleted;
client.DownloadProgressChanged += BackgroundDownloadProgressChanged;

// Use temp folder instead?
var tempDownloadPath = ApplicationData.Current.LocalFolder.Path + "\\" + TemporaryUpdatePackageName;
var tempDownloadPath = ApplicationData.Current.LocalFolder.Path + "\\" + TEMPORARY_UPDATE_PACKAGE_NAME;

Stopwatch timer = Stopwatch.StartNew();

await client.DownloadFileTaskAsync(DownloadUri, tempDownloadPath);
await using (var stream = await _client.GetStreamAsync(DownloadUri))
await using (var fileStream = new FileStream(tempDownloadPath, FileMode.OpenOrCreate))
await stream.CopyToAsync(fileStream);

timer.Stop();
var timespan = timer.Elapsed;

Logger.Info($"Download time taken: {timespan.Hours:00}:{timespan.Minutes:00}:{timespan.Seconds:00}");
Logger?.Info($"Download time taken: {timespan.Hours:00}:{timespan.Minutes:00}:{timespan.Seconds:00}");

client.DownloadFileCompleted -= BackgroundDownloadCompleted;
client.DownloadProgressChanged -= BackgroundDownloadProgressChanged;
IsUpdateAvailable = true;
}
catch (Exception e)
{
Logger.Error(e, e.Message);
Logger?.Error(e, e.Message);
}
}

private void BackgroundDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
DownloadPercentage = e.ProgressPercentage;
}

private void BackgroundDownloadCompleted(object sender, AsyncCompletedEventArgs e)
{
IsUpdateAvailable = true;
}

private async Task ApplyPackageUpdate()
{
if (!IsUpdateAvailable)
{
return;
}

IsUpdating = true;

PackageManager pm = new PackageManager();
DeploymentResult result = null;
DeploymentResult? result = null;

try
{
await Task.Run(async () =>
{
var bundlePath = new Uri(ApplicationData.Current.LocalFolder.Path + "\\" + TemporaryUpdatePackageName);
var bundlePath = new Uri(ApplicationData.Current.LocalFolder.Path + Path.DirectorySeparatorChar +
TEMPORARY_UPDATE_PACKAGE_NAME);

var deployment = pm.RequestAddPackageAsync(
bundlePath,
Expand All @@ -194,28 +162,30 @@ await Task.Run(async () =>
catch (Exception e)
{
if (result?.ExtendedErrorCode != null)
{
Logger.Info(result.ErrorText);
}
Logger?.Info(result.ErrorText);

Logger.Error(e, e.Message);
Logger?.Error(e, e.Message);
}
finally
{
// Reset fields.
IsUpdating = false;
IsUpdateAvailable = false;
DownloadPercentage = 0;
DownloadUri = null;
}
}

public void Dispose()
{
_client?.Dispose();
}
}

/// <summary>
/// AppInstaller class to hold information about remote updates.
/// </summary>
[XmlRoot(ElementName = "AppInstaller", Namespace = "http://schemas.microsoft.com/appx/appinstaller/2018")]
public class AppInstaller
public sealed class AppInstaller
{
[XmlElement("MainBundle")]
public MainBundle MainBundle { get; set; }
Expand All @@ -227,7 +197,7 @@ public class AppInstaller
public string Version { get; set; }
}

public class MainBundle
public sealed class MainBundle
{
[XmlAttribute("Name")]
public string Name { get; set; }
Expand Down
23 changes: 6 additions & 17 deletions src/Files.App/ServicesImplementation/UpdateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ namespace Files.App.ServicesImplementation
{
internal sealed class UpdateService : ObservableObject, IUpdateService
{
private StoreContext _storeContext;
private IList<StorePackageUpdate> _updatePackages;
private StoreContext? _storeContext;
private IList<StorePackageUpdate>? _updatePackages;

private bool IsMandatory => _updatePackages?.Where(e => e.Mandatory).ToList().Count >= 1;

Expand All @@ -23,28 +23,17 @@ internal sealed class UpdateService : ObservableObject, IUpdateService
public bool IsUpdateAvailable
{
get => _isUpdateAvailable;
set
{
_isUpdateAvailable = value;
OnPropertyChanged(nameof(IsUpdateAvailable));
}
set => SetProperty(ref _isUpdateAvailable, value);
}

private bool _isUpdating;

public bool IsUpdating
{
get => _isUpdating;
private set
{
_isUpdating = value;
OnPropertyChanged(nameof(IsUpdating));
}
private set => SetProperty(ref _isUpdating, value);
}

// TODO: This needs to be implemented in this service.
public int DownloadPercentage { get; }

public UpdateService()
{
_updatePackages = new List<StorePackageUpdate>();
Expand Down Expand Up @@ -108,7 +97,7 @@ public async Task CheckForUpdates()
private async Task DownloadAndInstall()
{
App.SaveSessionTabs();
var downloadOperation = _storeContext.RequestDownloadAndInstallStorePackageUpdatesAsync(_updatePackages);
var downloadOperation = _storeContext?.RequestDownloadAndInstallStorePackageUpdatesAsync(_updatePackages);
await downloadOperation.AsTask();
}

Expand Down Expand Up @@ -166,7 +155,7 @@ private void OnUpdateCompleted()
{
IsUpdating = false;
IsUpdateAvailable = false;
_updatePackages.Clear();
_updatePackages?.Clear();
}

private void OnUpdateCancelled()
Expand Down
2 changes: 0 additions & 2 deletions src/Files.Backend/Services/IUpdateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ public interface IUpdateService : INotifyPropertyChanged
/// </summary>
bool IsUpdating { get; }

int DownloadPercentage { get; }

Task DownloadUpdates();

Task DownloadMandatoryUpdates();
Expand Down