Files
v2rayN/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs
T
2dust 2c90ed1a94 Move configuration data classes to handlers
Rename the database and text configuration managers as handlers, move configuration data classes into Handler.Data, and relocate ProfileExManager. Update references across applications and tests.
2026-11-14 20:35:50 +08:00

445 lines
14 KiB
C#

namespace ServiceLib.ViewModels;
public partial class CheckUpdateViewModel : MyReactiveObject
{
private const string _geo = "GeoFiles";
private readonly ECoreType _v2rayN = ECoreType.v2rayN;
private List<CheckUpdateModel> _lstUpdated = [];
private static readonly string _tag = "CheckUpdateViewModel";
public EventChannel<RxVoid> ReloadRequested { get; } = new();
public BulkObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = [];
public ReactiveCommand<RxVoid, RxVoid> CheckUpdateCmd { get; }
public ReactiveCommand<RxVoid, RxVoid> CheckOnlyCmd { get; }
[Reactive] public partial bool EnableUpdateViaProxy { get; set; }
public CheckUpdateViewModel()
{
_config = ConfigTextHandler.Config;
CheckUpdateCmd = ReactiveCommand.CreateFromTask(CheckUpdate);
CheckUpdateCmd.ThrownExceptions.Subscribe(ex =>
{
Logging.SaveLog(_tag, ex);
_ = UpdateView(_v2rayN, ex.Message);
});
CheckOnlyCmd = ReactiveCommand.CreateFromTask(CheckOnly);
CheckOnlyCmd.ThrownExceptions.Subscribe(ex =>
{
Logging.SaveLog(_tag, ex);
_ = UpdateView(_v2rayN, ex.Message);
});
EnableUpdateViaProxy = _config.CheckUpdateItem.UpdateViaProxy;
this.WhenAnyValue(x => x.EnableUpdateViaProxy)
.Subscribe(c => _ = OnUpdateViaProxyChanged());
AppEvents.HasUpdateNotified
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(bl => RefreshCheckUpdateItems(bl));
RefreshCheckUpdateItems(true);
}
private void RefreshCheckUpdateItems(bool hasUpdate)
{
if (!hasUpdate)
{
return;
}
var models = CoreInfoManager.Instance.GetCheckUpdateCoreTypes()
.Select(t => GetCheckUpdateModel(t))
.ToList();
models.Add(GetGeoFileCheckUpdateModel());
CheckUpdateModels.ReplaceRange(models);
}
private CheckUpdateModel GetCheckUpdateModel(ECoreType coreType)
{
if (coreType == _v2rayN && Utils.IsPackagedInstall())
{
return new()
{
IsSelected = false,
IsCheckPreRelease = false,
ShowCheckPreRelease = false,
CoreType = coreType,
IsGeoFile = false,
Remarks = ResUI.menuCheckUpdate + $" ({ResUI.MsgNotSupport})",
};
}
AppManager.Instance.LastCheckUpdateResults.TryGetValue(coreType, out var lastResult);
return new()
{
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType.ToString()) ?? true,
IsCheckPreRelease = _config.CheckUpdateItem.CheckPreReleaseCoreTypes?.Contains(coreType.ToString()) ?? false,
ShowCheckPreRelease = CoreInfoManager.Instance.GetCheckPreRelease(coreType, true),
CoreType = coreType,
IsGeoFile = false,
Remarks = lastResult ?? ResUI.menuCheckUpdate,
};
}
private CheckUpdateModel GetGeoFileCheckUpdateModel()
{
return new()
{
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(_geo) ?? true,
IsCheckPreRelease = false,
ShowCheckPreRelease = false,
CoreType = null,
IsGeoFile = true,
Remarks = ResUI.menuCheckUpdate,
};
}
private async Task OnUpdateViaProxyChanged()
{
if (_config.CheckUpdateItem.UpdateViaProxy == EnableUpdateViaProxy)
{
return;
}
_config.CheckUpdateItem.UpdateViaProxy = EnableUpdateViaProxy;
await SaveSelectedCoreTypes();
}
private async Task SaveSelectedCoreTypes()
{
_config.CheckUpdateItem.SelectedCoreTypes =
CheckUpdateModels.Where(t => t.IsSelected == true)
.Select(t => t.CoreTypeForStorage)
.ToList();
_config.CheckUpdateItem.CheckPreReleaseCoreTypes =
CheckUpdateModels.Where(t => t.IsCheckPreRelease == true)
.Select(t => t.CoreTypeForStorage)
.ToList();
await ConfigTextHandler.SaveConfig();
}
private async Task CheckOnly()
{
await Task.Run(CheckOnlyTask);
}
private async Task CheckUpdate()
{
await Task.Run(CheckUpdateTask);
}
private async Task CheckOnlyTask()
{
await SaveSelectedCoreTypes();
for (var k = CheckUpdateModels.Count - 1; k >= 0; k--)
{
var item = CheckUpdateModels[k];
if (item.IsSelected != true)
{
continue;
}
await UpdateView(item.CoreType, "...");
if (item.IsGeoFile || item.CoreType == null)
{
await UpdateView(item.CoreType, ResUI.menuCheckOnly + $" ({ResUI.MsgNotSupport})");
continue;
}
if (item.CoreType == null)
{
await UpdateView(item.CoreType, ResUI.MsgNotSupport);
continue;
}
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(EnableUpdateViaProxy);
var updateService = new UpdateService(downloadService, _config, async (success, msg) => await Task.CompletedTask);
var result = await updateService.CheckHasUpdateOnly(item.CoreType.Value, item.IsCheckPreRelease);
if (result.Success && result.Version != null)
{
await UpdateView(item.CoreType, string.Format(ResUI.MsgCheckUpdateHasNewVersion, item.CoreType, result.Version));
}
else
{
await UpdateView(item.CoreType, result.Msg);
}
}
}
private async Task CheckUpdateTask()
{
_lstUpdated.Clear();
_lstUpdated = CheckUpdateModels
.Where(x => x.IsSelected == true)
.Select(x => new CheckUpdateModel()
{
CoreType = x.CoreType,
IsGeoFile = x.IsGeoFile
})
.ToList();
await SaveSelectedCoreTypes();
for (var k = CheckUpdateModels.Count - 1; k >= 0; k--)
{
var item = CheckUpdateModels[k];
if (item.IsSelected != true)
{
continue;
}
await UpdateView(item.CoreType, "...");
if (item.IsGeoFile)
{
await CheckUpdateGeo();
}
else if (item.CoreType == _v2rayN)
{
if (Utils.IsPackagedInstall())
{
await UpdateView(_v2rayN, ResUI.MsgNotSupport);
continue;
}
await CheckUpdateN(item.IsCheckPreRelease);
}
else if (item.CoreType.HasValue)
{
await CheckUpdateCore(item, item.IsCheckPreRelease);
}
}
await UpdateFinished();
}
private void UpdatedPlusPlus(ECoreType? coreType, string fileName)
{
var item = _lstUpdated.FirstOrDefault(x => x.CoreType == coreType);
if (item == null)
{
return;
}
item.IsFinished = true;
if (!fileName.IsNullOrEmpty())
{
item.FileName = fileName;
}
}
private async Task CheckUpdateGeo()
{
async Task _updateUI(bool success, string msg)
{
await UpdateView(null, msg);
if (success)
{
UpdatedPlusPlus(null, "");
}
}
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(EnableUpdateViaProxy);
await new UpdateService(downloadService, _config, _updateUI).UpdateGeoFileAll()
.ContinueWith(t => UpdatedPlusPlus(null, ""));
}
private async Task CheckUpdateN(bool preRelease)
{
async Task _updateUI(bool success, string msg)
{
await UpdateView(_v2rayN, msg);
if (success)
{
await UpdateView(_v2rayN, ResUI.OperationSuccess);
UpdatedPlusPlus(_v2rayN, msg);
}
}
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(EnableUpdateViaProxy);
await new UpdateService(downloadService, _config, _updateUI).CheckUpdateGuiN(preRelease)
.ContinueWith(t => UpdatedPlusPlus(_v2rayN, ""));
}
private async Task CheckUpdateCore(CheckUpdateModel model, bool preRelease)
{
async Task _updateUI(bool success, string msg)
{
await UpdateView(model.CoreType, msg);
if (success)
{
await UpdateView(model.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfullyMore);
UpdatedPlusPlus(model.CoreType, msg);
}
}
if (model.CoreType.HasValue)
{
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(EnableUpdateViaProxy);
await new UpdateService(downloadService, _config, _updateUI).CheckUpdateCore(model.CoreType.Value, preRelease)
.ContinueWith(t => UpdatedPlusPlus(model.CoreType, ""));
}
}
private async Task UpdateFinished()
{
if (_lstUpdated.Count > 0 && _lstUpdated.Count(x => x.IsFinished == true) == _lstUpdated.Count)
{
await UpdateFinishedSub(false);
await Task.Delay(2000);
await UpgradeCore();
if (_lstUpdated.Any(x => x.CoreType == _v2rayN && x.IsFinished == true))
{
await Task.Delay(1000);
await UpgradeN();
}
await Task.Delay(1000);
await UpdateFinishedSub(true);
}
}
private async Task UpdateFinishedSub(bool blReload)
{
RxSchedulers.MainThreadScheduler.Schedule(() =>
{
_ = UpdateFinishedResult(blReload);
});
await Task.CompletedTask;
}
public async Task UpdateFinishedResult(bool blReload)
{
if (blReload)
{
ReloadRequested.Publish();
}
else
{
await CoreManager.Instance.CoreStop();
}
}
private async Task UpgradeN()
{
try
{
var fileName = _lstUpdated.FirstOrDefault(x => x.CoreType == _v2rayN)?.FileName;
if (fileName.IsNullOrEmpty())
{
return;
}
if (!Utils.UpgradeAppExists(out var upgradeFileName))
{
await UpdateView(_v2rayN, ResUI.UpgradeAppNotExistTip);
NoticeManager.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip);
Logging.SaveLog("UpgradeApp does not exist");
return;
}
var id = ProcUtils.ProcessStart(upgradeFileName, fileName, Utils.StartupPath());
if (id > 0)
{
await AppManager.Instance.AppExitAsync(true);
}
}
catch (Exception ex)
{
await UpdateView(_v2rayN, ex.Message);
}
}
private async Task UpgradeCore()
{
foreach (var item in _lstUpdated)
{
if (item.FileName.IsNullOrEmpty() || item.IsGeoFile)
{
continue;
}
var fileName = item.FileName;
if (!File.Exists(fileName))
{
continue;
}
var coreTypeStr = item.CoreType?.ToString() ?? "";
var toPath = Utils.GetBinPath("", coreTypeStr);
if (fileName.Contains(".tar.gz"))
{
FileUtils.DecompressTarFile(fileName, toPath);
var dir = new DirectoryInfo(toPath);
if (dir.Exists)
{
foreach (var subDir in dir.GetDirectories())
{
FileUtils.CopyDirectory(subDir.FullName, toPath, false, true);
subDir.Delete(true);
}
}
}
else if (fileName.Contains(".gz"))
{
FileUtils.DecompressFile(fileName, toPath, coreTypeStr);
}
else
{
FileUtils.ZipExtractToFile(fileName, toPath, "geo");
}
if (Utils.IsNonWindows())
{
var filesList = new DirectoryInfo(toPath).GetFiles().Select(u => u.FullName).ToList();
foreach (var file in filesList)
{
await Utils.SetLinuxChmod(Path.Combine(toPath, coreTypeStr.ToLower()));
}
}
await UpdateView(item.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfully);
if (File.Exists(fileName))
{
File.Delete(fileName);
}
}
}
private async Task UpdateView(ECoreType? coreType, string msg)
{
var item = new CheckUpdateModel()
{
CoreType = coreType,
IsGeoFile = coreType == null,
Remarks = msg,
};
RxSchedulers.MainThreadScheduler.Schedule(() =>
{
_ = UpdateViewResult(item);
});
await Task.CompletedTask;
}
public async Task UpdateViewResult(CheckUpdateModel model)
{
var found = CheckUpdateModels.FirstOrDefault(t => t.CoreType == model.CoreType && t.IsGeoFile == model.IsGeoFile);
if (found == null)
{
return;
}
found.Remarks = model.Remarks;
await Task.CompletedTask;
}
}