Lots of rewrites, optimization, logging stuff
This commit is contained in:
parent
8489d0c25c
commit
6d1959fb4c
70
App.axaml.cs
70
App.axaml.cs
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Data.Core.Plugins;
|
||||
@ -6,27 +7,69 @@ using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Threading;
|
||||
using BetterRaid.Services;
|
||||
using BetterRaid.Services.Implementations;
|
||||
using BetterRaid.ViewModels;
|
||||
using BetterRaid.Views;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterRaid;
|
||||
|
||||
public class App : Application
|
||||
{
|
||||
private ServiceProvider? _serviceProvider;
|
||||
private ILogger<App>? _logger;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_serviceProvider = InitializeServices();
|
||||
_logger = _serviceProvider.GetRequiredService<ILogger<App>>();
|
||||
|
||||
if (TryLoadDatabase() == false)
|
||||
{
|
||||
_logger?.LogError("Failed to load or initialize database");
|
||||
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
AvaloniaXamlLoader.Load(_serviceProvider, this);
|
||||
}
|
||||
|
||||
private bool TryLoadDatabase()
|
||||
{
|
||||
if (_serviceProvider == null)
|
||||
{
|
||||
throw new FieldAccessException($"\"{nameof(_serviceProvider)}\" was null");
|
||||
}
|
||||
|
||||
var db = _serviceProvider.GetRequiredService<IDatabaseService>();
|
||||
|
||||
try
|
||||
{
|
||||
db.LoadOrCreate();
|
||||
Task.Run(db.UpdateLoadedChannels);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger?.LogError(e, "Failed to load database");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private ServiceProvider InitializeServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging(logging =>
|
||||
{
|
||||
logging.SetMinimumLevel(LogLevel.Debug);
|
||||
logging.AddConsole();
|
||||
});
|
||||
services.AddSingleton<ITwitchService, TwitchService>();
|
||||
services.AddSingleton<ISynchronizaionService, DispatcherService>(serviceProvider => new DispatcherService(Dispatcher.UIThread));
|
||||
services.AddTransient<IMainViewModelFactory, MainWindowViewModelFactory>();
|
||||
services.AddSingleton<IWebToolsService, WebToolsService>();
|
||||
services.AddSingleton<IDatabaseService, DatabaseService>();
|
||||
services.AddSingleton<ISynchronizaionService, DispatcherService>(_ => new DispatcherService(Dispatcher.UIThread));
|
||||
services.AddTransient<MainWindowViewModel>();
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
@ -40,17 +83,16 @@ public class App : Application
|
||||
throw new FieldAccessException($"\"{nameof(_serviceProvider)}\" was null");
|
||||
}
|
||||
|
||||
var viewModelFactory = _serviceProvider.GetRequiredService<IMainViewModelFactory>();
|
||||
var mainWindowViewModel = viewModelFactory.CreateMainWindowViewModel();
|
||||
var mainWindow = new MainWindow
|
||||
{
|
||||
DataContext = mainWindowViewModel
|
||||
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
|
||||
};
|
||||
|
||||
switch (ApplicationLifetime)
|
||||
{
|
||||
case IClassicDesktopStyleApplicationLifetime desktop:
|
||||
desktop.MainWindow = mainWindow;
|
||||
desktop.Exit += OnDesktopOnExit;
|
||||
break;
|
||||
|
||||
case ISingleViewApplicationLifetime singleViewPlatform:
|
||||
@ -60,4 +102,22 @@ public class App : Application
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
private void OnDesktopOnExit(object? o, ControlledApplicationLifetimeExitEventArgs controlledApplicationLifetimeExitEventArgs)
|
||||
{
|
||||
if (_serviceProvider == null)
|
||||
{
|
||||
throw new FieldAccessException($"\"{nameof(_serviceProvider)}\" was null");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var db = _serviceProvider.GetRequiredService<IDatabaseService>();
|
||||
db.Save();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger?.LogError(e, "Failed to save database");
|
||||
}
|
||||
}
|
||||
}
|
@ -27,6 +27,9 @@
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
<PackageReference Include="TwitchLib" Version="3.5.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,149 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BetterRaid.Models;
|
||||
|
||||
[JsonObject]
|
||||
public class BetterRaidDatabase : INotifyPropertyChanged
|
||||
{
|
||||
[JsonIgnore]
|
||||
private string? _databaseFilePath;
|
||||
private bool _onlyOnline;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
public bool OnlyOnline
|
||||
{
|
||||
get => _onlyOnline;
|
||||
set
|
||||
{
|
||||
if (value == _onlyOnline)
|
||||
return;
|
||||
|
||||
_onlyOnline = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
public List<string> Channels { get; set; } = [];
|
||||
public Dictionary<string, DateTime?> LastRaided = [];
|
||||
public bool AutoSave { get; set; }
|
||||
|
||||
public static BetterRaidDatabase LoadFromFile(string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNullOrEmpty(path);
|
||||
|
||||
path = Path.Combine(Environment.CurrentDirectory, path);
|
||||
|
||||
if (File.Exists(path) == false)
|
||||
{
|
||||
throw new FileNotFoundException("Database file not found", path);
|
||||
}
|
||||
|
||||
var dbStr = File.ReadAllText(path);
|
||||
var dbObj = JsonConvert.DeserializeObject<BetterRaidDatabase>(dbStr);
|
||||
|
||||
if (dbObj == null)
|
||||
{
|
||||
throw new JsonException("Failed to read database file");
|
||||
}
|
||||
|
||||
dbObj._databaseFilePath = path;
|
||||
|
||||
foreach (var channel in dbObj.Channels)
|
||||
{
|
||||
if (dbObj.LastRaided.ContainsKey(channel) == false)
|
||||
{
|
||||
dbObj.LastRaided.Add(channel, null);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("[DEBUG] Loaded database from {0}", path);
|
||||
|
||||
return dbObj;
|
||||
}
|
||||
|
||||
public void Save(string? path = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_databaseFilePath) && string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentException("No target path given to save database at");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(path) == false && string.IsNullOrEmpty(_databaseFilePath))
|
||||
{
|
||||
_databaseFilePath = path;
|
||||
}
|
||||
|
||||
var dbStr = JsonConvert.SerializeObject(this);
|
||||
var targetPath = (path ?? _databaseFilePath)!;
|
||||
|
||||
File.WriteAllText(targetPath, dbStr);
|
||||
|
||||
Console.WriteLine("[DEBUG] Saved database to {0}", targetPath);
|
||||
}
|
||||
|
||||
public void AddChannel(string channel)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel);
|
||||
|
||||
if (Channels.Contains(channel))
|
||||
return;
|
||||
|
||||
Channels.Add(channel);
|
||||
OnPropertyChanged(nameof(Channels));
|
||||
}
|
||||
|
||||
public void RemoveChannel(string channel)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel);
|
||||
|
||||
if (Channels.Contains(channel) == false)
|
||||
return;
|
||||
|
||||
Channels.Remove(channel);
|
||||
OnPropertyChanged(nameof(Channels));
|
||||
}
|
||||
|
||||
public void SetRaided(string channel, DateTime dateTime)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel);
|
||||
|
||||
if (LastRaided.ContainsKey(channel))
|
||||
{
|
||||
LastRaided[channel] = dateTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
LastRaided.Add(channel, dateTime);
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(LastRaided));
|
||||
}
|
||||
|
||||
public DateTime? GetLastRaided(string channel)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel);
|
||||
|
||||
if (LastRaided.ContainsKey(channel))
|
||||
{
|
||||
return LastRaided[channel];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
if (AutoSave && _databaseFilePath != null)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
11
Models/Database/BetterRaidDatabase.cs
Normal file
11
Models/Database/BetterRaidDatabase.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BetterRaid.Models.Database;
|
||||
|
||||
[JsonObject]
|
||||
public class BetterRaidDatabase
|
||||
{
|
||||
public bool OnlyOnline { get; set; }
|
||||
public List<TwitchChannel> Channels { get; set; } = [];
|
||||
}
|
@ -3,12 +3,15 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BetterRaid.Services;
|
||||
using Newtonsoft.Json;
|
||||
using TwitchLib.PubSub.Events;
|
||||
|
||||
namespace BetterRaid.Models;
|
||||
|
||||
[JsonObject]
|
||||
public class TwitchChannel : INotifyPropertyChanged
|
||||
{
|
||||
private string? _id;
|
||||
private string? _broadcasterId;
|
||||
private string? _viewerCount;
|
||||
private bool _isLive;
|
||||
@ -18,7 +21,6 @@ public class TwitchChannel : INotifyPropertyChanged
|
||||
private string? _category;
|
||||
private string? _title;
|
||||
private DateTime? _lastRaided;
|
||||
private string? _id;
|
||||
|
||||
public string? Id
|
||||
{
|
||||
@ -59,6 +61,7 @@ public class TwitchChannel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsLive
|
||||
{
|
||||
get => _isLive;
|
||||
@ -72,6 +75,7 @@ public class TwitchChannel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string? ViewerCount
|
||||
{
|
||||
get => _viewerCount;
|
||||
@ -111,6 +115,7 @@ public class TwitchChannel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string? Category
|
||||
{
|
||||
get => _category;
|
||||
@ -124,6 +129,7 @@ public class TwitchChannel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string? Title
|
||||
{
|
||||
get => _title;
|
||||
|
162
Services/DatabaseService.cs
Normal file
162
Services/DatabaseService.cs
Normal file
@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BetterRaid.Misc;
|
||||
using BetterRaid.Models;
|
||||
using BetterRaid.Models.Database;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BetterRaid.Services;
|
||||
|
||||
public interface IDatabaseService
|
||||
{
|
||||
bool OnlyOnline { get; set; }
|
||||
bool AutoSave { get; set; }
|
||||
BetterRaidDatabase? Database { get; set; }
|
||||
void LoadOrCreate();
|
||||
void LoadFromFile(string path, bool createIfNotExist = false);
|
||||
Task UpdateLoadedChannels();
|
||||
void Save(string? path = null);
|
||||
bool TrySetRaided(TwitchChannel channel, DateTime dateTime);
|
||||
}
|
||||
|
||||
public class DatabaseService : IDatabaseService
|
||||
{
|
||||
private string? _databaseFilePath;
|
||||
private readonly ILogger<DatabaseService> _logger;
|
||||
private readonly ITwitchService _twitch;
|
||||
|
||||
public bool OnlyOnline { get; set; }
|
||||
public bool AutoSave { get; set; }
|
||||
|
||||
public BetterRaidDatabase? Database { get; set; }
|
||||
|
||||
public DatabaseService(ILogger<DatabaseService> logger, ITwitchService twitch)
|
||||
{
|
||||
_logger = logger;
|
||||
_twitch = twitch;
|
||||
}
|
||||
|
||||
public void LoadOrCreate()
|
||||
{
|
||||
LoadFromFile(Constants.DatabaseFilePath, true);
|
||||
}
|
||||
|
||||
public void LoadFromFile(string path, bool createIfNotExist = false)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||
|
||||
path = Path.Combine(Environment.CurrentDirectory, path);
|
||||
var exists = File.Exists(path);
|
||||
|
||||
switch (exists)
|
||||
{
|
||||
case false when createIfNotExist == false:
|
||||
throw new FileNotFoundException("Database file not found", path);
|
||||
|
||||
case false when createIfNotExist:
|
||||
_logger.LogWarning("Database file not found, creating new database");
|
||||
|
||||
Database = new BetterRaidDatabase();
|
||||
Save(path);
|
||||
|
||||
_logger.LogDebug("Created new database at {path}", path);
|
||||
|
||||
return;
|
||||
|
||||
case true:
|
||||
var dbStr = File.ReadAllText(path);
|
||||
var dbObj = JsonConvert.DeserializeObject<BetterRaidDatabase>(dbStr);
|
||||
|
||||
_databaseFilePath = path;
|
||||
Database = dbObj ?? throw new JsonException("Failed to read database file");
|
||||
|
||||
_logger.LogDebug("Loaded database from {path}", path);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateLoadedChannels()
|
||||
{
|
||||
if (Database == null || Database.Channels.Count == 0)
|
||||
return;
|
||||
|
||||
await Parallel.ForAsync(0, Database.Channels.Count, (i, c) =>
|
||||
{
|
||||
if (c.IsCancellationRequested)
|
||||
return ValueTask.FromCanceled(c);
|
||||
|
||||
var channel = Database.Channels[i];
|
||||
channel.UpdateChannelData(_twitch);
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
public void Save(string? path = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_databaseFilePath) && string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentException("No target path given to save database at");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(path) == false && string.IsNullOrEmpty(_databaseFilePath))
|
||||
{
|
||||
_databaseFilePath = path;
|
||||
}
|
||||
|
||||
var dbStr = JsonConvert.SerializeObject(Database, Formatting.Indented);
|
||||
var targetPath = _databaseFilePath!;
|
||||
|
||||
File.WriteAllText(targetPath, dbStr);
|
||||
|
||||
_logger.LogDebug("Saved database to {targetPath}", targetPath);
|
||||
}
|
||||
|
||||
public void AddChannel(TwitchChannel channel)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel);
|
||||
|
||||
if (Database == null)
|
||||
throw new InvalidOperationException("Database is not loaded");
|
||||
|
||||
if (Database.Channels.Any(c => c.Name?.Equals(c.Name, StringComparison.CurrentCultureIgnoreCase) == true))
|
||||
return;
|
||||
|
||||
Database.Channels.Add(channel);
|
||||
}
|
||||
|
||||
public void RemoveChannel(TwitchChannel channel)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel);
|
||||
|
||||
if (Database == null)
|
||||
throw new InvalidOperationException("Database is not loaded");
|
||||
|
||||
var index = Database.Channels.FindIndex(c => c.Name?.Equals(c.Name, StringComparison.CurrentCultureIgnoreCase) == true);
|
||||
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
Database.Channels.RemoveAt(index);
|
||||
}
|
||||
|
||||
public bool TrySetRaided(TwitchChannel channel, DateTime dateTime)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel);
|
||||
|
||||
if (Database == null)
|
||||
throw new InvalidOperationException("Database is not loaded");
|
||||
|
||||
var twitchChannel = Database.Channels.FirstOrDefault(c => c.Name?.Equals(channel.Name, StringComparison.CurrentCultureIgnoreCase) == true);
|
||||
|
||||
if (twitchChannel == null)
|
||||
return false;
|
||||
|
||||
twitchChannel.LastRaided = dateTime;
|
||||
return true;
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace BetterRaid.Services.Implementations;
|
||||
|
||||
public class MainWindowViewModelFactory : IMainViewModelFactory
|
||||
public class MainWindowViewModelFactory// : IMainViewModelFactory
|
||||
{
|
||||
private readonly ITwitchService _twitchService;
|
||||
private readonly ISynchronizaionService _synchronizaionService;
|
||||
@ -13,8 +13,8 @@ public class MainWindowViewModelFactory : IMainViewModelFactory
|
||||
_synchronizaionService = synchronizaionService;
|
||||
}
|
||||
|
||||
public MainWindowViewModel CreateMainWindowViewModel()
|
||||
{
|
||||
return new MainWindowViewModel(_twitchService, _synchronizaionService);
|
||||
}
|
||||
//public MainWindowViewModel CreateMainWindowViewModel()
|
||||
//{
|
||||
// return new MainWindowViewModel(_twitchService, _synchronizaionService);
|
||||
//}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using BetterRaid.Misc;
|
||||
using BetterRaid.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TwitchLib.Api;
|
||||
using TwitchLib.Api.Helix.Models.Users.GetUsers;
|
||||
using TwitchLib.PubSub;
|
||||
@ -40,8 +41,9 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
private bool _isRaidStarted;
|
||||
private int _raidParticipants;
|
||||
private TwitchChannel? _userChannel;
|
||||
private readonly List<TwitchChannel> _registeredChannels;
|
||||
private User? _user;
|
||||
private readonly ILogger<TwitchService> _logger;
|
||||
private readonly IWebToolsService _webTools;
|
||||
|
||||
public string AccessToken { get; private set; } = string.Empty;
|
||||
|
||||
@ -80,46 +82,46 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
set => SetField(ref _raidParticipants, value);
|
||||
}
|
||||
|
||||
public TwitchService()
|
||||
public TwitchService(ILogger<TwitchService> logger, IWebToolsService webTools)
|
||||
{
|
||||
_registeredChannels = [];
|
||||
_logger = logger;
|
||||
_webTools = webTools;
|
||||
|
||||
TwitchApi = new TwitchAPI();
|
||||
TwitchEvents = new TwitchPubSub();
|
||||
|
||||
if (TryLoadAccessToken(out var token))
|
||||
{
|
||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Found access token.");
|
||||
_logger.LogInformation("Found access token.");
|
||||
Task.Run(() => ConnectApiAsync(Constants.TwitchClientId, token))
|
||||
.ContinueWith(_ => ConnectTwitchEvents(token));
|
||||
.ContinueWith(_ => ConnectTwitchEvents());
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] No access token found.");
|
||||
_logger.LogInformation("No access token found.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectTwitchEvents(string token)
|
||||
private async Task ConnectTwitchEvents()
|
||||
{
|
||||
if (UserChannel == null || User == null)
|
||||
return;
|
||||
|
||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Connecting to Twitch Events ...");
|
||||
_logger.LogInformation("Connecting to Twitch Events ...");
|
||||
|
||||
TwitchEvents.OnRaidGo += OnUserRaidGo;
|
||||
TwitchEvents.OnRaidUpdate += OnUserRaidUpdate;
|
||||
TwitchEvents.OnStreamUp += OnUserStreamUp;
|
||||
TwitchEvents.OnStreamDown += OnUserStreamDown;
|
||||
TwitchEvents.OnViewCount += OnViewCount;
|
||||
TwitchEvents.OnLog += (sender, args) => Console.WriteLine($"[INFO][{nameof(TwitchService)}] {args.Data}");
|
||||
TwitchEvents.OnPubSubServiceError += (sender, args) => Console.WriteLine($"[ERROR][{nameof(TwitchService)}] {args.Exception.Message}");
|
||||
TwitchEvents.OnPubSubServiceConnected += (sender, args) => Console.WriteLine($"[INFO][{nameof(TwitchService)}] Connected to Twitch PubSub.");
|
||||
TwitchEvents.OnPubSubServiceClosed += (sender, args) => Console.WriteLine($"[INFO][{nameof(TwitchService)}] Disconnected from Twitch PubSub.");
|
||||
TwitchEvents.OnLog += OnPubSubLog;
|
||||
TwitchEvents.OnPubSubServiceError += OnPubSubServiceError;
|
||||
TwitchEvents.OnPubSubServiceConnected += OnPubSubServiceConnected;
|
||||
TwitchEvents.OnPubSubServiceClosed += OnPubSubServiceClosed;
|
||||
|
||||
TwitchEvents.ListenToVideoPlayback(UserChannel.BroadcasterId);
|
||||
TwitchEvents.ListenToRaid(UserChannel.BroadcasterId);
|
||||
|
||||
TwitchEvents.SendTopics(token);
|
||||
TwitchEvents.Connect();
|
||||
|
||||
await Task.CompletedTask;
|
||||
@ -127,7 +129,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
|
||||
public async Task ConnectApiAsync(string clientId, string accessToken)
|
||||
{
|
||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Connecting to Twitch API ...");
|
||||
_logger.LogInformation("Connecting to Twitch API ...");
|
||||
|
||||
AccessToken = accessToken;
|
||||
|
||||
@ -142,24 +144,24 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
{
|
||||
User = null;
|
||||
|
||||
Console.WriteLine($"[ERROR][{nameof(TwitchService)}] Could not get user.");
|
||||
_logger.LogError("Could not get user with client id {clientId} - please check your clientId and accessToken", clientId);
|
||||
}
|
||||
|
||||
if (TryGetUserChannel(out var channel))
|
||||
{
|
||||
UserChannel = channel;
|
||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Connected to Twitch API as {channel?.Name}.");
|
||||
_logger.LogInformation("Connected to Twitch API as {channelName} with broadcaster id {channelBroadcasterId}.", channel?.Name, channel?.BroadcasterId);
|
||||
}
|
||||
else
|
||||
{
|
||||
UserChannel = null;
|
||||
|
||||
Console.WriteLine($"[ERROR][{nameof(TwitchService)}] Could not get user channel.");
|
||||
_logger.LogError("Could not get user channel.");
|
||||
}
|
||||
|
||||
if (User == null || UserChannel == null)
|
||||
{
|
||||
Console.WriteLine($"[ERROR][{nameof(TwitchService)}] Could not connect to Twitch API.");
|
||||
_logger.LogError("Could not connect to Twitch API.");
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
@ -199,7 +201,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"[ERROR][{nameof(TwitchService)}] {e.Message}");
|
||||
_logger.LogError(e, "Could not get user.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -218,7 +220,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
|
||||
public void RegisterForEvents(TwitchChannel channel)
|
||||
{
|
||||
Console.WriteLine($"[DEBUG][{nameof(TwitchService)}] Registering for events for {channel.Name} ...");
|
||||
_logger.LogDebug("Registering for events for {channelName} with broadcaster id {channelBroadcasterId} ...", channel.Name, channel.BroadcasterId);
|
||||
|
||||
TwitchEvents.OnStreamUp += channel.OnStreamUp;
|
||||
TwitchEvents.OnStreamDown += channel.OnStreamDown;
|
||||
@ -227,12 +229,12 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
TwitchEvents.ListenToVideoPlayback(channel.Id);
|
||||
|
||||
TwitchEvents.SendTopics(AccessToken);
|
||||
|
||||
_registeredChannels.Add(channel);
|
||||
}
|
||||
|
||||
public void UnregisterFromEvents(TwitchChannel channel)
|
||||
{
|
||||
_logger.LogDebug("Unregistering from events for {channelName} with broadcaster id {channelBroadcasterId} ...", channel.Name, channel.BroadcasterId);
|
||||
|
||||
TwitchEvents.OnStreamUp -= channel.OnStreamUp;
|
||||
TwitchEvents.OnStreamDown -= channel.OnStreamDown;
|
||||
TwitchEvents.OnViewCount -= channel.OnViewCount;
|
||||
@ -240,8 +242,6 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
TwitchEvents.ListenToVideoPlayback(channel.Id);
|
||||
|
||||
TwitchEvents.SendTopics(AccessToken, true);
|
||||
|
||||
_registeredChannels.Remove(channel);
|
||||
}
|
||||
|
||||
public string GetOAuthUrl()
|
||||
@ -304,7 +304,28 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
|
||||
var url = $"https://twitch.tv/{channelName}";
|
||||
|
||||
Tools.OpenUrl(url);
|
||||
_webTools.OpenUrl(url);
|
||||
}
|
||||
|
||||
private void OnPubSubServiceClosed(object? sender, EventArgs e)
|
||||
{
|
||||
_logger.LogWarning("PubSub: Connection closed.");
|
||||
}
|
||||
|
||||
private void OnPubSubServiceError(object? sender, OnPubSubServiceErrorArgs e)
|
||||
{
|
||||
_logger.LogError(e.Exception, "PubSub: {exception}", e.Exception);
|
||||
}
|
||||
|
||||
private void OnPubSubLog(object? sender, OnLogArgs e)
|
||||
{
|
||||
_logger.LogInformation("PubSub: {data}", e.Data);
|
||||
}
|
||||
|
||||
private void OnPubSubServiceConnected(object? sender, EventArgs e)
|
||||
{
|
||||
TwitchEvents.SendTopics(AccessToken);
|
||||
_logger.LogInformation("PubSub: Connected.");
|
||||
}
|
||||
|
||||
// TODO Not called while raid is ongoing
|
||||
@ -314,7 +335,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
// return;
|
||||
|
||||
RaidParticipants = e.ViewerCount;
|
||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Raid participants: {RaidParticipants}");
|
||||
_logger.LogInformation("Raid participants: {participants}", RaidParticipants);
|
||||
}
|
||||
|
||||
private void OnViewCount(object? sender, OnViewCountArgs e)
|
||||
@ -333,7 +354,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
if (e.ChannelId != UserChannel?.Id)
|
||||
return;
|
||||
|
||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Raid started.");
|
||||
_logger.LogInformation("Raid started.");
|
||||
|
||||
IsRaidStarted = false;
|
||||
}
|
||||
@ -346,7 +367,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
if (e.ChannelId != UserChannel?.Id)
|
||||
return;
|
||||
|
||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Stream down.");
|
||||
_logger.LogInformation("Stream down.");
|
||||
|
||||
IsRaidStarted = false;
|
||||
|
||||
@ -361,10 +382,9 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
||||
if (e.ChannelId != UserChannel?.Id)
|
||||
return;
|
||||
|
||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Stream up.");
|
||||
_logger.LogInformation("Stream up.");
|
||||
|
||||
IsRaidStarted = false;
|
||||
|
||||
UserChannel.IsLive = true;
|
||||
}
|
||||
|
||||
|
@ -6,16 +6,29 @@ using System.Text;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BetterRaid.Services;
|
||||
using BetterRaid.Misc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterRaid.Misc;
|
||||
namespace BetterRaid.Services;
|
||||
|
||||
public static class Tools
|
||||
public interface IWebToolsService
|
||||
{
|
||||
private static HttpListener? _oauthListener;
|
||||
void StartOAuthLogin(ITwitchService twitch, Action? callback = null, CancellationToken token = default);
|
||||
void OpenUrl(string url);
|
||||
}
|
||||
|
||||
public class WebToolsService : IWebToolsService
|
||||
{
|
||||
private HttpListener? _oauthListener;
|
||||
private readonly ILogger<WebToolsService> _logger;
|
||||
|
||||
public WebToolsService(ILogger<WebToolsService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// Source: https://stackoverflow.com/a/43232486
|
||||
public static void StartOAuthLogin(ITwitchService twitchService, Action? callback = null, CancellationToken token = default)
|
||||
public void StartOAuthLogin(ITwitchService twitch, Action? callback = null, CancellationToken token = default)
|
||||
{
|
||||
if (_oauthListener == null)
|
||||
{
|
||||
@ -23,13 +36,13 @@ public static class Tools
|
||||
_oauthListener.Prefixes.Add(Constants.TwitchOAuthRedirectUrl + "/");
|
||||
_oauthListener.Start();
|
||||
|
||||
Task.Run(() => WaitForCallback(callback, token, twitchService), token);
|
||||
Task.Run(() => WaitForCallback(twitch, callback, token), token);
|
||||
}
|
||||
|
||||
OpenUrl(twitchService.GetOAuthUrl());
|
||||
OpenUrl(twitch.GetOAuthUrl());
|
||||
}
|
||||
|
||||
private static async Task WaitForCallback(Action? callback, CancellationToken token, ITwitchService twitchService)
|
||||
private async Task WaitForCallback(ITwitchService twitch, Action? callback, CancellationToken token)
|
||||
{
|
||||
if (_oauthListener == null)
|
||||
return;
|
||||
@ -37,7 +50,7 @@ public static class Tools
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
Console.WriteLine("Starting token listener");
|
||||
_logger.LogDebug("Starting token listener");
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
@ -48,7 +61,7 @@ public static class Tools
|
||||
if (req.Url == null)
|
||||
continue;
|
||||
|
||||
Console.WriteLine("{0} {1}", req.HttpMethod, req.Url);
|
||||
_logger.LogDebug("{method} {url}", req.HttpMethod, req.Url);
|
||||
|
||||
// Response, that may contain the access token as fragment
|
||||
// It must be extracted client-side in browser
|
||||
@ -64,7 +77,7 @@ public static class Tools
|
||||
|
||||
req.InputStream.Close();
|
||||
|
||||
Console.WriteLine(data.ToString());
|
||||
_logger.LogTrace("{data}", data);
|
||||
|
||||
res.StatusCode = 200;
|
||||
await res.OutputStream.WriteAsync(Encoding.UTF8.GetBytes(OAUTH_CLIENT_DOCUMENT).AsMemory(0, OAUTH_CLIENT_DOCUMENT.Length), token);
|
||||
@ -88,8 +101,8 @@ public static class Tools
|
||||
|
||||
if (jsonData == null)
|
||||
{
|
||||
Console.WriteLine("[ERROR] Failed to parse JSON data:");
|
||||
Console.WriteLine(json);
|
||||
_logger.LogError("Failed to parse JSON data:");
|
||||
_logger.LogError("{json}", json);
|
||||
|
||||
res.StatusCode = 400;
|
||||
res.Close();
|
||||
@ -98,21 +111,21 @@ public static class Tools
|
||||
|
||||
if (jsonData["access_token"] == null)
|
||||
{
|
||||
Console.WriteLine("[ERROR] Missing access_token in JSON data.");
|
||||
_logger.LogError("Missing access_token in JSON data.");
|
||||
|
||||
res.StatusCode = 400;
|
||||
res.Close();
|
||||
continue;
|
||||
}
|
||||
|
||||
var accessToken = jsonData["access_token"]?.ToString();
|
||||
var accessToken = jsonData["access_token"]?.ToString()!;
|
||||
|
||||
twitchService.ConnectApiAsync(Constants.TwitchClientId, accessToken!);
|
||||
await twitch.ConnectApiAsync(Constants.TwitchClientId, accessToken);
|
||||
|
||||
res.StatusCode = 200;
|
||||
res.Close();
|
||||
|
||||
Console.WriteLine("[INFO] Received access token!");
|
||||
_logger.LogInformation("Received access token!");
|
||||
|
||||
callback?.Invoke();
|
||||
|
||||
@ -122,7 +135,7 @@ public static class Tools
|
||||
}
|
||||
}
|
||||
|
||||
public static void OpenUrl(string url)
|
||||
public void OpenUrl(string url)
|
||||
{
|
||||
try
|
||||
{
|
@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterRaid.ViewModels;
|
||||
|
||||
public class AddChannelWindowViewModel : ViewModelBase
|
||||
{
|
||||
public AddChannelWindowViewModel()
|
||||
public AddChannelWindowViewModel(ILogger<AddChannelWindowViewModel> logger)
|
||||
{
|
||||
Console.WriteLine("[DEBUG] AddChannelWindowViewModel created");
|
||||
logger.LogDebug("AddChannelWindowViewModel created");
|
||||
}
|
||||
}
|
@ -10,27 +10,24 @@ using BetterRaid.Misc;
|
||||
using BetterRaid.Models;
|
||||
using BetterRaid.Services;
|
||||
using BetterRaid.Views;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterRaid.ViewModels;
|
||||
|
||||
public class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
private string? _filter;
|
||||
private ObservableCollection<TwitchChannel> _channels = [];
|
||||
private readonly BetterRaidDatabase? _db;
|
||||
private readonly ISynchronizaionService _synchronizaionService;
|
||||
|
||||
public BetterRaidDatabase? Database
|
||||
{
|
||||
get => _db;
|
||||
private init
|
||||
{
|
||||
if (SetProperty(ref _db, value) && _db != null)
|
||||
{
|
||||
LoadChannelsFromDb();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ISynchronizaionService _synchronizationService;
|
||||
private readonly ILogger<MainWindowViewModel> _logger;
|
||||
private readonly IWebToolsService _webTools;
|
||||
private readonly IDatabaseService _db;
|
||||
private readonly ITwitchService _twitch;
|
||||
|
||||
private string? _filter;
|
||||
private bool _onlyOnline;
|
||||
|
||||
public ITwitchService Twitch => _twitch;
|
||||
|
||||
public ObservableCollection<TwitchChannel> Channels
|
||||
{
|
||||
@ -40,25 +37,36 @@ public class MainWindowViewModel : ViewModelBase
|
||||
|
||||
public ObservableCollection<TwitchChannel> FilteredChannels => GetFilteredChannels();
|
||||
|
||||
public ITwitchService Twitch { get; }
|
||||
|
||||
public string? Filter
|
||||
{
|
||||
get => _filter;
|
||||
set => SetProperty(ref _filter, value);
|
||||
}
|
||||
|
||||
public bool IsLoggedIn => Twitch.UserChannel != null;
|
||||
|
||||
public MainWindowViewModel(ITwitchService twitch, ISynchronizaionService synchronizaionService)
|
||||
|
||||
public bool OnlyOnline
|
||||
{
|
||||
_synchronizaionService = synchronizaionService;
|
||||
|
||||
Twitch = twitch;
|
||||
Twitch.PropertyChanged += OnTwitchPropertyChanged;
|
||||
get => _db.OnlyOnline;
|
||||
set => SetProperty(ref _onlyOnline, value);
|
||||
}
|
||||
|
||||
Database = BetterRaidDatabase.LoadFromFile(Constants.DatabaseFilePath);
|
||||
Database.PropertyChanged += OnDatabasePropertyChanged;
|
||||
public bool IsLoggedIn => _twitch.UserChannel != null;
|
||||
|
||||
public MainWindowViewModel(
|
||||
ILogger<MainWindowViewModel> logger,
|
||||
ITwitchService twitch,
|
||||
IWebToolsService webTools,
|
||||
IDatabaseService db,
|
||||
ISynchronizaionService synchronizationService)
|
||||
{
|
||||
_logger = logger;
|
||||
_twitch = twitch;
|
||||
_webTools = webTools;
|
||||
_db = db;
|
||||
_synchronizationService = synchronizationService;
|
||||
|
||||
_twitch.PropertyChanged += OnTwitchPropertyChanged;
|
||||
|
||||
LoadChannelsFromDb();
|
||||
}
|
||||
|
||||
public void ExitApplication()
|
||||
@ -76,7 +84,7 @@ public class MainWindowViewModel : ViewModelBase
|
||||
|
||||
public void LoginWithTwitch()
|
||||
{
|
||||
Tools.StartOAuthLogin(Twitch, OnTwitchLoginCallback, CancellationToken.None);
|
||||
_webTools.StartOAuthLogin(_twitch, OnTwitchLoginCallback, CancellationToken.None);
|
||||
}
|
||||
|
||||
private void OnTwitchLoginCallback()
|
||||
@ -86,28 +94,25 @@ public class MainWindowViewModel : ViewModelBase
|
||||
|
||||
private void LoadChannelsFromDb()
|
||||
{
|
||||
if (_db == null)
|
||||
if (_db.Database == null)
|
||||
{
|
||||
_logger.LogError("Database is null");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
foreach (var channel in Channels)
|
||||
{
|
||||
Twitch.UnregisterFromEvents(channel);
|
||||
_twitch.UnregisterFromEvents(channel);
|
||||
}
|
||||
|
||||
Channels.Clear();
|
||||
|
||||
var channels = _db.Channels
|
||||
.Select(channelName => new TwitchChannel(channelName))
|
||||
.ToList();
|
||||
|
||||
foreach (var channel in channels)
|
||||
foreach (var channel in _db.Database.Channels)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
channel.UpdateChannelData(Twitch);
|
||||
Twitch.RegisterForEvents(channel);
|
||||
channel.UpdateChannelData(_twitch);
|
||||
_twitch.RegisterForEvents(channel);
|
||||
});
|
||||
|
||||
Channels.Add(channel);
|
||||
@ -117,7 +122,7 @@ public class MainWindowViewModel : ViewModelBase
|
||||
private ObservableCollection<TwitchChannel> GetFilteredChannels()
|
||||
{
|
||||
var filteredChannels = Channels
|
||||
.Where(channel => Database?.OnlyOnline == false || channel.IsLive)
|
||||
.Where(channel => OnlyOnline == false || channel.IsLive)
|
||||
.Where(channel => string.IsNullOrWhiteSpace(Filter) || channel.Name?.Contains(Filter, StringComparison.OrdinalIgnoreCase) == true)
|
||||
.ToList();
|
||||
|
||||
@ -126,7 +131,7 @@ public class MainWindowViewModel : ViewModelBase
|
||||
|
||||
private void OnTwitchPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != nameof(Twitch.UserChannel))
|
||||
if (e.PropertyName != nameof(_twitch.UserChannel))
|
||||
return;
|
||||
|
||||
OnPropertyChanged(nameof(IsLoggedIn));
|
||||
@ -141,12 +146,4 @@ public class MainWindowViewModel : ViewModelBase
|
||||
OnPropertyChanged(nameof(FilteredChannels));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDatabasePropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != nameof(BetterRaidDatabase.OnlyOnline))
|
||||
return;
|
||||
|
||||
OnPropertyChanged(nameof(FilteredChannels));
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@
|
||||
Spacing="5">
|
||||
|
||||
<CheckBox Content="Only Online"
|
||||
IsChecked="{Binding Database.OnlyOnline, Mode=TwoWay, FallbackValue=False}" />
|
||||
IsChecked="{Binding OnlyOnline, Mode=TwoWay, FallbackValue=False}" />
|
||||
|
||||
<TextBox Width="200"
|
||||
Margin="5, 10, 5, 10"
|
||||
|
Reference in New Issue
Block a user