Skip to content

Commit

Permalink
Bump version to v6.8.0
Browse files Browse the repository at this point in the history
* Refactor Axuno.Tools.FileSystem.DelayedFileSystemWatcher with bugfixing
* Axuno.Tools.GermanHolidays evaluates elements/attributes case-sensitive when loading XML
* Bugfix: League.Caching.ReportSheetCache
* Move RazorViewToStringRenderer to namespace League.Emailing
* Bugfix: League.TextTemplatingModule.EmailTemplateDefinitionProvider
* Bugfix: Views.Match.ReportSheet.cshtml
* Bugfix: League.Tests.UnitTestHelpers
  • Loading branch information
axunonb committed Apr 18, 2024
1 parent 346dab0 commit c7c5755
Show file tree
Hide file tree
Showing 16 changed files with 139 additions and 119 deletions.
89 changes: 58 additions & 31 deletions Axuno.Tools/FileSystem/DelayedFileSystemWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ public class DelayedFileSystemWatcher : IDisposable
/// <summary>
/// Lock order is _enterThread, _events.SyncRoot
/// </summary>
private readonly object _enterThread = new (); // Only one timer event is processed at any given moment
private readonly object _enterThread = new(); // Only one timer event is processed at any given moment

/// <summary>
/// Stores the events fired by <see cref="FileSystemWatcher"/>.
/// </summary>
private ArrayList _events = new ();
private ArrayList _events = new();

private int _consolidationInterval = 1000; // initial value in milliseconds
private readonly System.Timers.Timer _timer;
Expand Down Expand Up @@ -76,7 +76,7 @@ public DelayedFileSystemWatcher(string path, IEnumerable<string> typeFilters)
{
_fileSystemWatcher = new FileSystemWatcher(path);
foreach (var filter in typeFilters) _fileSystemWatcher.Filters.Add(filter);

Initialize(out _timer);
}

Expand Down Expand Up @@ -323,19 +323,31 @@ private void RenamedEventHandler(object sender, RenamedEventArgs e)

private void ElapsedEventHandler(object? sender, ElapsedEventArgs e)
{
if (!Monitor.TryEnter(_enterThread))
return;
var eventsToBeFired = TryProcessEvents();

try
if (eventsToBeFired != null)
{
var eventsToBeFired = ProcessEvents();
if (eventsToBeFired != null)
RaiseEvents(eventsToBeFired);
RaiseEvents(eventsToBeFired);
}
finally
}

private Queue? TryProcessEvents()
{
Queue? eventsToBeFired = null;

if (Monitor.TryEnter(_enterThread))
{
Monitor.Exit(_enterThread);
try
{
eventsToBeFired = ProcessEvents();
}
finally
{
Monitor.Exit(_enterThread);
}
}

return eventsToBeFired;
}

private Queue? ProcessEvents()
Expand All @@ -346,13 +358,16 @@ private void ElapsedEventHandler(object? sender, ElapsedEventArgs e)
{
for (var i = 0; i < _events.Count; i++)
{
if (_events[i] is not DelayedEvent current)
continue;
var current = _events[i] as DelayedEvent;

if (current.Delayed)
if (current is { Delayed: true })
{
ProcessDelayedEvent(current, eventsToBeFired, ref i);
}
else
current.Delayed = true;
{
DelayEvent(current);
}
}
}

Expand All @@ -361,41 +376,53 @@ private void ElapsedEventHandler(object? sender, ElapsedEventArgs e)

private void ProcessDelayedEvent(DelayedEvent current, Queue eventsToBeFired, ref int i)
{
if (current.Args.ChangeType is not (WatcherChangeTypes.Created or WatcherChangeTypes.Changed))
return;
RemoveDuplicateEvents(current, i);

if (!CanOpenFile(current.Args.FullPath))
if (!ShouldRaiseEvent(current))
return;

RemoveDuplicates(current);
eventsToBeFired.Enqueue(current);
_events.RemoveAt(i);
i--; // Decrement i to process the next event correctly
i--;
}

private bool CanOpenFile(string fullPath)
private void RemoveDuplicateEvents(DelayedEvent current, int i)
{
try
for (var j = _events.Count - 1; j > i; j--)
{
using var stream = File.Open(fullPath, FileMode.Open, FileAccess.Read, FileShare.None);
return true;
if (current.IsDuplicate(_events[j]))
{
_events.RemoveAt(j);
}
}
catch (IOException)
}

private bool ShouldRaiseEvent(DelayedEvent current)
{
if (current.Args.ChangeType is WatcherChangeTypes.Created or WatcherChangeTypes.Changed)
{
return false;
try
{
using var stream = File.Open(current.Args.FullPath, FileMode.Open, FileAccess.Read, FileShare.None);
return true;
}
catch (IOException)
{
return false;
}
}

return true;
}

private void RemoveDuplicates(DelayedEvent current)
private void DelayEvent(DelayedEvent? current)
{
for (var j = _events.Count - 1; j > 0; j--)
if (current != null)
{
if (current.IsDuplicate(_events[j]))
_events.RemoveAt(j);
current.Delayed = true;
}
}


/// <summary>
/// Gets or sets the interval in milliseconds, after which events will be fired.
/// </summary>
Expand Down
21 changes: 11 additions & 10 deletions Axuno.Tools/GermanHolidays.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,15 +380,15 @@ public List<GermanHoliday> GetFiltered(Predicate<GermanHoliday> filter)
/// <param name="path">A URI string referencing the holidays XML file to load.</param>
public void Load(string path)
{
var holidays = XElement.Load(path).Elements("holiday");
var holidays = XElement.Load(path).Elements("Holiday");

foreach (var holiday in holidays)
{
var holidayId = GetHolidayId(holiday);
var action = GetActionType(holiday);
var (dateFrom, dateTo) = GetDateRange(holiday);
var holidayType = GetHolidayType(holiday);
var name = holiday.Element("name")?.Value ?? string.Empty;
var name = holiday.Element("Name")?.Value ?? string.Empty;
var stateIds = GetStateIds(holiday);

ProcessHoliday(holidayId, action, dateFrom, dateTo, holidayType, name, stateIds);
Expand All @@ -397,14 +397,14 @@ public void Load(string path)

private Id? GetHolidayId(XElement holiday)
{
if (holiday.Attribute("id") != null && Enum.TryParse<Id>(holiday.Attribute("id")?.Value, true, out var id))
if (holiday.Attribute("Id") != null && Enum.TryParse<Id>(holiday.Attribute("Id")?.Value, true, out var id))
return id;
return null;
}

private ActionType GetActionType(XElement holiday)
{
if (Enum.TryParse<ActionType>(holiday.Attribute("action")?.Value, true, out var action))
if (Enum.TryParse<ActionType>(holiday.Attribute("Action")?.Value, true, out var action))
return action;
return ActionType.Merge;
}
Expand All @@ -414,10 +414,10 @@ private ActionType GetActionType(XElement holiday)
var dateFrom = DateTime.MinValue;
var dateTo = DateTime.MinValue;

if (holiday.Element("datefrom") != null && holiday.Element("dateto") != null)
if (holiday.Element("DateFrom") != null && holiday.Element("DateTo") != null)
{
dateFrom = DateTime.ParseExact(holiday.Element("datefrom")?.Value!, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None);
dateTo = DateTime.ParseExact(holiday.Element("dateto")?.Value!, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None);
dateFrom = DateTime.ParseExact(holiday.Element("DateFrom")?.Value!, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None);
dateTo = DateTime.ParseExact(holiday.Element("DateTo")?.Value!, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None);

if (dateFrom > dateTo)
(dateFrom, dateTo) = (dateTo, dateFrom);
Expand All @@ -428,7 +428,7 @@ private ActionType GetActionType(XElement holiday)

private Type GetHolidayType(XElement holiday)
{
if (Enum.TryParse<Type>(holiday.Element("type")?.Value, true, out var type))
if (Enum.TryParse<Type>(holiday.Element("Type")?.Value, true, out var type))
return type;
throw new InvalidOperationException("Missing or invalid holiday type.");
}
Expand All @@ -437,7 +437,7 @@ private Type GetHolidayType(XElement holiday)
{
var stateIds = new List<GermanFederalStates.Id>();

var publicHolidayStateIds = holiday.Element("publicholidaystateids");
var publicHolidayStateIds = holiday.Element("PublicHolidayStateIds");
if (publicHolidayStateIds != null && publicHolidayStateIds.HasElements)
{
stateIds.AddRange(publicHolidayStateIds.Elements().Select(stateId =>
Expand All @@ -447,7 +447,8 @@ private Type GetHolidayType(XElement holiday)
return stateIds;
}

private void ProcessHoliday(Id? holidayId, ActionType action, DateTime dateFrom, DateTime dateTo, Type holidayType, string name, List<GermanFederalStates.Id> stateIds)
private void ProcessHoliday(Id? holidayId, ActionType action, DateTime dateFrom, DateTime dateTo, Type holidayType,
string name, List<GermanFederalStates.Id> stateIds)
{
if (action == ActionType.Remove && holidayId.HasValue)
{
Expand Down
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<Copyright>Copyright 2011-$(CurrentYear) axuno gGmbH</Copyright>
<RepositoryUrl>https://github.com/axuno/Volleyball-League</RepositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<Version>6.7.3</Version>
<FileVersion>6.7.3</FileVersion>
<Version>6.8.0</Version>
<FileVersion>6.8.0</FileVersion>
<AssemblyVersion>6.0.0.0</AssemblyVersion> <!--only update AssemblyVersion with major releases -->
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
Expand Down
10 changes: 5 additions & 5 deletions League.Tests/Identity/RoleStoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ await da.ExecuteSQLAsync(CancellationToken.None,
_appDb.DbContext.CommandTimeOut = 2;
// new claim
var role = new ApplicationRole() {Name = Constants.RoleName.TournamentManager};
Assert.Multiple(async () =>
await Assert.MultipleAsync(async () =>
{
Assert.That(await _roleStore.CreateAsync(role, CancellationToken.None), Is.Not.EqualTo(IdentityResult.Success));
Assert.That(await _roleStore.DeleteAsync(role, CancellationToken.None), Is.Not.EqualTo(IdentityResult.Success));
Expand Down Expand Up @@ -150,7 +150,7 @@ public async Task Create_Role()
Assert.That(await _roleStore.CreateAsync(role, CancellationToken.None), Is.EqualTo(IdentityResult.Success));

var createdRole = await _roleStore.FindByNameAsync(role.Name, CancellationToken.None);
Assert.Multiple(async () =>
await Assert.MultipleAsync(async () =>
{
Assert.That(createdRole.Name, Is.EqualTo(role.Name));
Expand All @@ -170,7 +170,7 @@ public async Task Update_Role()

// trying to update a role with an unknown name should fail
createdRole.Name = "some-illegal-role-name";
Assert.Multiple(async () =>
await Assert.MultipleAsync(async () =>
{
Assert.That(await _roleStore.UpdateAsync(createdRole, CancellationToken.None), Is.Not.EqualTo(IdentityResult.Success));
Expand All @@ -180,7 +180,7 @@ public async Task Update_Role()

// update with allowed role name should succeed
createdRole.Name = Constants.RoleName.Player;
Assert.Multiple(async () =>
await Assert.MultipleAsync(async () =>
{
Assert.That(await _roleStore.UpdateAsync(createdRole, CancellationToken.None), Is.EqualTo(IdentityResult.Success));
Assert.That(await _roleStore.FindByNameAsync(Constants.RoleName.Player, CancellationToken.None), Is.Not.Null);
Expand All @@ -203,7 +203,7 @@ public async Task Delete_Role()
var newRole = new ApplicationRole { Name = Constants.RoleName.SystemManager };
Assert.That(await _roleStore.CreateAsync(newRole, CancellationToken.None), Is.EqualTo(IdentityResult.Success));
var createdRole = await _roleStore.FindByNameAsync(Constants.RoleName.SystemManager, CancellationToken.None);
Assert.Multiple(async () =>
await Assert.MultipleAsync(async () =>
{
Assert.That(createdRole, Is.Not.Null);
Expand Down
9 changes: 3 additions & 6 deletions League.Tests/TestComponents/UnitTestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
using NLog.Extensions.Logging;
using SD.LLBLGen.Pro.ORMSupportClasses;
using TournamentManager.MultiTenancy;
using ILogger = Microsoft.Extensions.Logging.ILogger;

namespace League.Tests;

Expand All @@ -30,9 +29,8 @@ public class UnitTestHelpers
public UnitTestHelpers()
{
_configPath = DirectoryLocator.GetTargetConfigurationPath();
var msSqlPath = Path.Combine(DirectoryLocator.GetTargetProjectPath(typeof(League.LeagueStartup)), @"..\..\MsSqlDb");
var msSqlPath = Path.GetFullPath(Path.Combine(DirectoryLocator.GetTargetProjectPath(typeof(League.LeagueStartup)), @"..\..\MsSqlDb"));

// For the unit tests we
_tenantContext = new TenantContext
{
DbContext =
Expand Down Expand Up @@ -64,7 +62,6 @@ private void InitializeLlBlGenPro()
RuntimeConfiguration.ConfigureDQE<SD.LLBLGen.Pro.DQE.SqlServer.SQLServerDQEConfiguration>(c => c
.SetTraceLevel(System.Diagnostics.TraceLevel.Verbose)
.AddDbProviderFactory(typeof(System.Data.SqlClient.SqlClientFactory)));
//RuntimeConfiguration.SetDependencyInjectionInfo(new[] { typeof(TournamentManager.Validators.UserEntityValidator).Assembly }, new[] { "TournamentManager.Validators" });

RuntimeConfiguration.Tracing.SetTraceLevel("ORMPersistenceExecution", System.Diagnostics.TraceLevel.Verbose);
RuntimeConfiguration.Tracing.SetTraceLevel("ORMPlainSQLQueryExecution", System.Diagnostics.TraceLevel.Verbose);
Expand Down Expand Up @@ -126,7 +123,7 @@ public static ServiceProvider GetTextTemplatingServiceProvider(ITenantContext te
{
// The complete Templates folder is embedded in the project file
vfs.FileSets.AddEmbedded<LeagueTemplateRenderer>(nameof(League) + ".Templates");
// vfs.FileSets.AddPhysical(Path.Combine(Directory.GetCurrentDirectory(), "Templates"));
// To use physical files: vfs.FileSets.AddPhysical(Path.Combine(Directory.GetCurrentDirectory(), "Templates"));
},
locOpt =>
{
Expand Down Expand Up @@ -174,7 +171,7 @@ public static TestServer GetLeagueTestServer()

private void HowToUseServices()
{
var logger = (ILogger) GetStandardServiceProvider().GetRequiredService(typeof(ILogger<UnitTestHelpers>));
var logger = (Microsoft.Extensions.Logging.ILogger) GetStandardServiceProvider().GetRequiredService(typeof(ILogger<UnitTestHelpers>));
logger.LogError("error");
var localizer = (IStringLocalizer)GetStandardServiceProvider().GetRequiredService(typeof(IStringLocalizer<League.Controllers.Account>));
_ = localizer["This is your password recovery key"].Value;
Expand Down
15 changes: 4 additions & 11 deletions League/Caching/ReportSheetCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,18 @@ private void EnsureCacheFolder()
public async Task<Stream> GetOrCreatePdf(MatchReportSheetRow data, string html, CancellationToken cancellationToken)
{
EnsureCacheFolder();
var model =
(await _tenantContext.DbContext.AppDb.MatchRepository.GetMatchReportSheetAsync(
_tenantContext.TournamentContext.MatchPlanTournamentId, data.Id, cancellationToken));

if (model is null) return Stream.Null;

var matchId = model.Id;

var cacheFile = GetPathToCacheFile(matchId);
var cacheFile = GetPathToCacheFile(data.Id);

if (!File.Exists(cacheFile) || IsOutdated(cacheFile, data.ModifiedOn))
{
_logger.LogDebug("Create new match report for tenant '{Tenant}', match '{MatchId}'", _tenantContext.Identifier, matchId);
cacheFile = await GetReportSheetChromium(matchId, html, cancellationToken);
_logger.LogDebug("Create new match report for tenant '{Tenant}', match '{MatchId}'", _tenantContext.Identifier, data.Id);
cacheFile = await GetReportSheetChromium(data.Id, html, cancellationToken);
// GetReportSheetPuppeteer() still throws on production server
if (cacheFile == null) return Stream.Null;
}

_logger.LogDebug("Read match report from cache for tenant '{Tenant}', match '{MatchId}'", _tenantContext.Identifier, matchId);
_logger.LogDebug("Read match report from cache for tenant '{Tenant}', match '{MatchId}'", _tenantContext.Identifier, data.Id);
var stream = File.OpenRead(cacheFile);
return stream;
}
Expand Down
1 change: 1 addition & 0 deletions League/Controllers/Match.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Text;
using League.BackgroundTasks;
using League.Caching;
using League.Emailing;
using League.Emailing.Creators;
using League.Helpers;
using League.Models.MatchViewModels;
Expand Down
2 changes: 1 addition & 1 deletion League/Emailing/RazorViewToStringRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Original code modified by axuno. Modifications Copyright (c) by axuno.
https://github.com/RickStrahl/WestwindToolkit/blob/master/Westwind.Web.Mvc/Utils/ViewRenderer.cs (MIT License)
*/

namespace MailMergeLib.AspNet;
namespace League.Emailing;

public class RazorViewToStringRenderer
{
Expand Down
1 change: 1 addition & 0 deletions League/LeagueStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using League.BackgroundTasks;
using League.Caching;
using League.ConfigurationPoco;
using League.Emailing;
using League.MultiTenancy;
using TournamentManager.DI;
using TournamentManager.MultiTenancy;
Expand Down
Loading

0 comments on commit c7c5755

Please sign in to comment.