-
Notifications
You must be signed in to change notification settings - Fork 67
Add ADB Sync V2 supports #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
wherewhere
commented
Jan 12, 2026
- 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
Use unsafe to read bytes
Update adb source link
Add ColorDataTest Upgrade to xunit v3
There was a problem hiding this 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 thethismodifier on the first parameter.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 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); | ||
| } | ||
| } |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| /// <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>); |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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 ==.
| /// <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>); |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| 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); | ||
| } |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| byte[] data = File.ReadAllBytes("Assets/FramebufferHeader.V1.bin"); | ||
|
|
||
| FramebufferHeader header = [.. data]; | ||
| FramebufferHeader header = Read(default, data); |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| } | ||
|
|
||
| [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Read")] | ||
| private static extern FramebufferHeader Read(FramebufferHeader header, byte[] data); |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| Assert.False(commandLineClient.ServerStarted); | ||
|
|
||
| StartServerResult result = await adbServer.StartServerAsync(ServerName, false); | ||
| StartServerResult result = await adbServer.StartServerAsync(ServerName, false, TestContext.Current.CancellationToken); |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| StartServerResult result = await adbServer.StartServerAsync(ServerName, false, TestContext.Current.CancellationToken); | |
| _ = await adbServer.StartServerAsync(ServerName, false, TestContext.Current.CancellationToken); |
|
|
||
| entry = await reader.ReadEntryAsync(); | ||
| entry = await reader.ReadEntryAsync(); | ||
| entry = await reader.ReadEntryAsync(TestContext.Current.CancellationToken); |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| entry = await reader.ReadEntryAsync(); | ||
| entry = await reader.ReadEntryAsync(); | ||
| entry = await reader.ReadEntryAsync(TestContext.Current.CancellationToken); | ||
| entry = await reader.ReadEntryAsync(TestContext.Current.CancellationToken); |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| { | ||
| using SyncService service = new(Socket, Device); | ||
| await foreach (FileStatistics stat in service.GetDirectoryAsyncListing("/storage")) | ||
| await foreach (FileStatistics stat in service.GetDirectoryAsyncListing("/storage", ctx)) |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| await foreach (FileStatistics stat in service.GetDirectoryAsyncListing("/storage", ctx)) | |
| await foreach (var _ in service.GetDirectoryAsyncListing("/storage", ctx)) |
|
@Alex4SSB Try this. If works fine I will merge it. |
|
Yes, this does work correctly for me, although it does break the code that uses it, requiring small changes. Is it not better to set V2 globally? I doubt that there are many use cases where one would mix the usage of V1 / V2 intentionally. |
|
V2 is only supports Android 8(or 11?)+. The default should supports the widest. It should be chosen by developers. |
|
Yes, I understand that, I meant that it would be easier for me as a developer-library-user, to just set the stat version once, and then each time stat is called it will be called with the version that I defined. |