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;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Data.Core.Plugins;
|
using Avalonia.Data.Core.Plugins;
|
||||||
@ -6,27 +7,69 @@ using Avalonia.Markup.Xaml;
|
|||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using BetterRaid.Services;
|
using BetterRaid.Services;
|
||||||
using BetterRaid.Services.Implementations;
|
using BetterRaid.Services.Implementations;
|
||||||
|
using BetterRaid.ViewModels;
|
||||||
using BetterRaid.Views;
|
using BetterRaid.Views;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace BetterRaid;
|
namespace BetterRaid;
|
||||||
|
|
||||||
public class App : Application
|
public class App : Application
|
||||||
{
|
{
|
||||||
private ServiceProvider? _serviceProvider;
|
private ServiceProvider? _serviceProvider;
|
||||||
|
private ILogger<App>? _logger;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
_serviceProvider = InitializeServices();
|
_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);
|
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()
|
private ServiceProvider InitializeServices()
|
||||||
{
|
{
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
|
services.AddLogging(logging =>
|
||||||
|
{
|
||||||
|
logging.SetMinimumLevel(LogLevel.Debug);
|
||||||
|
logging.AddConsole();
|
||||||
|
});
|
||||||
services.AddSingleton<ITwitchService, TwitchService>();
|
services.AddSingleton<ITwitchService, TwitchService>();
|
||||||
services.AddSingleton<ISynchronizaionService, DispatcherService>(serviceProvider => new DispatcherService(Dispatcher.UIThread));
|
services.AddSingleton<IWebToolsService, WebToolsService>();
|
||||||
services.AddTransient<IMainViewModelFactory, MainWindowViewModelFactory>();
|
services.AddSingleton<IDatabaseService, DatabaseService>();
|
||||||
|
services.AddSingleton<ISynchronizaionService, DispatcherService>(_ => new DispatcherService(Dispatcher.UIThread));
|
||||||
|
services.AddTransient<MainWindowViewModel>();
|
||||||
|
|
||||||
return services.BuildServiceProvider();
|
return services.BuildServiceProvider();
|
||||||
}
|
}
|
||||||
@ -40,17 +83,16 @@ public class App : Application
|
|||||||
throw new FieldAccessException($"\"{nameof(_serviceProvider)}\" was null");
|
throw new FieldAccessException($"\"{nameof(_serviceProvider)}\" was null");
|
||||||
}
|
}
|
||||||
|
|
||||||
var viewModelFactory = _serviceProvider.GetRequiredService<IMainViewModelFactory>();
|
|
||||||
var mainWindowViewModel = viewModelFactory.CreateMainWindowViewModel();
|
|
||||||
var mainWindow = new MainWindow
|
var mainWindow = new MainWindow
|
||||||
{
|
{
|
||||||
DataContext = mainWindowViewModel
|
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (ApplicationLifetime)
|
switch (ApplicationLifetime)
|
||||||
{
|
{
|
||||||
case IClassicDesktopStyleApplicationLifetime desktop:
|
case IClassicDesktopStyleApplicationLifetime desktop:
|
||||||
desktop.MainWindow = mainWindow;
|
desktop.MainWindow = mainWindow;
|
||||||
|
desktop.Exit += OnDesktopOnExit;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ISingleViewApplicationLifetime singleViewPlatform:
|
case ISingleViewApplicationLifetime singleViewPlatform:
|
||||||
@ -60,4 +102,22 @@ public class App : Application
|
|||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
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 Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.0" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
<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" />
|
<PackageReference Include="TwitchLib" Version="3.5.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</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.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BetterRaid.Services;
|
using BetterRaid.Services;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using TwitchLib.PubSub.Events;
|
using TwitchLib.PubSub.Events;
|
||||||
|
|
||||||
namespace BetterRaid.Models;
|
namespace BetterRaid.Models;
|
||||||
|
|
||||||
|
[JsonObject]
|
||||||
public class TwitchChannel : INotifyPropertyChanged
|
public class TwitchChannel : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
|
private string? _id;
|
||||||
private string? _broadcasterId;
|
private string? _broadcasterId;
|
||||||
private string? _viewerCount;
|
private string? _viewerCount;
|
||||||
private bool _isLive;
|
private bool _isLive;
|
||||||
@ -18,7 +21,6 @@ public class TwitchChannel : INotifyPropertyChanged
|
|||||||
private string? _category;
|
private string? _category;
|
||||||
private string? _title;
|
private string? _title;
|
||||||
private DateTime? _lastRaided;
|
private DateTime? _lastRaided;
|
||||||
private string? _id;
|
|
||||||
|
|
||||||
public string? Id
|
public string? Id
|
||||||
{
|
{
|
||||||
@ -59,6 +61,7 @@ public class TwitchChannel : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public bool IsLive
|
public bool IsLive
|
||||||
{
|
{
|
||||||
get => _isLive;
|
get => _isLive;
|
||||||
@ -72,6 +75,7 @@ public class TwitchChannel : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public string? ViewerCount
|
public string? ViewerCount
|
||||||
{
|
{
|
||||||
get => _viewerCount;
|
get => _viewerCount;
|
||||||
@ -111,6 +115,7 @@ public class TwitchChannel : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public string? Category
|
public string? Category
|
||||||
{
|
{
|
||||||
get => _category;
|
get => _category;
|
||||||
@ -124,6 +129,7 @@ public class TwitchChannel : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public string? Title
|
public string? Title
|
||||||
{
|
{
|
||||||
get => _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;
|
namespace BetterRaid.Services.Implementations;
|
||||||
|
|
||||||
public class MainWindowViewModelFactory : IMainViewModelFactory
|
public class MainWindowViewModelFactory// : IMainViewModelFactory
|
||||||
{
|
{
|
||||||
private readonly ITwitchService _twitchService;
|
private readonly ITwitchService _twitchService;
|
||||||
private readonly ISynchronizaionService _synchronizaionService;
|
private readonly ISynchronizaionService _synchronizaionService;
|
||||||
@ -13,8 +13,8 @@ public class MainWindowViewModelFactory : IMainViewModelFactory
|
|||||||
_synchronizaionService = synchronizaionService;
|
_synchronizaionService = synchronizaionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MainWindowViewModel CreateMainWindowViewModel()
|
//public MainWindowViewModel CreateMainWindowViewModel()
|
||||||
{
|
//{
|
||||||
return new MainWindowViewModel(_twitchService, _synchronizaionService);
|
// return new MainWindowViewModel(_twitchService, _synchronizaionService);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BetterRaid.Misc;
|
using BetterRaid.Misc;
|
||||||
using BetterRaid.Models;
|
using BetterRaid.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using TwitchLib.Api;
|
using TwitchLib.Api;
|
||||||
using TwitchLib.Api.Helix.Models.Users.GetUsers;
|
using TwitchLib.Api.Helix.Models.Users.GetUsers;
|
||||||
using TwitchLib.PubSub;
|
using TwitchLib.PubSub;
|
||||||
@ -40,8 +41,9 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
private bool _isRaidStarted;
|
private bool _isRaidStarted;
|
||||||
private int _raidParticipants;
|
private int _raidParticipants;
|
||||||
private TwitchChannel? _userChannel;
|
private TwitchChannel? _userChannel;
|
||||||
private readonly List<TwitchChannel> _registeredChannels;
|
|
||||||
private User? _user;
|
private User? _user;
|
||||||
|
private readonly ILogger<TwitchService> _logger;
|
||||||
|
private readonly IWebToolsService _webTools;
|
||||||
|
|
||||||
public string AccessToken { get; private set; } = string.Empty;
|
public string AccessToken { get; private set; } = string.Empty;
|
||||||
|
|
||||||
@ -80,46 +82,46 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
set => SetField(ref _raidParticipants, value);
|
set => SetField(ref _raidParticipants, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TwitchService()
|
public TwitchService(ILogger<TwitchService> logger, IWebToolsService webTools)
|
||||||
{
|
{
|
||||||
_registeredChannels = [];
|
_logger = logger;
|
||||||
|
_webTools = webTools;
|
||||||
|
|
||||||
TwitchApi = new TwitchAPI();
|
TwitchApi = new TwitchAPI();
|
||||||
TwitchEvents = new TwitchPubSub();
|
TwitchEvents = new TwitchPubSub();
|
||||||
|
|
||||||
if (TryLoadAccessToken(out var token))
|
if (TryLoadAccessToken(out var token))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Found access token.");
|
_logger.LogInformation("Found access token.");
|
||||||
Task.Run(() => ConnectApiAsync(Constants.TwitchClientId, token))
|
Task.Run(() => ConnectApiAsync(Constants.TwitchClientId, token))
|
||||||
.ContinueWith(_ => ConnectTwitchEvents(token));
|
.ContinueWith(_ => ConnectTwitchEvents());
|
||||||
}
|
}
|
||||||
else
|
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)
|
if (UserChannel == null || User == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Connecting to Twitch Events ...");
|
_logger.LogInformation("Connecting to Twitch Events ...");
|
||||||
|
|
||||||
TwitchEvents.OnRaidGo += OnUserRaidGo;
|
TwitchEvents.OnRaidGo += OnUserRaidGo;
|
||||||
TwitchEvents.OnRaidUpdate += OnUserRaidUpdate;
|
TwitchEvents.OnRaidUpdate += OnUserRaidUpdate;
|
||||||
TwitchEvents.OnStreamUp += OnUserStreamUp;
|
TwitchEvents.OnStreamUp += OnUserStreamUp;
|
||||||
TwitchEvents.OnStreamDown += OnUserStreamDown;
|
TwitchEvents.OnStreamDown += OnUserStreamDown;
|
||||||
TwitchEvents.OnViewCount += OnViewCount;
|
TwitchEvents.OnViewCount += OnViewCount;
|
||||||
TwitchEvents.OnLog += (sender, args) => Console.WriteLine($"[INFO][{nameof(TwitchService)}] {args.Data}");
|
TwitchEvents.OnLog += OnPubSubLog;
|
||||||
TwitchEvents.OnPubSubServiceError += (sender, args) => Console.WriteLine($"[ERROR][{nameof(TwitchService)}] {args.Exception.Message}");
|
TwitchEvents.OnPubSubServiceError += OnPubSubServiceError;
|
||||||
TwitchEvents.OnPubSubServiceConnected += (sender, args) => Console.WriteLine($"[INFO][{nameof(TwitchService)}] Connected to Twitch PubSub.");
|
TwitchEvents.OnPubSubServiceConnected += OnPubSubServiceConnected;
|
||||||
TwitchEvents.OnPubSubServiceClosed += (sender, args) => Console.WriteLine($"[INFO][{nameof(TwitchService)}] Disconnected from Twitch PubSub.");
|
TwitchEvents.OnPubSubServiceClosed += OnPubSubServiceClosed;
|
||||||
|
|
||||||
TwitchEvents.ListenToVideoPlayback(UserChannel.BroadcasterId);
|
TwitchEvents.ListenToVideoPlayback(UserChannel.BroadcasterId);
|
||||||
TwitchEvents.ListenToRaid(UserChannel.BroadcasterId);
|
TwitchEvents.ListenToRaid(UserChannel.BroadcasterId);
|
||||||
|
|
||||||
TwitchEvents.SendTopics(token);
|
|
||||||
TwitchEvents.Connect();
|
TwitchEvents.Connect();
|
||||||
|
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
@ -127,7 +129,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
|
|
||||||
public async Task ConnectApiAsync(string clientId, string accessToken)
|
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;
|
AccessToken = accessToken;
|
||||||
|
|
||||||
@ -142,24 +144,24 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
{
|
{
|
||||||
User = null;
|
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))
|
if (TryGetUserChannel(out var channel))
|
||||||
{
|
{
|
||||||
UserChannel = 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
|
else
|
||||||
{
|
{
|
||||||
UserChannel = null;
|
UserChannel = null;
|
||||||
|
|
||||||
Console.WriteLine($"[ERROR][{nameof(TwitchService)}] Could not get user channel.");
|
_logger.LogError("Could not get user channel.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (User == null || UserChannel == null)
|
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;
|
await Task.CompletedTask;
|
||||||
@ -199,7 +201,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[ERROR][{nameof(TwitchService)}] {e.Message}");
|
_logger.LogError(e, "Could not get user.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,7 +220,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
|
|
||||||
public void RegisterForEvents(TwitchChannel channel)
|
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.OnStreamUp += channel.OnStreamUp;
|
||||||
TwitchEvents.OnStreamDown += channel.OnStreamDown;
|
TwitchEvents.OnStreamDown += channel.OnStreamDown;
|
||||||
@ -227,12 +229,12 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
TwitchEvents.ListenToVideoPlayback(channel.Id);
|
TwitchEvents.ListenToVideoPlayback(channel.Id);
|
||||||
|
|
||||||
TwitchEvents.SendTopics(AccessToken);
|
TwitchEvents.SendTopics(AccessToken);
|
||||||
|
|
||||||
_registeredChannels.Add(channel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnregisterFromEvents(TwitchChannel 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.OnStreamUp -= channel.OnStreamUp;
|
||||||
TwitchEvents.OnStreamDown -= channel.OnStreamDown;
|
TwitchEvents.OnStreamDown -= channel.OnStreamDown;
|
||||||
TwitchEvents.OnViewCount -= channel.OnViewCount;
|
TwitchEvents.OnViewCount -= channel.OnViewCount;
|
||||||
@ -240,8 +242,6 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
TwitchEvents.ListenToVideoPlayback(channel.Id);
|
TwitchEvents.ListenToVideoPlayback(channel.Id);
|
||||||
|
|
||||||
TwitchEvents.SendTopics(AccessToken, true);
|
TwitchEvents.SendTopics(AccessToken, true);
|
||||||
|
|
||||||
_registeredChannels.Remove(channel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetOAuthUrl()
|
public string GetOAuthUrl()
|
||||||
@ -304,7 +304,28 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
|
|
||||||
var url = $"https://twitch.tv/{channelName}";
|
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
|
// TODO Not called while raid is ongoing
|
||||||
@ -314,7 +335,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
// return;
|
// return;
|
||||||
|
|
||||||
RaidParticipants = e.ViewerCount;
|
RaidParticipants = e.ViewerCount;
|
||||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Raid participants: {RaidParticipants}");
|
_logger.LogInformation("Raid participants: {participants}", RaidParticipants);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnViewCount(object? sender, OnViewCountArgs e)
|
private void OnViewCount(object? sender, OnViewCountArgs e)
|
||||||
@ -333,7 +354,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
if (e.ChannelId != UserChannel?.Id)
|
if (e.ChannelId != UserChannel?.Id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Raid started.");
|
_logger.LogInformation("Raid started.");
|
||||||
|
|
||||||
IsRaidStarted = false;
|
IsRaidStarted = false;
|
||||||
}
|
}
|
||||||
@ -346,7 +367,7 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
if (e.ChannelId != UserChannel?.Id)
|
if (e.ChannelId != UserChannel?.Id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Stream down.");
|
_logger.LogInformation("Stream down.");
|
||||||
|
|
||||||
IsRaidStarted = false;
|
IsRaidStarted = false;
|
||||||
|
|
||||||
@ -361,10 +382,9 @@ public sealed class TwitchService : ITwitchService, INotifyPropertyChanged, INot
|
|||||||
if (e.ChannelId != UserChannel?.Id)
|
if (e.ChannelId != UserChannel?.Id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Console.WriteLine($"[INFO][{nameof(TwitchService)}] Stream up.");
|
_logger.LogInformation("Stream up.");
|
||||||
|
|
||||||
IsRaidStarted = false;
|
IsRaidStarted = false;
|
||||||
|
|
||||||
UserChannel.IsLive = true;
|
UserChannel.IsLive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,16 +6,29 @@ using System.Text;
|
|||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
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
|
// 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)
|
if (_oauthListener == null)
|
||||||
{
|
{
|
||||||
@ -23,13 +36,13 @@ public static class Tools
|
|||||||
_oauthListener.Prefixes.Add(Constants.TwitchOAuthRedirectUrl + "/");
|
_oauthListener.Prefixes.Add(Constants.TwitchOAuthRedirectUrl + "/");
|
||||||
_oauthListener.Start();
|
_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)
|
if (_oauthListener == null)
|
||||||
return;
|
return;
|
||||||
@ -37,7 +50,7 @@ public static class Tools
|
|||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Console.WriteLine("Starting token listener");
|
_logger.LogDebug("Starting token listener");
|
||||||
|
|
||||||
while (!token.IsCancellationRequested)
|
while (!token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@ -48,7 +61,7 @@ public static class Tools
|
|||||||
if (req.Url == null)
|
if (req.Url == null)
|
||||||
continue;
|
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
|
// Response, that may contain the access token as fragment
|
||||||
// It must be extracted client-side in browser
|
// It must be extracted client-side in browser
|
||||||
@ -64,7 +77,7 @@ public static class Tools
|
|||||||
|
|
||||||
req.InputStream.Close();
|
req.InputStream.Close();
|
||||||
|
|
||||||
Console.WriteLine(data.ToString());
|
_logger.LogTrace("{data}", data);
|
||||||
|
|
||||||
res.StatusCode = 200;
|
res.StatusCode = 200;
|
||||||
await res.OutputStream.WriteAsync(Encoding.UTF8.GetBytes(OAUTH_CLIENT_DOCUMENT).AsMemory(0, OAUTH_CLIENT_DOCUMENT.Length), token);
|
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)
|
if (jsonData == null)
|
||||||
{
|
{
|
||||||
Console.WriteLine("[ERROR] Failed to parse JSON data:");
|
_logger.LogError("Failed to parse JSON data:");
|
||||||
Console.WriteLine(json);
|
_logger.LogError("{json}", json);
|
||||||
|
|
||||||
res.StatusCode = 400;
|
res.StatusCode = 400;
|
||||||
res.Close();
|
res.Close();
|
||||||
@ -98,21 +111,21 @@ public static class Tools
|
|||||||
|
|
||||||
if (jsonData["access_token"] == null)
|
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.StatusCode = 400;
|
||||||
res.Close();
|
res.Close();
|
||||||
continue;
|
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.StatusCode = 200;
|
||||||
res.Close();
|
res.Close();
|
||||||
|
|
||||||
Console.WriteLine("[INFO] Received access token!");
|
_logger.LogInformation("Received access token!");
|
||||||
|
|
||||||
callback?.Invoke();
|
callback?.Invoke();
|
||||||
|
|
||||||
@ -122,7 +135,7 @@ public static class Tools
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenUrl(string url)
|
public void OpenUrl(string url)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace BetterRaid.ViewModels;
|
namespace BetterRaid.ViewModels;
|
||||||
|
|
||||||
public class AddChannelWindowViewModel : ViewModelBase
|
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.Models;
|
||||||
using BetterRaid.Services;
|
using BetterRaid.Services;
|
||||||
using BetterRaid.Views;
|
using BetterRaid.Views;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace BetterRaid.ViewModels;
|
namespace BetterRaid.ViewModels;
|
||||||
|
|
||||||
public class MainWindowViewModel : ViewModelBase
|
public class MainWindowViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private string? _filter;
|
|
||||||
private ObservableCollection<TwitchChannel> _channels = [];
|
private ObservableCollection<TwitchChannel> _channels = [];
|
||||||
private readonly BetterRaidDatabase? _db;
|
|
||||||
private readonly ISynchronizaionService _synchronizaionService;
|
private readonly ISynchronizaionService _synchronizationService;
|
||||||
|
private readonly ILogger<MainWindowViewModel> _logger;
|
||||||
public BetterRaidDatabase? Database
|
private readonly IWebToolsService _webTools;
|
||||||
{
|
private readonly IDatabaseService _db;
|
||||||
get => _db;
|
private readonly ITwitchService _twitch;
|
||||||
private init
|
|
||||||
{
|
private string? _filter;
|
||||||
if (SetProperty(ref _db, value) && _db != null)
|
private bool _onlyOnline;
|
||||||
{
|
|
||||||
LoadChannelsFromDb();
|
public ITwitchService Twitch => _twitch;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableCollection<TwitchChannel> Channels
|
public ObservableCollection<TwitchChannel> Channels
|
||||||
{
|
{
|
||||||
@ -40,25 +37,36 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
public ObservableCollection<TwitchChannel> FilteredChannels => GetFilteredChannels();
|
public ObservableCollection<TwitchChannel> FilteredChannels => GetFilteredChannels();
|
||||||
|
|
||||||
public ITwitchService Twitch { get; }
|
|
||||||
|
|
||||||
public string? Filter
|
public string? Filter
|
||||||
{
|
{
|
||||||
get => _filter;
|
get => _filter;
|
||||||
set => SetProperty(ref _filter, value);
|
set => SetProperty(ref _filter, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsLoggedIn => Twitch.UserChannel != null;
|
public bool OnlyOnline
|
||||||
|
|
||||||
public MainWindowViewModel(ITwitchService twitch, ISynchronizaionService synchronizaionService)
|
|
||||||
{
|
{
|
||||||
_synchronizaionService = synchronizaionService;
|
get => _db.OnlyOnline;
|
||||||
|
set => SetProperty(ref _onlyOnline, value);
|
||||||
Twitch = twitch;
|
}
|
||||||
Twitch.PropertyChanged += OnTwitchPropertyChanged;
|
|
||||||
|
|
||||||
Database = BetterRaidDatabase.LoadFromFile(Constants.DatabaseFilePath);
|
public bool IsLoggedIn => _twitch.UserChannel != null;
|
||||||
Database.PropertyChanged += OnDatabasePropertyChanged;
|
|
||||||
|
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()
|
public void ExitApplication()
|
||||||
@ -76,7 +84,7 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
public void LoginWithTwitch()
|
public void LoginWithTwitch()
|
||||||
{
|
{
|
||||||
Tools.StartOAuthLogin(Twitch, OnTwitchLoginCallback, CancellationToken.None);
|
_webTools.StartOAuthLogin(_twitch, OnTwitchLoginCallback, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTwitchLoginCallback()
|
private void OnTwitchLoginCallback()
|
||||||
@ -86,28 +94,25 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
private void LoadChannelsFromDb()
|
private void LoadChannelsFromDb()
|
||||||
{
|
{
|
||||||
if (_db == null)
|
if (_db.Database == null)
|
||||||
{
|
{
|
||||||
|
_logger.LogError("Database is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var channel in Channels)
|
foreach (var channel in Channels)
|
||||||
{
|
{
|
||||||
Twitch.UnregisterFromEvents(channel);
|
_twitch.UnregisterFromEvents(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
Channels.Clear();
|
Channels.Clear();
|
||||||
|
|
||||||
var channels = _db.Channels
|
foreach (var channel in _db.Database.Channels)
|
||||||
.Select(channelName => new TwitchChannel(channelName))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var channel in channels)
|
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
channel.UpdateChannelData(Twitch);
|
channel.UpdateChannelData(_twitch);
|
||||||
Twitch.RegisterForEvents(channel);
|
_twitch.RegisterForEvents(channel);
|
||||||
});
|
});
|
||||||
|
|
||||||
Channels.Add(channel);
|
Channels.Add(channel);
|
||||||
@ -117,7 +122,7 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
private ObservableCollection<TwitchChannel> GetFilteredChannels()
|
private ObservableCollection<TwitchChannel> GetFilteredChannels()
|
||||||
{
|
{
|
||||||
var filteredChannels = Channels
|
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)
|
.Where(channel => string.IsNullOrWhiteSpace(Filter) || channel.Name?.Contains(Filter, StringComparison.OrdinalIgnoreCase) == true)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@ -126,7 +131,7 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
private void OnTwitchPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
private void OnTwitchPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.PropertyName != nameof(Twitch.UserChannel))
|
if (e.PropertyName != nameof(_twitch.UserChannel))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
OnPropertyChanged(nameof(IsLoggedIn));
|
OnPropertyChanged(nameof(IsLoggedIn));
|
||||||
@ -141,12 +146,4 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
OnPropertyChanged(nameof(FilteredChannels));
|
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">
|
Spacing="5">
|
||||||
|
|
||||||
<CheckBox Content="Only Online"
|
<CheckBox Content="Only Online"
|
||||||
IsChecked="{Binding Database.OnlyOnline, Mode=TwoWay, FallbackValue=False}" />
|
IsChecked="{Binding OnlyOnline, Mode=TwoWay, FallbackValue=False}" />
|
||||||
|
|
||||||
<TextBox Width="200"
|
<TextBox Width="200"
|
||||||
Margin="5, 10, 5, 10"
|
Margin="5, 10, 5, 10"
|
||||||
|
Reference in New Issue
Block a user