NativeNodeEnumerator
using Relativity.DataTransfer.Nodes;
using Relativity.Transfer.Enumeration.Helpers;
using Relativity.Transfer.Enumeration.Interfaces;
using Relativity.Transfer.Enumeration.Native;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
namespace Relativity.Transfer.Enumeration
{
internal sealed class NativeNodeEnumerator : IEnumerator<INode>, IDisposable, IEnumerator
{
private INode _parentNode;
private SafeFindHandle _fileHandle;
private NativeFindData _findData;
private readonly INodeFactory _nodeFactory;
private readonly ITransferLog _logger;
private readonly CancellationToken _token;
private readonly INativeMethods _nativeMethods;
private bool _isEmpty = true;
private bool _nodeEnumerated;
private bool _selfFound;
object IEnumerator.Current {
get {
return Current;
}
}
public INode Current { get; set; }
private DateTime LastModified => SafeGetLastWriteTime();
private bool IsDirectory => NativeMethodHelper.IsDirectory(_findData);
private bool IsSymbolicLink => NativeMethodHelper.IsSymbolicLink(_findData);
private long Size => NativeMethodHelper.ToSize(_findData);
public NativeNodeEnumerator(INode initialNode, INodeFactory nodeFactory, INativeMethods nativeMethods, ITransferLog logger, CancellationToken token)
{
_nodeFactory = nodeFactory;
_nativeMethods = nativeMethods;
_logger = logger;
_token = token;
Current = initialNode;
Init(initialNode);
}
public bool MoveNext()
{
bool flag;
do {
if (_token.IsCancellationRequested)
throw new OperationCanceledException("NativeNodeEnumerator was requested to abort the enumeration.");
if (_nodeEnumerated)
return false;
if (_fileHandle.IsInvalid)
return HandleInvalidNode();
if (!_selfFound) {
if (IsDirectory && !IsSymbolicLink) {
SetCurrentAsDirectory(false);
_isEmpty = false;
_selfFound = !_selfFound;
return true;
}
if (!IsDirectory) {
SetCurrentAsFile();
_isEmpty = false;
_selfFound = !_selfFound;
return true;
}
}
flag = FindNextFileWithErrorLogging();
} while (flag && NativeMethodHelper.IsDotOrTwoDotPath(_findData));
if (flag && IsDirectory && !IsSymbolicLink) {
SetCurrentAsDirectory(false);
_isEmpty = false;
return true;
}
if (flag && !IsDirectory) {
SetCurrentAsFile();
_isEmpty = false;
return true;
}
if (_isEmpty) {
_nodeEnumerated = true;
SetCurrentAsSelfDirectory(true);
return true;
}
return false;
}
private bool FindNextFileWithErrorLogging()
{
bool num = _nativeMethods.FindNextFile(_fileHandle, _findData);
if (!num)
LogLastWin32ErrorCode();
return num;
}
private void LogLastWin32ErrorCode()
{
int lastWin32Error = Marshal.GetLastWin32Error();
if (!IsEnumerationCompletedWithoutError(lastWin32Error))
_logger.LogInformation(new Win32Exception(lastWin32Error), "Error when calling FindNextFile {0}", lastWin32Error);
}
private static bool IsEnumerationCompletedWithoutError(int lastWin32Error)
{
if (lastWin32Error != 0)
return lastWin32Error == 18;
return true;
}
private bool HandleInvalidNode()
{
int lastWin32Error = Marshal.GetLastWin32Error();
string text = Current?.AbsolutePath;
if (NativeMethodHelper.FileExists(text)) {
SetCurrentAsSelfFile();
_nodeEnumerated = true;
_logger.LogInformation("Enumeration yielded {0}. Returning it as file (even though file handle is invalid).", text, LogRedaction.OnPositions(default(int)));
return true;
}
if (NativeMethodHelper.DirectoryExists(text)) {
SetCurrentAsSelfDirectory(true);
_nodeEnumerated = true;
_logger.LogInformation("Enumeration yielded {0}. Returning it as directory (even though file handle is invalid).", text, LogRedaction.OnPositions(default(int)));
return true;
}
return AbortEnumeration(lastWin32Error);
}
public void Reset()
{
_findData = new NativeFindData();
Current = _parentNode;
FindFile(_parentNode);
}
public void Dispose()
{
_fileHandle?.Dispose();
}
private void Init(INode initialNode)
{
_parentNode = initialNode;
Reset();
}
private void FindFile(INode node)
{
_fileHandle?.Dispose();
string fileName = LongPathHelper.ToNativeFormat(node.AbsolutePath);
_fileHandle = _nativeMethods.FindFirstFile(fileName, _findData);
if (NativeMethodHelper.IsDotOrTwoDotPath(_findData))
_selfFound = true;
}
private void SetCurrentAsFile()
{
Current = _nodeFactory.CreateFile(_findData.cFileName, _parentNode, LastModified, Size);
}
private void SetCurrentAsSelfFile()
{
IFile file = Current as IFile;
Current = _nodeFactory.CreateFile(file.Name, file.Parent, file.Modified, file.Size);
}
private void SetCurrentAsDirectory(bool isEmpty)
{
Current = _nodeFactory.CreateDirectory(_findData.cFileName, _parentNode, LastModified, isEmpty);
}
private void SetCurrentAsSelfDirectory(bool isEmpty)
{
IDirectory directoryNode = Current as IDirectory;
Current = _nodeFactory.CreateDirectory(directoryNode, isEmpty);
}
private static bool AbortEnumeration(int lastErrorCode)
{
if (lastErrorCode == 3)
throw new PathDoesNotExistException();
throw new EnumerationException(lastErrorCode);
}
private DateTime SafeGetLastWriteTime()
{
try {
return NativeMethodHelper.ToDateTime(_findData);
} catch (ArgumentOutOfRangeException exception) {
long num = NativeMethodHelper.ToFileTime(_findData);
_logger.LogWarning(exception, "Unsupported file time occurred [ftLastWriteTime_dwHighDateTime={0}, ftLastWriteTime_dwLowDateTime={1}, fileTime={2}].", _findData.ftLastWriteTime_dwHighDateTime, _findData.ftLastWriteTime_dwLowDateTime, num);
return DateTime.MinValue;
}
}
}
}