mirror of
https://github.com/DarthAffe/OBD.NET.git
synced 2026-11-11 13:06:25 +00:00
Merge pull request #2 from romanlum/master
Refactored command handling and added uwp implementation
This commit is contained in:
commit
ea2ac35046
164 files changed
+1397
-371
No files matched your search
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,29 @@
|
||||
namespace OBD.NET.Common.Communication.EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Event args for receiving serial data
|
||||
/// </summary>
|
||||
public class DataReceivedEventArgs:System.EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataReceivedEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="count">The count.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
public DataReceivedEventArgs(int count, byte[] data)
|
||||
{
|
||||
Count = count;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Count of valid data bytes in the buffer
|
||||
/// </summary>
|
||||
public int Count { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Data buffer holding the bytes
|
||||
/// </summary>
|
||||
public byte[] Data { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using OBD.NET.Common.Communication.EventArgs;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OBD.NET.Communication
|
||||
{
|
||||
/// <summary>
|
||||
/// Serial connection interface
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
public interface ISerialConnection : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is open.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is open; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
bool IsOpen { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance uses asynchronous IO
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Has to be set to true if asynchronous IO is supported.
|
||||
/// If true async methods have to be implemented
|
||||
/// </remarks>
|
||||
bool IsAsync { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a full line was received
|
||||
/// </summary>
|
||||
event EventHandler<DataReceivedEventArgs> DataReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Connects the serial port.
|
||||
/// </summary>
|
||||
void Connect();
|
||||
|
||||
/// <summary>
|
||||
/// Connects the serial port asynchronous
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task ConnectAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified data to the serial connection
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
void Write(byte[] data);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified data to the serial connection asynchronous
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
Task WriteAsync(byte[] data);
|
||||
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace OBD.NET.Common.Devices
|
||||
{
|
||||
/// <summary>
|
||||
/// Class used for queued command
|
||||
/// </summary>
|
||||
public class QueuedCommand
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance
|
||||
/// </summary>
|
||||
/// <param name="commandText"></param>
|
||||
public QueuedCommand(string commandText)
|
||||
{
|
||||
CommandResult = new CommandResult();
|
||||
CommandText = commandText;
|
||||
}
|
||||
|
||||
public string CommandText { get; set; }
|
||||
|
||||
public CommandResult CommandResult { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using OBD.NET.Util;
|
||||
|
||||
namespace OBD.NET.Common.Devices
|
||||
{
|
||||
public class CommandResult
|
||||
{
|
||||
public CommandResult()
|
||||
{
|
||||
WaitHandle = new AsyncManualResetEvent();
|
||||
}
|
||||
|
||||
public object Result { get; set; }
|
||||
public AsyncManualResetEvent WaitHandle { get; }
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using OBD.NET.Events.EventArgs;
|
||||
using OBD.NET.Extensions;
|
||||
using OBD.NET.Logging;
|
||||
using OBD.NET.OBDData;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OBD.NET.Devices
|
||||
{
|
||||
@@ -22,7 +23,7 @@ namespace OBD.NET.Devices
|
||||
protected static Dictionary<byte, Type> DataTypeCache { get; } = new Dictionary<byte, Type>();
|
||||
|
||||
protected Mode Mode { get; set; } = Mode.ShowCurrentData; //TODO DarthAffe 26.06.2016: Implement different modes
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
@@ -36,7 +37,7 @@ namespace OBD.NET.Devices
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ELM327(SerialConnection connection, IOBDLogger logger = null)
|
||||
public ELM327(ISerialConnection connection, IOBDLogger logger = null)
|
||||
: base(connection, logger: logger)
|
||||
{ }
|
||||
|
||||
@@ -44,17 +45,26 @@ namespace OBD.NET.Devices
|
||||
|
||||
#region Methods
|
||||
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
await base.InitializeAsync();
|
||||
InternalInitialize();
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InternalInitialize();
|
||||
}
|
||||
|
||||
private void InternalInitialize()
|
||||
{
|
||||
Logger?.WriteLine("Initializing ...", OBDLogLevel.Debug);
|
||||
|
||||
try
|
||||
{
|
||||
Logger?.WriteLine("Resetting Device ...", OBDLogLevel.Debug);
|
||||
SendCommand(ATCommand.ResetDevice);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
Logger?.WriteLine("Turning Echo Off ...", OBDLogLevel.Debug);
|
||||
SendCommand(ATCommand.EchoOff);
|
||||
@@ -71,7 +81,6 @@ namespace OBD.NET.Devices
|
||||
Logger?.WriteLine("Setting the Protocol to 'Auto' ...", OBDLogLevel.Debug);
|
||||
SendCommand(ATCommand.SetProtocolAuto);
|
||||
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
// DarthAffe 21.02.2017: This seems to happen sometimes, i don't know why - just retry.
|
||||
catch
|
||||
@@ -81,11 +90,20 @@ namespace OBD.NET.Devices
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends the AT command.
|
||||
/// </summary>
|
||||
/// <param name="command">The command.</param>
|
||||
public virtual void SendCommand(ATCommand command)
|
||||
{
|
||||
SendCommand(command.Command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests the data and calls the handler
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public virtual void RequestData<T>()
|
||||
where T : class, IOBDData, new()
|
||||
{
|
||||
@@ -101,13 +119,33 @@ namespace OBD.NET.Devices
|
||||
SendCommand(((byte)Mode).ToString("X2") + pid.ToString("X2"));
|
||||
}
|
||||
|
||||
protected override void ProcessMessage(string message)
|
||||
/// <summary>
|
||||
/// Requests the data asynchronous and return the data when available
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<T> RequestDataAsync<T>()
|
||||
where T : class, IOBDData, new()
|
||||
{
|
||||
Logger?.WriteLine("Requesting Type " + typeof(T).Name + " ...", OBDLogLevel.Debug);
|
||||
byte pid = ResolvePid<T>();
|
||||
Logger?.WriteLine("Requesting PID " + pid.ToString("X2") + " ...", OBDLogLevel.Debug);
|
||||
var result = SendCommand(((byte)Mode).ToString("X2") + pid.ToString("X2"));
|
||||
|
||||
await result.WaitHandle.WaitAsync();
|
||||
return result.Result as T;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override object ProcessMessage(string message)
|
||||
{
|
||||
DateTime timestamp = DateTime.Now;
|
||||
|
||||
RawDataReceived?.Invoke(this, new RawDataReceivedEventArgs(message, timestamp));
|
||||
|
||||
if (message.Length > 4)
|
||||
{
|
||||
if (message[0] == '4')
|
||||
{
|
||||
byte mode = (byte)message[1].GetHexVal();
|
||||
@@ -119,13 +157,17 @@ namespace OBD.NET.Devices
|
||||
{
|
||||
IOBDData obdData = (IOBDData)Activator.CreateInstance(dataType);
|
||||
obdData.Load(message.Substring(4, message.Length - 4));
|
||||
|
||||
|
||||
IDataEventManager dataEventManager;
|
||||
if (_dataReceivedEventHandlers.TryGetValue(dataType, out dataEventManager))
|
||||
dataEventManager.RaiseEvent(this, obdData, timestamp);
|
||||
|
||||
return obdData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected virtual byte ResolvePid<T>()
|
||||
@@ -155,12 +197,11 @@ namespace OBD.NET.Devices
|
||||
if (sendCloseProtocol)
|
||||
{
|
||||
SendCommand(ATCommand.CloseProtocol);
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
_dataReceivedEventHandlers = null;
|
||||
_dataReceivedEventHandlers.Clear();
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace OBD.NET.Devices
|
||||
|
||||
#region Constructors
|
||||
|
||||
public STN1170(SerialConnection connection, IOBDLogger logger = null)
|
||||
public STN1170(ISerialConnection connection, IOBDLogger logger = null)
|
||||
: base(connection, logger)
|
||||
{ }
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
using System;
|
||||
using OBD.NET.Communication;
|
||||
using OBD.NET.Exceptions;
|
||||
using OBD.NET.Logging;
|
||||
using System.Threading.Tasks;
|
||||
using OBD.NET.Common.Communication.EventArgs;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Collections.Concurrent;
|
||||
using OBD.NET.Common.Devices;
|
||||
|
||||
namespace OBD.NET.Devices
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class used for communicating with the device
|
||||
/// </summary>
|
||||
public abstract class SerialDevice : IDisposable
|
||||
{
|
||||
private BlockingCollection<QueuedCommand> commandQueue;
|
||||
|
||||
private readonly StringBuilder _lineBuffer = new StringBuilder();
|
||||
|
||||
private readonly AutoResetEvent commandFinishedEvent = new AutoResetEvent(false);
|
||||
|
||||
private Task commandWorkerTask;
|
||||
|
||||
private CancellationTokenSource commandCancellationToken;
|
||||
|
||||
protected QueuedCommand currentCommand;
|
||||
|
||||
/// <summary>
|
||||
/// Logger instance
|
||||
/// </summary>
|
||||
protected IOBDLogger Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Low level connection
|
||||
/// </summary>
|
||||
protected ISerialConnection Connection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Terminator of the protocol message
|
||||
/// </summary>
|
||||
protected char Terminator { get; set; }
|
||||
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Prevents a default instance of the <see cref="SerialDevice"/> class from being created.
|
||||
/// </summary>
|
||||
private SerialDevice()
|
||||
{
|
||||
commandQueue = new BlockingCollection<QueuedCommand>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SerialDevice"/> class.
|
||||
/// </summary>
|
||||
/// <param name="connection">connection.</param>
|
||||
/// <param name="terminator">terminator used for terminating the command message</param>
|
||||
/// <param name="logger">logger instance</param>
|
||||
protected SerialDevice(ISerialConnection connection, char terminator = '\r', IOBDLogger logger = null)
|
||||
:this()
|
||||
{
|
||||
Connection = connection;
|
||||
Terminator = terminator;
|
||||
Logger = logger;
|
||||
|
||||
connection.DataReceived += OnDataReceived;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the device
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
Connection.Connect();
|
||||
CheckConnectionAndStartWorker();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the device
|
||||
/// </summary>
|
||||
public virtual async Task InitializeAsync()
|
||||
{
|
||||
await Connection.ConnectAsync();
|
||||
CheckConnectionAndStartWorker();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the connection and starts background worker which is sending the commands
|
||||
/// </summary>
|
||||
/// <exception cref="SerialException">Failed to open Serial-Connection.</exception>
|
||||
private void CheckConnectionAndStartWorker()
|
||||
{
|
||||
if (!Connection.IsOpen)
|
||||
{
|
||||
Logger?.WriteLine("Failed to open Serial-Connection.", OBDLogLevel.Error);
|
||||
throw new SerialException("Failed to open Serial-Connection.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger?.WriteLine("Opened Serial-Connection!", OBDLogLevel.Debug);
|
||||
}
|
||||
|
||||
commandCancellationToken = new CancellationTokenSource();
|
||||
commandWorkerTask = Task.Factory.StartNew(CommandWorker);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends the command.
|
||||
/// </summary>
|
||||
/// <param name="command">command string</param>
|
||||
/// <exception cref="System.InvalidOperationException">Not connected</exception>
|
||||
protected virtual CommandResult SendCommand(string command)
|
||||
{
|
||||
if (!Connection.IsOpen)
|
||||
{
|
||||
throw new InvalidOperationException("Not connected");
|
||||
}
|
||||
|
||||
|
||||
command = PrepareCommand(command);
|
||||
Logger?.WriteLine("Queuing Command: '" + command.Replace('\r', '\'') + "'", OBDLogLevel.Verbose);
|
||||
|
||||
var cmd = new QueuedCommand(command);
|
||||
commandQueue.Add(cmd);
|
||||
return cmd.CommandResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares the command
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual string PrepareCommand(string command)
|
||||
{
|
||||
if (command == null) throw new ArgumentNullException(nameof(command));
|
||||
|
||||
if (!command.EndsWith(Terminator.ToString(), StringComparison.Ordinal))
|
||||
command += Terminator;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when data is received from the serial device
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender.</param>
|
||||
/// <param name="e">The <see cref="DataReceivedEventArgs"/> instance containing the event data.</param>
|
||||
private void OnDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
for (int i = 0; i < e.Count; i++)
|
||||
{
|
||||
char c = (char)e.Data[i];
|
||||
switch (c)
|
||||
{
|
||||
case '\r':
|
||||
FinishLine();
|
||||
break;
|
||||
|
||||
case '>':
|
||||
currentCommand.CommandResult.WaitHandle.Set();
|
||||
commandFinishedEvent.Set();
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
case (char)0x00:
|
||||
break; // ignore
|
||||
|
||||
default:
|
||||
_lineBuffer.Append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signals a final message
|
||||
/// </summary>
|
||||
private void FinishLine()
|
||||
{
|
||||
string line = _lineBuffer.ToString().Trim();
|
||||
_lineBuffer.Clear();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(line)) return;
|
||||
Logger?.WriteLine("Response: '" + line + "'", OBDLogLevel.Verbose);
|
||||
|
||||
InternalProcessMessage(line);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process message and sets the result
|
||||
/// </summary>
|
||||
/// <param name="message">The message.</param>
|
||||
private void InternalProcessMessage(string message)
|
||||
{
|
||||
var data = ProcessMessage(message);
|
||||
currentCommand.CommandResult.Result = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the message.
|
||||
/// </summary>
|
||||
/// <param name="message">message received</param>
|
||||
/// <returns>result data</returns>
|
||||
protected abstract object ProcessMessage(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Worker method for sending commands
|
||||
/// </summary>
|
||||
private async void CommandWorker()
|
||||
{
|
||||
while (!commandCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
currentCommand = null;
|
||||
if(commandQueue.TryTake(out currentCommand, Timeout.Infinite, commandCancellationToken.Token))
|
||||
{
|
||||
Logger?.WriteLine("Writing Command: '" + currentCommand.CommandText.Replace('\r', '\'') + "'", OBDLogLevel.Verbose);
|
||||
|
||||
if (Connection.IsAsync)
|
||||
{
|
||||
await Connection.WriteAsync(Encoding.ASCII.GetBytes(currentCommand.CommandText));
|
||||
}
|
||||
else
|
||||
{
|
||||
Connection.Write(Encoding.ASCII.GetBytes(currentCommand.CommandText));
|
||||
|
||||
}
|
||||
//wait for command to finish
|
||||
commandFinishedEvent.WaitOne();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
commandCancellationToken?.Cancel();
|
||||
commandWorkerTask?.Wait();
|
||||
Connection?.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-5
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace OBD.NET.Exceptions
|
||||
{
|
||||
@@ -17,10 +16,7 @@ namespace OBD.NET.Exceptions
|
||||
public SerialException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{ }
|
||||
|
||||
protected SerialException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{ }
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
+1
-8
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace OBD.NET.Exceptions
|
||||
{
|
||||
@@ -34,13 +33,7 @@ namespace OBD.NET.Exceptions
|
||||
this.Result = result;
|
||||
this.ExpectedResult = expectedResult;
|
||||
}
|
||||
|
||||
protected UnexpectedResultException(SerializationInfo info, StreamingContext context, string result, string expectedResult)
|
||||
: base(info, context)
|
||||
{
|
||||
this.Result = result;
|
||||
this.ExpectedResult = expectedResult;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,17 @@
|
||||
using OBD.NET.Logging;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace OBD.NET.Common.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple debug logger
|
||||
/// </summary>
|
||||
/// <seealso cref="OBD.NET.Logging.IOBDLogger" />
|
||||
public class OBDDebugLogger : IOBDLogger
|
||||
{
|
||||
public void WriteLine(string text, OBDLogLevel level)
|
||||
{
|
||||
Debug.WriteLine($"{level}: {text}");
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard1.4</TargetFramework>
|
||||
<Authors>Wyrez / Roman Lumetsberger</Authors>
|
||||
<Company>-</Company>
|
||||
<Product>OBD.NET</Product>
|
||||
<Description>C#-Library to read/write data from/to a car through an ELM327-/STN1170-Adapter</Description>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<Version>1.1.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 164 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user