Skip to content

Commit

Permalink
Releases/1.0.0 (#4)
Browse files Browse the repository at this point in the history
* Release 1.0.0
* Add linting
* Refactor tests and improve logging messages
  • Loading branch information
domsleee authored Jul 3, 2023
1 parent 0efb01d commit 782d1cc
Show file tree
Hide file tree
Showing 29 changed files with 387 additions and 276 deletions.
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ csharp_hide_private_members = true:suggestion
dotnet_diagnostic.CS0127.severity = none
dotnet_diagnostic.CA1416.severity = none
dotnet_diagnostic.IDE0005.severity = error
dotnet_diagnostic.IDE0044.severity = error
dotnet_sort_system_directives_first = true
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,8 @@ jobs:
- name: Build
run: dotnet build

- name: Lint
run: dotnet format --verify-no-changes

- name: Test
run: dotnet test --no-build --verbosity normal
17 changes: 17 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project>
<PropertyGroup>
<Description>By hook or by crook, perform operations on files and directories. If they are
in use by a process, kill the process.</Description>
<Version>1.0.0</Version>
<PackageProjectUrl>https://github.com/domsleee/forceops</PackageProjectUrl>
<RepositoryUrl>https://github.com/domsleee/forceops</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Authors>Dom Slee</Authors>
<PackageTags>lock file directory force delete</PackageTags>
<PackageReleaseNotes>https://github.com/domsleee/forceops/blob/main/CHANGELOG.md</PackageReleaseNotes>

<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageOutputPath>./nupkg</PackageOutputPath>
</PropertyGroup>
</Project>
6 changes: 5 additions & 1 deletion ForceOps.Lib/ForceOps.Lib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>ForceOps</RootNamespace>
</PropertyGroup>
<ItemGroup>
<!-- Include README here so it shows up in the nuget.org page -->
<None Include="..\README.md" Link="README.md" Pack="true" PackagePath="\" />
<None Include="..\LICENSE.txt" Pack="true" PackagePath=""/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="LockCheck" Version="0.9.25-g8ea8d862e5" />
<PackageReference Include="Serilog" Version="3.0.1" />
Expand Down
2 changes: 1 addition & 1 deletion ForceOps.Lib/src/DirectoryUtils.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace ForceOps;
namespace ForceOps.Lib;

public static class DirectoryUtils
{
Expand Down
129 changes: 129 additions & 0 deletions ForceOps.Lib/src/FileAndDirectoryDeleter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System.Runtime.Versioning;
using Serilog;
using static ForceOps.Lib.DirectoryUtils;
using static LockCheck.LockManager;

namespace ForceOps.Lib;

[SupportedOSPlatform("windows")]
public class FileAndDirectoryDeleter
{
readonly ILogger logger;
readonly ForceOpsContext forceOpsContext;

public FileAndDirectoryDeleter(ForceOpsContext forceOpsContext, ILogger? logger = null)
{
this.forceOpsContext = forceOpsContext;
this.logger = logger ?? forceOpsContext.loggerFactory.CreateLogger<FileAndDirectoryDeleter>();
}

/// <summary>
/// Delete a file or a folder, not following symlinks.
/// If the delete fails, it will attempt to find processes using the file or directory
/// </summary>
/// <param name="fileOrDirectory">File or directory to delete.</param>
public void DeleteFileOrDirectory(string fileOrDirectory)
{
fileOrDirectory = CombineWithCWDAndGetAbsolutePath(fileOrDirectory);
if (File.Exists(fileOrDirectory))
{
DeleteFile(new FileInfo(fileOrDirectory));
return;
}
if (Directory.Exists(fileOrDirectory))
{
DeleteDirectory(new DirectoryInfo(fileOrDirectory));
return;
}
// if the file/folder doesn't exist, it has already been deleted
logger.Debug($"{fileOrDirectory} already deleted.");
return;
}

internal void DeleteFile(FileInfo file)
{
for (var attempt = 1; attempt <= forceOpsContext.maxAttempts; attempt++)
{
try
{
file.IsReadOnly = false;
file.Delete();
break;
}
catch when (!file.Exists) { }
catch (Exception ex) when (ex is IOException || ex is System.UnauthorizedAccessException)
{
var getProcessesLockingFileFunc = () => GetLockingProcessInfos(new[] { file.FullName });
var shouldThrow = KillProcessesAndLogInfo("DeleteFile", attempt, file.FullName, getProcessesLockingFileFunc);
if (shouldThrow) throw;
}
}
}

bool KillProcessesAndLogInfo(string actionName, int attemptNumber, string fileOrDirectoryPath, Func<IEnumerable<LockCheck.ProcessInfo>> getProcessesLockingFileFunc)
{
var isProcessElevated = forceOpsContext.elevateUtils.IsProcessElevated();
var processElevatedMessage = isProcessElevated
? "ForceOps process is elevated"
: "ForceOps process is not elevated";
var messagePrefix = $"{actionName} failed attempt {attemptNumber}/{forceOpsContext.maxAttempts} for [{fileOrDirectoryPath}]. {processElevatedMessage}.";

if (attemptNumber == forceOpsContext.maxAttempts)
{
logger.Information($"{messagePrefix} No attempts remain, so the exception will be thrown.");
return true;
}

var processes = getProcessesLockingFileFunc().ToList();
var processPlural = processes.Count == 1 ? "process" : "processes";
var processLogString = string.Join(", ", processes.Select(process => ProcessInfoToString(process)));
logger.Information($"{messagePrefix} Found {processes.Count} {processPlural} to try to kill: [{processLogString}]");
forceOpsContext.processKiller.KillProcesses(processes);
return false;
}

static string ProcessInfoToString(LockCheck.ProcessInfo? process)
{
if (process == null)
{
return "<null>";
}
return $"{process?.ProcessId} - {process?.ExecutableName}";
}

internal void DeleteDirectory(DirectoryInfo directory)
{
if (!IsSymLink(directory))
{
DeleteFilesInFolder(directory);
}

for (var attempt = 1; attempt <= forceOpsContext.maxAttempts; attempt++)
{
try
{
directory.Delete();
break;
}
catch when (!directory.Exists) { }
catch (Exception ex) when (ex is IOException)
{
var getProcessesLockingFileFunc = () => GetLockingProcessInfos(new[] { directory.FullName }, LockCheck.LockManagerFeatures.UseLowLevelApi);
var shouldThrow = KillProcessesAndLogInfo("DeleteDirectory", attempt, directory.FullName, getProcessesLockingFileFunc);
if (shouldThrow) throw;
}
}
}

void DeleteFilesInFolder(DirectoryInfo directory)
{
foreach (var file in directory.GetFiles())
{
DeleteFile(file);
}
foreach (var subDirectory in directory.GetDirectories())
{
DeleteDirectory(subDirectory);
}
}
}
124 changes: 0 additions & 124 deletions ForceOps.Lib/src/FileAndFolderDeleter.cs

This file was deleted.

15 changes: 0 additions & 15 deletions ForceOps.Lib/src/ForceOpsContext.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Security.Principal;

namespace ForceOpsLib;
namespace ForceOps.Lib;

public class ElevateUtils : IElevateUtils
{
Expand All @@ -10,9 +10,4 @@ public bool IsProcessElevated()
var principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
}

public interface IElevateUtils
{
public bool IsProcessElevated();
}
23 changes: 23 additions & 0 deletions ForceOps.Lib/src/ForceOpsContext/ForceOpsContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace ForceOps.Lib;

public class ForceOpsContext
{
public int maxAttempts = 5;
public IProcessKiller processKiller;
public IElevateUtils elevateUtils;
public IRelaunchAsElevated relaunchAsElevated;
public ILoggerFactory loggerFactory;

public ForceOpsContext(
IProcessKiller? processKiller = null,
IElevateUtils? elevateUtils = null,
IRelaunchAsElevated? relaunchAsElevated = null,
ILoggerFactory? loggerFactory = null
)
{
this.processKiller = processKiller ?? new ProcessKiller();
this.elevateUtils = elevateUtils ?? new ElevateUtils();
this.relaunchAsElevated = relaunchAsElevated ?? new RelaunchAsElevated();
this.loggerFactory = loggerFactory ?? new LoggerFactory();
}
}
7 changes: 7 additions & 0 deletions ForceOps.Lib/src/ForceOpsContext/IElevateUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace ForceOps.Lib;


public interface IElevateUtils
{
public bool IsProcessElevated();
}
8 changes: 8 additions & 0 deletions ForceOps.Lib/src/ForceOpsContext/ILoggerFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Serilog;

namespace ForceOps.Lib;

public interface ILoggerFactory
{
public ILogger CreateLogger<T>();
}
6 changes: 6 additions & 0 deletions ForceOps.Lib/src/ForceOpsContext/IProcessKiller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ForceOps;

public interface IProcessKiller
{
void KillProcesses(IEnumerable<LockCheck.ProcessInfo?> processes);
}
Loading

0 comments on commit 782d1cc

Please sign in to comment.