Skip to content

Conversation

@wherewhere
Copy link
Member

  • Add stat v2 and list v2 5108b52
  • Add send v2 and recv v2 8d23a47
  • Set SyncCommand to their real value 64e398a
  • Use unsafe to read bytes 5108b52

Copilot AI review requested due to automatic review settings January 12, 2026 02:15
@wherewhere wherewhere linked an issue Jan 12, 2026 that may be closed by this pull request
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for ADB Sync V2 protocol, which includes new sync commands (SND2, RCV2, STA2, LIS2, DNT2) and enhanced file statistics with extended metadata. The changes include new data structures for V2 file statistics, updated sync service methods to support both V1 and V2 protocols, and optimizations using unsafe code for better performance.

Changes:

  • Added V2 sync commands (SND2, RCV2, STA2, LIS2, DNT2) and SyncFlags enum
  • Introduced FileStatisticsEx with extended metadata (device, inode, user/group IDs, multiple timestamps)
  • Refactored FileStatistics to use a base class pattern with FileStatisticsData structures
  • Updated SyncService methods (Push, Pull, Stat, GetDirectoryListing) with optional useV2 parameters
  • Implemented unsafe code optimizations using Unsafe.As for struct conversions
  • Added UnixErrorCode enum and IFileStatistics interface

Reviewed changes

Copilot reviewed 72 out of 72 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
SyncCommand.cs Updated enum values to match actual protocol values and added V2 commands
FileStatistics*.cs Refactored to use base class with data structs, added V2 extended version
SyncService*.cs Added useV2 parameters to Push/Pull/Stat/List methods
Unsafe.cs Added polyfill for System.Runtime.CompilerServices.Unsafe
BitConverterExtensions.cs Added extension methods for BitConverter (has syntax errors)
UnixFileStatusExtensions.cs Refactored to use extension syntax (has syntax errors)
SyncCommandConverter.cs Simplified using BitConverter instead of string parsing
Test files Added comprehensive tests for V2 functionality
Comments suppressed due to low confidence (1)

AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs:33

  • The extension(SyncCommand command) syntax is not valid C# syntax. This appears to be a non-standard language extension that won't compile. Extension methods should be implemented as static methods in a static class with the this modifier on the first parameter.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +20 to 260
extension(UnixFileStatus mode)
{
/// <summary>
/// Gets the type of the given file status.
/// </summary>
/// <returns>The type of the file status.</returns>
public UnixFileStatus GetFileType() => mode & UnixFileStatus.TypeMask;

/// <summary>
/// Gets the access permissions of the given file status.
/// </summary>
/// <param name="mode">File status to process.</param>
/// <returns>The access permissions of the given file status.</returns>
public static UnixFileStatus GetAccessPermissions(this UnixFileStatus mode) => mode & UnixFileStatus.AccessPermissions;
/// <summary>
/// Gets the permissions of the given file status.
/// </summary>
/// <returns>The permissions of the given file status.</returns>
public UnixFileStatus GetPermissions() => mode & UnixFileStatus.AllPermissions;

/// <summary>
/// Checks if the given file status corresponds to a directory, as if determined by <see cref="UnixFileStatus.Directory"/>.
/// </summary>
/// <param name="mode">File status to check.</param>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a directory, otherwise <see langword="false"/>.</returns>
public static bool IsDirectory(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.Directory;
/// <summary>
/// Gets the access permissions of the given file status.
/// </summary>
/// <returns>The access permissions of the given file status.</returns>
public UnixFileStatus GetAccessPermissions() => mode & UnixFileStatus.AccessPermissions;

/// <summary>
/// Checks if the given file status or path corresponds to a character special file, as if determined by <see cref="UnixFileStatus.Character"/>.
/// Examples of character special files are character devices such as <c>/dev/null</c>, <c>/dev/tty</c>, <c>/dev/audio</c>, or <c>/dev/nvram</c> on Linux.
/// </summary>
/// <param name="mode">File status to check.</param>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a character device, otherwise <see langword="false"/>.</returns>
public static bool IsCharacterFile(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.Character;
/// <summary>
/// Checks if the given file status corresponds to a directory, as if determined by <see cref="UnixFileStatus.Directory"/>.
/// </summary>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a directory, otherwise <see langword="false"/>.</returns>
public bool IsDirectory() => mode.GetFileType() == UnixFileStatus.Directory;

/// <summary>
/// Checks if the given file status corresponds to a block special file, as if determined by <see cref="UnixFileStatus.Block"/>.
/// Examples of block special files are block devices such as <c>/dev/sda</c> or <c>/dev/loop0</c> on Linux.
/// </summary>
/// <param name="mode">File status to check.</param>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a block device, otherwise <see langword="false"/>.</returns>
public static bool IsBlockFile(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.Block;
/// <summary>
/// Checks if the given file status or path corresponds to a character special file, as if determined by <see cref="UnixFileStatus.Character"/>.
/// Examples of character special files are character devices such as <c>/dev/null</c>, <c>/dev/tty</c>, <c>/dev/audio</c>, or <c>/dev/nvram</c> on Linux.
/// </summary>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a character device, otherwise <see langword="false"/>.</returns>
public bool IsCharacterFile() => mode.GetFileType() == UnixFileStatus.Character;

/// <summary>
/// Checks if the given file status corresponds to a regular file, as if determined by <see cref="UnixFileStatus.Regular"/>.
/// </summary>
/// <param name="mode">File status to check.</param>
/// <returns><see langword="true"/> if the type indicated by <paramref name="mode"/> refers to a regular file, otherwise <see langword="false"/>.</returns>
public static bool IsRegularFile(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.Regular;
/// <summary>
/// Checks if the given file status corresponds to a block special file, as if determined by <see cref="UnixFileStatus.Block"/>.
/// Examples of block special files are block devices such as <c>/dev/sda</c> or <c>/dev/loop0</c> on Linux.
/// </summary>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a block device, otherwise <see langword="false"/>.</returns>
public bool IsBlockFile() => mode.GetFileType() == UnixFileStatus.Block;

/// <summary>
/// Checks if the given file status corresponds to a FIFO or pipe file as if determined by <see cref="UnixFileStatus.FIFO"/>.
/// </summary>
/// <param name="mode">File status to check.</param>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a FIFO pipe, otherwise <see langword="false"/>.</returns>
public static bool IsFIFO(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.FIFO;
/// <summary>
/// Checks if the given file status corresponds to a regular file, as if determined by <see cref="UnixFileStatus.Regular"/>.
/// </summary>
/// <returns><see langword="true"/> if the type indicated by <paramref name="mode"/> refers to a regular file, otherwise <see langword="false"/>.</returns>
public bool IsRegularFile() => mode.GetFileType() == UnixFileStatus.Regular;

/// <summary>
/// Checks if the given file status corresponds to a symbolic link, as if determined by <see cref="UnixFileStatus.SymbolicLink"/>.
/// </summary>
/// <param name="mode">File status to check.</param>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a symbolic link, otherwise <see langword="false"/>.</returns>
public static bool IsSymbolicLink(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.SymbolicLink;
/// <summary>
/// Checks if the given file status corresponds to a FIFO or pipe file as if determined by <see cref="UnixFileStatus.FIFO"/>.
/// </summary>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a FIFO pipe, otherwise <see langword="false"/>.</returns>
public bool IsFIFO() => mode.GetFileType() == UnixFileStatus.FIFO;

/// <summary>
/// Checks if the given file status or path corresponds to a named IPC socket, as if determined by <see cref="UnixFileStatus.Socket"/>.
/// </summary>
/// <param name="mode">File status to check.</param>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a named socket, otherwise <see langword="false"/>.</returns>
public static bool IsSocket(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.Socket;
/// <summary>
/// Checks if the given file status corresponds to a symbolic link, as if determined by <see cref="UnixFileStatus.SymbolicLink"/>.
/// </summary>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a symbolic link, otherwise <see langword="false"/>.</returns>
public bool IsSymbolicLink() => mode.GetFileType() == UnixFileStatus.SymbolicLink;

/// <summary>
/// Checks if the given file status corresponds to a file of type other type. That is, the file exists, but is neither regular file, nor directory nor a symlink.
/// </summary>
/// <param name="mode">File status to check.</param>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a file that is not regular file, directory, or a symlink, otherwise <see langword="false"/>.</returns>
public static bool IsOther(this UnixFileStatus mode) => mode.GetFileType() is not (UnixFileStatus.Regular or UnixFileStatus.Directory or UnixFileStatus.SymbolicLink);
/// <summary>
/// Checks if the given file status or path corresponds to a named IPC socket, as if determined by <see cref="UnixFileStatus.Socket"/>.
/// </summary>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a named socket, otherwise <see langword="false"/>.</returns>
public bool IsSocket() => mode.GetFileType() == UnixFileStatus.Socket;

/// <summary>
/// Checks if the given file type is known, equivalent to <c>mode.GetFileType() != <see cref="UnixFileStatus.None"/></c>.
/// </summary>
/// <param name="mode">File type to check.</param>
/// <returns><see langword="true"/> if the given file type is a known file type, otherwise <see langword="false"/>.</returns>
public static bool IsTypeKnown(this UnixFileStatus mode) => mode.GetFileType() != UnixFileStatus.None;
/// <summary>
/// Checks if the given file status corresponds to a file of type other type. That is, the file exists, but is neither regular file, nor directory nor a symlink.
/// </summary>
/// <returns><see langword="true"/> if the type indicated <paramref name="mode"/> refers to a file that is not regular file, directory, or a symlink, otherwise <see langword="false"/>.</returns>
public bool IsOther() => mode.GetFileType() is not (UnixFileStatus.Regular or UnixFileStatus.Directory or UnixFileStatus.SymbolicLink);

/// <summary>
/// Parses a Unix permission code into a <see cref="UnixFileStatus"/>.
/// </summary>
/// <param name="code">The permission code to parse.</param>
/// <returns>A <see cref="UnixFileStatus"/> representing the parsed permission code.</returns>
public static UnixFileStatus FromPermissionCode(string code)
{
ArgumentNullException.ThrowIfNull(code);
/// <summary>
/// Checks if the given file type is known, equivalent to <c>mode.GetFileType() != <see cref="UnixFileStatus.None"/></c>.
/// </summary>
/// <returns><see langword="true"/> if the given file type is a known file type, otherwise <see langword="false"/>.</returns>
public bool IsTypeKnown() => mode.GetFileType() != UnixFileStatus.None;

if (code.Length is not (9 or 10))
/// <summary>
/// Parses a Unix permission code into a <see cref="UnixFileStatus"/>.
/// </summary>
/// <param name="code">The permission code to parse.</param>
/// <returns>A <see cref="UnixFileStatus"/> representing the parsed permission code.</returns>
public static UnixFileStatus FromPermissionCode(string code)
{
try
{
return (UnixFileStatus)Convert.ToInt32(code, 8);
}
catch
ArgumentNullException.ThrowIfNull(code);

if (code.Length is not (9 or 10))
{
throw new ArgumentOutOfRangeException(nameof(code), $"The length of {nameof(code)} should be 9 or 10, but it is {code.Length}.");
try
{
return (UnixFileStatus)Convert.ToInt32(code, 8);
}
catch
{
throw new ArgumentOutOfRangeException(nameof(code), $"The length of {nameof(code)} should be 9 or 10, but it is {code.Length}.");
}
}
}

UnixFileStatus mode = UnixFileStatus.None;
int index = code.Length;

mode |= code[--index] switch
{
'x' => UnixFileStatus.OtherExecute,
't' => UnixFileStatus.StickyBit | UnixFileStatus.OtherExecute,
'T' => UnixFileStatus.StickyBit,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 't', 'T' or '-', but it is {code[index]}.")
};
mode |= code[--index] switch
{
'w' => UnixFileStatus.OtherWrite,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.")
};
mode |= code[--index] switch
{
'r' => UnixFileStatus.OtherRead,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.")
};
UnixFileStatus result = UnixFileStatus.None;
int index = code.Length;

mode |= code[--index] switch
{
'x' => UnixFileStatus.GroupExecute,
's' => UnixFileStatus.SetGroup | UnixFileStatus.GroupExecute,
'S' => UnixFileStatus.SetGroup,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 's', 'S' or '-', but it is {code[index]}.")
};
mode |= code[--index] switch
{
'w' => UnixFileStatus.GroupWrite,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.")
};
mode |= code[--index] switch
{
'r' => UnixFileStatus.GroupRead,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.")
};
result |= code[--index] switch
{
'x' => UnixFileStatus.OtherExecute,
't' => UnixFileStatus.StickyBit | UnixFileStatus.OtherExecute,
'T' => UnixFileStatus.StickyBit,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 't', 'T' or '-', but it is {code[index]}.")
};
result |= code[--index] switch
{
'w' => UnixFileStatus.OtherWrite,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.")
};
result |= code[--index] switch
{
'r' => UnixFileStatus.OtherRead,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.")
};

mode |= code[--index] switch
{
'x' => UnixFileStatus.UserExecute,
's' => UnixFileStatus.SetUser | UnixFileStatus.UserExecute,
'S' => UnixFileStatus.SetUser,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 's', 'S' or '-', but it is {code[index]}.")
};
mode |= code[--index] switch
{
'w' => UnixFileStatus.UserWrite,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.")
};
mode |= code[--index] switch
{
'r' => UnixFileStatus.UserRead,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.")
};
result |= code[--index] switch
{
'x' => UnixFileStatus.GroupExecute,
's' => UnixFileStatus.SetGroup | UnixFileStatus.GroupExecute,
'S' => UnixFileStatus.SetGroup,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 's', 'S' or '-', but it is {code[index]}.")
};
result |= code[--index] switch
{
'w' => UnixFileStatus.GroupWrite,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.")
};
result |= code[--index] switch
{
'r' => UnixFileStatus.GroupRead,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.")
};

if (index == 1)
{
mode |= code[0] switch
result |= code[--index] switch
{
'p' => UnixFileStatus.FIFO,
'c' => UnixFileStatus.Character,
'd' => UnixFileStatus.Directory,
'b' => UnixFileStatus.Block,
'-' => UnixFileStatus.Regular,
'l' => UnixFileStatus.SymbolicLink,
's' => UnixFileStatus.Socket,
_ => UnixFileStatus.None
'x' => UnixFileStatus.UserExecute,
's' => UnixFileStatus.SetUser | UnixFileStatus.UserExecute,
'S' => UnixFileStatus.SetUser,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 's', 'S' or '-', but it is {code[index]}.")
};
result |= code[--index] switch
{
'w' => UnixFileStatus.UserWrite,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.")
};
result |= code[--index] switch
{
'r' => UnixFileStatus.UserRead,
'-' => UnixFileStatus.None,
_ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.")
};
}

return mode;
}
if (index == 1)
{
result |= code[0] switch
{
'p' => UnixFileStatus.FIFO,
'c' => UnixFileStatus.Character,
'd' => UnixFileStatus.Directory,
'b' => UnixFileStatus.Block,
'-' => UnixFileStatus.Regular,
'l' => UnixFileStatus.SymbolicLink,
's' => UnixFileStatus.Socket,
_ => UnixFileStatus.None
};
}

/// <summary>
/// Provides a string representation of the given <see cref="UnixFileStatus"/>.
/// </summary>
/// <param name="mode">The <see cref="UnixFileStatus"/> to process.</param>
/// <returns>A string representation of the given <see cref="UnixFileStatus"/>.</returns>
public static string ToPermissionCode(this UnixFileStatus mode)
{
return result;
}

/// <summary>
/// Provides a string representation of the given <see cref="UnixFileStatus"/>.
/// </summary>
/// <returns>A string representation of the given <see cref="UnixFileStatus"/>.</returns>
public string ToPermissionCode()
{
#if HAS_BUFFERS
Span<char> code = stackalloc char[10];
Span<char> code = stackalloc char[10];
#else
char[] code = new char[10];
char[] code = new char[10];
#endif
BitArray array = new([(int)mode]);
BitArray array = new([(int)mode]);

code[9] = array[0]
? array[9] ? 't' : 'x'
: array[9] ? 'T' : '-';
code[8] = array[1] ? 'w' : '-';
code[7] = array[2] ? 'r' : '-';
code[9] = array[0]
? array[9] ? 't' : 'x'
: array[9] ? 'T' : '-';
code[8] = array[1] ? 'w' : '-';
code[7] = array[2] ? 'r' : '-';

code[6] = array[3]
? array[10] ? 's' : 'x'
: array[10] ? 'S' : '-';
code[5] = array[4] ? 'w' : '-';
code[4] = array[5] ? 'r' : '-';
code[6] = array[3]
? array[10] ? 's' : 'x'
: array[10] ? 'S' : '-';
code[5] = array[4] ? 'w' : '-';
code[4] = array[5] ? 'r' : '-';

code[3] = array[6]
? array[11] ? 's' : 'x'
: array[11] ? 'S' : '-';
code[2] = array[7] ? 'w' : '-';
code[1] = array[8] ? 'r' : '-';
code[3] = array[6]
? array[11] ? 's' : 'x'
: array[11] ? 'S' : '-';
code[2] = array[7] ? 'w' : '-';
code[1] = array[8] ? 'r' : '-';

code[0] = mode.GetFileType() switch
{
UnixFileStatus.FIFO => 'p',
UnixFileStatus.Character => 'c',
UnixFileStatus.Directory => 'd',
UnixFileStatus.Block => 'b',
UnixFileStatus.Regular => '-',
UnixFileStatus.SymbolicLink => 'l',
UnixFileStatus.Socket => 's',
UnixFileStatus.None => '\0',
_ => '?'
};
code[0] = mode.GetFileType() switch
{
UnixFileStatus.FIFO => 'p',
UnixFileStatus.Character => 'c',
UnixFileStatus.Directory => 'd',
UnixFileStatus.Block => 'b',
UnixFileStatus.Regular => '-',
UnixFileStatus.SymbolicLink => 'l',
UnixFileStatus.Socket => 's',
UnixFileStatus.None => '\0',
_ => '?'
};

return new string(code);
}
return new string(code);
}

/// <summary>
/// Returns an enumerator that iterates through the <see cref="UnixFileStatus"/>.
/// </summary>
/// <returns>An enumerator that can be used to iterate through the <see cref="UnixFileStatus"/>.</returns>
public static IEnumerator<byte> GetEnumerator(this UnixFileStatus mode)
{
int num = (int)mode;
yield return (byte)num;
yield return (byte)(num >> 8);
yield return (byte)(num >> 16);
yield return (byte)(num >> 24);
/// <summary>
/// Returns an enumerator that iterates through the <see cref="UnixFileStatus"/>.
/// </summary>
/// <returns>An enumerator that can be used to iterate through the <see cref="UnixFileStatus"/>.</returns>
public IEnumerator<byte> GetEnumerator()
{
int num = (int)mode;
yield return (byte)num;
yield return (byte)(num >> 8);
yield return (byte)(num >> 16);
yield return (byte)(num >> 24);
}
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extension(UnixFileStatus mode) syntax is not valid C# syntax. This appears to be a non-standard language extension that won't compile. Extension methods for enums should be implemented as static methods in a static class with the this modifier on the first parameter.

Copilot uses AI. Check for mistakes.
/// <param name="right">The <see cref="FileStatistics"/> structure that is to the right of the equality operator.</param>
/// <returns>This operator returns <see langword="true"/> if the two <see cref="FileStatistics"/> structures are equally; otherwise <see langword="false"/>.</returns>
public static bool operator ==(FileStatistics left, FileStatistics right) => left.Equals(right);
public static bool operator ==(FileStatistics? left, FileStatistics? right) => left == (right as FileStatisticsBase<FileStatisticsData, FileStatistics>);
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This equality operator implementation causes infinite recursion. The expression left == (right as FileStatisticsBase<FileStatisticsData, FileStatistics>) will call this same operator again, leading to a stack overflow. The operator should compare the base class references using the base class operator, not cast and compare with ==.

Copilot uses AI. Check for mistakes.
/// <param name="left">The <see cref="FileStatisticsEx"/> structure that is to the left of the equality operator.</param>
/// <param name="right">The <see cref="FileStatisticsEx"/> structure that is to the right of the equality operator.</param>
/// <returns>This operator returns <see langword="true"/> if the two <see cref="FileStatisticsEx"/> structures are equally; otherwise <see langword="false"/>.</returns>
public static bool operator ==(FileStatisticsEx? left, FileStatisticsEx? right) => left == (right as FileStatisticsBase<FileStatisticsDataEx, FileStatisticsEx>);
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This equality operator implementation causes infinite recursion. The expression left == (right as FileStatisticsBase<FileStatisticsDataEx, FileStatisticsEx>) will call this same operator again, leading to a stack overflow. The operator should use the base class operator correctly to avoid recursion.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +40
extension(BitConverter)
{
/// <summary>
/// Returns a 32-bit signed integer converted from four bytes in a byte array.
/// </summary>
/// <param name="value">An array of bytes that includes the four bytes to convert.</param>
/// <returns>A 32-bit signed integer representing the converted bytes.</returns>
public static int ToInt32(byte[] value) => BitConverter.ToInt32(value, 0);

/// <summary>
/// Returns a 16-bit unsigned integer converted from two bytes in a byte array.
/// </summary>
/// <param name="value">The array of bytes that includes the two bytes to convert.</param>
/// <returns>An 16-bit unsigned integer representing the converted bytes.</returns>
public static ushort ToUInt16(byte[] value) => BitConverter.ToUInt16(value, 0);

/// <summary>
/// Returns a 32-bit unsigned integer converted from four bytes in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <returns>A 32-bit unsigned integer representing the converted bytes.</returns>
public static uint ToUInt32(byte[] value) => BitConverter.ToUInt32(value, 0);
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly added extension syntax with extension(BitConverter) is not valid C# syntax. This appears to be a non-standard language extension that won't compile in standard C#. Extension methods should be implemented as static methods in a static class with the this modifier on the first parameter.

Copilot uses AI. Check for mistakes.
byte[] data = File.ReadAllBytes("Assets/FramebufferHeader.V1.bin");

FramebufferHeader header = [.. data];
FramebufferHeader header = Read(default, data);
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace this call with a call to managed code if possible.

Copilot uses AI. Check for mistakes.
}

[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Read")]
private static extern FramebufferHeader Read(FramebufferHeader header, byte[] data);
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimise the use of unmanaged code.

Copilot uses AI. Check for mistakes.
Assert.False(commandLineClient.ServerStarted);

StartServerResult result = await adbServer.StartServerAsync(ServerName, false);
StartServerResult result = await adbServer.StartServerAsync(ServerName, false, TestContext.Current.CancellationToken);
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to result is useless, since its value is never read.

Suggested change
StartServerResult result = await adbServer.StartServerAsync(ServerName, false, TestContext.Current.CancellationToken);
_ = await adbServer.StartServerAsync(ServerName, false, TestContext.Current.CancellationToken);

Copilot uses AI. Check for mistakes.

entry = await reader.ReadEntryAsync();
entry = await reader.ReadEntryAsync();
entry = await reader.ReadEntryAsync(TestContext.Current.CancellationToken);
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to entry is useless, since its value is never read.

Copilot uses AI. Check for mistakes.
entry = await reader.ReadEntryAsync();
entry = await reader.ReadEntryAsync();
entry = await reader.ReadEntryAsync(TestContext.Current.CancellationToken);
entry = await reader.ReadEntryAsync(TestContext.Current.CancellationToken);
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to entry is useless, since its value is never read.

Copilot uses AI. Check for mistakes.
{
using SyncService service = new(Socket, Device);
await foreach (FileStatistics stat in service.GetDirectoryAsyncListing("/storage"))
await foreach (FileStatistics stat in service.GetDirectoryAsyncListing("/storage", ctx))
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to stat is useless, since its value is never read.

Suggested change
await foreach (FileStatistics stat in service.GetDirectoryAsyncListing("/storage", ctx))
await foreach (var _ in service.GetDirectoryAsyncListing("/storage", ctx))

Copilot uses AI. Check for mistakes.
@wherewhere
Copy link
Member Author

@Alex4SSB Try this. If works fine I will merge it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect progress on pulling large files

2 participants