Update Subtasks In Markdown Files

Better ISO support

Only reviewing Files when comparing

Extracted sections from UpdateSubTasksInMarkdownFiles
This commit is contained in:
Mike Phares 2024-12-26 14:14:31 -07:00
parent 2361796bbf
commit fb9289a572
13 changed files with 1208 additions and 395 deletions

33
.vscode/launch.json vendored
View File

@ -13,17 +13,42 @@
"args": [
"s",
"X",
"D:/5-Other-Small/Kanban/DEP08CEPIEPSILON",
"D:/6-Other-Large-Z/https-linux-ubuntu-server/etc",
"Day-Helper-2024-12-24",
"dorico.phares.duckdns.org",
"php",
"444",
"555",
"666",
"777",
"888",
"999",
"s",
"X",
"D:/5-Other-Small/Kanban/Year-Season",
"Day-Helper-2024-06-23",
"*.md",
"## Sub-tasks",
"##_Sub-tasks",
"code-insiders",
"index.md",
"- [,](",
"## Done",
"-_[,](",
"##_Done",
".kan",
"D:/5-Other-Small/Kanban/Year-Season",
"316940400000",
"s",
"X",
"L:/",
"Day-Helper-2024-12-17",
"job.json",
"333",
"444",
"555",
"666",
"777",
"888",
"999",
"X",
"L:/Git/Linux-Ubuntu-Server/etc/nginx/include",
"Day-Helper-2024-09-16",
"*.conf",

View File

@ -27,6 +27,7 @@
"Kanban",
"kanbn",
"Kofax",
"mesfs",
"NpgSql",
"NSFX",
"OBJE",
@ -34,6 +35,7 @@
"Permyriad",
"pged",
"Phares",
"Renci",
"Reparse",
"Rijndael",
"Serilog",

View File

@ -1,31 +1,96 @@
using File_Folder_Helper.Helpers;
using File_Folder_Helper.Models;
using Microsoft.Extensions.Logging;
using System.Collections.ObjectModel;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace File_Folder_Helper.ADO2024.PI2;
internal static partial class Helper20240623
{
private record SubTaskLine(string Text, bool Done, long? Ticks, int? Line);
private record Record(int? CodeInsidersLine, string File, string[] Lines, int? StopLine, int? SubTasksLine);
[GeneratedRegex("([A-Z]+(.))")]
private static partial Regex UpperCase();
private static List<Record> GetRecords(string sourceDirectory, string searchPattern, string codeInsiders, string subTasks, string directoryFilter)
[GeneratedRegex("[\\s!?.,@:;|\\\\/\"'`£$%\\^&*{}[\\]()<>~#+\\-=_¬]+")]
private static partial Regex InvalidCharacter();
private record H1AndParamCase(string H1, string ParamCase);
private record SubTaskLine(string Text, bool Done, long? Ticks, int? Line);
private record Record(int? CodeInsidersLine, FileInfo FileInfo, LineNumber LineNumber, int? StopLine, int? SubTasksLine);
private record Input(long? AfterEpochTotalMilliseconds,
string CodeInsiders,
string? DestinationDirectory,
string DirectoryFilter,
string Done,
string IndexFile,
string SearchPattern,
string SubTasks,
string SourceDirectory,
ReadOnlyCollection<string> Tasks);
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Input))]
private partial class InputSourceGenerationContext : JsonSerializerContext
{
}
private static Record GetRecord(Input input, FileInfo fileInfo)
{
Record result;
int? stopLine = null;
int? subTasksLine = null;
int? codeInsidersLine = null;
LineNumber lineNumber = HelperMarkdown.GetLineNumbers(fileInfo);
for (int i = 0; i < lineNumber.Lines.Count; i++)
{
if (lineNumber.Lines[i].StartsWith(input.CodeInsiders) && lineNumber.Lines[i][^1] == '"')
{
if (lineNumber.Lines.Count > i + 1 && lineNumber.Lines[i + 1] == "```")
codeInsidersLine = i;
}
if (lineNumber.Lines[i] != input.SubTasks)
continue;
subTasksLine = i;
if (codeInsidersLine is null)
break;
if (lineNumber.Lines.Count > i)
{
for (int j = i + 1; j < lineNumber.Lines.Count; j++)
{
if (lineNumber.Lines[j].Length > 0 && lineNumber.Lines[j][0] == '#')
{
stopLine = j;
break;
}
}
}
stopLine ??= lineNumber.Lines.Count;
break;
}
result = new(codeInsidersLine, fileInfo, lineNumber, stopLine, subTasksLine);
return result;
}
private static List<Record> GetRecords(Input input)
{
List<Record> results = [];
int? stopLine;
string[] lines;
int? subTasksLine;
int? codeInsidersLine;
ReadOnlyCollection<string> directoryNames = HelperDirectory.GetDirectoryNames(sourceDirectory);
if (!directoryNames.Any(l => l.StartsWith(directoryFilter, StringComparison.CurrentCultureIgnoreCase)))
Record record;
FileInfo fileInfo;
string sourceDirectory = input.SourceDirectory;
ReadOnlyCollection<string> directoryNames = HelperDirectory.GetDirectoryNames(input.SourceDirectory);
if (!directoryNames.Any(l => l.StartsWith(input.DirectoryFilter, StringComparison.CurrentCultureIgnoreCase)))
{
string directoryName;
string[] checkDirectories = Directory.GetDirectories(sourceDirectory, "*", SearchOption.TopDirectoryOnly);
string[] checkDirectories = Directory.GetDirectories(input.SourceDirectory, "*", SearchOption.TopDirectoryOnly);
foreach (string checkDirectory in checkDirectories)
{
directoryName = Path.GetFileName(checkDirectory);
if (directoryName.StartsWith(directoryFilter, StringComparison.CurrentCultureIgnoreCase))
if (directoryName.StartsWith(input.DirectoryFilter, StringComparison.CurrentCultureIgnoreCase))
{
sourceDirectory = checkDirectory;
break;
@ -33,204 +98,506 @@ internal static partial class Helper20240623
}
}
string[] subDirectories = Directory.GetDirectories(sourceDirectory, "*", SearchOption.TopDirectoryOnly);
List<string> files = Directory.GetFiles(sourceDirectory, searchPattern, SearchOption.TopDirectoryOnly).ToList();
List<string> files = Directory.GetFiles(sourceDirectory, input.SearchPattern, SearchOption.TopDirectoryOnly).ToList();
foreach (string subDirectory in subDirectories)
files.AddRange(Directory.GetFiles(subDirectory, searchPattern, SearchOption.TopDirectoryOnly));
files.AddRange(Directory.GetFiles(subDirectory, input.SearchPattern, SearchOption.TopDirectoryOnly));
foreach (string file in files)
{
stopLine = null;
subTasksLine = null;
codeInsidersLine = null;
lines = File.ReadAllLines(file);
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].StartsWith(codeInsiders) && lines[i][^1] == '"')
{
if (lines.Length > i + 1 && lines[i + 1] == "```")
codeInsidersLine = i;
}
if (lines[i] != subTasks)
continue;
subTasksLine = i;
if (codeInsidersLine is null)
break;
if (lines.Length > i)
{
for (int j = i + 1; j < lines.Length; j++)
{
if (lines[j].Length > 0 && lines[j][0] == '#')
{
stopLine = j;
break;
}
}
}
stopLine ??= lines.Length;
break;
}
results.Add(new(codeInsidersLine, file, lines, stopLine, subTasksLine));
fileInfo = new(file);
record = GetRecord(input, fileInfo);
results.Add(record);
}
return results;
}
private static ReadOnlyCollection<SubTaskLine> GetSubTasks(string subTasks, string[] tasks, bool? foundDone, string fallbackLine, FileInfo fileInfo)
private static string GetParamCase(string value)
{
string result;
StringBuilder stringBuilder = new(value);
Match[] matches = UpperCase().Matches(value).ToArray();
for (int i = matches.Length - 1; i > -1; i--)
_ = stringBuilder.Insert(matches[i].Index, '-');
string[] segments = InvalidCharacter().Split(stringBuilder.ToString().ToLower());
result = string.Join('-', segments).Trim('-');
return result;
}
private static ReadOnlyCollection<SubTaskLine> GetSubTaskLines(Input input, bool? foundDone, string fallbackLine, Record record)
{
List<SubTaskLine> results = [];
char done;
string line;
string text;
bool doneValue;
string? h1 = null;
SubTaskLine subTaskLine;
bool foundSubTasks = false;
int tasksZeroLength = tasks[0].Length;
string[] lines = File.ReadAllLines(fileInfo.FullName);
for (int i = 0; i < lines.Length; i++)
int tasksZeroLength = input.Tasks[0].Length;
long ticks = record.FileInfo.LastWriteTime.Ticks;
for (int i = 0; i < record.LineNumber.Lines.Count; i++)
{
line = lines[i];
if (line.StartsWith("# "))
h1 = line[2..];
if (!foundSubTasks && line == subTasks)
line = record.LineNumber.Lines[i];
if (!foundSubTasks && line == input.SubTasks)
foundSubTasks = true;
if (!foundSubTasks)
continue;
if (line.Length <= tasksZeroLength || !line.StartsWith(tasks[0]) || line[tasksZeroLength] is not ' ' and not 'x' || line[tasksZeroLength + 1] != ']')
if (line.Length <= tasksZeroLength || !line.StartsWith(input.Tasks[0]) || line[tasksZeroLength] is not ' ' and not 'x' || line[tasksZeroLength + 1] != ']')
continue;
doneValue = foundDone is not null && foundDone.Value;
subTaskLine = new($" {line}", doneValue, fileInfo.LastWriteTime.Ticks, i);
subTaskLine = new($" {line}", doneValue, ticks, i);
results.Add(subTaskLine);
}
doneValue = foundDone is not null && foundDone.Value;
if (h1 is null)
subTaskLine = new(fallbackLine, doneValue, fileInfo.LastWriteTime.Ticks, Line: null);
if (record.LineNumber.H1 is null)
subTaskLine = new(fallbackLine, doneValue, ticks, Line: null);
else
{
fallbackLine = foundDone is null || !foundDone.Value ? $"- [ ] {fileInfo.LastWriteTime.Ticks} ~~~ {h1}" : $"- [x] {fileInfo.LastWriteTime.Ticks} ~~~ {h1}";
subTaskLine = new(fallbackLine, doneValue, fileInfo.LastWriteTime.Ticks, Line: 0);
done = foundDone is null || !foundDone.Value ? ' ' : 'x';
string codeInsidersLine = record.CodeInsidersLine is null ? string.Empty : $" ~~{record.LineNumber.Lines[record.CodeInsidersLine.Value]}~~";
text = $"- [{done}] {ticks} {record.LineNumber.Lines[record.LineNumber.H1.Value]}{codeInsidersLine}";
subTaskLine = new(text, doneValue, ticks, Line: 0);
}
results.Add(subTaskLine);
return new(results);
}
internal static void UpdateSubTasksInMarkdownFiles(ILogger<Worker> logger, List<string> args)
private static string GetSeasonName(int dayOfYear)
{
int lineCheck;
string result = dayOfYear switch
{
< 78 => "0.Winter",
< 124 => "1.Spring",
< 171 => "2.Spring",
< 217 => "3.Summer",
< 264 => "4.Summer",
< 309 => "5.Fall",
< 354 => "6.Fall",
_ => "7.Winter"
};
return result;
}
private static string[] GetIndexLines(string h1, ReadOnlyCollection<H1AndParamCase> h1ParamCaseCollection) =>
[
"---",
"startedColumns:",
" - 'In Progress'",
"completedColumns:",
" - Done",
"---",
string.Empty,
$"# {h1}",
string.Empty,
"## Backlog",
string.Empty,
string.Join(Environment.NewLine, h1ParamCaseCollection.Select(l => $"- [{l.ParamCase}](tasks/{l.ParamCase}.md)")),
string.Empty,
"## Todo",
string.Empty,
"## In Progress",
string.Empty,
"## Done",
string.Empty
];
private static string[] GetCascadingStyleSheetsLines() =>
[
".kanbn-column-done .kanbn-column-task-list {",
" border-color: #198038;",
"}",
string.Empty,
".kanbn-task-data-created {",
" display: none;",
"}",
string.Empty,
".kanbn-task-data-workload {",
" display: none;",
"}"
];
private static string GetSettingsLines() =>
/*lang=json,strict*/ """
{
"[markdown]": {
"editor.wordWrap": "off"
},
"cSpell.words": [
"kanbn"
]
}
""";
private static string GetTasksLines(string directory) =>
/*lang=json,strict*/ """
{
"version": "2.0.0",
"tasks": [
{
"label": "File-Folder-Helper AOT s X Day-Helper-2024-06-23",
"type": "shell",
"command": "L:/DevOps/Mesa_FI/File-Folder-Helper/bin/Release/net8.0/win-x64/publish/File-Folder-Helper.exe",
"args": [
"s",
"X",
"{}",
"Day-Helper-2024-06-23",
"*.md",
"##_Sub-tasks",
"code-insiders",
"index.md",
"-_[,](",
"##_Done",
".kan",
"D:/5-Other-Small/Kanban/Year-Season",
"316940400000"
],
"problemMatcher": []
}
]
}
""".Replace("{}", directory.Replace('\\', '/'));
private static void FileWriteAllText(string path, string contents)
{
// string checkJson = Regex.Replace(File.ReadAllText(path), @"\s+", " ", RegexOptions.Multiline);
// if (Regex.Replace(singletonJson, @"\s+", " ", RegexOptions.Multiline) != checkJson)
// File.WriteAllText(path, singletonJson);
string old = !File.Exists(path) ? string.Empty : File.ReadAllText(path);
if (old != contents)
File.WriteAllText(path, contents);
}
private static void FileWriteAllText(string path, string[] contents) =>
FileWriteAllText(path, string.Join(Environment.NewLine, contents));
private static ReadOnlyCollection<H1AndParamCase> GetH1ParamCaseCollection(Input input, ReadOnlyCollection<string> lines)
{
List<H1AndParamCase> results = [];
string h1;
string line;
string paramCase;
bool foundSubTasks = false;
H1AndParamCase h1AndParamCase;
int tasksZeroLength = input.Tasks[0].Length;
for (int i = 0; i < lines.Count; i++)
{
line = lines[i];
if (!foundSubTasks && line == input.SubTasks)
foundSubTasks = true;
if (!foundSubTasks)
continue;
if (line.Length <= tasksZeroLength || !line.StartsWith(input.Tasks[0]) || line[tasksZeroLength] is not ' ' and not 'x' || line[tasksZeroLength + 1] != ']')
continue;
h1 = line[(tasksZeroLength + 3)..];
if (string.IsNullOrEmpty(h1))
continue;
paramCase = GetParamCase(h1);
h1AndParamCase = new(h1, paramCase);
results.Add(h1AndParamCase);
}
return results.AsReadOnly();
}
private static void CreateFiles(string directory, ReadOnlyCollection<H1AndParamCase> h1ParamCaseCollection)
{
foreach (H1AndParamCase h1ParamCase in h1ParamCaseCollection)
FileWriteAllText(Path.Combine(directory, $"{h1ParamCase.ParamCase}.md"), $"# {h1ParamCase.H1}");
}
private static string WriteAndGetIndexFile(string destinationDirectory, string h1, long verified, ReadOnlyCollection<H1AndParamCase> h1ParamCaseCollection)
{
string result;
string[] indexLines = GetIndexLines(h1, h1ParamCaseCollection);
DateTime utcEpochDateTime = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime dateTime = utcEpochDateTime.AddMilliseconds(verified).ToLocalTime();
string seasonName = GetSeasonName(dateTime.DayOfYear);
string verifiedDirectory = Path.Combine(destinationDirectory, $"{dateTime.Year}", $"{dateTime.Year}-{seasonName}", verified.ToString());
string kanbanDirectory = Path.Combine(verifiedDirectory, ".kanbn");
string tasksKanbanDirectory = Path.Combine(kanbanDirectory, "tasks");
if (!Directory.Exists(tasksKanbanDirectory))
_ = Directory.CreateDirectory(tasksKanbanDirectory);
string verifiedVisualStudioCodeDirectory = Path.Combine(verifiedDirectory, ".vscode");
if (!Directory.Exists(verifiedVisualStudioCodeDirectory))
_ = Directory.CreateDirectory(verifiedVisualStudioCodeDirectory);
result = Path.Combine(kanbanDirectory, "index.md");
CreateFiles(tasksKanbanDirectory, h1ParamCaseCollection);
FileWriteAllText(result, indexLines);
FileWriteAllText(Path.Combine(kanbanDirectory, "board.css"), GetCascadingStyleSheetsLines());
FileWriteAllText(Path.Combine(verifiedVisualStudioCodeDirectory, "settings.json"), GetSettingsLines());
FileWriteAllText(Path.Combine(verifiedVisualStudioCodeDirectory, "tasks.json"), GetTasksLines(verifiedDirectory));
return result;
}
private static ReadOnlyCollection<SubTaskLine> GetSubTaskLines(Input input, FileInfo fileInfo, LineNumber lineNumber)
{
List<SubTaskLine> results = [];
char done;
FileInfo f;
Record record;
bool doneValue;
bool? foundDone;
FileInfo fileInfo;
string[] newLines;
string[] segments;
List<string> lines;
string fallbackLine;
string[] indexLines;
string checkDirectory;
string done = args[7];
List<string> indexFiles;
SubTaskLine subTaskLine;
string subTasks = args[3];
List<string> oldLines = [];
bool? foundDone = null;
ReadOnlyCollection<SubTaskLine> subTaskLines;
for (int i = 0; i < lineNumber.Lines.Count; i++)
{
if (lineNumber.Lines[i] == input.Done)
foundDone = true;
segments = lineNumber.Lines[i].Split(input.Tasks[1]);
doneValue = foundDone is not null && foundDone.Value;
if (segments.Length > 2 || !segments[0].StartsWith(input.Tasks[0]))
continue;
done = foundDone is null || !foundDone.Value ? ' ' : 'x';
fallbackLine = $"- [{done}] {segments[0][input.Tasks[0].Length..]} ~~FallbackLine~~";
if (string.IsNullOrEmpty(fileInfo.DirectoryName))
continue;
f = new(Path.GetFullPath(Path.Combine(fileInfo.DirectoryName, segments[1][..^1])));
if (!f.Exists)
{
results.Add(new(fallbackLine, doneValue, Ticks: null, Line: null));
continue;
}
record = GetRecord(input, f);
if (lineNumber.H1 is not null && record.LineNumber.H1 is not null)
{
string a = lineNumber.Lines[lineNumber.H1.Value];
string b = record.LineNumber.Lines[record.LineNumber.H1.Value];
if (b != a)
{
if (b != a)
{
}
}
}
subTaskLines = GetSubTaskLines(input, doneValue, fallbackLine, record);
for (int j = subTaskLines.Count - 1; j >= 0; j--)
results.Add(subTaskLines[j]);
}
return results.AsReadOnly();
}
private static Input GetInput(List<string> args)
{
string indexFile = args[5];
string searchPattern = args[2];
string directoryFilter = args[8];
string[] tasks = args[6].Split(',');
string codeInsiders = $"{args[4]} \"";
List<SubTaskLine> allSubTaskLines = [];
ReadOnlyCollection<SubTaskLine> subTaskLines;
string done = args[7].Replace('_', ' ');
string subTasks = args[3].Replace('_', ' ');
string sourceDirectory = Path.GetFullPath(args[0]);
List<Record> records = GetRecords(sourceDirectory, searchPattern, codeInsiders, subTasks, directoryFilter);
foreach (Record record in from l in records orderby l.SubTasksLine is null, l.CodeInsidersLine is null select l)
string? destinationDirectory = args.Count < 8 ? null : Path.GetFullPath(args[9]);
long? afterEpochTotalMilliseconds = args.Count < 9 ? null : long.Parse(args[10]);
ReadOnlyCollection<string> tasks = args[6].Split(',').Select(l => l.Replace('_', ' ')).ToArray().AsReadOnly();
Input input = new(afterEpochTotalMilliseconds, codeInsiders, destinationDirectory, directoryFilter, done, indexFile, searchPattern, subTasks, sourceDirectory, tasks);
if (input.Tasks[0] != "- [" || input.Tasks[1] != "](")
throw new Exception(JsonSerializer.Serialize(input, InputSourceGenerationContext.Default.Input));
return input;
}
private static string? MaybeWriteAndGetIndexFile(Input input, Record record, string? checkDirectory)
{
string? result;
if (string.IsNullOrEmpty(checkDirectory) || input.AfterEpochTotalMilliseconds is null || string.IsNullOrEmpty(input.DestinationDirectory) || !checkDirectory.Contains(input.DestinationDirectory))
result = null;
else
{
if (record.SubTasksLine is null)
continue;
if (record.CodeInsidersLine is not null)
logger.LogInformation("<{file}> has [{subTasks}]", Path.GetFileNameWithoutExtension(record.File), subTasks);
if (record.LineNumber.H1 is null)
result = null;
else
{
logger.LogWarning("<{file}> has [{subTasks}] but doesn't have [{codeInsiders}]!", Path.GetFileNameWithoutExtension(record.File), subTasks, codeInsiders);
continue;
}
if (record.StopLine is null)
continue;
checkDirectory = record.Lines[record.CodeInsidersLine.Value][codeInsiders.Length..^1];
if (!Directory.Exists(checkDirectory))
{
logger.LogError("<{checkDirectory}> doesn't exist", Path.GetFileName(checkDirectory));
continue;
}
indexFiles = Directory.GetFiles(checkDirectory, indexFile, SearchOption.AllDirectories).ToList();
if (indexFiles.Count != 1)
{
for (int i = indexFiles.Count - 1; i > -1; i--)
string segment = Path.GetFileName(checkDirectory);
DateTime utcEpochDateTime = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
long utcEpochTotalMilliseconds = (long)Math.Floor(DateTime.UtcNow.Subtract(utcEpochDateTime).TotalMilliseconds);
if (!long.TryParse(segment, out long check) || check < input.AfterEpochTotalMilliseconds || check > utcEpochTotalMilliseconds)
result = null;
else
{
if (!indexFiles[i].Contains(directoryFilter, StringComparison.CurrentCultureIgnoreCase))
indexFiles.RemoveAt(i);
}
if (indexFiles.Count != 1)
{
logger.LogError("<{checkDirectory}> doesn't have a [{indexFile}]", Path.GetFileName(checkDirectory), indexFile);
continue;
ReadOnlyCollection<H1AndParamCase> h1ParamCaseCollection = GetH1ParamCaseCollection(input, record.LineNumber.Lines);
if (h1ParamCaseCollection.Count == 0)
result = null;
else
result = WriteAndGetIndexFile(input.DestinationDirectory, record.LineNumber.Lines[record.LineNumber.H1.Value], check, h1ParamCaseCollection);
}
}
foundDone = null;
oldLines.Clear();
allSubTaskLines.Clear();
indexLines = File.ReadAllLines(indexFiles[0]);
checkDirectory = Path.GetDirectoryName(indexFiles[0]) ?? throw new Exception();
for (int i = 0; i < indexLines.Length; i++)
}
return result;
}
private static bool FileWrite(Record record, List<string> newLines, double percent)
{
bool result = false;
if (record.StopLine is not null && record.SubTasksLine is not null)
{
string contents;
string progressLine;
List<string> resultLines;
resultLines = record.LineNumber.Lines.ToList();
if (record.LineNumber.FrontMatterYamlEnd is not null)
{
if (indexLines[i] == done)
foundDone = true;
segments = indexLines[i].Split(tasks[1]);
doneValue = foundDone is not null && foundDone.Value;
if (segments.Length > 2 || !segments[0].StartsWith(tasks[0]))
progressLine = $"progress: {percent}";
if (record.LineNumber.Progress is not null)
resultLines[record.LineNumber.Progress.Value] = progressLine;
else
{
resultLines.Insert(record.LineNumber.FrontMatterYamlEnd.Value, progressLine);
contents = string.Join(Environment.NewLine, resultLines);
FileWriteAllText(record.FileInfo.FullName, contents);
result = true;
}
if (!result && record.LineNumber.Completed is null && percent > 99.9)
{
resultLines.Insert(record.LineNumber.FrontMatterYamlEnd.Value, $"completed: {DateTime.Now:yyyy-MM-dd}");
contents = string.Join(Environment.NewLine, resultLines);
FileWriteAllText(record.FileInfo.FullName, contents);
result = true;
}
if (!result && record.LineNumber.Completed is not null && percent < 99.9)
{
resultLines.RemoveAt(record.LineNumber.Completed.Value);
contents = string.Join(Environment.NewLine, resultLines);
FileWriteAllText(record.FileInfo.FullName, contents);
result = true;
}
}
if (!result)
{
for (int i = record.StopLine.Value - 1; i > record.SubTasksLine.Value + 1; i--)
resultLines.RemoveAt(i);
if (record.StopLine.Value == record.LineNumber.Lines.Count && resultLines[^1].Length == 0)
resultLines.RemoveAt(resultLines.Count - 1);
for (int i = 0; i < newLines.Count; i++)
resultLines.Insert(record.SubTasksLine.Value + 1 + i, newLines[i]);
resultLines.Insert(record.SubTasksLine.Value + 1, string.Empty);
contents = string.Join(Environment.NewLine, resultLines);
FileWriteAllText(record.FileInfo.FullName, contents);
}
}
return result;
}
private static FileInfo GetIndexFileInfo(Input input, Record record, string codeInsidersLine)
{
FileInfo result;
string? indexFile;
List<string> results;
string? checkDirectory = codeInsidersLine[input.CodeInsiders.Length..^1];
if (!Directory.Exists(checkDirectory))
{
if (!string.IsNullOrEmpty(input.DestinationDirectory) && checkDirectory.Contains(input.DestinationDirectory))
_ = Directory.CreateDirectory(checkDirectory);
}
if (!Directory.Exists(checkDirectory))
results = [];
else
{
results = Directory.GetFiles(checkDirectory, input.IndexFile, SearchOption.AllDirectories).ToList();
if (results.Count != 1)
{
for (int i = results.Count - 1; i > -1; i--)
{
if (!results[i].Contains(input.DirectoryFilter, StringComparison.CurrentCultureIgnoreCase))
results.RemoveAt(i);
}
}
if (results.Count == 0)
{
indexFile = MaybeWriteAndGetIndexFile(input, record, checkDirectory);
if (!string.IsNullOrEmpty(indexFile))
results.Add(indexFile);
}
}
result = results.Count == 0 ? new(Path.Combine(checkDirectory, input.IndexFile)) : new(results[0]);
return result;
}
internal static void UpdateSubTasksInMarkdownFiles(ILogger<Worker> logger, List<string> args)
{
bool reload;
int allCount;
int lineCheck;
double percent;
double doneCount;
FileInfo fileInfo;
List<Record> records;
LineNumber lineNumber;
List<string> newLines;
bool reloadAny = false;
string? checkDirectory;
string codeInsidersLine;
List<string> oldLines = [];
Input input = GetInput(args);
string fileNameWithoutExtension;
ReadOnlyCollection<SubTaskLine> subTaskLines;
for (int z = 0; z < 9; z++)
{
records = GetRecords(input);
foreach (Record record in from l in records orderby l.SubTasksLine is null, l.CodeInsidersLine is null select l)
{
if (record.SubTasksLine is null)
continue;
fallbackLine = foundDone is null || !foundDone.Value ? $"- [ ] {segments[0][tasks[0].Length..]}" : $"- [x] {segments[0][tasks[0].Length..]}";
fileInfo = new(Path.GetFullPath(Path.Combine(checkDirectory, segments[1][..^1])));
fileNameWithoutExtension = Path.GetFileNameWithoutExtension(record.FileInfo.FullName);
if (record.CodeInsidersLine is not null)
logger.LogInformation("<{file}> has [{subTasks}]", fileNameWithoutExtension, input.SubTasks);
else
{
logger.LogWarning("<{file}> has [{subTasks}] but doesn't have [{codeInsiders}]!", fileNameWithoutExtension, input.SubTasks, input.CodeInsiders);
continue;
}
if (record.StopLine is null)
continue;
codeInsidersLine = record.LineNumber.Lines[record.CodeInsidersLine.Value];
fileInfo = GetIndexFileInfo(input, record, codeInsidersLine);
if (!fileInfo.Exists)
{
allSubTaskLines.Add(new(fallbackLine, doneValue, Ticks: null, Line: null));
checkDirectory = codeInsidersLine[input.CodeInsiders.Length..^1];
logger.LogError("<{checkDirectory}> doesn't have a [{indexFile}]", Path.GetFileName(checkDirectory), input.IndexFile);
continue;
}
subTaskLines = GetSubTasks(subTasks, tasks, doneValue, fallbackLine, fileInfo);
if (subTaskLines.Count > 0)
oldLines.Clear();
checkDirectory = fileInfo.DirectoryName;
lineNumber = HelperMarkdown.GetLineNumbers(fileInfo);
subTaskLines = GetSubTaskLines(input, fileInfo, lineNumber);
if (subTaskLines.Count == 0)
continue;
lineCheck = 0;
for (int i = record.SubTasksLine.Value + 1; i < record.StopLine.Value - 1; i++)
oldLines.Add(record.LineNumber.Lines[i]);
if (subTaskLines.Any(l => l.Ticks is null))
newLines = (from l in subTaskLines select l.Text).ToList();
else
newLines = (from l in subTaskLines orderby l.Done descending, l.Ticks, l.Line select l.Text).ToList();
if (subTaskLines.Count == 0)
percent = 0;
else
{
subTaskLine = new($"", false, null, null);
allSubTaskLines.Add(subTaskLine);
for (int j = subTaskLines.Count - 1; j >= 0; j--)
allSubTaskLines.Add(subTaskLines[j]);
allCount = (from l in subTaskLines where l.Line is not null && l.Line.Value == 0 select 1).Count();
doneCount = (from l in subTaskLines where l.Line is not null && l.Line.Value == 0 && l.Done select 1).Count();
// done = allCount != doneCount ? ' ' : 'x';
percent = allCount == 0 ? 0 : Math.Round(doneCount / allCount, 3);
// newLines.Insert(0, $"- [{done}] Sub-tasks {doneCount} of {allCount} [{percent * 100}%]");
}
}
if (allSubTaskLines.Count == 0)
continue;
lineCheck = 0;
for (int i = record.SubTasksLine.Value + 1; i < record.StopLine.Value - 1; i++)
oldLines.Add(record.Lines[i]);
if (allSubTaskLines.Any(l => l.Ticks is null))
newLines = (from l in allSubTaskLines select l.Text).ToArray();
else
newLines = (from l in allSubTaskLines orderby l.Done descending, l.Ticks, l.Line select l.Text).ToArray();
if (newLines.Length == oldLines.Count)
{
for (int i = 0; i < newLines.Length; i++)
if (newLines.Count == oldLines.Count)
{
if (newLines[i] != record.Lines[record.SubTasksLine.Value + 1 + i])
for (int i = 0; i < newLines.Count; i++)
{
if (newLines[i] != record.LineNumber.Lines[record.SubTasksLine.Value + 1 + i])
continue;
lineCheck++;
}
if (lineCheck == newLines.Count)
continue;
lineCheck++;
}
if (lineCheck == newLines.Length)
if (string.IsNullOrEmpty(checkDirectory))
continue;
checkDirectory = Path.Combine(checkDirectory, DateTime.Now.Ticks.ToString());
_ = Directory.CreateDirectory(checkDirectory);
Thread.Sleep(500);
Directory.Delete(checkDirectory);
reload = FileWrite(record, newLines, percent);
if (!reloadAny && reload)
reloadAny = true;
}
checkDirectory = Path.Combine(checkDirectory, DateTime.Now.Ticks.ToString());
_ = Directory.CreateDirectory(checkDirectory);
Thread.Sleep(500);
Directory.Delete(checkDirectory);
lines = record.Lines.ToList();
for (int i = record.StopLine.Value - 1; i > record.SubTasksLine.Value + 1; i--)
lines.RemoveAt(i);
if (record.StopLine.Value == record.Lines.Length && lines[^1].Length == 0)
lines.RemoveAt(lines.Count - 1);
for (int i = 0; i < newLines.Length; i++)
lines.Insert(record.SubTasksLine.Value + 1 + i, newLines[i]);
lines.Insert(record.SubTasksLine.Value + 1, string.Empty);
File.WriteAllLines(record.File, lines);
if (!reloadAny)
break;
}
}

View File

@ -1,206 +0,0 @@
using File_Folder_Helper.Models;
using Microsoft.Extensions.Logging;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Text.Json;
namespace File_Folder_Helper.ADO2024.PI2;
internal static partial class Helper20240724
{
private record FileConnectorConfigurationSystem(string AlternateTargetFolder,
string FileAgeThreshold,
string[] SourceFileFilters,
string TargetFileLocation);
#pragma warning disable IDE0028, IDE0056, IDE0300, IDE0240, IDE0241
private static readonly HttpClient _HttpClient = new();
private static readonly string _StaticFileServer = "localhost:5054";
private static readonly FileConnectorConfigurationSystem _FileConnectorConfiguration = new(
"D:/Tmp/Phares/AlternateTargetFolder",
"000:20:00:01",
[".txt"],
"D:/Tmp/Phares/TargetFileLocation");
private static string[] GetValidDays(DateTime fileAgeThresholdDateTime)
{
DateTime dateTime = DateTime.Now;
return new string[] { dateTime.ToString("yyyy-MM-dd"), fileAgeThresholdDateTime.ToString("yyyy-MM-dd") }.Distinct().ToArray();
}
private static string[] GetValidWeeks(DateTime fileAgeThresholdDateTime)
{
DateTime dateTime = DateTime.Now;
Calendar calendar = new CultureInfo("en-US").Calendar;
string weekOfYear = $"{dateTime:yyyy}_Week_{calendar.GetWeekOfYear(dateTime, CalendarWeekRule.FirstDay, DayOfWeek.Sunday):00}";
string lastWeekOfYear = $"{fileAgeThresholdDateTime:yyyy}_Week_{calendar.GetWeekOfYear(fileAgeThresholdDateTime, CalendarWeekRule.FirstDay, DayOfWeek.Sunday):00}";
return new string[] { weekOfYear, lastWeekOfYear }.Distinct().ToArray();
}
private static ReadOnlyCollection<NginxFileSystem> GetDayNginxFileSystemCollection(DateTime fileAgeThresholdDateTime, string week, string day, string dayUrl, NginxFileSystem[] dayNginxFileSystemCollection)
{
List<NginxFileSystem> results = new();
DateTime dateTime;
string nginxFormat = "ddd, dd MMM yyyy HH:mm:ss zzz";
foreach (NginxFileSystem dayNginxFileSystem in dayNginxFileSystemCollection)
{
if (!DateTime.TryParseExact(dayNginxFileSystem.MTime.Replace("GMT", "+00:00"), nginxFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime))
continue;
if (dateTime < fileAgeThresholdDateTime)
continue;
results.Add(new(
Path.GetFullPath(Path.Combine(_FileConnectorConfiguration.TargetFileLocation, week, day, dayNginxFileSystem.Name)),
string.Concat(dayUrl, '/', dayNginxFileSystem.Name),
dateTime.ToString(),
dayNginxFileSystem.Size));
}
return results.AsReadOnly();
}
private static DateTime GetFileAgeThresholdDateTime(string fileAgeThreshold)
{
DateTime result = DateTime.Now;
string[] segments = fileAgeThreshold.Split(':');
for (int i = 0; i < segments.Length; i++)
{
result = i switch
{
0 => result.AddDays(double.Parse(segments[i]) * -1),
1 => result.AddHours(double.Parse(segments[i]) * -1),
2 => result.AddMinutes(double.Parse(segments[i]) * -1),
3 => result.AddSeconds(double.Parse(segments[i]) * -1),
_ => throw new Exception(),
};
}
return result;
}
private static ReadOnlyCollection<NginxFileSystem> GetDayNginxFileSystemCollection(DateTime fileAgeThresholdDateTime)
{
#nullable enable
List<NginxFileSystem> results = new();
string dayUrl;
string dayJson;
string weekJson;
string checkWeek;
Task<HttpResponseMessage> task;
NginxFileSystem[]? dayNginxFileSystemCollection;
NginxFileSystem[]? weekNginxFileSystemCollection;
string[] days = GetValidDays(fileAgeThresholdDateTime);
string[] weeks = GetValidWeeks(fileAgeThresholdDateTime);
foreach (string week in weeks)
{
checkWeek = string.Concat("http://", _StaticFileServer, '/', week);
task = _HttpClient.GetAsync(checkWeek);
task.Wait();
if (!task.Result.IsSuccessStatusCode)
continue;
weekJson = _HttpClient.GetStringAsync(checkWeek).Result;
weekNginxFileSystemCollection = JsonSerializer.Deserialize(weekJson, NginxFileSystemCollectionSourceGenerationContext.Default.NginxFileSystemArray);
if (weekNginxFileSystemCollection is null)
continue;
foreach (NginxFileSystem weekNginxFileSystem in weekNginxFileSystemCollection)
{
if (!(from l in days where weekNginxFileSystem.Name == l select false).Any())
continue;
dayUrl = string.Concat(checkWeek, '/', weekNginxFileSystem.Name);
dayJson = _HttpClient.GetStringAsync(dayUrl).Result;
dayNginxFileSystemCollection = JsonSerializer.Deserialize(dayJson, NginxFileSystemCollectionSourceGenerationContext.Default.NginxFileSystemArray);
if (dayNginxFileSystemCollection is null)
continue;
results.AddRange(GetDayNginxFileSystemCollection(fileAgeThresholdDateTime, week, weekNginxFileSystem.Name, dayUrl, dayNginxFileSystemCollection));
}
}
return results.AsReadOnly();
#nullable disable
}
private static ReadOnlyCollection<Tuple<DateTime, FileInfo, FileInfo, string>> GetPossible()
{
List<Tuple<DateTime, FileInfo, FileInfo, string>> results = new();
DateTime dateTime;
FileInfo targetFileInfo;
FileInfo alternateFileInfo;
DateTime fileAgeThresholdDateTime = GetFileAgeThresholdDateTime(_FileConnectorConfiguration.FileAgeThreshold);
ReadOnlyCollection<NginxFileSystem> dayNginxFileSystemCollection = GetDayNginxFileSystemCollection(fileAgeThresholdDateTime);
foreach (NginxFileSystem nginxFileSystem in dayNginxFileSystemCollection)
{
targetFileInfo = new FileInfo(nginxFileSystem.Name);
if (targetFileInfo.Directory is null)
continue;
if (!Directory.Exists(targetFileInfo.Directory.FullName))
_ = Directory.CreateDirectory(targetFileInfo.Directory.FullName);
if (!DateTime.TryParse(nginxFileSystem.MTime, out dateTime))
continue;
if (targetFileInfo.Exists && targetFileInfo.LastWriteTime == dateTime)
continue;
alternateFileInfo = new(Path.Combine(_FileConnectorConfiguration.AlternateTargetFolder, nginxFileSystem.Name));
results.Add(new(dateTime, targetFileInfo, alternateFileInfo, nginxFileSystem.Type));
}
return (from l in results orderby l.Item1 select l).ToList().AsReadOnly();
}
private static void Test()
{
#nullable enable
if (_HttpClient is null)
throw new Exception();
if (string.IsNullOrEmpty(_StaticFileServer))
throw new Exception();
if (string.IsNullOrEmpty(_StaticFileServer))
{
ReadOnlyCollection<Tuple<DateTime, FileInfo, FileInfo, string>> possibleDownload = GetPossible();
if (possibleDownload.Count > 0)
{
string targetFileName = possibleDownload[0].Item4;
FileInfo targetFileInfo = possibleDownload[0].Item2;
FileInfo alternateFileInfo = possibleDownload[0].Item3;
DateTime matchNginxFileSystemDateTime = possibleDownload[0].Item1;
// if (alternateFileInfo.Exists)
// File.Delete(alternateFileInfo.FullName);
if (targetFileInfo.Exists)
File.Delete(targetFileInfo.FullName);
string targetJson = _HttpClient.GetStringAsync(targetFileName).Result;
File.WriteAllText(targetFileInfo.FullName, targetJson);
targetFileInfo.LastWriteTime = matchNginxFileSystemDateTime;
// File.Copy(targetFileInfo.FullName, alternateFileInfo.FullName);
File.AppendAllText(alternateFileInfo.FullName, targetJson);
}
}
#nullable disable
}
internal static void CopyDirectories(ILogger<Worker> logger, List<string> args)
{
Test();
string[] files;
Process process;
string checkDirectory;
string filter = args[3];
string replaceWith = args[4];
string searchPattern = args[2];
string sourceDirectory = Path.GetFullPath(args[0]);
string[] foundDirectories = Directory.GetDirectories(sourceDirectory, searchPattern, SearchOption.AllDirectories);
logger.LogInformation($"Found {foundDirectories.Length} directories");
foreach (string foundDirectory in foundDirectories)
{
if (!foundDirectory.Contains(filter))
continue;
logger.LogDebug(foundDirectory);
checkDirectory = foundDirectory.Replace(filter, replaceWith);
if (Directory.Exists(checkDirectory))
{
files = Directory.GetFiles(checkDirectory, "*", SearchOption.AllDirectories);
if (files.Length > 0)
continue;
Directory.Delete(checkDirectory);
}
process = Process.Start("cmd.exe", $"/c xCopy \"{foundDirectory}\" \"{checkDirectory}\" /S /E /I /H /Y");
process.WaitForExit();
}
}
}

View File

@ -0,0 +1,383 @@
using DiscUtils.Iso9660;
using Microsoft.Extensions.Logging;
using Renci.SshNet;
using Renci.SshNet.Sftp;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO.Compression;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace File_Folder_Helper.ADO2024.PI4;
internal static partial class Helper20241217
{
private record Record(string Directory, Job? Job, string Path);
private record Job(string AlternatePath, string Directory, string Extension, File[] Files, int FilesCount, double FilesTotalLength, int Keep, Target[] Targets);
private record SecureShell(string Host, string Key, string Path, bool Required, string User);
private record ServerMessageBlock(string Path, bool Required);
private record Target(SecureShell? SecureShell, ServerMessageBlock? ServerMessageBlock);
private record File(long LastWriteTicks, long Length, string RelativePath);
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Job))]
private partial class JobSourceGenerationContext : JsonSerializerContext
{
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(File[]))]
private partial class FilesSourceGenerationContext : JsonSerializerContext
{
}
private static IEnumerable<Record> GetRecords(string directory, string searchPattern)
{
Job? job;
string json;
Record record;
string fileName;
string directoryName;
IEnumerable<string> files = Directory.EnumerateFiles(directory, searchPattern, new EnumerationOptions { IgnoreInaccessible = true, RecurseSubdirectories = true });
foreach (string file in files)
{
fileName = Path.GetFileName(file);
directoryName = Path.GetDirectoryName(file) ?? throw new Exception();
if (!fileName.StartsWith('.'))
{
System.IO.File.Move(file, Path.Combine(directoryName, $".{fileName}"));
continue;
}
json = System.IO.File.ReadAllText(file);
job = JsonSerializer.Deserialize(json, JobSourceGenerationContext.Default.Job);
record = new(directoryName, job, file);
yield return record;
}
}
private static ReadOnlyCollection<File> GetFiles(string directory, string searchPattern, string[] ignoreFileNames)
{
List<File> results = [];
File file;
string relativePath;
string[] files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories);
FileInfo[] fileInfoCollection = files.Select(l => new FileInfo(l)).ToArray();
foreach (FileInfo fileInfo in fileInfoCollection)
{
if (fileInfo.Name == searchPattern)
continue;
if (ignoreFileNames.Any(l => l == fileInfo.Name))
continue;
if (!string.IsNullOrEmpty(fileInfo.LinkTarget))
continue;
relativePath = Path.GetRelativePath(directory, fileInfo.FullName).Replace(';', '_');
if (relativePath.StartsWith(".."))
relativePath = relativePath[3..];
file = new(fileInfo.LastWriteTime.Ticks, fileInfo.Length, relativePath);
results.Add(file);
}
return results.AsReadOnly();
}
private static ReadOnlyCollection<File> GetFiles(string searchPattern, string[] ignoreFileNames, Record record) =>
GetFiles(record.Directory, searchPattern, ignoreFileNames);
private static string? GetJsonIfNotEqual(string searchPattern, string[] ignoreFileNames, Record record, Job job, ReadOnlyCollection<File> files)
{
string? result;
string? jsonNew;
string? jsonOld;
string fileName;
int ignoreCount = 0;
double filesTotalLengthNew = 0;
File[] filesArray = files.ToArray();
double filesTotalLengthOld = job.FilesTotalLength;
foreach (File file in files)
filesTotalLengthNew += file.Length;
Job jobNew = new(job.AlternatePath,
record.Directory,
job.Extension,
filesArray,
files.Count,
filesTotalLengthNew,
job.Keep,
job.Targets);
result = JsonSerializer.Serialize(jobNew, JobSourceGenerationContext.Default.Job);
if (filesTotalLengthNew != filesTotalLengthOld)
{
filesTotalLengthOld = 0;
foreach (File file in job.Files)
{
fileName = Path.GetFileName(file.RelativePath);
if (fileName == searchPattern || ignoreFileNames.Any(l => l == fileName))
{
ignoreCount += 1;
continue;
}
if (file.Length == 0)
{
ignoreCount += 1;
continue;
}
filesTotalLengthOld += file.Length;
}
}
if (filesTotalLengthNew != filesTotalLengthOld || files.Count != (job.Files.Length - ignoreCount))
{
jsonNew = null;
jsonOld = null;
}
else
{
jsonNew = JsonSerializer.Serialize((from l in filesArray orderby l.RelativePath.Length, l.RelativePath select l).ToArray(), FilesSourceGenerationContext.Default.FileArray);
jsonOld = JsonSerializer.Serialize((from l in job.Files orderby l.RelativePath.Length, l.RelativePath where l.RelativePath != searchPattern select l).ToArray(), FilesSourceGenerationContext.Default.FileArray);
}
if (!string.IsNullOrEmpty(jsonNew) && !string.IsNullOrEmpty(jsonOld) && jsonNew == jsonOld)
result = null;
return result;
}
private static void WriteISO(Record record, ReadOnlyCollection<File> files, string path, string directoryName)
{
CDBuilder builder = new() { UseJoliet = true, VolumeIdentifier = directoryName.Length < 25 ? directoryName : directoryName[..25] };
foreach (File file in files)
_ = builder.AddFile(file.RelativePath, Path.Combine(record.Directory, file.RelativePath));
builder.Build(path);
}
private static void WriteZIP(Record record, ReadOnlyCollection<File> files, string path)
{
using ZipArchive zip = ZipFile.Open(path, ZipArchiveMode.Create);
string directoryEntry;
List<string> directoryEntries = [];
foreach (File file in files)
{
directoryEntry = Path.GetDirectoryName(file.RelativePath) ?? throw new Exception();
if (!directoryEntries.Contains(directoryEntry))
continue;
directoryEntries.Add(directoryEntry);
_ = zip.CreateEntry(file.RelativePath);
}
foreach (File file in files)
_ = zip.CreateEntryFromFile(Path.Combine(record.Directory, file.RelativePath), file.RelativePath);
}
private static void WriteExtension(Record record, Job job, ReadOnlyCollection<File> files, string path)
{
string directoryName = Path.GetFileName(record.Directory);
if (job.Extension.Equals(".iso", StringComparison.OrdinalIgnoreCase))
WriteISO(record, files, path, directoryName);
else if (job.Extension.Equals(".zip", StringComparison.OrdinalIgnoreCase))
WriteZIP(record, files, path);
else
throw new NotImplementedException();
}
private static void PushTo(Job job, SecureShell secureShell, string path)
{
string remotePath = string.Concat(secureShell.Path, '/', Path.GetFileName(path));
using SftpClient client = new(secureShell.Host, secureShell.User, new PrivateKeyFile(secureShell.Key));
client.Connect();
if (job.Files.Length == 0)
{
string directoryName = Path.GetDirectoryName(secureShell.Path) ?? throw new Exception();
try
{ client.CreateDirectory(Path.GetDirectoryName(directoryName) ?? throw new Exception()); }
catch (Exception) { }
try
{ client.CreateDirectory(directoryName); }
catch (Exception) { }
try
{ client.CreateDirectory(secureShell.Path); }
catch (Exception) { }
}
using FileStream fileStream = System.IO.File.OpenRead(path);
client.UploadFile(fileStream, remotePath);
}
private static void PushTo(ServerMessageBlock serverMessageBlock, string path)
{
string remotePath = Path.Combine(serverMessageBlock.Path, Path.GetFileName(path));
System.IO.File.Copy(path, remotePath);
}
private static void PushTo(string directory, string path)
{
string remotePath = Path.Combine(directory, Path.GetFileName(path));
System.IO.File.Copy(path, remotePath);
}
private static ReadOnlyCollection<Exception> PushTo(Job job, string path)
{
List<Exception> results = [];
foreach (Target target in job.Targets)
{
if (target.SecureShell is not null)
{
try
{ PushTo(job, target.SecureShell, path); }
catch (Exception ex)
{
if (target.SecureShell.Required)
results.Add(ex);
}
}
else if (target.ServerMessageBlock is not null)
{
try
{ PushTo(target.ServerMessageBlock, path); }
catch (Exception ex)
{
if (target.ServerMessageBlock.Required)
results.Add(ex);
}
}
else
throw new NotImplementedException();
}
return results.AsReadOnly();
}
private static void DeleteOld(Job job, SecureShell secureShell, string path)
{
List<string> results = [];
using SftpClient client = new(secureShell.Host, secureShell.User, new PrivateKeyFile(secureShell.Key));
client.Connect();
foreach (ISftpFile file in client.ListDirectory(secureShell.Path))
{
if (file.Name == path)
continue;
if (!file.Name.EndsWith(job.Extension, StringComparison.OrdinalIgnoreCase))
continue;
results.Add(file.FullName);
}
for (int i = job.Keep - 1; i < results.Count; i++)
client.DeleteFile(results[i]);
}
private static void DeleteOld(Job job, ServerMessageBlock serverMessageBlock, string path)
{
List<string> results = [];
string[] files = Directory.GetFiles(serverMessageBlock.Path, $"*{job.Extension}", SearchOption.TopDirectoryOnly);
foreach (string file in files)
{
if (file == path)
continue;
results.Add(file);
}
for (int i = job.Keep - 1; i < results.Count; i++)
System.IO.File.Delete(results[i]);
}
private static ReadOnlyCollection<Exception> DeleteOld(Job job, string path)
{
List<Exception> results = [];
foreach (Target target in job.Targets)
{
if (target.SecureShell is not null)
{
try
{ DeleteOld(job, target.SecureShell, path); }
catch (Exception ex)
{
if (target.SecureShell.Required)
results.Add(ex);
}
}
else if (target.ServerMessageBlock is not null)
{
try
{ DeleteOld(job, target.ServerMessageBlock, path); }
catch (Exception ex)
{
if (target.ServerMessageBlock.Required)
results.Add(ex);
}
}
else
throw new NotImplementedException();
}
return results.AsReadOnly();
}
private static void Verify(string searchPattern, string[] ignoreFileNames)
{
List<Target> targets = [
new(new SecureShell("free.file.sync.root", "C:/Users/phares/.ssh/id_ed25519", "\\home", true, "root"), null),
new(null, new ServerMessageBlock("\\\\mesfs.infineon.com\\EC_APC\\DEV", true))
];
string directory = Path.Combine(Environment.CurrentDirectory, ".vscode", "helper");
if (!Directory.Exists(directory))
_ = Directory.CreateDirectory(directory);
ReadOnlyCollection<File> files = GetFiles(directory, searchPattern, ignoreFileNames);
double filesTotalLength = 0;
foreach (File file in files)
filesTotalLength += file.Length;
Job job = new(
"C:/Users/phares",
directory,
"*.iso",
files.ToArray(),
files.Count,
filesTotalLength,
3,
targets.ToArray());
string json = JsonSerializer.Serialize(job, JobSourceGenerationContext.Default.Job);
System.IO.File.WriteAllText(Path.Combine(directory, "verify.json"), json);
}
internal static void Backup(ILogger<Worker> logger, List<string> args)
{
string path;
string? json;
string directoryName;
ReadOnlyCollection<File> files;
string searchPattern = args[2];
ReadOnlyCollection<Exception> exceptions;
string[] ignoreFileNames = args[3].Split('|');
string sourceDirectory = Path.GetFullPath(args[0]);
logger.LogInformation("Searching <{sourceDirectory}> with search pattern {searchPattern}", args[0], searchPattern);
if (Debugger.IsAttached)
Verify(searchPattern, ignoreFileNames);
IEnumerable<Record> records = GetRecords(sourceDirectory, searchPattern);
foreach (Record record in records)
{
if (record.Job is null || record.Job.Targets.Length == 0 || string.IsNullOrEmpty(record.Job.Extension))
continue;
logger.LogInformation("Searching <{directory}>", record.Directory);
files = GetFiles(searchPattern, ignoreFileNames, record);
json = GetJsonIfNotEqual(searchPattern, ignoreFileNames, record, record.Job, files);
if (string.IsNullOrEmpty(json))
continue;
directoryName = Path.GetFileName(record.Directory);
path = Path.Combine(record.Directory, $"{directoryName}-{DateTime.Now:yyyy-MM-dd-HH-mm-ss-fff}{record.Job.Extension}");
logger.LogInformation("Writing <{directory}> extension", record.Directory);
WriteExtension(record, record.Job, files, path);
logger.LogInformation("Pushing <{directory}> extension", record.Directory);
exceptions = PushTo(record.Job, path);
if (exceptions.Count != 0)
{
foreach (Exception exception in exceptions)
logger.LogError(exception, exception.Message);
PushTo(record.Job.AlternatePath, path);
}
System.IO.File.WriteAllText(record.Path, json);
System.IO.File.Delete(path);
logger.LogInformation("Deleting old <{directory}> extension", record.Directory);
exceptions = DeleteOld(record.Job, path);
if (exceptions.Count != 0)
{
foreach (Exception exception in exceptions)
logger.LogError(exception, exception.Message);
}
}
if (Debugger.IsAttached && records.Count() == 0)
{
files = GetFiles(sourceDirectory, searchPattern, ignoreFileNames);
json = JsonSerializer.Serialize(files.ToArray(), FilesSourceGenerationContext.Default.FileArray);
System.IO.File.WriteAllText(Path.Combine(Environment.CurrentDirectory, ".vscode", "helper", ".json"), json);
}
}
}

View File

@ -0,0 +1,195 @@
using File_Folder_Helper.Models;
using Microsoft.Extensions.Logging;
#if ShellProgressBar
using ShellProgressBar;
#endif
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text.Json;
namespace File_Folder_Helper.ADO2024.PI4;
internal static partial class Helper20241224
{
private static readonly HttpClient _HttpClient = new();
private record Record(Uri URI, string Path, DateTime LastModified);
private static ReadOnlyCollection<NginxFileSystem>? GetRecursiveCollection(string host, string page)
{
List<NginxFileSystem>? results;
Uri uri = new($"https://{host}/{page}");
string format = NginxFileSystem.GetFormat();
TimeZoneInfo timeZoneInfo = TimeZoneInfo.Local;
Task<HttpResponseMessage> taskHttpResponseMessage = _HttpClient.GetAsync(uri);
taskHttpResponseMessage.Wait();
if (!taskHttpResponseMessage.Result.IsSuccessStatusCode)
results = null;
else
{
Task<string> taskString = taskHttpResponseMessage.Result.Content.ReadAsStringAsync();
taskString.Wait();
NginxFileSystem[]? nginxFileSystems = JsonSerializer.Deserialize(taskString.Result, NginxFileSystemCollectionSourceGenerationContext.Default.NginxFileSystemArray);
if (nginxFileSystems is null)
results = null;
else
{
results = [];
NginxFileSystem nginxFileSystem;
ReadOnlyCollection<NginxFileSystem>? directory;
for (int i = 0; i < nginxFileSystems.Length; i++)
{
nginxFileSystem = NginxFileSystem.Get(format, timeZoneInfo, uri, nginxFileSystems[i]);
if (nginxFileSystem.Type == "file")
results.Add(nginxFileSystem);
else
{
directory = GetRecursiveCollection(host, $"{page}/{nginxFileSystem.Name}");
if (directory is null)
continue;
results.AddRange(directory);
}
}
}
}
return results?.AsReadOnly();
}
private static ReadOnlyCollection<NginxFileSystem>? GetCollection(string format, TimeZoneInfo timeZoneInfo, Uri uri)
{
List<NginxFileSystem>? results;
Task<HttpResponseMessage> taskHttpResponseMessage = _HttpClient.GetAsync(uri);
taskHttpResponseMessage.Wait();
if (!taskHttpResponseMessage.Result.IsSuccessStatusCode)
results = null;
else
{
Task<string> taskString = taskHttpResponseMessage.Result.Content.ReadAsStringAsync();
taskString.Wait();
NginxFileSystem[]? nginxFileSystems = JsonSerializer.Deserialize(taskString.Result, NginxFileSystemCollectionSourceGenerationContext.Default.NginxFileSystemArray);
if (nginxFileSystems is null)
results = null;
else
{
results = [];
NginxFileSystem nginxFileSystem;
for (int i = 0; i < nginxFileSystems.Length; i++)
{
nginxFileSystem = NginxFileSystem.Get(format, timeZoneInfo, uri, nginxFileSystems[i]);
results.Add(nginxFileSystem);
}
}
}
return results?.AsReadOnly();
}
private static Record? CompareFile(string host, ReadOnlyCollection<string> directoryNames, string compareDirectory, NginxFileSystem nginxFileSystem)
{
Record? result;
if (nginxFileSystem.LastModified is null || nginxFileSystem.Length is null)
result = null;
else
{
Uri uri = new($"https://{host}/{string.Join('/', directoryNames)}/{nginxFileSystem.Name}");
FileInfo fileInfo = new($"{compareDirectory}\\{string.Join('\\', directoryNames)}\\{nginxFileSystem.Name}");
if (!fileInfo.Exists || fileInfo.Length != nginxFileSystem.Length.Value)
result = new(uri, fileInfo.FullName, nginxFileSystem.LastModified.Value);
else
{
double totalSeconds = new TimeSpan(fileInfo.LastWriteTime.Ticks - nginxFileSystem.LastModified.Value.Ticks).TotalSeconds;
if (totalSeconds is < 2 and > -2)
result = null;
else
result = new(uri, fileInfo.FullName, nginxFileSystem.LastModified.Value);
}
}
return result;
}
private static ReadOnlyCollection<Record> CompareDirectory(string format, TimeZoneInfo timeZoneInfo, string host, ReadOnlyCollection<string> directoryNames, string compareDirectory, NginxFileSystem nginxFileSystem)
{
ReadOnlyCollection<Record> results;
List<string> collection = directoryNames.ToList();
collection.Add(nginxFileSystem.Name);
results = GetRecord(format, timeZoneInfo, host, collection.AsReadOnly(), compareDirectory);
return results;
}
private static ReadOnlyCollection<Record> GetRecord(string format, TimeZoneInfo timeZoneInfo, string host, ReadOnlyCollection<string> directoryNames, string compareDirectory)
{
List<Record> results = [];
Uri uri = new($"https://{host}/{string.Join('/', directoryNames)}");
ReadOnlyCollection<NginxFileSystem>? nginxFileSystems = GetCollection(format, timeZoneInfo, uri);
if (nginxFileSystems is not null)
{
NginxFileSystem nginxFileSystem;
ReadOnlyCollection<Record> records;
string checkDirectory = $"{compareDirectory}\\{string.Join('\\', directoryNames)}";
if (!Directory.Exists(checkDirectory))
_ = Directory.CreateDirectory(checkDirectory);
for (int i = 0; i < nginxFileSystems.Count; i++)
{
nginxFileSystem = NginxFileSystem.Get(format, timeZoneInfo, uri, nginxFileSystems[i]);
if (nginxFileSystem.Type == "file")
{
Record? record = CompareFile(host, directoryNames, compareDirectory, nginxFileSystem);
if (record is not null)
results.Add(record);
}
else
{
records = CompareDirectory(format, timeZoneInfo, host, directoryNames, compareDirectory, nginxFileSystem);
foreach (Record record in records)
results.Add(record);
}
}
}
return results.AsReadOnly();
}
private static void Download(Record record)
{
Task<HttpResponseMessage> taskHttpResponseMessage = _HttpClient.GetAsync(record.URI);
taskHttpResponseMessage.Wait();
if (taskHttpResponseMessage.Result.IsSuccessStatusCode)
{
Task<string> taskString = taskHttpResponseMessage.Result.Content.ReadAsStringAsync();
taskString.Wait();
File.WriteAllText(record.Path, taskString.Result);
File.SetLastWriteTime(record.Path, record.LastModified);
}
}
internal static void Compare(ILogger<Worker> logger, List<string> args)
{
string host = args[2];
string rootDirectoryName = args[3];
string format = NginxFileSystem.GetFormat();
TimeZoneInfo timeZoneInfo = TimeZoneInfo.Local;
string compareDirectory = Path.GetFullPath(args[0]);
logger.LogInformation("Comparing files on {host}", host);
ReadOnlyCollection<Record> records = GetRecord(format, timeZoneInfo, host, new([rootDirectoryName]), compareDirectory);
#if ShellProgressBar
ProgressBar progressBar = new(records.Count, "Downloading", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true });
#endif
foreach (Record record in records)
{
#if ShellProgressBar
progressBar.Tick();
#endif
Download(record);
}
#if ShellProgressBar
progressBar.Dispose();
#endif
if (Debugger.IsAttached)
{
ReadOnlyCollection<NginxFileSystem>? recursiveCollection = GetRecursiveCollection(host, rootDirectoryName);
string? json = recursiveCollection is null ? null : JsonSerializer.Serialize(recursiveCollection.ToArray(), NginxFileSystemCollectionSourceGenerationContext.Default.NginxFileSystemArray);
if (!string.IsNullOrEmpty(json))
File.WriteAllText(Path.Combine(Environment.CurrentDirectory, ".vscode", "helper", ".json"), json);
}
}
}

View File

@ -81,8 +81,6 @@ internal static class HelperDay
ADO2024.PI2.Helper20240711.GitRemoteRemove(logger, args);
else if (args[1] == "Day-Helper-2024-07-18")
ADO2024.PI2.Helper20240718.JsonToMarkdown(logger, args);
else if (args[1] == "Day-Helper-2024-07-24")
ADO2024.PI2.Helper20240724.CopyDirectories(logger, args);
else if (args[1] == "Day-Helper-2024-07-28")
ADO2024.PI2.Helper20240728.DownloadSslCertificates(logger, args);
else if (args[1] == "Day-Helper-2024-08-05")
@ -123,6 +121,10 @@ internal static class HelperDay
ADO2024.PI4.Helper20241204.ConvertToUTF8(logger, args);
else if (args[1] == "Day-Helper-2024-12-12")
ADO2024.PI4.Helper20241212.Rename(logger, args);
else if (args[1] == "Day-Helper-2024-12-17")
ADO2024.PI4.Helper20241217.Backup(logger, args);
else if (args[1] == "Day-Helper-2024-12-24")
ADO2024.PI4.Helper20241224.Compare(logger, args);
else
throw new Exception(appSettings.Company);
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
@ -18,9 +18,10 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
<PackageReference Include="runtime.win-x64.Microsoft.DotNet.ILCompiler" Version="8.0.11" />
<PackageReference Include="SSH.NET" Version="2024.2.0" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="TextCopy" Version="6.2.1" />
<PackageReference Include="WindowsShortcutFactory" Version="1.2.0" />
<PackageReference Include="YamlDotNet" Version="16.2.0" />
<PackageReference Include="YamlDotNet" Version="16.2.1" />
</ItemGroup>
</Project>

View File

@ -133,31 +133,30 @@ internal static partial class HelperKanbanMetadata
List<string> results = [kanbanIndexH1, string.Empty];
string h1;
TimeSpan timeSpan;
List<string> lines;
LineNumber lineNumber;
Record[] sorted = (from l in records orderby l.GroupCount, l.FileInfo.LastWriteTime descending select l).ToArray();
foreach (Record record in sorted)
{
if (record.ItemLineNumber == 0)
throw new NotSupportedException();
(lines, lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(record.FileInfo);
if (lines.Count == 0)
lineNumber = HelperMarkdown.GetLineNumbers(record.FileInfo);
if (lineNumber.Lines.Count == 0)
continue;
timeSpan = new(record.FileInfo.LastWriteTime.Ticks - record.FileInfo.CreationTime.Ticks);
h1 = lineNumber.H1 is null ? Path.GetFileNameWithoutExtension(record.FileInfo.Name) : lines[lineNumber.H1.Value];
h1 = lineNumber.H1 is null ? Path.GetFileNameWithoutExtension(record.FileInfo.Name) : lineNumber.Lines[lineNumber.H1.Value];
results.Add($"#{h1}");
results.Add(string.Empty);
results.Add("```yaml");
results.Add($"CreationTime: {record.FileInfo.CreationTime:yyyy-MM-dd}");
results.Add($"LastWriteTime: {record.FileInfo.LastWriteTime:yyyy-MM-dd}");
results.Add($"TotalDays: {Math.Round(timeSpan.TotalDays, 2)}");
if (lineNumber.FrontMatterYamlEnd is not null && lines.Count >= lineNumber.FrontMatterYamlEnd.Value)
if (lineNumber.FrontMatterYamlEnd is not null && lineNumber.Lines.Count >= lineNumber.FrontMatterYamlEnd.Value)
{
for (int i = 0; i < lineNumber.FrontMatterYamlEnd; i++)
{
if (lines[i] == "---")
if (lineNumber.Lines[i] == "---")
continue;
results.Add(lines[i]);
results.Add(lineNumber.Lines[i]);
}
}
results.Add($"status: {record.GroupCount}-{record.Group}");
@ -175,7 +174,7 @@ internal static partial class HelperKanbanMetadata
File.WriteAllText(file, string.Join(Environment.NewLine, results));
}
internal static void SetMetadata(string sourceDirectory, ReadOnlyCollection<string> kanbanIndexFileLines, LineNumber kanbanIndexFileLineNumber, ReadOnlyCollection<string> gitOthersModifiedAndDeletedExcludingStandardFiles)
internal static void SetMetadata(string sourceDirectory, LineNumber kanbanIndexFileLineNumber, ReadOnlyCollection<string> gitOthersModifiedAndDeletedExcludingStandardFiles)
{
bool? match;
bool gitCheck;
@ -184,23 +183,24 @@ internal static partial class HelperKanbanMetadata
List<string> lines;
LineNumber lineNumber;
string? directory = Path.GetDirectoryName(sourceDirectory);
List<Record> records = GetCollectionFromIndex(sourceDirectory, kanbanIndexFileLines);
List<Record> records = GetCollectionFromIndex(sourceDirectory, kanbanIndexFileLineNumber.Lines);
if (directory is not null && kanbanIndexFileLineNumber.H1 is not null)
{
string checkDirectory = Path.Combine(directory, ".vscode", "helper");
if (Directory.Exists(checkDirectory))
{
WriteKanbanBoardFile(checkDirectory, records, kanbanIndexFileLines[kanbanIndexFileLineNumber.H1.Value]);
WriteKanbanBoardYmlView(checkDirectory, records, kanbanIndexFileLines[kanbanIndexFileLineNumber.H1.Value]);
WriteKanbanBoardFile(checkDirectory, records, kanbanIndexFileLineNumber.Lines[kanbanIndexFileLineNumber.H1.Value]);
WriteKanbanBoardYmlView(checkDirectory, records, kanbanIndexFileLineNumber.Lines[kanbanIndexFileLineNumber.H1.Value]);
}
}
foreach (Record record in records)
{
if (record.ItemLineNumber == 0)
throw new NotSupportedException();
(lines, lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(record.FileInfo);
if (lines.Count == 0)
lineNumber = HelperMarkdown.GetLineNumbers(record.FileInfo);
if (lineNumber.Lines.Count == 0)
continue;
lines = lineNumber.Lines.ToList();
statusLine = $"status: {record.GroupCount}-{record.Group}";
paramCase = lineNumber.H1 is null ? null : GetParamCase(lines[lineNumber.H1.Value]);
match = lineNumber.H1 is null || paramCase is null ? null : Path.GetFileNameWithoutExtension(record.FileInfo.Name) == paramCase;
@ -235,8 +235,8 @@ internal static partial class HelperKanbanMetadata
else
{
FileInfo fileInfo = new(indexFile);
(List<string> lines, LineNumber lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(fileInfo);
SetMetadata(fullPath, new(lines), lineNumber, gitOthersModifiedAndDeletedExcludingStandardFiles: new([]));
LineNumber lineNumber = HelperMarkdown.GetLineNumbers(fileInfo);
SetMetadata(fullPath, lineNumber, gitOthersModifiedAndDeletedExcludingStandardFiles: new([]));
}
}

View File

@ -193,7 +193,7 @@ internal static partial class HelperMarkdown
return result;
}
private static ReadOnlyCollection<string> GetFromMatterYamlLines(List<string> lines, LineNumber lineNumber)
private static ReadOnlyCollection<string> GetFromMatterYamlLines(ReadOnlyCollection<string> lines, LineNumber lineNumber)
{
List<string> results = [];
if (lineNumber.FrontMatterYamlEnd is not null && lines.Count >= lineNumber.FrontMatterYamlEnd.Value)
@ -307,7 +307,7 @@ internal static partial class HelperMarkdown
return new(results);
}
internal static (List<string>, LineNumber) GetStatusAndFrontMatterYamlEndLineNumbers(FileInfo fileInfo)
internal static LineNumber GetLineNumbers(FileInfo fileInfo)
{
string line;
int? h1LineNumber = null;
@ -315,6 +315,8 @@ internal static partial class HelperMarkdown
int? statusLineNumber = null;
int? createdLineNumber = null;
int? updatedLineNumber = null;
int? progressLineNumber = null;
int? completedLineNumber = null;
int? frontMatterYamlEndLineNumber = null;
Encoding? encoding = GetEncoding(fileInfo.FullName) ?? Encoding.Default;
string[] lines = File.ReadAllLines(fileInfo.FullName, encoding);
@ -350,6 +352,16 @@ internal static partial class HelperMarkdown
updatedLineNumber = i;
continue;
}
if (line.Length > 10 && line[..10] == "progress: ")
{
progressLineNumber = i;
continue;
}
if (line.Length > 11 && line[..11] == "completed: ")
{
completedLineNumber = i;
continue;
}
if (h1LineNumber is null && line.Length > 2 && line[0] == '#' && line[1] == ' ')
{
h1LineNumber = i;
@ -357,12 +369,15 @@ internal static partial class HelperMarkdown
}
}
LineNumber lineNumber = new(createdLineNumber,
completedLineNumber,
h1LineNumber,
frontMatterYamlEndLineNumber,
lines.AsReadOnly(),
progressLineNumber,
statusLineNumber,
typeLineNumber,
updatedLineNumber);
return (lines.ToList(), lineNumber);
return lineNumber;
}
private static Dictionary<string, object> GetFromMatterYaml(ReadOnlyCollection<string> frontMatterYamlLines)
@ -387,7 +402,7 @@ internal static partial class HelperMarkdown
return results;
}
private static ReadOnlyDictionary<string, object> GetFromMatterYaml(List<string> lines, LineNumber lineNumber)
private static ReadOnlyDictionary<string, object> GetFromMatterYaml(ReadOnlyCollection<string> lines, LineNumber lineNumber)
{
Dictionary<string, object> results = [];
#pragma warning disable IL3050
@ -660,12 +675,12 @@ internal static partial class HelperMarkdown
string key;
string type;
bool isKanbanIndex;
List<string> lines;
bool isWithinSource;
bool isKanbanMarkdown;
LineNumber lineNumber;
MarkdownFile markdownFile;
string fileNameWithoutExtension;
ReadOnlyCollection<string> lines;
ReadOnlyDictionary<string, object> frontMatterYaml;
bool isGitOthersModifiedAndDeletedExcludingStandard;
ReadOnlyCollection<FileInfo> files = GetFiles(appSettings, input);
@ -678,7 +693,8 @@ internal static partial class HelperMarkdown
isGitOthersModifiedAndDeletedExcludingStandard = gitOthersModifiedAndDeletedExcludingStandardFiles.Contains(fileInfo.FullName);
if (!isWithinSource && results.ContainsKey(key))
continue;
(lines, lineNumber) = GetStatusAndFrontMatterYamlEndLineNumbers(fileInfo);
lineNumber = GetLineNumbers(fileInfo);
lines = lineNumber.Lines;
fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileInfo.FullName);
h1 = fileNameWithoutExtension.ToLower().Replace("%20", "-").Replace(' ', '-');
frontMatterYaml = GetFromMatterYaml(lines, lineNumber);
@ -690,7 +706,7 @@ internal static partial class HelperMarkdown
continue;
type = appSettings.DefaultNoteType;
File.WriteAllLines(fileInfo.FullName, ["---", $"type: {type}\"", "---", string.Empty, $"# {h1}"]);
lines = File.ReadAllLines(fileInfo.FullName).ToList();
lines = File.ReadAllLines(fileInfo.FullName).AsReadOnly();
}
isKanbanMarkdown = fileInfo.Name.EndsWith(".knb.md");
isKanbanIndex = fileNameWithoutExtension == "index" && type.StartsWith("kanb", StringComparison.OrdinalIgnoreCase);
@ -1042,7 +1058,7 @@ internal static partial class HelperMarkdown
}
}
private static (string type, string h1) GetTypeAndH1(AppSettings appSettings, string h1, List<string> lines, LineNumber lineNumber)
private static (string type, string h1) GetTypeAndH1(AppSettings appSettings, string h1, ReadOnlyCollection<string> lines, LineNumber lineNumber)
{
string type = lineNumber.Type is null ? appSettings.DefaultNoteType : lines[lineNumber.Type.Value][5..].Trim().Trim('"');
string h1FromFile = lineNumber.H1 is null ? h1 : lines[lineNumber.H1.Value][2..];
@ -1080,7 +1096,7 @@ internal static partial class HelperMarkdown
createdLine = $"created: {creationDateTime.ToUniversalTime():yyyy-MM-ddTHH:mm:ss.fffZ}";
updatedLine = $"updated: {markdownFile.LastWriteDateTime.ToUniversalTime():yyyy-MM-ddTHH:mm:ss.fffZ}";
if (markdownFile.IsKanbanIndex)
HelperKanbanMetadata.SetMetadata(markdownFile.Directory, new(lines), markdownFile.LineNumber, gitOthersModifiedAndDeletedExcludingStandardFiles);
HelperKanbanMetadata.SetMetadata(markdownFile.Directory, markdownFile.LineNumber, gitOthersModifiedAndDeletedExcludingStandardFiles);
if (markdownFile.LineNumber.FrontMatterYamlEnd is null)
{
if (markdownFile.LineNumber.H1 is not null)

View File

@ -1,10 +1,14 @@
using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Models;
internal record LineNumber(int? Created,
int? Completed,
int? H1,
int? FrontMatterYamlEnd,
ReadOnlyCollection<string> Lines,
int? Progress,
int? Status,
int? Type,
int? Updated);

View File

@ -1,12 +1,15 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Models;
internal record NginxFileSystem(string Name,
string Type,
string MTime,
float Size)
internal record NginxFileSystem([property: JsonPropertyName("name")] string Name,
DateTime? LastModified,
[property: JsonPropertyName("mtime")] string MTime,
Uri? URI,
[property: JsonPropertyName("type")] string Type,
[property: JsonPropertyName("size")] float? Length)
{
public override string ToString()
@ -15,6 +18,25 @@ internal record NginxFileSystem(string Name,
return result;
}
public static NginxFileSystem Get(string format, TimeZoneInfo timeZoneInfo, string name, string mTime, Uri uri, string type, float? size)
{
NginxFileSystem result;
DateTime dateTime;
DateTime? nullableDateTime;
if (mTime.Length != format.Length + 4 || !DateTime.TryParseExact(mTime[..format.Length], format, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime))
nullableDateTime = null;
else
nullableDateTime = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(dateTime, mTime[(format.Length + 1)..], timeZoneInfo.Id);
result = new(name, nullableDateTime, mTime, uri, type, size);
return result;
}
public static string GetFormat() =>
"ddd, dd MMM yyyy HH:mm:ss";
public static NginxFileSystem Get(string format, TimeZoneInfo timeZoneInfo, Uri uri, NginxFileSystem nginxFileSystem) =>
Get(format, timeZoneInfo, nginxFileSystem.Name, nginxFileSystem.MTime, uri, nginxFileSystem.Type, nginxFileSystem.Length);
}
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]

View File

@ -14,6 +14,8 @@
// console.log("ticks: " + ticks);
// var dateText = ticks + " - " + date.toString();
// console.log("dateText: " + dateText);
// DateTime utcMeDateTime = new(1980, 1, 17, 0, 0, 0, DateTimeKind.Utc);
// long meTotalSeconds = (long)Math.Floor(fileInfo.LastWriteTime.ToUniversalTime().Subtract(utcMeDateTime).TotalSeconds);
let date = new Date();
let timezoneOffset = date.getTimezoneOffset();
let seconds = date.getTime().valueOf() + timezoneOffset;