diff --git a/references/Steam4NET b/references/Steam4NET index 19674e89d..8dcff45ab 160000 --- a/references/Steam4NET +++ b/references/Steam4NET @@ -1 +1 @@ -Subproject commit 19674e89da6385330f83771b0d88a2e1a67578a8 +Subproject commit 8dcff45ab4eb569173c48313ca241799a3b3aed5 diff --git a/references/SteamAchievementManager b/references/SteamAchievementManager index 197e1df42..19b585c58 160000 --- a/references/SteamAchievementManager +++ b/references/SteamAchievementManager @@ -1 +1 @@ -Subproject commit 197e1df429851c177451da4f075eb6531a2ef795 +Subproject commit 19b585c58e933f282675c4d5d20ae275a2621aad diff --git a/release-template.md b/release-template.md index cf4e304eb..d5d079c05 100644 --- a/release-template.md +++ b/release-template.md @@ -1,5 +1,8 @@ ### 版本亮点 1. ASF 升级至 V5.2.5.5 +2. 新增 Steam 云存档管理功能,可自行上传或删除 Steam 云存档 +3. 库存游戏支持筛选支持 Steam 云存档的游戏 +4. 更新 Avalonia 至 0.10.15 + + + + + + + + + + + + + + + diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Controls/CustomFilePicker.xaml.cs b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Controls/CustomFilePicker.xaml.cs index eaf11329c..2ea7e2bcb 100644 --- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Controls/CustomFilePicker.xaml.cs +++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Controls/CustomFilePicker.xaml.cs @@ -4,6 +4,7 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Markup.Xaml; using Avalonia.Metadata; +using System.Application.UI.Resx; using System.Collections; using System.IO; using static System.Application.FilePicker2; @@ -75,8 +76,6 @@ namespace System.Application.UI.Views.Controls private async void CustomFilePicker_Tapped(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { - FilePickerFileType fileTypes = new ValueTuple[] { ("Image Files", FileEx.ImageFileExtensions) }; - //if (string.IsNullOrEmpty(FileExtensions) || FileExtensions == "*") //{ // fileTypes = new ValueTuple[] { ("All Files", new[] { "*" }) }; @@ -89,10 +88,15 @@ namespace System.Application.UI.Views.Controls // }; //} - await PickAsync((file) => + var result = await PickAsync(PickOptions.Images); + if (result != null) { - FileName = file; - }, fileTypes); + FileName = result.FullPath; + } + else + { + Toast.Show(AppResources.PleaseSelect); + } } } } diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/GameListPage.axaml b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/GameListPage.axaml index 8cf0b421d..8e68032f8 100644 --- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/GameListPage.axaml +++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/GameListPage.axaml @@ -169,24 +169,31 @@ --> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/GameListPage/CloudArchiveWindow.axaml.cs b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/GameListPage/CloudArchiveWindow.axaml.cs new file mode 100644 index 000000000..7d42a40fb --- /dev/null +++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/GameListPage/CloudArchiveWindow.axaml.cs @@ -0,0 +1,23 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using System.Application.UI.ViewModels; + +namespace System.Application.UI.Views.Windows +{ + public class CloudArchiveWindow : FluentWindow + { + public CloudArchiveWindow() : base() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/NewVersionWindow.axaml b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/NewVersionWindow.axaml index cb813959b..3c099bc50 100644 --- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/NewVersionWindow.axaml +++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/NewVersionWindow.axaml @@ -44,6 +44,7 @@ Margin="10,0"> + + + CloudArchiveWindow.axaml + + + \ No newline at end of file diff --git a/src/ST.Client.Desktop.Windows/Services/Implementation/SteamworksLocalApiServiceImpl.cs b/src/ST.Client.Desktop.Windows/Services/Implementation/SteamworksLocalApiServiceImpl.cs index 976eb091e..acbfa24ab 100644 --- a/src/ST.Client.Desktop.Windows/Services/Implementation/SteamworksLocalApiServiceImpl.cs +++ b/src/ST.Client.Desktop.Windows/Services/Implementation/SteamworksLocalApiServiceImpl.cs @@ -131,6 +131,7 @@ namespace System.Application.Services.Implementation return SteamClient.SteamApps008.GetAvailableGameLanguages(); } + #region SteamUserStats public bool GetStatValue(string name, out int value) { return SteamClient.SteamUserStats.GetStatValue(name, out value); @@ -214,10 +215,59 @@ namespace System.Application.Services.Implementation { SteamClient.SteamUserStats.RequestGlobalAchievementPercentages(); } + #endregion public void RunCallbacks(bool server) { SteamClient.RunCallbacks(server); } + + #region SteamRemoteStorage + + public bool GetCloudArchiveQuota(out ulong totalBytes, out ulong availableBytes) + { + return SteamClient.SteamRemoteStorage.GetQuota(out totalBytes, out availableBytes); + } + + public List? GetCloudArchiveFiles() + { + List files = new List(); + + int fileCount = SteamClient.SteamRemoteStorage.GetFileCount(); + for (int i = 0; i < fileCount; ++i) + { + string name = SteamClient.SteamRemoteStorage.GetFileNameAndSize(i, out int length); + var file = new SteamRemoteFile(name, length, SteamClient.SteamRemoteStorage.FileExists(name) + , SteamClient.SteamRemoteStorage.FilePersisted(name), SteamClient.SteamRemoteStorage.GetFileTimestamp(name)) + { + SyncPlatforms = (SteamKit2.ERemoteStoragePlatform)SteamClient.SteamRemoteStorage.GetSyncPlatforms(name), + }; + files.Add(file); + } + + return files; + } + + public int FileRead(string name, byte[] buffer) + { + return SteamClient.SteamRemoteStorage.FileRead(name, buffer); + } + + public bool FileWrite(string name, byte[] buffer) + { + return SteamClient.SteamRemoteStorage.FileWrite(name, buffer); + } + + public bool FileForget(string name) + { + return SteamClient.SteamRemoteStorage.FileForget(name); + } + + public bool FileDelete(string name) + { + return SteamClient.SteamRemoteStorage.FileDelete(name); + } + + #endregion } } \ No newline at end of file diff --git a/src/ST.Client/Models/Enums/SteamAppRunType.cs b/src/ST.Client/Models/Enums/SteamAppRunType.cs new file mode 100644 index 000000000..0b3f137df --- /dev/null +++ b/src/ST.Client/Models/Enums/SteamAppRunType.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Application.Models +{ + public enum SteamAppRunType + { + Idle, + UnlockAchievement, + CloudManager, + } +} diff --git a/src/ST.Client/Models/Steam/SteamApp.cs b/src/ST.Client/Models/Steam/SteamApp.cs index 339554abd..6d308f80c 100644 --- a/src/ST.Client/Models/Steam/SteamApp.cs +++ b/src/ST.Client/Models/Steam/SteamApp.cs @@ -535,21 +535,15 @@ namespace System.Application.Models public SteamAppPropertyTable? ChangesData => _properties; - //public event EventHandler? Modified; - - //private void OnEntryModified(object sender, EventArgs e) - //{ - // var modified = Modified; - // if (modified == null) - // { - // return; - // } - // modified(this, new EventArgs()); - //} - - public Process? StartSteamAppProcess(bool show = false) + public Process? StartSteamAppProcess(SteamAppRunType runType = SteamAppRunType.Idle) { - string arguments = $"-clt app {(show ? "" : "-silence")} -id {AppId}"; + var arg = runType switch + { + SteamAppRunType.UnlockAchievement => "-achievement", + SteamAppRunType.CloudManager => "-cloudmanager", + _ => "-silence", + }; + string arguments = $"-clt app {arg} -id {AppId}"; if (OperatingSystem2.IsWindows()) { return Process = Process2.Start(IApplication.ProgramPath, arguments); diff --git a/src/ST.Client/Models/Steam/SteamRemoteFile.cs b/src/ST.Client/Models/Steam/SteamRemoteFile.cs new file mode 100644 index 000000000..3c7dab4fd --- /dev/null +++ b/src/ST.Client/Models/Steam/SteamRemoteFile.cs @@ -0,0 +1,70 @@ +using ReactiveUI; +using SteamKit2; +using System; +using System.Application.Services; +using System.Collections.Generic; +using System.IO; +using System.Text; +using FilePath = System.IO.Path; + +namespace System.Application.Models +{ + public class SteamRemoteFile : ReactiveObject + { + static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + + public string Name { get; private set; } + + public bool Exists { get; } + + public bool IsPersisted { get; } + + public long Size { get; } + + public DateTimeOffset Timestamp { get; } + + public ERemoteStoragePlatform SyncPlatforms { get; set; } + + public SteamRemoteFile(string name) + { + Name = name; + } + + public SteamRemoteFile(string name, long length, bool exists, bool isPersisted, long timestamp) + { + Name = name; + Size = length; + Exists = exists; + IsPersisted = isPersisted; + Timestamp = timestamp.ToDateTimeOffsetS(); + } + + public int Read(byte[] buffer) + { + return ISteamworksLocalApiService.Instance.FileRead(Name, buffer); + } + + public byte[] ReadAllBytes() + { + byte[] buffer = new byte[Size]; + int read = Read(buffer); + if (read != buffer.Length) throw new IOException("Could not read entire file."); + return buffer; + } + + public bool WriteAllBytes(byte[] buffer) + { + return ISteamworksLocalApiService.Instance.FileWrite(Name, buffer); + } + + public bool Forget() + { + return ISteamworksLocalApiService.Instance.FileForget(Name); + } + + public bool Delete() + { + return ISteamworksLocalApiService.Instance.FileDelete(Name); + } + } +} diff --git a/src/ST.Client/Services/ISteamworksLocalApiService.cs b/src/ST.Client/Services/ISteamworksLocalApiService.cs index a33ab9e9b..b58033ba7 100644 --- a/src/ST.Client/Services/ISteamworksLocalApiService.cs +++ b/src/ST.Client/Services/ISteamworksLocalApiService.cs @@ -77,5 +77,17 @@ namespace System.Application.Services int Result { get; } } + + bool GetCloudArchiveQuota(out ulong totalBytes, out ulong availableBytes); + + List? GetCloudArchiveFiles(); + + int FileRead(string name, byte[] buffer); + + bool FileWrite(string name, byte[] buffer); + + bool FileForget(string name); + + bool FileDelete(string name); } } \ No newline at end of file diff --git a/src/ST.Client/Services/IViewModelManager.cs b/src/ST.Client/Services/IViewModelManager.cs index 3c6c35231..7297b59a6 100644 --- a/src/ST.Client/Services/IViewModelManager.cs +++ b/src/ST.Client/Services/IViewModelManager.cs @@ -25,6 +25,8 @@ namespace System.Application.Services void InitUnlockAchievement(int appid); + void InitCloudManageMain(int appid); + /// /// 打开托盘菜单窗口 /// diff --git a/src/ST.Client/Services/Implementation/SteamworksLocalApiServiceImpl.cs b/src/ST.Client/Services/Implementation/SteamworksLocalApiServiceImpl.cs index 566bb20fa..a6e466f72 100644 --- a/src/ST.Client/Services/Implementation/SteamworksLocalApiServiceImpl.cs +++ b/src/ST.Client/Services/Implementation/SteamworksLocalApiServiceImpl.cs @@ -88,5 +88,41 @@ namespace System.Application.Services.Implementation public void RunCallbacks(bool server) { } + + #region SteamRemoteStorage + + public bool GetCloudArchiveQuota(out ulong totalBytes, out ulong availableBytes) + { + totalBytes = 0; + availableBytes = 0; + return false; + } + + public List? GetCloudArchiveFiles() + { + return null; + } + + public int FileRead(string name, byte[] buffer) + { + return 0; + } + + public bool FileWrite(string name, byte[] buffer) + { + return false; + } + + public bool FileForget(string name) + { + return false; + } + + public bool FileDelete(string name) + { + return false; + } + + #endregion } } \ No newline at end of file diff --git a/src/ST.Client/Services/Implementation/ViewModelManager.cs b/src/ST.Client/Services/Implementation/ViewModelManager.cs index 5a66640b3..66235e29f 100644 --- a/src/ST.Client/Services/Implementation/ViewModelManager.cs +++ b/src/ST.Client/Services/Implementation/ViewModelManager.cs @@ -11,8 +11,7 @@ namespace System.Application.Services.Implementation { internal sealed class ViewModelManager : ReactiveObject, IViewModelManager { - MainWindowViewModel? mainWindow; - AchievementWindowViewModel? achievementWindow; + //MainWindowViewModel? mainWindow; TaskBarWindowViewModel? taskbarWindow; readonly CompositeDisposable compositeDisposable = new(); @@ -29,15 +28,19 @@ namespace System.Application.Services.Implementation { try { - if (appidUnlockAchievementHasValue) + if (isCloudManageMain) { - achievementWindow = new AchievementWindowViewModel(appidUnlockAchievement); - mMainWindow = achievementWindow; + mMainWindow = new CloudArchiveWindowViewModel(steamaAppid); + } + else if (isUnlockAchievementMain) + { + mMainWindow = new AchievementWindowViewModel(steamaAppid); } else { - mainWindow = new MainWindowViewModel(); - mMainWindow = mainWindow; + //mainWindow = new MainWindowViewModel(); + //mMainWindow = mainWindow; + mMainWindow = new MainWindowViewModel(); } } catch (Exception ex) @@ -51,7 +54,7 @@ namespace System.Application.Services.Implementation { if (OperatingSystem2.IsWindows() && StartupOptions.Value.HasNotifyIcon) { - taskbarWindow = new TaskBarWindowViewModel(mainWindow); + taskbarWindow = new TaskBarWindowViewModel(mMainWindow); } } catch (Exception ex) @@ -62,13 +65,20 @@ namespace System.Application.Services.Implementation } } - int appidUnlockAchievement; - bool appidUnlockAchievementHasValue; + int steamaAppid; + bool isUnlockAchievementMain; + bool isCloudManageMain; public void InitUnlockAchievement(int appid) { - appidUnlockAchievement = appid; - appidUnlockAchievementHasValue = true; + steamaAppid = appid; + isUnlockAchievementMain = true; + } + + public void InitCloudManageMain(int appid) + { + steamaAppid = appid; + isCloudManageMain = true; } //public Window GetMainWindow() diff --git a/src/ST.Client/Settings/GameLibrarySettings.Avalonia.cs b/src/ST.Client/Settings/GameLibrarySettings.Avalonia.cs index 05b9813c9..a4e349775 100644 --- a/src/ST.Client/Settings/GameLibrarySettings.Avalonia.cs +++ b/src/ST.Client/Settings/GameLibrarySettings.Avalonia.cs @@ -13,13 +13,23 @@ namespace System.Application.Settings static readonly SerializableProperty _GameIsInstalledFilter = GetProperty(defaultValue: false, autoSave: true); /// - /// 游戏类型筛选状态列表 + /// 已安装游戏筛选 /// [SupportedOSPlatform("Windows7.0")] [SupportedOSPlatform("macOS")] [SupportedOSPlatform("Linux")] public static SerializableProperty GameIsInstalledFilter => _GameIsInstalledFilter; + static readonly SerializableProperty _GameIsCloudArchiveFilter = GetProperty(defaultValue: false, autoSave: true); + + /// + /// 支持云存档游戏筛选状态 + /// + [SupportedOSPlatform("Windows7.0")] + [SupportedOSPlatform("macOS")] + [SupportedOSPlatform("Linux")] + public static SerializableProperty GameIsCloudArchiveFilter => _GameIsCloudArchiveFilter; + static readonly SerializableProperty>? _GameTypeFiltres = IApplication.IsDesktopPlatform ? GetProperty(defaultValue: new List { SteamAppType.Game, SteamAppType.Application, SteamAppType.Demo, SteamAppType.Beta }, autoSave: true) : null; /// diff --git a/src/ST.Client/SteamApiUrls.cs b/src/ST.Client/SteamApiUrls.cs index b69d4cd1b..7387b86ec 100644 --- a/src/ST.Client/SteamApiUrls.cs +++ b/src/ST.Client/SteamApiUrls.cs @@ -29,12 +29,13 @@ namespace System.Application public const string STEAM_VIDEO_URL = "https://store.steampowered.com/video/watch/{0}/"; public const string STEAM_REGISTERKEY_URL = "https://store.steampowered.com/account/registerkey?key={0}"; public const string STEAM_FRIENDMESSAGESLOG_URL = "https://help.steampowered.com/zh-cn/accountdata/GetFriendMessagesLog"; + public const string STEAM_REMOTESTORAGE_URL = "https://store.steampowered.com/account/remotestorage"; + public const string STEAM_KEY_URL = "https://store.steampowered.com/account/registerkey?key={0}"; public const string STEAM_INSTALL_URL = "steam://install/{0}"; public const string STEAM_RUNGAME_URL = "steam://rungameid/{0}"; public const string STEAM_NAVGAME_URL = "steam://nav/games/details/{0}"; public const string STEAM_OPENURL = "steam://openurl/{0}"; - public const string STEAM_RUN_KEYURL = "steam://openurl/https://store.steampowered.com/account/registerkey?key={0}"; public const string STEAMDB_USERINFO_URL = "https://api.steamdb.ml/v1/users/{0}"; public const string STEAMDB_APPINFO_URL = "https://api.steamdb.ml/v1/apps/{0}"; diff --git a/src/ST.Client/UI/Resx/AppResources.Designer.cs b/src/ST.Client/UI/Resx/AppResources.Designer.cs index 7b4d1ed07..4a48b7ba7 100644 --- a/src/ST.Client/UI/Resx/AppResources.Designer.cs +++ b/src/ST.Client/UI/Resx/AppResources.Designer.cs @@ -1607,6 +1607,15 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 创建时间 的本地化字符串。 + /// + public static string CreateTime { + get { + return ResourceManager.GetString("CreateTime", resourceCulture); + } + } + /// /// 查找类似 选择文件 的本地化字符串。 /// @@ -1787,6 +1796,15 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 下载 {0} 成功 的本地化字符串。 + /// + public static string Download_Success_FileNameTip { + get { + return ResourceManager.GetString("Download_Success_FileNameTip", resourceCulture); + } + } + /// /// 查找类似 已下载 的本地化字符串。 /// @@ -1931,6 +1949,15 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 {0}个文件 的本地化字符串。 + /// + public static string FileNumber { + get { + return ResourceManager.GetString("FileNumber", resourceCulture); + } + } + /// /// 查找类似 文件路径 的本地化字符串。 /// @@ -1940,6 +1967,15 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 文件大小 的本地化字符串。 + /// + public static string FileSize { + get { + return ResourceManager.GetString("FileSize", resourceCulture); + } + } + /// /// 查找类似 文件大小过大 的本地化字符串。 /// @@ -2039,6 +2075,24 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 确定要删除当前选中的存档数据吗? 的本地化字符串。 + /// + public static string GameList_CloudArchiveDeleteTip { + get { + return ResourceManager.GetString("GameList_CloudArchiveDeleteTip", resourceCulture); + } + } + + /// + /// 查找类似 Steam云存档管理 的本地化字符串。 + /// + public static string GameList_CloudArchiveManager { + get { + return ResourceManager.GetString("GameList_CloudArchiveManager", resourceCulture); + } + } + /// /// 查找类似 是否移除全部挂时长的游戏? 的本地化字符串。 /// @@ -2183,6 +2237,15 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 仅显示支持Steam云存档的游戏 的本地化字符串。 + /// + public static string GameList_OnlyCloudArchive { + get { + return ResourceManager.GetString("GameList_OnlyCloudArchive", resourceCulture); + } + } + /// /// 查找类似 仅显示已安装的游戏 的本地化字符串。 /// @@ -2436,6 +2499,15 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 手动上传存档文件 的本地化字符串。 + /// + public static string GameList_UploadCloudArchiveFile { + get { + return ResourceManager.GetString("GameList_UploadCloudArchiveFile", resourceCulture); + } + } + /// /// 查找类似 游戏工具 的本地化字符串。 /// @@ -5763,6 +5835,24 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 上传失败 的本地化字符串。 + /// + public static string UploadFailed { + get { + return ResourceManager.GetString("UploadFailed", resourceCulture); + } + } + + /// + /// 查找类似 上传成功 的本地化字符串。 + /// + public static string UploadSuccess { + get { + return ResourceManager.GetString("UploadSuccess", resourceCulture); + } + } + /// /// 查找类似 上传您的头像 的本地化字符串。 /// diff --git a/src/ST.Client/UI/Resx/AppResources.resx b/src/ST.Client/UI/Resx/AppResources.resx index d1b2546cc..acb461599 100644 --- a/src/ST.Client/UI/Resx/AppResources.resx +++ b/src/ST.Client/UI/Resx/AppResources.resx @@ -591,6 +591,9 @@ AAAAA-BBBBB-CCCC3 仅显示已安装的游戏 + + 仅显示支持Steam云存档的游戏 + 浏览安装文件夹 @@ -2267,4 +2270,34 @@ AAAAA-BBBBB-CCCC3 原名 + + 文件大小 + + + 创建时间 + + + Steam云存档管理 + + + 确定要删除当前选中的存档数据吗? + + + 手动上传存档文件 + + + 刷新列表 + + + {0}个文件 + + + 上传成功 + + + 上传失败 + + + 下载 {0} 成功 + \ No newline at end of file diff --git a/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs b/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs index 1b53ec0ed..63e070221 100644 --- a/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs +++ b/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs @@ -97,7 +97,7 @@ namespace System.Application.UI.ViewModels #region InitTabItems - AddTabItem(); + //AddTabItem(); AddTabItem(); if (IApplication.IsDesktopPlatform) { diff --git a/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs b/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs index d5bf606e1..98616c3b1 100644 --- a/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs +++ b/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs @@ -70,6 +70,16 @@ namespace System.Application.UI.ViewModels }; } + Func PredicateCloudArchive(bool isCloudArchiveFilter) + { + return s => + { + if (isCloudArchiveFilter) + return s.IsCloudArchive; + return true; + }; + } + public GameListPageViewModel() { _IconKey = nameof(GameListPageViewModel); @@ -91,6 +101,8 @@ namespace System.Application.UI.ViewModels IsInstalledFilter = GameLibrarySettings.GameIsInstalledFilter.Value; + //IsCloudArchiveFilter = GameLibrarySettings.GameIsInstalledFilter.Value; + this.WhenValueChanged(x => x.IsInstalledFilter, false) .Subscribe(s => GameLibrarySettings.GameIsInstalledFilter.Value = s); @@ -98,6 +110,8 @@ namespace System.Application.UI.ViewModels var installFilter = this.WhenAnyValue(x => x.IsInstalledFilter).Select(PredicateInstalled); + var isCloudArchiveFilter = this.WhenAnyValue(x => x.IsCloudArchiveFilter).Select(PredicateCloudArchive); + var typeFilter = this.WhenAnyValue(x => x.EnableAppTypeFiltres).Select(PredicateType); this.WhenAnyValue(x => x.AppTypeFiltres) @@ -119,6 +133,7 @@ namespace System.Application.UI.ViewModels .Filter(nameFilter) .Filter(typeFilter) .Filter(installFilter) + .Filter(isCloudArchiveFilter) .ObserveOn(RxApp.MainThreadScheduler) .Sort(SortExpressionComparer.Ascending(x => x.DisplayName).ThenByDescending(s => s.SizeOnDisk)) .Bind(out _SteamApps) @@ -195,6 +210,14 @@ namespace System.Application.UI.ViewModels set => this.RaiseAndSetIfChanged(ref _IsInstalledFilter, value); } + private bool _IsCloudArchiveFilter; + + public bool IsCloudArchiveFilter + { + get => _IsCloudArchiveFilter; + set => this.RaiseAndSetIfChanged(ref _IsCloudArchiveFilter, value); + } + private bool _IsAppInfoOpen; public bool IsAppInfoOpen @@ -388,7 +411,7 @@ namespace System.Application.UI.ViewModels if (result.IsOK()) { Toast.Show(AppResources.GameList_RuningWait); - app.StartSteamAppProcess(true); + app.StartSteamAppProcess(SteamAppRunType.UnlockAchievement); SteamConnectService.Current.RuningSteamApps.TryAdd(app.AppId, app); } break; @@ -398,6 +421,21 @@ namespace System.Application.UI.ViewModels } } + public static async void ManageCloudArchive_Click(SteamApp app) + { + if (!ISteamService.Instance.IsRunningSteamProcess) + { + Toast.Show(AppResources.GameList_SteamNotRuning); + return; + } + if (app.IsCloudArchive) + { + Toast.Show(AppResources.GameList_RuningWait); + app.StartSteamAppProcess(SteamAppRunType.CloudManager); + SteamConnectService.Current.RuningSteamApps.TryAdd(app.AppId, app); + } + } + public static void AppGridReSize() { UISettings.AppGridSize.Value = UISettings.AppGridSize.Value == 200 ? 150 : 200; diff --git a/src/ST.Client/UI/ViewModels/Windows/GameListPage/CloudArchiveWindowViewModel.cs b/src/ST.Client/UI/ViewModels/Windows/GameListPage/CloudArchiveWindowViewModel.cs new file mode 100644 index 000000000..a7162b50b --- /dev/null +++ b/src/ST.Client/UI/ViewModels/Windows/GameListPage/CloudArchiveWindowViewModel.cs @@ -0,0 +1,188 @@ +using DynamicData; +using DynamicData.Binding; +using ReactiveUI; +using System.Application.Models; +using System.Application.Services; +using System.Application.UI.Resx; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Properties; +using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Application.UI.ViewModels +{ + public class CloudArchiveWindowViewModel : WindowViewModel + { + public static string DisplayName => AppResources.GameList_CloudArchiveManager; + + private int AppId { get; } + + #region 云存档列表 + private readonly ReadOnlyObservableCollection? _CloudArchivews; + private readonly SourceList _CloudArchivewSourceList; + + public ReadOnlyObservableCollection? CloudArchivews => _CloudArchivews; + + public string CloudArchivewCountStr => string.Format(AppResources.FileNumber, CloudArchivews?.Count ?? 0); + #endregion + + //private bool? _IsCheckAll; + + //public bool? IsCheckAll + //{ + // get => _IsCheckAll; + // set => this.RaiseAndSetIfChanged(ref _IsCheckAll, value); + //} + + public bool IsCloudEnabledForAccount { get; } + + private bool _IsCloudEnabledForApp; + + public bool IsCloudEnabledForApp + { + get => _IsCloudEnabledForApp; + set => this.RaiseAndSetIfChanged(ref _IsCloudEnabledForApp, value); + } + + private int _UsedQutoa; + + public int UsedQutoa + { + get => _UsedQutoa; + set => this.RaiseAndSetIfChanged(ref _UsedQutoa, value); + } + + private int _TotalQutoa; + + public int TotalQutoa + { + get => _TotalQutoa; + set => this.RaiseAndSetIfChanged(ref _TotalQutoa, value); + } + + public CloudArchiveWindowViewModel(int appid) + { + Title = GetTitleByDisplayName(DisplayName); + + SteamConnectService.Current.Initialize(appid); + + if (SteamConnectService.Current.IsConnectToSteam == false) + { + MessageBox.ShowAsync(AppResources.Achievement_Warning_1, Title, MessageBox.Button.OK).ContinueWith(s => + { + EnforceClose(); + }).Wait(); + } + + _CloudArchivewSourceList = new SourceList(); + + _CloudArchivewSourceList + .Connect() + .ObserveOn(RxApp.MainThreadScheduler) + .Sort(SortExpressionComparer.Descending(x => x.Timestamp).ThenByAscending(x => x.Name)) + .Bind(out _CloudArchivews) + .Subscribe(_ => this.RaisePropertyChanged(nameof(CloudArchivewCountStr))); + + AppId = appid; + string name = ISteamworksLocalApiService.Instance.GetAppData((uint)appid, "name"); + name ??= appid.ToString(); + Title = Constants.HARDCODED_APP_NAME + " | " + name; + ToastService.Current.Set(AppResources.Achievement_LoadData); + + RefreshList(); + } + + public void RefreshList() + { + var results = ISteamworksLocalApiService.Instance.GetCloudArchiveFiles(); + _CloudArchivewSourceList.Clear(); + if (results.Any_Nullable()) + _CloudArchivewSourceList.AddRange(results); + + ISteamworksLocalApiService.Instance.GetCloudArchiveQuota(out var totalBytes, out var availBytes); + TotalQutoa = (int)(totalBytes / 1024 / 1024); + UsedQutoa = (int)((totalBytes - availBytes) / 1024 / 1024); + } + + public async void ClearAllFiles() + { + var result = await MessageBox.ShowAsync(AppResources.Achievement_ResetWaring_1, Title, MessageBox.Button.OKCancel); + if (result.IsOK()) + { + RefreshList(); + } + } + + public async void UploadFile() + { + var result = await FilePicker2.PickAsync(new PickOptions + { + PickerTitle = "", + Multiselect = true, + }); + + if (result != null) + { + var file = new SteamRemoteFile(result.FileName.ToLowerInvariant()); + + try + { + byte[] data = File.ReadAllBytes(result.FullPath); + if (!file.WriteAllBytes(data)) + { + throw new IOException("Upload File Write Failed"); + } + Toast.Show(AppResources.UploadSuccess); + } + catch (IOException) + { + Toast.Show(AppResources.UploadFailed); + } + } + + RefreshList(); + } + + public async void DownloadFile(SteamRemoteFile remoteFile) + { + var result = await FilePicker2.SaveAsync(new SaveOptions + { + PickerTitle = "Download " + remoteFile.Name, + InitialFileName = remoteFile.Name, + }); + + if (result != null) + { + using var dest = result.OpenWrite(); + var b = remoteFile.ReadAllBytes(); + await dest.WriteAsync(b, 0, b.Length); + dest.Close(); + + Toast.Show(string.Format(AppResources.Download_Success_FileNameTip, remoteFile.Name)); + } + } + + public async void DeleteFile(SteamRemoteFile remoteFile) + { + var result = await MessageBox.ShowAsync(AppResources.GameList_CloudArchiveDeleteTip, Title, MessageBox.Button.OKCancel); + if (result.IsOK()) + { + if (remoteFile.Delete()) + { + RefreshList(); + } + } + } + + void EnforceClose() + { + Process.GetCurrentProcess().Kill(); + } + } +} \ No newline at end of file diff --git a/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.cs b/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.cs index 393d564f9..fc62e9dbf 100644 --- a/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.cs +++ b/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.cs @@ -43,7 +43,7 @@ namespace System.Application.UI.ViewModels } } - TaskBarWindowViewModel(WindowViewModel? mainwindow) : this(mainwindow is MainWindowViewModel mainwindow2 ? mainwindow2 : null) + public TaskBarWindowViewModel(WindowViewModel? mainwindow) : this(mainwindow is MainWindowViewModel mainwindow2 ? mainwindow2 : null) { }