mirror of
https://github.com/BubbaGumpShrump/AutoTrackR2.git
synced 2025-07-24 02:17:50 +00:00
Added process Log Back and Import CSV
Still working on fixing the malformed csv issue but we're getting really close. Process Log Back will re-import all kills that were not recorded to the api. This was added so that any kills that were missed during downtime can be re-submitted to the api.
This commit is contained in:
parent
316911ba7d
commit
0f97d758f8
7 changed files with 413 additions and 227 deletions
|
@ -52,115 +52,115 @@
|
||||||
Margin="0,0,0,10"
|
Margin="0,0,0,10"
|
||||||
VerticalAlignment="Top">
|
VerticalAlignment="Top">
|
||||||
<StackPanel
|
<StackPanel
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Width="152"
|
Width="152"
|
||||||
Margin="10,5,10,5">
|
Margin="10,5,10,5">
|
||||||
<TextBlock Name="PilotNameTitle"
|
<TextBlock Name="PilotNameTitle"
|
||||||
Text="Pilot"
|
Text="Pilot"
|
||||||
Width="152"
|
Width="152"
|
||||||
Height="20"
|
Height="20"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
FontFamily="{StaticResource Orbitron}"
|
FontFamily="{StaticResource Orbitron}"
|
||||||
Margin="0,5,0,0"
|
Margin="0,5,0,0"
|
||||||
Foreground="{DynamicResource AltTextBrush}"
|
Foreground="{DynamicResource AltTextBrush}"
|
||||||
FontSize="14"/>
|
FontSize="14"/>
|
||||||
<TextBlock Name="PilotNameTextBox"
|
<TextBlock Name="PilotNameTextBox"
|
||||||
Text=""
|
Text=""
|
||||||
Width="152"
|
Width="152"
|
||||||
Height="20"
|
Height="20"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
FontFamily="{StaticResource Orbitron}"
|
FontFamily="{StaticResource Orbitron}"
|
||||||
Margin="0,0,0,0"
|
Margin="0,0,0,0"
|
||||||
Foreground="{DynamicResource TextBrush}"
|
Foreground="{DynamicResource TextBrush}"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
TextAlignment="Center"/>
|
TextAlignment="Center"/>
|
||||||
<TextBlock Name="PlayerShipTitle"
|
<TextBlock Name="PlayerShipTitle"
|
||||||
Text="Ship"
|
Text="Ship"
|
||||||
Width="152"
|
Width="152"
|
||||||
Height="20"
|
Height="20"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
FontFamily="{StaticResource Orbitron}"
|
FontFamily="{StaticResource Orbitron}"
|
||||||
Margin="0,5,0,0"
|
Margin="0,5,0,0"
|
||||||
Foreground="{DynamicResource AltTextBrush}"
|
Foreground="{DynamicResource AltTextBrush}"
|
||||||
FontSize="14"/>
|
FontSize="14"/>
|
||||||
<TextBlock Name="PlayerShipTextBox"
|
<TextBlock Name="PlayerShipTextBox"
|
||||||
Text=""
|
Text=""
|
||||||
Width="152"
|
Width="152"
|
||||||
Height="20"
|
Height="20"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
FontFamily="{StaticResource Orbitron}"
|
FontFamily="{StaticResource Orbitron}"
|
||||||
Margin="0,0,0,0"
|
Margin="0,0,0,0"
|
||||||
Foreground="{DynamicResource TextBrush}"
|
Foreground="{DynamicResource TextBrush}"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
TextAlignment="Center"/>
|
TextAlignment="Center"/>
|
||||||
<TextBlock Name="GameModeTitle"
|
<TextBlock Name="GameModeTitle"
|
||||||
Text="Game Mode"
|
Text="Game Mode"
|
||||||
Width="152"
|
Width="152"
|
||||||
Height="20"
|
Height="20"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
FontFamily="{StaticResource Orbitron}"
|
FontFamily="{StaticResource Orbitron}"
|
||||||
Margin="0,5,0,0"
|
Margin="0,5,0,0"
|
||||||
Foreground="{DynamicResource AltTextBrush}"
|
Foreground="{DynamicResource AltTextBrush}"
|
||||||
FontSize="14"/>
|
FontSize="14"/>
|
||||||
<TextBlock Name="GameModeTextBox"
|
<TextBlock Name="GameModeTextBox"
|
||||||
Text=""
|
Text=""
|
||||||
Width="152"
|
Width="152"
|
||||||
Height="20"
|
Height="20"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
FontFamily="{StaticResource Orbitron}"
|
FontFamily="{StaticResource Orbitron}"
|
||||||
Margin="0,0,0,0"
|
Margin="0,0,0,0"
|
||||||
Foreground="{DynamicResource TextBrush}"
|
Foreground="{DynamicResource TextBrush}"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
TextAlignment="Center"/>
|
TextAlignment="Center"/>
|
||||||
<TextBlock Name="LocationTitle"
|
<TextBlock Name="LocationTitle"
|
||||||
Text="Location"
|
Text="Location"
|
||||||
Width="152"
|
Width="152"
|
||||||
Height="20"
|
Height="20"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
FontFamily="{StaticResource Orbitron}"
|
FontFamily="{StaticResource Orbitron}"
|
||||||
Margin="0,5,0,0"
|
Margin="0,5,0,0"
|
||||||
Foreground="{DynamicResource AltTextBrush}"
|
Foreground="{DynamicResource AltTextBrush}"
|
||||||
FontSize="14"/>
|
FontSize="14"/>
|
||||||
<TextBlock Name="LocationTextBox"
|
<TextBlock Name="LocationTextBox"
|
||||||
Text="Unknown"
|
Text="Unknown"
|
||||||
Width="152"
|
Width="152"
|
||||||
Height="20"
|
Height="20"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
FontFamily="{StaticResource Orbitron}"
|
FontFamily="{StaticResource Orbitron}"
|
||||||
Margin="0,0,0,0"
|
Margin="0,0,0,0"
|
||||||
Foreground="{DynamicResource TextBrush}"
|
Foreground="{DynamicResource TextBrush}"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
TextAlignment="Center"/>
|
TextAlignment="Center"/>
|
||||||
<TextBlock Name="KillTallyTitle"
|
<TextBlock Name="KillTallyTitle"
|
||||||
Text="Kill Tally"
|
Text="Kill Tally"
|
||||||
Width="152"
|
Width="152"
|
||||||
Height="20"
|
Height="20"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
FontFamily="{StaticResource Orbitron}"
|
FontFamily="{StaticResource Orbitron}"
|
||||||
Margin="0,5,0,0"
|
Margin="0,5,0,0"
|
||||||
Foreground="{DynamicResource AltTextBrush}"
|
Foreground="{DynamicResource AltTextBrush}"
|
||||||
FontSize="14"/>
|
FontSize="14"/>
|
||||||
<TextBlock Name="KillTallyTextBox"
|
<TextBlock Name="KillTallyTextBox"
|
||||||
Text=""
|
Text=""
|
||||||
Width="152"
|
Width="152"
|
||||||
Height="20"
|
Height="20"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
FontFamily="{StaticResource Orbitron}"
|
FontFamily="{StaticResource Orbitron}"
|
||||||
Margin="0,0,0,0"
|
Margin="0,0,0,0"
|
||||||
Foreground="{DynamicResource TextBrush}"
|
Foreground="{DynamicResource TextBrush}"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
TextAlignment="Center"/>
|
TextAlignment="Center"/>
|
||||||
<TextBox x:Name="DebugPanel"
|
<TextBox x:Name="DebugPanel"
|
||||||
Text=""
|
Text=""
|
||||||
Width="152"
|
Width="152"
|
||||||
Height="98"
|
Height="98"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
FontFamily="{StaticResource Orbitron}"
|
FontFamily="{StaticResource Orbitron}"
|
||||||
Foreground="{DynamicResource TextBrush}"
|
Foreground="{DynamicResource TextBrush}"
|
||||||
FontSize="8"
|
FontSize="8"
|
||||||
BorderThickness="0"
|
BorderThickness="0"
|
||||||
Margin="0,9,0,0"/>
|
Margin="0,9,0,0"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<StackPanel Grid.Row="1"
|
<StackPanel Grid.Row="1"
|
||||||
|
|
|
@ -21,14 +21,14 @@ public partial class HomePage : UserControl
|
||||||
|
|
||||||
private LogHandler? _logHandler;
|
private LogHandler? _logHandler;
|
||||||
private KillHistoryManager _killHistoryManager;
|
private KillHistoryManager _killHistoryManager;
|
||||||
|
private LogBackupProcessor? _logBackupProcessor;
|
||||||
private bool _UIEventsRegistered = false;
|
private bool _UIEventsRegistered = false;
|
||||||
private System.Timers.Timer _statusCheckTimer;
|
private System.Timers.Timer _statusCheckTimer;
|
||||||
private bool _isLogHandlerRunning = false;
|
private bool _isLogHandlerRunning = false;
|
||||||
private int _counter = 1;
|
|
||||||
private System.Timers.Timer _counterTimer;
|
|
||||||
private bool _isInitializing = false;
|
private bool _isInitializing = false;
|
||||||
private System.Timers.Timer? _initializationTimer;
|
private System.Timers.Timer? _initializationTimer;
|
||||||
private bool _wasStarCitizenRunningOnStart = false;
|
private bool _wasStarCitizenRunningOnStart = false;
|
||||||
|
private bool _isProcessingLogBackups = false;
|
||||||
|
|
||||||
public HomePage()
|
public HomePage()
|
||||||
{
|
{
|
||||||
|
@ -62,15 +62,16 @@ public partial class HomePage : UserControl
|
||||||
_statusCheckTimer = new System.Timers.Timer(1000); // Check every second
|
_statusCheckTimer = new System.Timers.Timer(1000); // Check every second
|
||||||
_statusCheckTimer.Elapsed += CheckStarCitizenStatus;
|
_statusCheckTimer.Elapsed += CheckStarCitizenStatus;
|
||||||
_statusCheckTimer.Start();
|
_statusCheckTimer.Start();
|
||||||
|
|
||||||
// Initialize and start the counter timer
|
|
||||||
_counterTimer = new System.Timers.Timer(1000); // Update every second
|
|
||||||
_counterTimer.Elapsed += UpdateCounter;
|
|
||||||
_counterTimer.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckStarCitizenStatus(object? sender, ElapsedEventArgs e)
|
private void CheckStarCitizenStatus(object? sender, ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_isProcessingLogBackups)
|
||||||
|
{
|
||||||
|
// Simulate TrackR as running during log backup processing
|
||||||
|
Dispatcher.Invoke(() => UpdateStatusIndicator(true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
bool isRunning = IsStarCitizenRunning();
|
bool isRunning = IsStarCitizenRunning();
|
||||||
Dispatcher.Invoke(() =>
|
Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
|
@ -569,12 +570,40 @@ public partial class HomePage : UserControl
|
||||||
return Process.GetProcessesByName("StarCitizen").Length > 0;
|
return Process.GetProcessesByName("StarCitizen").Length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCounter(object? sender, ElapsedEventArgs e)
|
public async void ProcessLogBackups_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Dispatcher.Invoke(() =>
|
if (_isProcessingLogBackups)
|
||||||
{
|
{
|
||||||
DebugPanel.Text = _counter.ToString();
|
MessageBox.Show("Already processing log backups. Please wait.", "Processing", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
_counter = (_counter % 10) + 1; // Count from 1 to 10 and loop
|
return;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_isProcessingLogBackups = true;
|
||||||
|
UpdateStatusIndicator(true, true); // Set to yellow for processing
|
||||||
|
|
||||||
|
if (_logBackupProcessor == null)
|
||||||
|
{
|
||||||
|
var logBackupsPath = Path.Combine(Path.GetDirectoryName(ConfigManager.LogFile)!, "logbackups");
|
||||||
|
_logBackupProcessor = new LogBackupProcessor(logBackupsPath, _killHistoryManager, _logHandler?.GetEventHandlers() ?? new List<ILogEventHandler>());
|
||||||
|
}
|
||||||
|
|
||||||
|
await _logBackupProcessor.ProcessLogBackupsAsync((logFile) =>
|
||||||
|
{
|
||||||
|
DebugPanel.Text = $"Processing: {Path.GetFileName(logFile)}";
|
||||||
|
});
|
||||||
|
MessageBox.Show("Log backups processed successfully!", "Success", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
DebugPanel.Text = ""; // Clear the debug panel after successful processing
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Error processing log backups: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isProcessingLogBackups = false;
|
||||||
|
UpdateStatusIndicator(IsStarCitizenRunning());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,117 +2,132 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace AutoTrackR2;
|
namespace AutoTrackR2;
|
||||||
|
|
||||||
public class KillHistoryManager
|
public class KillHistoryManager
|
||||||
{
|
{
|
||||||
private string _killHistoryPath;
|
private readonly string _killHistoryPath;
|
||||||
private readonly string _headers = "KillTime,EnemyPilot,EnemyShip,Enlisted,RecordNumber,OrgAffiliation,Player,Weapon,Ship,Method,Mode,GameVersion,TrackRver,Logged,PFP,Hash\n";
|
private readonly string _headers = "KillTime,EnemyPilot,EnemyShip,Enlisted,RecordNumber,OrgAffiliation,Player,Weapon,Ship,Method,Mode,GameVersion,TrackRver,Logged,PFP,Hash\n";
|
||||||
private readonly KillStreakManager _killStreakManager;
|
private readonly KillStreakManager _killStreakManager;
|
||||||
|
private readonly ConcurrentQueue<KillData> _killQueue;
|
||||||
|
private readonly CancellationTokenSource _cancellationTokenSource;
|
||||||
|
private readonly Task _processingTask;
|
||||||
|
private bool _killStreakSoundEnabled = true;
|
||||||
|
|
||||||
public KillHistoryManager(string logPath, string soundsPath)
|
public KillHistoryManager(string logPath, string soundsPath)
|
||||||
{
|
{
|
||||||
_killHistoryPath = logPath;
|
var appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AutoTrackR2");
|
||||||
_killStreakManager = new KillStreakManager(soundsPath);
|
Directory.CreateDirectory(appDataPath); // Ensure the directory exists
|
||||||
|
_killHistoryPath = Path.Combine(appDataPath, "Kill-log.csv");
|
||||||
|
|
||||||
|
// Create the CSV file with headers if it doesn't exist
|
||||||
if (!File.Exists(_killHistoryPath))
|
if (!File.Exists(_killHistoryPath))
|
||||||
{
|
{
|
||||||
File.WriteAllText(_killHistoryPath, _headers);
|
File.WriteAllText(_killHistoryPath, _headers);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
_killStreakManager = new KillStreakManager(soundsPath);
|
||||||
|
_killQueue = new ConcurrentQueue<KillData>();
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
// Start the background processing task
|
||||||
|
_processingTask = Task.Run(ProcessKillQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessKillQueue()
|
||||||
|
{
|
||||||
|
while (!_cancellationTokenSource.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
CheckAndFixMalformedCsv();
|
try
|
||||||
|
{
|
||||||
|
if (_killQueue.TryDequeue(out var kill))
|
||||||
|
{
|
||||||
|
await ProcessKillAsync(kill);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(100, _cancellationTokenSource.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Error processing kill: {ex.Message}");
|
||||||
|
await Task.Delay(1000, _cancellationTokenSource.Token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckAndFixMalformedCsv()
|
private async Task ProcessKillAsync(KillData kill)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Try to read the file to check if it's malformed
|
// Ensure all fields are properly escaped for CSV
|
||||||
using var reader = new StreamReader(_killHistoryPath);
|
var fields = new[]
|
||||||
var firstLine = reader.ReadLine();
|
|
||||||
|
|
||||||
// If the file is empty or doesn't start with the correct headers, it's malformed
|
|
||||||
if (string.IsNullOrEmpty(firstLine) || firstLine != _headers.TrimEnd('\n'))
|
|
||||||
{
|
{
|
||||||
// Create a backup of the malformed file
|
kill.KillTime.ToString(),
|
||||||
string backupPath = Path.Combine(
|
EscapeCsvField(kill.EnemyPilot),
|
||||||
Path.GetDirectoryName(_killHistoryPath)!,
|
EscapeCsvField(kill.EnemyShip),
|
||||||
"Kill-log.old"
|
EscapeCsvField(kill.Enlisted),
|
||||||
);
|
EscapeCsvField(kill.RecordNumber),
|
||||||
|
EscapeCsvField(kill.OrgAffiliation),
|
||||||
|
EscapeCsvField(kill.Player),
|
||||||
|
EscapeCsvField(kill.Weapon),
|
||||||
|
EscapeCsvField(kill.Ship),
|
||||||
|
EscapeCsvField(kill.Method),
|
||||||
|
EscapeCsvField(kill.Mode),
|
||||||
|
EscapeCsvField(kill.GameVersion),
|
||||||
|
EscapeCsvField(kill.TrackRver),
|
||||||
|
EscapeCsvField(kill.Logged),
|
||||||
|
EscapeCsvField(kill.PFP),
|
||||||
|
EscapeCsvField(kill.Hash)
|
||||||
|
};
|
||||||
|
|
||||||
// If Kill-log.old already exists, delete it
|
var csvLine = string.Join(",", fields);
|
||||||
if (File.Exists(backupPath))
|
|
||||||
{
|
|
||||||
File.Delete(backupPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename the malformed file
|
// Use FileShare.Read to allow other processes to read while we write
|
||||||
File.Move(_killHistoryPath, backupPath);
|
using var stream = new FileStream(_killHistoryPath, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||||
|
using var writer = new StreamWriter(stream);
|
||||||
// Create a new file with correct headers
|
await writer.WriteLineAsync(csvLine);
|
||||||
File.WriteAllText(_killHistoryPath, _headers);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// If there's any error reading the file, consider it malformed
|
Debug.WriteLine($"Error writing kill to CSV: {ex.Message}");
|
||||||
Console.WriteLine($"Error reading CSV file: {ex.Message}");
|
throw;
|
||||||
string backupPath = Path.Combine(
|
|
||||||
Path.GetDirectoryName(_killHistoryPath)!,
|
|
||||||
"Kill-log.old"
|
|
||||||
);
|
|
||||||
|
|
||||||
// If Kill-log.old already exists, delete it
|
|
||||||
if (File.Exists(backupPath))
|
|
||||||
{
|
|
||||||
File.Delete(backupPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename the malformed file
|
|
||||||
File.Move(_killHistoryPath, backupPath);
|
|
||||||
|
|
||||||
// Create a new file with correct headers
|
|
||||||
File.WriteAllText(_killHistoryPath, _headers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddKill(KillData killData)
|
private string EscapeCsvField(string field)
|
||||||
{
|
{
|
||||||
// Ensure the CSV file exists
|
if (string.IsNullOrEmpty(field)) return "";
|
||||||
// This should only happen if the file was deleted or corrupted
|
|
||||||
if (!File.Exists(_killHistoryPath))
|
|
||||||
{
|
|
||||||
File.WriteAllText(_killHistoryPath, _headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove comma from Enlisted
|
|
||||||
killData.Enlisted = killData.Enlisted?.Replace(",", string.Empty);
|
|
||||||
|
|
||||||
// Append the new kill data to the CSV file
|
|
||||||
var csv = new StringBuilder();
|
|
||||||
csv.AppendLine($"\"{killData.KillTime}\",\"{killData.EnemyPilot}\",\"{killData.EnemyShip}\",\"{killData.Enlisted}\",\"{killData.RecordNumber}\",\"{killData.OrgAffiliation}\",\"{killData.Player}\",\"{killData.Weapon}\",\"{killData.Ship}\",\"{killData.Method}\",\"{killData.Mode}\",\"{killData.GameVersion}\",\"{killData.TrackRver}\",\"{killData.Logged}\",\"{killData.PFP}\",\"{killData.Hash}\"");
|
|
||||||
|
|
||||||
// Check file can be written to
|
// If the field contains any special characters, wrap it in quotes
|
||||||
try
|
if (field.Contains(",") || field.Contains("\"") || field.Contains("\n") || field.Contains("\r"))
|
||||||
{
|
{
|
||||||
using var fileStream = new FileStream(_killHistoryPath, FileMode.Append, FileAccess.Write, FileShare.Read);
|
// Double up any quotes
|
||||||
using var writer = new StreamWriter(fileStream);
|
field = field.Replace("\"", "\"\"");
|
||||||
writer.Write(csv.ToString());
|
return $"\"{field}\"";
|
||||||
|
}
|
||||||
|
|
||||||
// Trigger kill streak sound only if enabled
|
return field;
|
||||||
if (ConfigManager.KillStreakEnabled == 1)
|
}
|
||||||
{
|
|
||||||
_killStreakManager.OnKill();
|
public void AddKill(KillData kill)
|
||||||
}
|
{
|
||||||
}
|
_killQueue.Enqueue(kill);
|
||||||
catch (IOException ex)
|
}
|
||||||
{
|
|
||||||
// Handle the exception (e.g., log it)
|
public void PlayKillStreakSound()
|
||||||
Console.WriteLine($"Error writing to file: {ex.Message}");
|
{
|
||||||
}
|
_killStreakManager.OnKill();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetKillStreak()
|
public void ResetKillStreak()
|
||||||
|
@ -120,23 +135,30 @@ public class KillHistoryManager
|
||||||
_killStreakManager.OnDeath();
|
_killStreakManager.OnDeath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cancellationTokenSource.Cancel();
|
||||||
|
_processingTask.Wait();
|
||||||
|
_cancellationTokenSource.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
public List<KillData> GetKills()
|
public List<KillData> GetKills()
|
||||||
{
|
{
|
||||||
var kills = new List<KillData>();
|
var kills = new List<KillData>();
|
||||||
|
|
||||||
using var reader = new StreamReader(new FileStream(_killHistoryPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
using var reader = new StreamReader(new FileStream(_killHistoryPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
||||||
reader.ReadLine(); // Skip headers
|
reader.ReadLine(); // Skip headers
|
||||||
|
|
||||||
while (reader.Peek() >= 0)
|
while (reader.Peek() >= 0)
|
||||||
{
|
{
|
||||||
var line = reader.ReadLine();
|
var line = reader.ReadLine();
|
||||||
|
|
||||||
// Remove extra quotes from CSV data
|
// Remove extra quotes from CSV data
|
||||||
// Todo: These quotes are for handling commas in the data, but not sure if they're necessary
|
// Todo: These quotes are for handling commas in the data, but not sure if they're necessary
|
||||||
line = line?.Replace("\"", string.Empty);
|
line = line?.Replace("\"", string.Empty);
|
||||||
|
|
||||||
var data = line?.Split(',');
|
var data = line?.Split(',');
|
||||||
|
|
||||||
kills.Add(new KillData
|
kills.Add(new KillData
|
||||||
{
|
{
|
||||||
KillTime = data?[0],
|
KillTime = data?[0],
|
||||||
|
|
83
AutoTrackR2/LogBackupProcessor.cs
Normal file
83
AutoTrackR2/LogBackupProcessor.cs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using AutoTrackR2.LogEventHandlers;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace AutoTrackR2;
|
||||||
|
|
||||||
|
public class LogBackupProcessor
|
||||||
|
{
|
||||||
|
private readonly string _logBackupsPath;
|
||||||
|
private readonly KillHistoryManager _killHistoryManager;
|
||||||
|
private readonly List<ILogEventHandler> _logEventHandlers;
|
||||||
|
|
||||||
|
public LogBackupProcessor(string logBackupsPath, KillHistoryManager killHistoryManager, List<ILogEventHandler> logEventHandlers)
|
||||||
|
{
|
||||||
|
_logBackupsPath = logBackupsPath;
|
||||||
|
_killHistoryManager = killHistoryManager;
|
||||||
|
_logEventHandlers = logEventHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessLogBackupsAsync(Action<string>? onLogFileProcessed = null)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(_logBackupsPath))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Log backups directory not found: {_logBackupsPath}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var logFiles = Directory.GetFiles(_logBackupsPath, "*.log", SearchOption.AllDirectories)
|
||||||
|
.Where(file => File.GetLastWriteTime(file) >= new DateTime(2025, 3, 27))
|
||||||
|
.ToArray();
|
||||||
|
Array.Sort(logFiles); // Process files in chronological order
|
||||||
|
|
||||||
|
for (int i = 0; i < logFiles.Length; i++)
|
||||||
|
{
|
||||||
|
var logFile = logFiles[i];
|
||||||
|
onLogFileProcessed?.Invoke(logFile);
|
||||||
|
await ProcessLogFileAsync(logFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessLogFileAsync(string logFilePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var reader = new StreamReader(logFilePath);
|
||||||
|
string? line;
|
||||||
|
var lines = new List<string>();
|
||||||
|
while ((line = await reader.ReadLineAsync()) != null)
|
||||||
|
{
|
||||||
|
lines.Add(line);
|
||||||
|
}
|
||||||
|
int actorDeathCount = 0;
|
||||||
|
await Task.Run(() => Parallel.ForEach(lines, line =>
|
||||||
|
{
|
||||||
|
var entry = new LogEntry { Message = line };
|
||||||
|
foreach (var handler in _logEventHandlers)
|
||||||
|
{
|
||||||
|
if (handler.Pattern.IsMatch(line))
|
||||||
|
{
|
||||||
|
handler.Handle(entry);
|
||||||
|
if (handler is ActorDeathEvent)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref actorDeathCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
Console.WriteLine($"Processed {actorDeathCount} actor deaths in {logFilePath}");
|
||||||
|
// Wait 5 seconds after processing the file before moving to the next file
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error processing log file {logFilePath}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,9 +46,10 @@ public class LogHandler
|
||||||
new GameVersionEvent(),
|
new GameVersionEvent(),
|
||||||
new JumpDriveStateChangedEvent(),
|
new JumpDriveStateChangedEvent(),
|
||||||
new RequestJumpFailedEvent(),
|
new RequestJumpFailedEvent(),
|
||||||
new VehicleDestructionEvent()
|
new VehicleDestructionEvent(),
|
||||||
|
new ActorDeathEvent()
|
||||||
];
|
];
|
||||||
|
|
||||||
public LogHandler(string? logPath)
|
public LogHandler(string? logPath)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(logPath))
|
if (string.IsNullOrEmpty(logPath))
|
||||||
|
@ -104,8 +105,6 @@ public class LogHandler
|
||||||
HandleLogEntry(line);
|
HandleLogEntry(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensures that any deaths already in log aren't sent to the APIs until the monitor thread is running
|
|
||||||
_eventHandlers.Add(new ActorDeathEvent());
|
|
||||||
StartMonitoring();
|
StartMonitoring();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,4 +217,9 @@ public class LogHandler
|
||||||
}
|
}
|
||||||
_gameProcessState = newGameProcessState;
|
_gameProcessState = newGameProcessState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ILogEventHandler> GetEventHandlers()
|
||||||
|
{
|
||||||
|
return new List<ILogEventHandler>(_eventHandlers);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,37 +1,80 @@
|
||||||
<Window x:Class="AutoTrackR2.MainWindow"
|
<Window x:Class="AutoTrackR2.MainWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="AutoTrackR" Height="450" Width="800"
|
Title="AutoTrackR"
|
||||||
WindowStyle="None" ResizeMode="NoResize"
|
Height="450"
|
||||||
|
Width="800"
|
||||||
|
WindowStyle="None"
|
||||||
|
ResizeMode="NoResize"
|
||||||
AllowsTransparency="True"
|
AllowsTransparency="True"
|
||||||
Style="{StaticResource CustomWindowStyle}">
|
Style="{StaticResource CustomWindowStyle}">
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<!-- Custom Title Bar -->
|
<!-- Custom Title Bar -->
|
||||||
<DockPanel Height="30" VerticalAlignment="Top" MouseDown="TitleBar_MouseDown" Margin="5" Background="Transparent">
|
<DockPanel Height="30"
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
VerticalAlignment="Top"
|
||||||
<Button Content="_" Width="30" Height="25" Click="MinimizeWindow" Style="{StaticResource TitleButtonStyle}" FontFamily="{StaticResource Orbitron}"/>
|
MouseDown="TitleBar_MouseDown"
|
||||||
<Button Content="X" Width="30" Height="25" Click="CloseWindow" Style="{StaticResource TitleButtonStyle}" FontFamily="{StaticResource Orbitron}"/>
|
Margin="5"
|
||||||
|
Background="Transparent">
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<Button Content="_"
|
||||||
|
Width="30"
|
||||||
|
Height="25"
|
||||||
|
Click="MinimizeWindow"
|
||||||
|
Style="{StaticResource TitleButtonStyle}"
|
||||||
|
FontFamily="{StaticResource Orbitron}"/>
|
||||||
|
<Button Content="X"
|
||||||
|
Width="30"
|
||||||
|
Height="25"
|
||||||
|
Click="CloseWindow"
|
||||||
|
Style="{StaticResource TitleButtonStyle}"
|
||||||
|
FontFamily="{StaticResource Orbitron}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<!-- Main Content Area -->
|
<!-- Main Content Area -->
|
||||||
<Grid Margin="0,30,0,0">
|
<Grid Margin="0,30,0,0">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="150"/>
|
<ColumnDefinition Width="150"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- Left Tab Panel -->
|
<!-- Left Tab Panel -->
|
||||||
<StackPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="5,0,0,0">
|
<StackPanel VerticalAlignment="Stretch"
|
||||||
<Image x:Name="Logo" Height="138" Source="/Assets/AutoTrackR.png" Stretch="Fill" Width="141" RenderOptions.BitmapScalingMode="Fant"/>
|
HorizontalAlignment="Stretch"
|
||||||
<Button Content="Home" Name="HomeTab" Margin="10,40,10,10" Height="40" Style="{StaticResource TabButtonStyle}" Click="TabButton_Click"/>
|
Margin="5,0,0,0">
|
||||||
<Button Content="Config" Name="ConfigTab" Margin="10" Height="40" Style="{StaticResource TabButtonStyle}" Click="TabButton_Click"/>
|
<Image x:Name="Logo"
|
||||||
</StackPanel>
|
Height="138"
|
||||||
|
Source="/Assets/AutoTrackR.png"
|
||||||
|
Stretch="Fill"
|
||||||
|
Width="141"
|
||||||
|
RenderOptions.BitmapScalingMode="Fant"/>
|
||||||
|
<Button Content="Home"
|
||||||
|
Name="HomeTab"
|
||||||
|
Margin="10,40,10,10"
|
||||||
|
Height="40"
|
||||||
|
Style="{StaticResource TabButtonStyle}"
|
||||||
|
Click="TabButton_Click"/>
|
||||||
|
<Button Content="Config"
|
||||||
|
Name="ConfigTab"
|
||||||
|
Margin="10"
|
||||||
|
Height="40"
|
||||||
|
Style="{StaticResource TabButtonStyle}"
|
||||||
|
Click="TabButton_Click"/>
|
||||||
|
<Button Content="Process Log Backups"
|
||||||
|
Name="ProcessLogBackupsButton"
|
||||||
|
Margin="10"
|
||||||
|
Height="40"
|
||||||
|
Style="{StaticResource TabButtonStyle}"
|
||||||
|
Click="ProcessLogBackups_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Content Area -->
|
<!-- Content Area -->
|
||||||
<ContentControl Grid.Column="1" Name="ContentControl" Margin="10">
|
<ContentControl Grid.Column="1"
|
||||||
<!-- Default content can be set here -->
|
Name="ContentControl"
|
||||||
</ContentControl>
|
Margin="10">
|
||||||
</Grid>
|
<!-- Default content can be set here -->
|
||||||
|
</ContentControl>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
|
@ -120,6 +120,11 @@ namespace AutoTrackR2
|
||||||
UpdateTabVisuals();
|
UpdateTabVisuals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ProcessLogBackups_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
homePage.ProcessLogBackups_Click(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateTabStates(string activeTab)
|
private void UpdateTabStates(string activeTab)
|
||||||
{
|
{
|
||||||
foreach (var key in tabStates.Keys)
|
foreach (var key in tabStates.Keys)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue