first commit
This commit is contained in:
29
Nitrox.Launcher/Models/Validators/BackupAttribute.cs
Normal file
29
Nitrox.Launcher/Models/Validators/BackupAttribute.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using Nitrox.Launcher.Models.Design;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// Checks that value is a usable <see cref="BackupItem" />.
|
||||
/// </summary>
|
||||
public sealed class BackupAttribute : TypedValidationAttribute<BackupItem>
|
||||
{
|
||||
protected override ValidationResult IsValid(BackupItem value, ValidationContext context)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return new ValidationResult($"{context.DisplayName} must not be null.");
|
||||
}
|
||||
if (value.BackupFileName == null || value.BackupFileName.AsSpan().Trim().IsEmpty)
|
||||
{
|
||||
return new ValidationResult($"{context.DisplayName} must have a backup path assigned");
|
||||
}
|
||||
if (!File.Exists(value.BackupFileName))
|
||||
{
|
||||
return new ValidationResult($"{context.DisplayName} must point to a valid file.");
|
||||
}
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
}
|
28
Nitrox.Launcher/Models/Validators/FileNameAttribute.cs
Normal file
28
Nitrox.Launcher/Models/Validators/FileNameAttribute.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the value is usable as file name (excluding validity as file path or file extension).
|
||||
/// </summary>
|
||||
public sealed class FileNameAttribute : TypedValidationAttribute<string>
|
||||
{
|
||||
internal static readonly char[] InvalidPathCharacters = Path.GetInvalidFileNameChars();
|
||||
|
||||
protected override ValidationResult IsValid(string value, ValidationContext context)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
int indexOfAny = value.IndexOfAny(InvalidPathCharacters);
|
||||
if (indexOfAny > -1)
|
||||
{
|
||||
return new ValidationResult($"{context.DisplayName} must not contain '{value[indexOfAny]}'. All invalid characters: {string.Join(' ', InvalidPathCharacters.Where(c => c > 31))}");
|
||||
}
|
||||
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
}
|
58
Nitrox.Launcher/Models/Validators/NitroxUniqueSaveName.cs
Normal file
58
Nitrox.Launcher/Models/Validators/NitroxUniqueSaveName.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the save name doesn't conflict with other Nitrox saves.
|
||||
/// </summary>
|
||||
public sealed class NitroxUniqueSaveName : TypedValidationAttribute<string>
|
||||
{
|
||||
public string SavesFolderDirPropertyName { get; }
|
||||
public bool AllowCaseInsensitiveName { get; }
|
||||
public string OriginalValuePropertyName { get; }
|
||||
|
||||
public NitroxUniqueSaveName(string savesFolderDirPropertyName, bool allowCaseInsensitiveName = false, string originalValuePropertyName = null)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(savesFolderDirPropertyName);
|
||||
SavesFolderDirPropertyName = savesFolderDirPropertyName;
|
||||
AllowCaseInsensitiveName = allowCaseInsensitiveName;
|
||||
OriginalValuePropertyName = originalValuePropertyName;
|
||||
}
|
||||
|
||||
protected override ValidationResult IsValid(string value, ValidationContext context)
|
||||
{
|
||||
static bool SaveFolderExists(string folderName, bool matchExact, string savesFolderDir)
|
||||
{
|
||||
if (!matchExact)
|
||||
{
|
||||
foreach (string dir in Directory.EnumerateDirectories(savesFolderDir))
|
||||
{
|
||||
if (Path.GetFileName(dir).Equals(folderName, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return Path.Exists(Path.Combine(savesFolderDir, folderName));
|
||||
}
|
||||
|
||||
if (!Directory.Exists(ReadProperty<string>(context, SavesFolderDirPropertyName)))
|
||||
{
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(OriginalValuePropertyName) && value == ReadProperty<string>(context, OriginalValuePropertyName))
|
||||
{
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
if (SaveFolderExists(value, !AllowCaseInsensitiveName, ReadProperty<string>(context, SavesFolderDirPropertyName)))
|
||||
{
|
||||
return new ValidationResult($@"Save ""{value}"" already exists.");
|
||||
}
|
||||
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Validators;
|
||||
|
||||
public sealed partial class NitroxWorldSeedAttribute : TypedValidationAttribute<string>
|
||||
{
|
||||
[GeneratedRegex(@"^[a-zA-Z]{10}$")]
|
||||
private static partial Regex NitroxWorldSeedRegex { get; }
|
||||
|
||||
protected override ValidationResult IsValid(string value, ValidationContext context)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value) || NitroxWorldSeedRegex.IsMatch(value))
|
||||
{
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
|
||||
return new ValidationResult($"The field {context.DisplayName} must contain 10 alphabetical characters.");
|
||||
}
|
||||
}
|
19
Nitrox.Launcher/Models/Validators/NotEndsWithAttribute.cs
Normal file
19
Nitrox.Launcher/Models/Validators/NotEndsWithAttribute.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the value doesn't end with the specified text.
|
||||
/// </summary>
|
||||
public sealed class NotEndsWithAttribute(string text, StringComparison comparison = StringComparison.OrdinalIgnoreCase) : TypedValidationAttribute<string>
|
||||
{
|
||||
protected override ValidationResult IsValid(string value, ValidationContext context)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
return value.EndsWith(text, comparison) ? new ValidationResult($"{context.DisplayName} must not contain the text '{text}' at the end.") : ValidationResult.Success;
|
||||
}
|
||||
}
|
31
Nitrox.Launcher/Models/Validators/SaveNameAttribute.cs
Normal file
31
Nitrox.Launcher/Models/Validators/SaveNameAttribute.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Validators;
|
||||
|
||||
/// <summary>Validates a Nitrox save name.</summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
|
||||
public class SaveNameAttribute : DataTypeAttribute
|
||||
{
|
||||
public SaveNameAttribute() : base(DataType.Text)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsValid(object value)
|
||||
{
|
||||
if (value is not string str)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int indexOfAny = str.IndexOfAny(FileNameAttribute.InvalidPathCharacters);
|
||||
if (indexOfAny > -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (str.EndsWith(".", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Validators;
|
||||
|
||||
public abstract class TypedValidationAttribute<T> : ValidationAttribute
|
||||
{
|
||||
protected abstract ValidationResult IsValid(T value, ValidationContext context);
|
||||
|
||||
protected override ValidationResult IsValid(object value, ValidationContext context)
|
||||
{
|
||||
if (value == default)
|
||||
{
|
||||
return IsValid(default, context);
|
||||
}
|
||||
if (value is not T typedValue)
|
||||
{
|
||||
return new ValidationResult($"The field {context.DisplayName} must be of type {typeof(T).Name}.");
|
||||
}
|
||||
return IsValid(typedValue, context);
|
||||
}
|
||||
|
||||
protected static TResult ReadProperty<TResult>(ValidationContext context, string propertyName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(propertyName))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
object value = context.ObjectType.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(context.ObjectInstance);
|
||||
return value is TResult tValue ? tValue : default;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user