// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace QtVsTools.Core.Common
{
public static partial class Utils
{
///
/// Auto-rotating log file.
///
public class LogFile : Concurrent
{
public string FilePath { get; }
public int MaxSize { get; }
public int TruncSize { get; }
public List Delimiters { get; }
///
/// Create auto-rotating log file. Upon reaching the file is
/// truncated to . If any are specified,
/// the log is further truncated to align with the first record delimiter.
///
///
/// Path to log file
///
///
/// Maximum size of log file.
/// Log is truncated if it grows to a length of 'maxSize' or greater.
///
///
/// Size to which the log file is truncated to.
///
///
/// Log record delimiter(s).
/// After truncating, the start of file will align with the first delimiter.
///
///
/// * contains invalid characters.
///
///
/// * is zero or negative.
/// * is zero or negative.
/// * is greater than .
///
public LogFile(string path, int maxSize, int truncSize, params string[] delimiters)
{
FilePath = path switch
{
{ Length: > 0 } when !Path.GetInvalidPathChars().Any(path.Contains) => path,
_ => throw new ArgumentException(nameof(path))
};
MaxSize = maxSize switch
{
> 0 => maxSize,
_ => throw new ArgumentOutOfRangeException(nameof(maxSize))
};
TruncSize = truncSize switch
{
> 0 when truncSize < maxSize => truncSize,
_ => throw new ArgumentOutOfRangeException(nameof(truncSize))
};
Delimiters = delimiters?.Select(Encoding.UTF8.GetBytes).ToList() ?? new();
}
public void Write(string logEntry)
{
var data = Encoding.UTF8.GetBytes(logEntry);
lock (StaticCriticalSection) {
try {
using var log = new FileStream(FilePath,
FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, MaxSize);
log.Seek(0, SeekOrigin.End);
log.Write(data, 0, data.Length);
if (log.Length > MaxSize)
Rotate(log);
log.Flush();
} catch (Exception e) {
e.Log();
}
}
}
private void Rotate(FileStream log)
{
var data = new byte[TruncSize];
log.Seek(-TruncSize, SeekOrigin.End);
log.Read(data, 0, TruncSize);
log.Seek(0, SeekOrigin.Begin);
var idxStart = Delimiters switch
{
{ Count: > 0 } => Delimiters
.Select(data.IndexOfArray)
.Where(x => x >= 0)
.Append(TruncSize)
.Min(),
_ => 0
};
if (idxStart < TruncSize)
log.Write(data, idxStart, TruncSize - idxStart);
log.SetLength(TruncSize - idxStart);
}
}
}
}