mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-17 02:31:28 -07:00
Draft: game history (+ other minor fixes)
This commit is contained in:
parent
b4a9710177
commit
dfcd5b6777
@ -327,6 +327,7 @@ namespace Bloxstrap
|
||||
int gameClientPid;
|
||||
bool startEventSignalled;
|
||||
|
||||
// TODO: figure out why this is causing roblox to block for some users
|
||||
using (var startEvent = new EventWaitHandle(false, EventResetMode.ManualReset, AppData.StartEvent))
|
||||
{
|
||||
startEvent.Reset();
|
||||
@ -387,7 +388,7 @@ namespace Bloxstrap
|
||||
|
||||
if (App.Settings.Prop.EnableActivityTracking || autoclosePids.Any())
|
||||
{
|
||||
using var ipl = new InterProcessLock("Watcher");
|
||||
using var ipl = new InterProcessLock("Watcher", TimeSpan.FromSeconds(5));
|
||||
|
||||
if (ipl.IsAcquired)
|
||||
Process.Start(Paths.Process, $"-watcher \"{args}\"");
|
||||
|
@ -9,6 +9,7 @@ namespace Bloxstrap.Integrations
|
||||
private const string GameJoiningEntry = "[FLog::Output] ! Joining game";
|
||||
private const string GameJoiningPrivateServerEntry = "[FLog::GameJoinUtil] GameJoinUtil::joinGamePostPrivateServer";
|
||||
private const string GameJoiningReservedServerEntry = "[FLog::GameJoinUtil] GameJoinUtil::initiateTeleportToReservedServer";
|
||||
private const string GameJoiningUniverseEntry = "[FLog::GameJoinLoadTime] Report game_join_loadtime:";
|
||||
private const string GameJoiningUDMUXEntry = "[FLog::Network] UDMUX Address = ";
|
||||
private const string GameJoinedEntry = "[FLog::Network] serverId:";
|
||||
private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:";
|
||||
@ -17,6 +18,7 @@ namespace Bloxstrap.Integrations
|
||||
private const string GameLeavingEntry = "[FLog::SingleSurfaceApp] leaveUGCGameInternal";
|
||||
|
||||
private const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)";
|
||||
private const string GameJoiningUniversePattern = @"universeid:([0-9]+)";
|
||||
private const string GameJoiningUDMUXPattern = @"UDMUX Address = ([0-9\.]+), Port = [0-9]+ \| RCC Server Address = ([0-9\.]+), Port = [0-9]+";
|
||||
private const string GameJoinedEntryPattern = @"serverId: ([0-9\.]+)\|[0-9]+";
|
||||
private const string GameMessageEntryPattern = @"\[BloxstrapRPC\] (.*)";
|
||||
@ -39,8 +41,10 @@ namespace Bloxstrap.Integrations
|
||||
|
||||
// these are values to use assuming the player isn't currently in a game
|
||||
// hmm... do i move this to a model?
|
||||
public DateTime ActivityTimeJoined;
|
||||
public bool ActivityInGame = false;
|
||||
public long ActivityPlaceId = 0;
|
||||
public long ActivityUniverseId = 0;
|
||||
public string ActivityJobId = "";
|
||||
public string ActivityMachineAddress = "";
|
||||
public bool ActivityMachineUDMUX = false;
|
||||
@ -48,6 +52,8 @@ namespace Bloxstrap.Integrations
|
||||
public string ActivityLaunchData = "";
|
||||
public ServerType ActivityServerType = ServerType.Public;
|
||||
|
||||
public List<ActivityHistoryEntry> ActivityHistory = new();
|
||||
|
||||
public bool IsDisposed = false;
|
||||
|
||||
public async void Start()
|
||||
@ -131,6 +137,7 @@ namespace Bloxstrap.Integrations
|
||||
return deeplink;
|
||||
}
|
||||
|
||||
// TODO: i need to double check how this handles failed game joins (connection error, invalid permissions, etc)
|
||||
private void ReadLogEntry(string entry)
|
||||
{
|
||||
const string LOG_IDENT = "ActivityWatcher::ReadLogEntry";
|
||||
@ -151,6 +158,8 @@ namespace Bloxstrap.Integrations
|
||||
|
||||
if (!ActivityInGame && ActivityPlaceId == 0)
|
||||
{
|
||||
// We are not in a game, nor are in the process of joining one
|
||||
|
||||
if (entry.Contains(GameJoiningPrivateServerEntry))
|
||||
{
|
||||
// we only expect to be joining a private server if we're not already in a game
|
||||
@ -189,13 +198,28 @@ namespace Bloxstrap.Integrations
|
||||
}
|
||||
else if (!ActivityInGame && ActivityPlaceId != 0)
|
||||
{
|
||||
if (entry.Contains(GameJoiningUDMUXEntry))
|
||||
// We are not confirmed to be in a game, but we are in the process of joining one
|
||||
|
||||
if (entry.Contains(GameJoiningUniverseEntry))
|
||||
{
|
||||
var match = Regex.Match(entry, GameJoiningUniversePattern);
|
||||
|
||||
if (match.Groups.Count != 2)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Failed to assert format for game join universe entry");
|
||||
App.Logger.WriteLine(LOG_IDENT, entry);
|
||||
return;
|
||||
}
|
||||
|
||||
ActivityUniverseId = long.Parse(match.Groups[1].Value);
|
||||
}
|
||||
else if (entry.Contains(GameJoiningUDMUXEntry))
|
||||
{
|
||||
Match match = Regex.Match(entry, GameJoiningUDMUXPattern);
|
||||
|
||||
if (match.Groups.Count != 3 || match.Groups[2].Value != ActivityMachineAddress)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Failed to assert format for game join UDMUX entry");
|
||||
App.Logger.WriteLine(LOG_IDENT, "Failed to assert format for game join UDMUX entry");
|
||||
App.Logger.WriteLine(LOG_IDENT, entry);
|
||||
return;
|
||||
}
|
||||
@ -219,17 +243,35 @@ namespace Bloxstrap.Integrations
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Joined Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||
|
||||
ActivityInGame = true;
|
||||
ActivityTimeJoined = DateTime.Now;
|
||||
|
||||
OnGameJoin?.Invoke(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
else if (ActivityInGame && ActivityPlaceId != 0)
|
||||
{
|
||||
// We are confirmed to be in a game
|
||||
|
||||
if (entry.Contains(GameDisconnectedEntry))
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Disconnected from Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||
|
||||
// TODO: should this be including launchdata?
|
||||
if (ActivityServerType != ServerType.Reserved)
|
||||
{
|
||||
ActivityHistory.Insert(0, new ActivityHistoryEntry
|
||||
{
|
||||
PlaceId = ActivityPlaceId,
|
||||
UniverseId = ActivityUniverseId,
|
||||
JobId = ActivityJobId,
|
||||
TimeJoined = ActivityTimeJoined,
|
||||
TimeLeft = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
ActivityInGame = false;
|
||||
ActivityPlaceId = 0;
|
||||
ActivityUniverseId = 0;
|
||||
ActivityJobId = "";
|
||||
ActivityMachineAddress = "";
|
||||
ActivityMachineUDMUX = false;
|
||||
|
@ -203,15 +203,8 @@ namespace Bloxstrap.Integrations
|
||||
|
||||
// TODO: move this to its own function under the activity watcher?
|
||||
// TODO: show error if information cannot be queried instead of silently failing
|
||||
var universeIdResponse = await Http.GetJson<UniverseIdResponse>($"https://apis.roblox.com/universes/v1/places/{placeId}/universe");
|
||||
if (universeIdResponse is null)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Could not get Universe ID!");
|
||||
return false;
|
||||
}
|
||||
|
||||
long universeId = universeIdResponse.UniverseId;
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Got Universe ID as {universeId}");
|
||||
long universeId = _activityWatcher.ActivityUniverseId;
|
||||
|
||||
// preserve time spent playing if we're teleporting between places in the same universe
|
||||
if (_timeStartedUniverse is null || !_activityWatcher.ActivityIsTeleport || universeId != _currentUniverseId)
|
||||
@ -247,7 +240,7 @@ namespace Bloxstrap.Integrations
|
||||
buttons.Add(new Button
|
||||
{
|
||||
Label = "Join server",
|
||||
Url = $"roblox://experiences/start?placeId={placeId}&gameInstanceId={_activityWatcher.ActivityJobId}"
|
||||
Url = _activityWatcher.GetActivityDeeplink()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -215,7 +215,7 @@ namespace Bloxstrap
|
||||
App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper");
|
||||
|
||||
if (t.Exception is not null)
|
||||
App.FinalizeExceptionHandling(t.Exception, false);
|
||||
App.FinalizeExceptionHandling(t.Exception);
|
||||
}
|
||||
|
||||
App.Terminate();
|
||||
|
38
Bloxstrap/Models/ActivityHistoryEntry.cs
Normal file
38
Bloxstrap/Models/ActivityHistoryEntry.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Bloxstrap.Models
|
||||
{
|
||||
public class ActivityHistoryEntry
|
||||
{
|
||||
public long UniverseId { get; set; }
|
||||
|
||||
public long PlaceId { get; set; }
|
||||
|
||||
public string JobId { get; set; } = String.Empty;
|
||||
|
||||
public DateTime TimeJoined { get; set; }
|
||||
|
||||
public DateTime TimeLeft { get; set; }
|
||||
|
||||
public string TimeJoinedFriendly => String.Format("{0} - {1}", TimeJoined.ToString("h:mm tt"), TimeLeft.ToString("h:mm tt"));
|
||||
|
||||
public bool DetailsLoaded = false;
|
||||
|
||||
public string GameName { get; set; } = String.Empty;
|
||||
|
||||
public string GameThumbnail { get; set; } = String.Empty;
|
||||
|
||||
public ICommand RejoinServerCommand => new RelayCommand(RejoinServer);
|
||||
|
||||
private void RejoinServer()
|
||||
{
|
||||
string playerPath = Path.Combine(Paths.Versions, App.State.Prop.PlayerVersionGuid, "RobloxPlayerBeta.exe");
|
||||
string deeplink = $"roblox://experiences/start?placeId={PlaceId}&gameInstanceId={JobId}";
|
||||
|
||||
// start RobloxPlayerBeta.exe directly since Roblox can reuse the existing window
|
||||
// ideally, i'd like to find out how roblox is doing it
|
||||
Process.Start(playerPath, deeplink);
|
||||
}
|
||||
}
|
||||
}
|
27
Bloxstrap/Resources/Strings.Designer.cs
generated
27
Bloxstrap/Resources/Strings.Designer.cs
generated
@ -665,6 +665,24 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Rejoin.
|
||||
/// </summary>
|
||||
public static string ContextMenu_GameHistory_Rejoin {
|
||||
get {
|
||||
return ResourceManager.GetString("ContextMenu.GameHistory.Rejoin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Game history.
|
||||
/// </summary>
|
||||
public static string ContextMenu_GameHistory_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("ContextMenu.GameHistory.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Roblox is still launching. A log file will only be available once Roblox launches..
|
||||
/// </summary>
|
||||
@ -674,15 +692,6 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to See server details.
|
||||
/// </summary>
|
||||
public static string ContextMenu_SeeServerDetails {
|
||||
get {
|
||||
return ResourceManager.GetString("ContextMenu.SeeServerDetails", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Copy Instance ID.
|
||||
/// </summary>
|
||||
|
@ -268,9 +268,6 @@ Your ReShade configuration files will still be saved, and you can locate them by
|
||||
<data name="ContextMenu.CopyDeeplinkInvite" xml:space="preserve">
|
||||
<value>Copy invite deeplink</value>
|
||||
</data>
|
||||
<data name="ContextMenu.SeeServerDetails" xml:space="preserve">
|
||||
<value>See server details</value>
|
||||
</data>
|
||||
<data name="ContextMenu.ServerInformation.CopyInstanceId" xml:space="preserve">
|
||||
<value>Copy Instance ID</value>
|
||||
</data>
|
||||
@ -1168,4 +1165,10 @@ Are you sure you want to continue?</value>
|
||||
<data name="JsonManager.FastFlagsLoadFailed" xml:space="preserve">
|
||||
<value>Your Fast Flags could not be loaded. They have been reset to the default configuration.</value>
|
||||
</data>
|
||||
<data name="ContextMenu.GameHistory.Title" xml:space="preserve">
|
||||
<value>Game history</value>
|
||||
</data>
|
||||
<data name="ContextMenu.GameHistory.Rejoin" xml:space="preserve">
|
||||
<value>Rejoin</value>
|
||||
</data>
|
||||
</root>
|
@ -57,7 +57,19 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:SymbolIcon Grid.Column="0" Symbol="Info28"/>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="4,0,0,0" Text="{x:Static resources:Strings.ContextMenu_SeeServerDetails}" />
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="4,0,0,0" Text="{x:Static resources:Strings.ContextMenu_ServerInformation_Title}" />
|
||||
</Grid>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="JoinLastServerMenuItem" Visibility="Collapsed" Click="JoinLastServerMenuItem_Click">
|
||||
<MenuItem.Header>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="24" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:SymbolIcon Grid.Column="0" Symbol="History24"/>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="4,0,0,0" Text="{x:Static resources:Strings.ContextMenu_GameHistory_Title}" />
|
||||
</Grid>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
|
@ -80,6 +80,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
||||
public void ActivityWatcher_OnGameLeave(object? sender, EventArgs e)
|
||||
{
|
||||
Dispatcher.Invoke(() => {
|
||||
JoinLastServerMenuItem.Visibility = Visibility.Visible;
|
||||
InviteDeeplinkMenuItem.Visibility = Visibility.Collapsed;
|
||||
ServerDetailsMenuItem.Visibility = Visibility.Collapsed;
|
||||
|
||||
@ -129,5 +130,13 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
||||
|
||||
_watcher.KillRobloxProcess();
|
||||
}
|
||||
|
||||
private void JoinLastServerMenuItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_activityWatcher is null)
|
||||
throw new ArgumentNullException(nameof(_activityWatcher));
|
||||
|
||||
new ServerHistory(_activityWatcher).ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
89
Bloxstrap/UI/Elements/ContextMenu/ServerHistory.xaml
Normal file
89
Bloxstrap/UI/Elements/ContextMenu/ServerHistory.xaml
Normal file
@ -0,0 +1,89 @@
|
||||
<base:WpfUiWindow x:Class="Bloxstrap.UI.Elements.ContextMenu.ServerHistory"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.ContextMenu"
|
||||
xmlns:base="clr-namespace:Bloxstrap.UI.Elements.Base"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels.ContextMenu"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
d:DataContext="{d:DesignInstance Type=models:ServerHistoryViewModel}"
|
||||
mc:Ignorable="d"
|
||||
Title="{x:Static resources:Strings.ContextMenu_GameHistory_Title}"
|
||||
MinWidth="420"
|
||||
MinHeight="420"
|
||||
Width="580"
|
||||
Height="420"
|
||||
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ui:TitleBar Grid.Row="0" Grid.ColumnSpan="2" Padding="8" x:Name="RootTitleBar" Title="{x:Static resources:Strings.ContextMenu_GameHistory_Title}" ShowMinimize="False" ShowMaximize="False" CanMaximize="False" KeyboardNavigation.TabNavigation="None" Icon="pack://application:,,,/Bloxstrap.ico" />
|
||||
|
||||
<Border Grid.Row="1">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActivityHistory, Mode=OneWay}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Style>
|
||||
</Border.Style>
|
||||
|
||||
<ui:ProgressRing Grid.Row="1" IsIndeterminate="True" />
|
||||
</Border>
|
||||
|
||||
<ListView Grid.Row="1" ItemsSource="{Binding ActivityHistory, Mode=OneWay}" Margin="8">
|
||||
<ListView.Style>
|
||||
<Style TargetType="ListView" BasedOn="{StaticResource {x:Type ListView}}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActivityHistory, Mode=OneWay}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</Style>
|
||||
</ListView.Style>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ui:Card Padding="12">
|
||||
<Grid VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Width="84" Height="84" CornerRadius="4">
|
||||
<Border.Background>
|
||||
<ImageBrush ImageSource="{Binding GameThumbnail, IsAsync=True}" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Column="1" Margin="16,0,0,0" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding GameName}" FontSize="18" FontWeight="Medium" />
|
||||
<TextBlock Text="{Binding TimeJoinedFriendly}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<ui:Button Margin="0,8,0,0" Content="{x:Static resources:Strings.ContextMenu_GameHistory_Rejoin}" Command="{Binding RejoinServerCommand}" Appearance="Success" Icon="Play28" IconFilled="True" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ui:Card>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<Border Grid.Row="2" Padding="15" Background="{ui:ThemeResource SolidBackgroundFillColorSecondaryBrush}">
|
||||
<StackPanel Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Right">
|
||||
<Button Margin="12,0,0,0" MinWidth="100" Content="{x:Static resources:Strings.Common_Close}" IsCancel="True" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</base:WpfUiWindow>
|
21
Bloxstrap/UI/Elements/ContextMenu/ServerHistory.xaml.cs
Normal file
21
Bloxstrap/UI/Elements/ContextMenu/ServerHistory.xaml.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Bloxstrap.Integrations;
|
||||
using Bloxstrap.UI.ViewModels.ContextMenu;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.ContextMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ServerInformation.xaml
|
||||
/// </summary>
|
||||
public partial class ServerHistory
|
||||
{
|
||||
public ServerHistory(ActivityWatcher watcher)
|
||||
{
|
||||
var viewModel = new ServerHistoryViewModel(watcher);
|
||||
|
||||
viewModel.RequestCloseEvent += (_, _) => Close();
|
||||
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Bloxstrap.Integrations;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.ContextMenu
|
||||
{
|
||||
internal class ServerHistoryViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
||||
private readonly ActivityWatcher _activityWatcher;
|
||||
|
||||
public List<ActivityHistoryEntry>? ActivityHistory { get; private set; }
|
||||
|
||||
public ICommand CloseWindowCommand => new RelayCommand(RequestClose);
|
||||
|
||||
public EventHandler? RequestCloseEvent;
|
||||
|
||||
public ServerHistoryViewModel(ActivityWatcher activityWatcher)
|
||||
{
|
||||
_activityWatcher = activityWatcher;
|
||||
|
||||
_activityWatcher.OnGameLeave += (_, _) => LoadData();
|
||||
|
||||
LoadData();
|
||||
}
|
||||
|
||||
private async void LoadData()
|
||||
{
|
||||
var entries = _activityWatcher.ActivityHistory.Where(x => !x.DetailsLoaded);
|
||||
|
||||
if (entries.Any())
|
||||
{
|
||||
string universeIds = String.Join(',', entries.Select(x => x.UniverseId));
|
||||
|
||||
var gameDetailResponse = await Http.GetJson<ApiArrayResponse<GameDetailResponse>>($"https://games.roblox.com/v1/games?universeIds={universeIds}");
|
||||
|
||||
if (gameDetailResponse is null || !gameDetailResponse.Data.Any())
|
||||
return;
|
||||
|
||||
var universeThumbnailResponse = await Http.GetJson<ApiArrayResponse<ThumbnailResponse>>($"https://thumbnails.roblox.com/v1/games/icons?universeIds={universeIds}&returnPolicy=PlaceHolder&size=128x128&format=Png&isCircular=false");
|
||||
|
||||
if (universeThumbnailResponse is null || !universeThumbnailResponse.Data.Any())
|
||||
return;
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
entry.GameName = gameDetailResponse.Data.Where(x => x.Id == entry.UniverseId).Select(x => x.Name).First();
|
||||
entry.GameThumbnail = universeThumbnailResponse.Data.Where(x => x.TargetId == entry.UniverseId).Select(x => x.ImageUrl).First();
|
||||
entry.DetailsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
ActivityHistory = new(_activityWatcher.ActivityHistory);
|
||||
OnPropertyChanged(nameof(ActivityHistory));
|
||||
}
|
||||
|
||||
private void RequestClose() => RequestCloseEvent?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
@ -17,7 +17,15 @@ namespace Bloxstrap.Utility
|
||||
public InterProcessLock(string name, TimeSpan timeout)
|
||||
{
|
||||
Mutex = new Mutex(false, "Bloxstrap-" + name);
|
||||
IsAcquired = Mutex.WaitOne(timeout);
|
||||
|
||||
try
|
||||
{
|
||||
IsAcquired = Mutex.WaitOne(timeout);
|
||||
}
|
||||
catch (AbandonedMutexException)
|
||||
{
|
||||
IsAcquired = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
Loading…
Reference in New Issue
Block a user