using System.Globalization;
using System.IO;
using System.Text;
using CsvHelper;
using CsvHelper.Configuration;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Localization;
namespace SharepointToolbox.Services.Export;
///
/// Exports the failed subset of a run
/// to CSV. CsvHelper is used so the payload's
/// properties become columns automatically, plus one error-message and one
/// timestamp column appended at the end.
///
public class BulkResultCsvExportService
{
private static readonly CsvConfiguration CsvConfig = new(CultureInfo.InvariantCulture)
{
// Prevent CSV formula injection: prefix =, +, -, @, tab, CR with single quote
InjectionOptions = InjectionOptions.Escape,
};
///
/// Builds a CSV containing only items whose
/// is false. Columns: every public property of
/// followed by Error and Timestamp (ISO-8601).
///
public string BuildFailedItemsCsv(IReadOnlyList> failedItems)
{
var TL = TranslationSource.Instance;
using var writer = new StringWriter();
using var csv = new CsvWriter(writer, CsvConfig);
csv.WriteHeader();
csv.WriteField(TL["report.col.error"]);
csv.WriteField(TL["report.col.timestamp"]);
csv.NextRecord();
foreach (var item in failedItems.Where(r => !r.IsSuccess))
{
csv.WriteRecord(item.Item);
csv.WriteField(item.ErrorMessage);
csv.WriteField(item.Timestamp.ToString("o"));
csv.NextRecord();
}
return writer.ToString();
}
/// Writes the failed-items CSV to with UTF-8 BOM.
public async Task WriteFailedItemsCsvAsync(
IReadOnlyList> failedItems,
string filePath,
CancellationToken ct)
{
var content = BuildFailedItemsCsv(failedItems);
await ExportFileWriter.WriteCsvAsync(filePath, content, ct);
}
}