mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-28 13:01:28 +02:00
New: Ignore inaccessible files with getting files
(cherry picked from commit e5aa8584100d96a2077c57f74ae5b2ceab63de19)
This commit is contained in:
@@ -837,7 +837,7 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
|
|
||||||
// Note: never returns anything.
|
// Note: never returns anything.
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.GetFileInfos(It.IsAny<string>(), SearchOption.TopDirectoryOnly))
|
.Setup(v => v.GetFileInfos(It.IsAny<string>(), false))
|
||||||
.Returns(new List<FileInfo>());
|
.Returns(new List<FileInfo>());
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
@@ -875,8 +875,8 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
.Returns<string>(v => new DirectoryInfo(v).GetDirectories().ToList());
|
.Returns<string>(v => new DirectoryInfo(v).GetDirectories().ToList());
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.GetFileInfos(It.IsAny<string>(), SearchOption.TopDirectoryOnly))
|
.Setup(v => v.GetFileInfos(It.IsAny<string>(), false))
|
||||||
.Returns<string, SearchOption>((v, _) => new DirectoryInfo(v).GetFiles().ToList());
|
.Returns<string, bool>((v, _) => new DirectoryInfo(v).GetFiles().ToList());
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.GetFileSize(It.IsAny<string>()))
|
.Setup(v => v.GetFileSize(It.IsAny<string>()))
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using ICSharpCode.SharpZipLib.Core;
|
using ICSharpCode.SharpZipLib.Core;
|
||||||
using ICSharpCode.SharpZipLib.GZip;
|
using ICSharpCode.SharpZipLib.GZip;
|
||||||
@@ -11,7 +12,7 @@ namespace NzbDrone.Common
|
|||||||
public interface IArchiveService
|
public interface IArchiveService
|
||||||
{
|
{
|
||||||
void Extract(string compressedFile, string destination);
|
void Extract(string compressedFile, string destination);
|
||||||
void CreateZip(string path, params string[] files);
|
void CreateZip(string path, IEnumerable<string> files);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ArchiveService : IArchiveService
|
public class ArchiveService : IArchiveService
|
||||||
@@ -39,7 +40,7 @@ namespace NzbDrone.Common
|
|||||||
_logger.Debug("Extraction complete.");
|
_logger.Debug("Extraction complete.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateZip(string path, params string[] files)
|
public void CreateZip(string path, IEnumerable<string> files)
|
||||||
{
|
{
|
||||||
using (var zipFile = ZipFile.Create(path))
|
using (var zipFile = ZipFile.Create(path))
|
||||||
{
|
{
|
||||||
|
@@ -46,7 +46,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
{
|
{
|
||||||
CheckFolderExists(path);
|
CheckFolderExists(path);
|
||||||
|
|
||||||
var dirFiles = GetFiles(path, SearchOption.AllDirectories).ToList();
|
var dirFiles = GetFiles(path, true).ToList();
|
||||||
|
|
||||||
if (!dirFiles.Any())
|
if (!dirFiles.Any())
|
||||||
{
|
{
|
||||||
@@ -149,25 +149,29 @@ namespace NzbDrone.Common.Disk
|
|||||||
return Directory.EnumerateFileSystemEntries(path).Empty();
|
return Directory.EnumerateFileSystemEntries(path).Empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetDirectories(string path)
|
public IEnumerable<string> GetDirectories(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return Directory.GetDirectories(path);
|
return Directory.EnumerateDirectories(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetFiles(string path, SearchOption searchOption)
|
public IEnumerable<string> GetFiles(string path, bool recursive)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return Directory.GetFiles(path, "*.*", searchOption);
|
return Directory.EnumerateFiles(path, "*", new EnumerationOptions
|
||||||
|
{
|
||||||
|
RecurseSubdirectories = recursive,
|
||||||
|
IgnoreInaccessible = true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public long GetFolderSize(string path)
|
public long GetFolderSize(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return GetFiles(path, SearchOption.AllDirectories).Sum(e => new FileInfo(e).Length);
|
return GetFiles(path, true).Sum(e => new FileInfo(e).Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long GetFileSize(string path)
|
public long GetFileSize(string path)
|
||||||
@@ -288,8 +292,9 @@ namespace NzbDrone.Common.Disk
|
|||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
var files = Directory.GetFiles(path, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
|
var files = GetFiles(path, recursive);
|
||||||
Array.ForEach(files, RemoveReadOnly);
|
|
||||||
|
files.ToList().ForEach(RemoveReadOnly);
|
||||||
|
|
||||||
Directory.Delete(path, recursive);
|
Directory.Delete(path, recursive);
|
||||||
}
|
}
|
||||||
@@ -414,7 +419,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
foreach (var file in GetFiles(path, SearchOption.TopDirectoryOnly))
|
foreach (var file in GetFiles(path, false))
|
||||||
{
|
{
|
||||||
DeleteFile(file);
|
DeleteFile(file);
|
||||||
}
|
}
|
||||||
@@ -515,13 +520,17 @@ namespace NzbDrone.Common.Disk
|
|||||||
return new FileInfo(path);
|
return new FileInfo(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)
|
public List<FileInfo> GetFileInfos(string path, bool recursive = false)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
var di = new DirectoryInfo(path);
|
var di = new DirectoryInfo(path);
|
||||||
|
|
||||||
return di.GetFiles("*", searchOption).ToList();
|
return di.EnumerateFiles("*", new EnumerationOptions
|
||||||
|
{
|
||||||
|
RecurseSubdirectories = recursive,
|
||||||
|
IgnoreInaccessible = true
|
||||||
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveEmptySubfolders(string path)
|
public void RemoveEmptySubfolders(string path)
|
||||||
|
@@ -22,8 +22,8 @@ namespace NzbDrone.Common.Disk
|
|||||||
bool FileExists(string path, StringComparison stringComparison);
|
bool FileExists(string path, StringComparison stringComparison);
|
||||||
bool FolderWritable(string path);
|
bool FolderWritable(string path);
|
||||||
bool FolderEmpty(string path);
|
bool FolderEmpty(string path);
|
||||||
string[] GetDirectories(string path);
|
IEnumerable<string> GetDirectories(string path);
|
||||||
string[] GetFiles(string path, SearchOption searchOption);
|
IEnumerable<string> GetFiles(string path, bool recursive);
|
||||||
long GetFolderSize(string path);
|
long GetFolderSize(string path);
|
||||||
long GetFileSize(string path);
|
long GetFileSize(string path);
|
||||||
void CreateFolder(string path);
|
void CreateFolder(string path);
|
||||||
@@ -52,7 +52,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
IMount GetMount(string path);
|
IMount GetMount(string path);
|
||||||
List<DirectoryInfo> GetDirectoryInfos(string path);
|
List<DirectoryInfo> GetDirectoryInfos(string path);
|
||||||
FileInfo GetFileInfo(string path);
|
FileInfo GetFileInfo(string path);
|
||||||
List<FileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly);
|
List<FileInfo> GetFileInfos(string path, bool recursive = false);
|
||||||
void RemoveEmptySubfolders(string path);
|
void RemoveEmptySubfolders(string path);
|
||||||
void SaveStream(Stream stream, string path);
|
void SaveStream(Stream stream, string path);
|
||||||
bool IsValidFolderPermissionMask(string mask);
|
bool IsValidFolderPermissionMask(string mask);
|
||||||
|
@@ -159,7 +159,7 @@ namespace NzbDrone.Common.EnvironmentInfo
|
|||||||
|
|
||||||
private void CleanupSqLiteRollbackFiles()
|
private void CleanupSqLiteRollbackFiles()
|
||||||
{
|
{
|
||||||
_diskProvider.GetFiles(_appFolderInfo.AppDataFolder, SearchOption.TopDirectoryOnly)
|
_diskProvider.GetFiles(_appFolderInfo.AppDataFolder, false)
|
||||||
.Where(f => Path.GetFileName(f).StartsWith("nzbdrone.db"))
|
.Where(f => Path.GetFileName(f).StartsWith("nzbdrone.db"))
|
||||||
.ToList()
|
.ToList()
|
||||||
.ForEach(_diskProvider.DeleteFile);
|
.ForEach(_diskProvider.DeleteFile);
|
||||||
|
@@ -89,7 +89,7 @@ namespace NzbDrone.Core.Backup
|
|||||||
// Delete journal file created during database backup
|
// Delete journal file created during database backup
|
||||||
_diskProvider.DeleteFile(Path.Combine(_backupTempFolder, "prowlarr.db-journal"));
|
_diskProvider.DeleteFile(Path.Combine(_backupTempFolder, "prowlarr.db-journal"));
|
||||||
|
|
||||||
_archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, SearchOption.TopDirectoryOnly));
|
_archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, false));
|
||||||
|
|
||||||
Cleanup();
|
Cleanup();
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ namespace NzbDrone.Core.Backup
|
|||||||
|
|
||||||
_archiveService.Extract(backupFileName, temporaryPath);
|
_archiveService.Extract(backupFileName, temporaryPath);
|
||||||
|
|
||||||
foreach (var file in _diskProvider.GetFiles(temporaryPath, SearchOption.TopDirectoryOnly))
|
foreach (var file in _diskProvider.GetFiles(temporaryPath, false))
|
||||||
{
|
{
|
||||||
var fileName = Path.GetFileName(file);
|
var fileName = Path.GetFileName(file);
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ namespace NzbDrone.Core.Backup
|
|||||||
|
|
||||||
private IEnumerable<string> GetBackupFiles(string path)
|
private IEnumerable<string> GetBackupFiles(string path)
|
||||||
{
|
{
|
||||||
var files = _diskProvider.GetFiles(path, SearchOption.TopDirectoryOnly);
|
var files = _diskProvider.GetFiles(path, false);
|
||||||
|
|
||||||
return files.Where(f => BackupFileRegex.IsMatch(f));
|
return files.Where(f => BackupFileRegex.IsMatch(f));
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,7 @@ namespace NzbDrone.Mono.Test.EnvironmentInfo.VersionAdapters
|
|||||||
.Setup(c => c.FolderExists("/System/Library/CoreServices/")).Returns(true);
|
.Setup(c => c.FolderExists("/System/Library/CoreServices/")).Returns(true);
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(c => c.GetFiles("/System/Library/CoreServices/", SearchOption.TopDirectoryOnly))
|
.Setup(c => c.GetFiles("/System/Library/CoreServices/", false))
|
||||||
.Returns(new[] { plistPath });
|
.Returns(new[] { plistPath });
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
@@ -49,7 +49,7 @@ namespace NzbDrone.Mono.Test.EnvironmentInfo.VersionAdapters
|
|||||||
.Setup(c => c.FolderExists("/System/Library/CoreServices/")).Returns(true);
|
.Setup(c => c.FolderExists("/System/Library/CoreServices/")).Returns(true);
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(c => c.GetFiles("/System/Library/CoreServices/", SearchOption.TopDirectoryOnly))
|
.Setup(c => c.GetFiles("/System/Library/CoreServices/", false))
|
||||||
.Returns(new[] { plistPath });
|
.Returns(new[] { plistPath });
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
@@ -69,7 +69,7 @@ namespace NzbDrone.Mono.Test.EnvironmentInfo.VersionAdapters
|
|||||||
Subject.Read().Should().BeNull();
|
Subject.Read().Should().BeNull();
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Verify(c => c.GetFiles(It.IsAny<string>(), SearchOption.TopDirectoryOnly), Times.Never());
|
.Verify(c => c.GetFiles(It.IsAny<string>(), false), Times.Never());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -29,25 +29,25 @@ namespace NzbDrone.Mono.Test.EnvironmentInfo.VersionAdapters
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_null_if_etc_doestn_exist()
|
public void should_return_null_if_etc_doesnt_exist()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists("/etc/")).Returns(false);
|
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists("/etc/")).Returns(false);
|
||||||
Subject.Read().Should().BeNull();
|
Subject.Read().Should().BeNull();
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Verify(c => c.GetFiles(It.IsAny<string>(), SearchOption.TopDirectoryOnly), Times.Never());
|
.Verify(c => c.GetFiles(It.IsAny<string>(), false), Times.Never());
|
||||||
|
|
||||||
Subject.Read().Should().BeNull();
|
Subject.Read().Should().BeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_null_if_release_file_doestn_exist()
|
public void should_return_null_if_release_file_doesnt_exist()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists("/etc/")).Returns(true);
|
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists("/etc/")).Returns(true);
|
||||||
Subject.Read().Should().BeNull();
|
Subject.Read().Should().BeNull();
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(c => c.GetFiles(It.IsAny<string>(), SearchOption.TopDirectoryOnly)).Returns(Array.Empty<string>());
|
.Setup(c => c.GetFiles(It.IsAny<string>(), false)).Returns(Array.Empty<string>());
|
||||||
|
|
||||||
Subject.Read().Should().BeNull();
|
Subject.Read().Should().BeNull();
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ namespace NzbDrone.Mono.Test.EnvironmentInfo.VersionAdapters
|
|||||||
Subject.Read().Should().BeNull();
|
Subject.Read().Should().BeNull();
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(c => c.GetFiles(It.IsAny<string>(), SearchOption.TopDirectoryOnly)).Returns(new[]
|
.Setup(c => c.GetFiles(It.IsAny<string>(), false)).Returns(new[]
|
||||||
{
|
{
|
||||||
"/etc/lsb-release",
|
"/etc/lsb-release",
|
||||||
"/etc/os-release"
|
"/etc/os-release"
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
@@ -21,7 +20,7 @@ namespace NzbDrone.Mono.EnvironmentInfo.VersionAdapters
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var issueFile = _diskProvider.GetFiles("/etc/", SearchOption.TopDirectoryOnly).SingleOrDefault(c => c.EndsWith("/issue"));
|
var issueFile = _diskProvider.GetFiles("/etc/", false).SingleOrDefault(c => c.EndsWith("/issue"));
|
||||||
|
|
||||||
if (issueFile == null)
|
if (issueFile == null)
|
||||||
{
|
{
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NLog;
|
using NLog;
|
||||||
@@ -33,7 +32,7 @@ namespace NzbDrone.Mono.EnvironmentInfo.VersionAdapters
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var allFiles = _diskProvider.GetFiles(PLIST_DIR, SearchOption.TopDirectoryOnly);
|
var allFiles = _diskProvider.GetFiles(PLIST_DIR, false);
|
||||||
|
|
||||||
var versionFile = allFiles.SingleOrDefault(c =>
|
var versionFile = allFiles.SingleOrDefault(c =>
|
||||||
c.EndsWith("/SystemVersion.plist") ||
|
c.EndsWith("/SystemVersion.plist") ||
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
@@ -22,7 +21,7 @@ namespace NzbDrone.Mono.EnvironmentInfo.VersionAdapters
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var releaseFiles = _diskProvider.GetFiles("/etc/", SearchOption.TopDirectoryOnly).Where(c => c.EndsWith("release")).ToList();
|
var releaseFiles = _diskProvider.GetFiles("/etc/", false).Where(c => c.EndsWith("release")).ToList();
|
||||||
|
|
||||||
var name = "Linux";
|
var name = "Linux";
|
||||||
var fullName = "";
|
var fullName = "";
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
@@ -24,7 +23,7 @@ namespace NzbDrone.Mono.EnvironmentInfo.VersionAdapters
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var versionFile = _diskProvider.GetFiles("/etc.defaults/", SearchOption.TopDirectoryOnly).SingleOrDefault(c => c.EndsWith("VERSION"));
|
var versionFile = _diskProvider.GetFiles("/etc.defaults/", false).SingleOrDefault(c => c.EndsWith("VERSION"));
|
||||||
|
|
||||||
if (versionFile == null)
|
if (versionFile == null)
|
||||||
{
|
{
|
||||||
|
@@ -25,7 +25,7 @@ namespace Prowlarr.Api.V1.Logs
|
|||||||
|
|
||||||
protected override IEnumerable<string> GetLogFiles()
|
protected override IEnumerable<string> GetLogFiles()
|
||||||
{
|
{
|
||||||
return _diskProvider.GetFiles(_appFolderInfo.GetLogFolder(), SearchOption.TopDirectoryOnly);
|
return _diskProvider.GetFiles(_appFolderInfo.GetLogFolder(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetLogFilePath(string filename)
|
protected override string GetLogFilePath(string filename)
|
||||||
|
@@ -32,7 +32,7 @@ namespace Prowlarr.Api.V1.Logs
|
|||||||
return Enumerable.Empty<string>();
|
return Enumerable.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _diskProvider.GetFiles(_appFolderInfo.GetUpdateLogFolder(), SearchOption.TopDirectoryOnly)
|
return _diskProvider.GetFiles(_appFolderInfo.GetUpdateLogFolder(), false)
|
||||||
.Where(f => Regex.IsMatch(Path.GetFileName(f), LOGFILE_ROUTE.TrimStart('/'), RegexOptions.IgnoreCase))
|
.Where(f => Regex.IsMatch(Path.GetFileName(f), LOGFILE_ROUTE.TrimStart('/'), RegexOptions.IgnoreCase))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user