Update Subtasks In Markdown Files
Better ISO support Only reviewing Files when comparing Extracted sections from UpdateSubTasksInMarkdownFiles
This commit is contained in:
parent
2361796bbf
commit
fb9289a572
33
.vscode/launch.json
vendored
33
.vscode/launch.json
vendored
@ -13,17 +13,42 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"s",
|
"s",
|
||||||
"X",
|
"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",
|
"Day-Helper-2024-06-23",
|
||||||
"*.md",
|
"*.md",
|
||||||
"## Sub-tasks",
|
"##_Sub-tasks",
|
||||||
"code-insiders",
|
"code-insiders",
|
||||||
"index.md",
|
"index.md",
|
||||||
"- [,](",
|
"-_[,](",
|
||||||
"## Done",
|
"##_Done",
|
||||||
".kan",
|
".kan",
|
||||||
|
"D:/5-Other-Small/Kanban/Year-Season",
|
||||||
|
"316940400000",
|
||||||
"s",
|
"s",
|
||||||
"X",
|
"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",
|
"L:/Git/Linux-Ubuntu-Server/etc/nginx/include",
|
||||||
"Day-Helper-2024-09-16",
|
"Day-Helper-2024-09-16",
|
||||||
"*.conf",
|
"*.conf",
|
||||||
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -27,6 +27,7 @@
|
|||||||
"Kanban",
|
"Kanban",
|
||||||
"kanbn",
|
"kanbn",
|
||||||
"Kofax",
|
"Kofax",
|
||||||
|
"mesfs",
|
||||||
"NpgSql",
|
"NpgSql",
|
||||||
"NSFX",
|
"NSFX",
|
||||||
"OBJE",
|
"OBJE",
|
||||||
@ -34,6 +35,7 @@
|
|||||||
"Permyriad",
|
"Permyriad",
|
||||||
"pged",
|
"pged",
|
||||||
"Phares",
|
"Phares",
|
||||||
|
"Renci",
|
||||||
"Reparse",
|
"Reparse",
|
||||||
"Rijndael",
|
"Rijndael",
|
||||||
"Serilog",
|
"Serilog",
|
||||||
|
@ -1,31 +1,96 @@
|
|||||||
using File_Folder_Helper.Helpers;
|
using File_Folder_Helper.Helpers;
|
||||||
|
using File_Folder_Helper.Models;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Collections.ObjectModel;
|
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;
|
namespace File_Folder_Helper.ADO2024.PI2;
|
||||||
|
|
||||||
internal static partial class Helper20240623
|
internal static partial class Helper20240623
|
||||||
{
|
{
|
||||||
|
|
||||||
private record SubTaskLine(string Text, bool Done, long? Ticks, int? Line);
|
[GeneratedRegex("([A-Z]+(.))")]
|
||||||
private record Record(int? CodeInsidersLine, string File, string[] Lines, int? StopLine, int? SubTasksLine);
|
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 = [];
|
List<Record> results = [];
|
||||||
int? stopLine;
|
Record record;
|
||||||
string[] lines;
|
FileInfo fileInfo;
|
||||||
int? subTasksLine;
|
string sourceDirectory = input.SourceDirectory;
|
||||||
int? codeInsidersLine;
|
ReadOnlyCollection<string> directoryNames = HelperDirectory.GetDirectoryNames(input.SourceDirectory);
|
||||||
ReadOnlyCollection<string> directoryNames = HelperDirectory.GetDirectoryNames(sourceDirectory);
|
if (!directoryNames.Any(l => l.StartsWith(input.DirectoryFilter, StringComparison.CurrentCultureIgnoreCase)))
|
||||||
if (!directoryNames.Any(l => l.StartsWith(directoryFilter, StringComparison.CurrentCultureIgnoreCase)))
|
|
||||||
{
|
{
|
||||||
string directoryName;
|
string directoryName;
|
||||||
string[] checkDirectories = Directory.GetDirectories(sourceDirectory, "*", SearchOption.TopDirectoryOnly);
|
string[] checkDirectories = Directory.GetDirectories(input.SourceDirectory, "*", SearchOption.TopDirectoryOnly);
|
||||||
foreach (string checkDirectory in checkDirectories)
|
foreach (string checkDirectory in checkDirectories)
|
||||||
{
|
{
|
||||||
directoryName = Path.GetFileName(checkDirectory);
|
directoryName = Path.GetFileName(checkDirectory);
|
||||||
if (directoryName.StartsWith(directoryFilter, StringComparison.CurrentCultureIgnoreCase))
|
if (directoryName.StartsWith(input.DirectoryFilter, StringComparison.CurrentCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
sourceDirectory = checkDirectory;
|
sourceDirectory = checkDirectory;
|
||||||
break;
|
break;
|
||||||
@ -33,204 +98,506 @@ internal static partial class Helper20240623
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
string[] subDirectories = Directory.GetDirectories(sourceDirectory, "*", SearchOption.TopDirectoryOnly);
|
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)
|
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)
|
foreach (string file in files)
|
||||||
{
|
{
|
||||||
stopLine = null;
|
fileInfo = new(file);
|
||||||
subTasksLine = null;
|
record = GetRecord(input, fileInfo);
|
||||||
codeInsidersLine = null;
|
results.Add(record);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
return results;
|
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 = [];
|
List<SubTaskLine> results = [];
|
||||||
|
char done;
|
||||||
string line;
|
string line;
|
||||||
|
string text;
|
||||||
bool doneValue;
|
bool doneValue;
|
||||||
string? h1 = null;
|
|
||||||
SubTaskLine subTaskLine;
|
SubTaskLine subTaskLine;
|
||||||
bool foundSubTasks = false;
|
bool foundSubTasks = false;
|
||||||
int tasksZeroLength = tasks[0].Length;
|
int tasksZeroLength = input.Tasks[0].Length;
|
||||||
string[] lines = File.ReadAllLines(fileInfo.FullName);
|
long ticks = record.FileInfo.LastWriteTime.Ticks;
|
||||||
for (int i = 0; i < lines.Length; i++)
|
for (int i = 0; i < record.LineNumber.Lines.Count; i++)
|
||||||
{
|
{
|
||||||
line = lines[i];
|
line = record.LineNumber.Lines[i];
|
||||||
if (line.StartsWith("# "))
|
if (!foundSubTasks && line == input.SubTasks)
|
||||||
h1 = line[2..];
|
|
||||||
if (!foundSubTasks && line == subTasks)
|
|
||||||
foundSubTasks = true;
|
foundSubTasks = true;
|
||||||
if (!foundSubTasks)
|
if (!foundSubTasks)
|
||||||
continue;
|
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;
|
continue;
|
||||||
doneValue = foundDone is not null && foundDone.Value;
|
doneValue = foundDone is not null && foundDone.Value;
|
||||||
subTaskLine = new($" {line}", doneValue, fileInfo.LastWriteTime.Ticks, i);
|
subTaskLine = new($" {line}", doneValue, ticks, i);
|
||||||
results.Add(subTaskLine);
|
results.Add(subTaskLine);
|
||||||
}
|
}
|
||||||
doneValue = foundDone is not null && foundDone.Value;
|
doneValue = foundDone is not null && foundDone.Value;
|
||||||
if (h1 is null)
|
if (record.LineNumber.H1 is null)
|
||||||
subTaskLine = new(fallbackLine, doneValue, fileInfo.LastWriteTime.Ticks, Line: null);
|
subTaskLine = new(fallbackLine, doneValue, ticks, Line: null);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fallbackLine = foundDone is null || !foundDone.Value ? $"- [ ] {fileInfo.LastWriteTime.Ticks} ~~~ {h1}" : $"- [x] {fileInfo.LastWriteTime.Ticks} ~~~ {h1}";
|
done = foundDone is null || !foundDone.Value ? ' ' : 'x';
|
||||||
subTaskLine = new(fallbackLine, doneValue, fileInfo.LastWriteTime.Ticks, Line: 0);
|
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);
|
results.Add(subTaskLine);
|
||||||
return new(results);
|
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 doneValue;
|
||||||
bool? foundDone;
|
|
||||||
FileInfo fileInfo;
|
|
||||||
string[] newLines;
|
|
||||||
string[] segments;
|
string[] segments;
|
||||||
List<string> lines;
|
|
||||||
string fallbackLine;
|
string fallbackLine;
|
||||||
string[] indexLines;
|
bool? foundDone = null;
|
||||||
string checkDirectory;
|
ReadOnlyCollection<SubTaskLine> subTaskLines;
|
||||||
string done = args[7];
|
for (int i = 0; i < lineNumber.Lines.Count; i++)
|
||||||
List<string> indexFiles;
|
{
|
||||||
SubTaskLine subTaskLine;
|
if (lineNumber.Lines[i] == input.Done)
|
||||||
string subTasks = args[3];
|
foundDone = true;
|
||||||
List<string> oldLines = [];
|
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 indexFile = args[5];
|
||||||
string searchPattern = args[2];
|
string searchPattern = args[2];
|
||||||
string directoryFilter = args[8];
|
string directoryFilter = args[8];
|
||||||
string[] tasks = args[6].Split(',');
|
|
||||||
string codeInsiders = $"{args[4]} \"";
|
string codeInsiders = $"{args[4]} \"";
|
||||||
List<SubTaskLine> allSubTaskLines = [];
|
string done = args[7].Replace('_', ' ');
|
||||||
ReadOnlyCollection<SubTaskLine> subTaskLines;
|
string subTasks = args[3].Replace('_', ' ');
|
||||||
string sourceDirectory = Path.GetFullPath(args[0]);
|
string sourceDirectory = Path.GetFullPath(args[0]);
|
||||||
List<Record> records = GetRecords(sourceDirectory, searchPattern, codeInsiders, subTasks, directoryFilter);
|
string? destinationDirectory = args.Count < 8 ? null : Path.GetFullPath(args[9]);
|
||||||
foreach (Record record in from l in records orderby l.SubTasksLine is null, l.CodeInsidersLine is null select l)
|
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)
|
if (record.LineNumber.H1 is null)
|
||||||
continue;
|
result = null;
|
||||||
if (record.CodeInsidersLine is not null)
|
|
||||||
logger.LogInformation("<{file}> has [{subTasks}]", Path.GetFileNameWithoutExtension(record.File), subTasks);
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogWarning("<{file}> has [{subTasks}] but doesn't have [{codeInsiders}]!", Path.GetFileNameWithoutExtension(record.File), subTasks, codeInsiders);
|
string segment = Path.GetFileName(checkDirectory);
|
||||||
continue;
|
DateTime utcEpochDateTime = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||||
}
|
long utcEpochTotalMilliseconds = (long)Math.Floor(DateTime.UtcNow.Subtract(utcEpochDateTime).TotalMilliseconds);
|
||||||
if (record.StopLine is null)
|
if (!long.TryParse(segment, out long check) || check < input.AfterEpochTotalMilliseconds || check > utcEpochTotalMilliseconds)
|
||||||
continue;
|
result = null;
|
||||||
checkDirectory = record.Lines[record.CodeInsidersLine.Value][codeInsiders.Length..^1];
|
else
|
||||||
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--)
|
|
||||||
{
|
{
|
||||||
if (!indexFiles[i].Contains(directoryFilter, StringComparison.CurrentCultureIgnoreCase))
|
ReadOnlyCollection<H1AndParamCase> h1ParamCaseCollection = GetH1ParamCaseCollection(input, record.LineNumber.Lines);
|
||||||
indexFiles.RemoveAt(i);
|
if (h1ParamCaseCollection.Count == 0)
|
||||||
}
|
result = null;
|
||||||
if (indexFiles.Count != 1)
|
else
|
||||||
{
|
result = WriteAndGetIndexFile(input.DestinationDirectory, record.LineNumber.Lines[record.LineNumber.H1.Value], check, h1ParamCaseCollection);
|
||||||
logger.LogError("<{checkDirectory}> doesn't have a [{indexFile}]", Path.GetFileName(checkDirectory), indexFile);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foundDone = null;
|
}
|
||||||
oldLines.Clear();
|
return result;
|
||||||
allSubTaskLines.Clear();
|
}
|
||||||
indexLines = File.ReadAllLines(indexFiles[0]);
|
|
||||||
checkDirectory = Path.GetDirectoryName(indexFiles[0]) ?? throw new Exception();
|
private static bool FileWrite(Record record, List<string> newLines, double percent)
|
||||||
for (int i = 0; i < indexLines.Length; i++)
|
{
|
||||||
|
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)
|
progressLine = $"progress: {percent}";
|
||||||
foundDone = true;
|
if (record.LineNumber.Progress is not null)
|
||||||
segments = indexLines[i].Split(tasks[1]);
|
resultLines[record.LineNumber.Progress.Value] = progressLine;
|
||||||
doneValue = foundDone is not null && foundDone.Value;
|
else
|
||||||
if (segments.Length > 2 || !segments[0].StartsWith(tasks[0]))
|
{
|
||||||
|
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;
|
continue;
|
||||||
fallbackLine = foundDone is null || !foundDone.Value ? $"- [ ] {segments[0][tasks[0].Length..]}" : $"- [x] {segments[0][tasks[0].Length..]}";
|
fileNameWithoutExtension = Path.GetFileNameWithoutExtension(record.FileInfo.FullName);
|
||||||
fileInfo = new(Path.GetFullPath(Path.Combine(checkDirectory, segments[1][..^1])));
|
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)
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
subTaskLines = GetSubTasks(subTasks, tasks, doneValue, fallbackLine, fileInfo);
|
oldLines.Clear();
|
||||||
if (subTaskLines.Count > 0)
|
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);
|
allCount = (from l in subTaskLines where l.Line is not null && l.Line.Value == 0 select 1).Count();
|
||||||
allSubTaskLines.Add(subTaskLine);
|
doneCount = (from l in subTaskLines where l.Line is not null && l.Line.Value == 0 && l.Done select 1).Count();
|
||||||
for (int j = subTaskLines.Count - 1; j >= 0; j--)
|
// done = allCount != doneCount ? ' ' : 'x';
|
||||||
allSubTaskLines.Add(subTaskLines[j]);
|
percent = allCount == 0 ? 0 : Math.Round(doneCount / allCount, 3);
|
||||||
|
// newLines.Insert(0, $"- [{done}] Sub-tasks {doneCount} of {allCount} [{percent * 100}%]");
|
||||||
}
|
}
|
||||||
}
|
if (newLines.Count == oldLines.Count)
|
||||||
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[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;
|
continue;
|
||||||
lineCheck++;
|
|
||||||
}
|
}
|
||||||
if (lineCheck == newLines.Length)
|
if (string.IsNullOrEmpty(checkDirectory))
|
||||||
continue;
|
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());
|
if (!reloadAny)
|
||||||
_ = Directory.CreateDirectory(checkDirectory);
|
break;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
383
ADO2024/PI4/Helper-2024-12-17.cs
Normal file
383
ADO2024/PI4/Helper-2024-12-17.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
195
ADO2024/PI4/Helper-2024-12-24.cs
Normal file
195
ADO2024/PI4/Helper-2024-12-24.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -81,8 +81,6 @@ internal static class HelperDay
|
|||||||
ADO2024.PI2.Helper20240711.GitRemoteRemove(logger, args);
|
ADO2024.PI2.Helper20240711.GitRemoteRemove(logger, args);
|
||||||
else if (args[1] == "Day-Helper-2024-07-18")
|
else if (args[1] == "Day-Helper-2024-07-18")
|
||||||
ADO2024.PI2.Helper20240718.JsonToMarkdown(logger, args);
|
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")
|
else if (args[1] == "Day-Helper-2024-07-28")
|
||||||
ADO2024.PI2.Helper20240728.DownloadSslCertificates(logger, args);
|
ADO2024.PI2.Helper20240728.DownloadSslCertificates(logger, args);
|
||||||
else if (args[1] == "Day-Helper-2024-08-05")
|
else if (args[1] == "Day-Helper-2024-08-05")
|
||||||
@ -123,6 +121,10 @@ internal static class HelperDay
|
|||||||
ADO2024.PI4.Helper20241204.ConvertToUTF8(logger, args);
|
ADO2024.PI4.Helper20241204.ConvertToUTF8(logger, args);
|
||||||
else if (args[1] == "Day-Helper-2024-12-12")
|
else if (args[1] == "Day-Helper-2024-12-12")
|
||||||
ADO2024.PI4.Helper20241212.Rename(logger, args);
|
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
|
else
|
||||||
throw new Exception(appSettings.Company);
|
throw new Exception(appSettings.Company);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
@ -18,9 +18,10 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" 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="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="System.Text.Json" Version="8.0.5" />
|
||||||
<PackageReference Include="TextCopy" Version="6.2.1" />
|
<PackageReference Include="TextCopy" Version="6.2.1" />
|
||||||
<PackageReference Include="WindowsShortcutFactory" Version="1.2.0" />
|
<PackageReference Include="WindowsShortcutFactory" Version="1.2.0" />
|
||||||
<PackageReference Include="YamlDotNet" Version="16.2.0" />
|
<PackageReference Include="YamlDotNet" Version="16.2.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@ -133,31 +133,30 @@ internal static partial class HelperKanbanMetadata
|
|||||||
List<string> results = [kanbanIndexH1, string.Empty];
|
List<string> results = [kanbanIndexH1, string.Empty];
|
||||||
string h1;
|
string h1;
|
||||||
TimeSpan timeSpan;
|
TimeSpan timeSpan;
|
||||||
List<string> lines;
|
|
||||||
LineNumber lineNumber;
|
LineNumber lineNumber;
|
||||||
Record[] sorted = (from l in records orderby l.GroupCount, l.FileInfo.LastWriteTime descending select l).ToArray();
|
Record[] sorted = (from l in records orderby l.GroupCount, l.FileInfo.LastWriteTime descending select l).ToArray();
|
||||||
foreach (Record record in sorted)
|
foreach (Record record in sorted)
|
||||||
{
|
{
|
||||||
if (record.ItemLineNumber == 0)
|
if (record.ItemLineNumber == 0)
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
(lines, lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(record.FileInfo);
|
lineNumber = HelperMarkdown.GetLineNumbers(record.FileInfo);
|
||||||
if (lines.Count == 0)
|
if (lineNumber.Lines.Count == 0)
|
||||||
continue;
|
continue;
|
||||||
timeSpan = new(record.FileInfo.LastWriteTime.Ticks - record.FileInfo.CreationTime.Ticks);
|
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($"#{h1}");
|
||||||
results.Add(string.Empty);
|
results.Add(string.Empty);
|
||||||
results.Add("```yaml");
|
results.Add("```yaml");
|
||||||
results.Add($"CreationTime: {record.FileInfo.CreationTime:yyyy-MM-dd}");
|
results.Add($"CreationTime: {record.FileInfo.CreationTime:yyyy-MM-dd}");
|
||||||
results.Add($"LastWriteTime: {record.FileInfo.LastWriteTime:yyyy-MM-dd}");
|
results.Add($"LastWriteTime: {record.FileInfo.LastWriteTime:yyyy-MM-dd}");
|
||||||
results.Add($"TotalDays: {Math.Round(timeSpan.TotalDays, 2)}");
|
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++)
|
for (int i = 0; i < lineNumber.FrontMatterYamlEnd; i++)
|
||||||
{
|
{
|
||||||
if (lines[i] == "---")
|
if (lineNumber.Lines[i] == "---")
|
||||||
continue;
|
continue;
|
||||||
results.Add(lines[i]);
|
results.Add(lineNumber.Lines[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
results.Add($"status: {record.GroupCount}-{record.Group}");
|
results.Add($"status: {record.GroupCount}-{record.Group}");
|
||||||
@ -175,7 +174,7 @@ internal static partial class HelperKanbanMetadata
|
|||||||
File.WriteAllText(file, string.Join(Environment.NewLine, results));
|
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? match;
|
||||||
bool gitCheck;
|
bool gitCheck;
|
||||||
@ -184,23 +183,24 @@ internal static partial class HelperKanbanMetadata
|
|||||||
List<string> lines;
|
List<string> lines;
|
||||||
LineNumber lineNumber;
|
LineNumber lineNumber;
|
||||||
string? directory = Path.GetDirectoryName(sourceDirectory);
|
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)
|
if (directory is not null && kanbanIndexFileLineNumber.H1 is not null)
|
||||||
{
|
{
|
||||||
string checkDirectory = Path.Combine(directory, ".vscode", "helper");
|
string checkDirectory = Path.Combine(directory, ".vscode", "helper");
|
||||||
if (Directory.Exists(checkDirectory))
|
if (Directory.Exists(checkDirectory))
|
||||||
{
|
{
|
||||||
WriteKanbanBoardFile(checkDirectory, records, kanbanIndexFileLines[kanbanIndexFileLineNumber.H1.Value]);
|
WriteKanbanBoardFile(checkDirectory, records, kanbanIndexFileLineNumber.Lines[kanbanIndexFileLineNumber.H1.Value]);
|
||||||
WriteKanbanBoardYmlView(checkDirectory, records, kanbanIndexFileLines[kanbanIndexFileLineNumber.H1.Value]);
|
WriteKanbanBoardYmlView(checkDirectory, records, kanbanIndexFileLineNumber.Lines[kanbanIndexFileLineNumber.H1.Value]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (Record record in records)
|
foreach (Record record in records)
|
||||||
{
|
{
|
||||||
if (record.ItemLineNumber == 0)
|
if (record.ItemLineNumber == 0)
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
(lines, lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(record.FileInfo);
|
lineNumber = HelperMarkdown.GetLineNumbers(record.FileInfo);
|
||||||
if (lines.Count == 0)
|
if (lineNumber.Lines.Count == 0)
|
||||||
continue;
|
continue;
|
||||||
|
lines = lineNumber.Lines.ToList();
|
||||||
statusLine = $"status: {record.GroupCount}-{record.Group}";
|
statusLine = $"status: {record.GroupCount}-{record.Group}";
|
||||||
paramCase = lineNumber.H1 is null ? null : GetParamCase(lines[lineNumber.H1.Value]);
|
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;
|
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
|
else
|
||||||
{
|
{
|
||||||
FileInfo fileInfo = new(indexFile);
|
FileInfo fileInfo = new(indexFile);
|
||||||
(List<string> lines, LineNumber lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(fileInfo);
|
LineNumber lineNumber = HelperMarkdown.GetLineNumbers(fileInfo);
|
||||||
SetMetadata(fullPath, new(lines), lineNumber, gitOthersModifiedAndDeletedExcludingStandardFiles: new([]));
|
SetMetadata(fullPath, lineNumber, gitOthersModifiedAndDeletedExcludingStandardFiles: new([]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ internal static partial class HelperMarkdown
|
|||||||
return result;
|
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 = [];
|
List<string> results = [];
|
||||||
if (lineNumber.FrontMatterYamlEnd is not null && lines.Count >= lineNumber.FrontMatterYamlEnd.Value)
|
if (lineNumber.FrontMatterYamlEnd is not null && lines.Count >= lineNumber.FrontMatterYamlEnd.Value)
|
||||||
@ -307,7 +307,7 @@ internal static partial class HelperMarkdown
|
|||||||
return new(results);
|
return new(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static (List<string>, LineNumber) GetStatusAndFrontMatterYamlEndLineNumbers(FileInfo fileInfo)
|
internal static LineNumber GetLineNumbers(FileInfo fileInfo)
|
||||||
{
|
{
|
||||||
string line;
|
string line;
|
||||||
int? h1LineNumber = null;
|
int? h1LineNumber = null;
|
||||||
@ -315,6 +315,8 @@ internal static partial class HelperMarkdown
|
|||||||
int? statusLineNumber = null;
|
int? statusLineNumber = null;
|
||||||
int? createdLineNumber = null;
|
int? createdLineNumber = null;
|
||||||
int? updatedLineNumber = null;
|
int? updatedLineNumber = null;
|
||||||
|
int? progressLineNumber = null;
|
||||||
|
int? completedLineNumber = null;
|
||||||
int? frontMatterYamlEndLineNumber = null;
|
int? frontMatterYamlEndLineNumber = null;
|
||||||
Encoding? encoding = GetEncoding(fileInfo.FullName) ?? Encoding.Default;
|
Encoding? encoding = GetEncoding(fileInfo.FullName) ?? Encoding.Default;
|
||||||
string[] lines = File.ReadAllLines(fileInfo.FullName, encoding);
|
string[] lines = File.ReadAllLines(fileInfo.FullName, encoding);
|
||||||
@ -350,6 +352,16 @@ internal static partial class HelperMarkdown
|
|||||||
updatedLineNumber = i;
|
updatedLineNumber = i;
|
||||||
continue;
|
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] == ' ')
|
if (h1LineNumber is null && line.Length > 2 && line[0] == '#' && line[1] == ' ')
|
||||||
{
|
{
|
||||||
h1LineNumber = i;
|
h1LineNumber = i;
|
||||||
@ -357,12 +369,15 @@ internal static partial class HelperMarkdown
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
LineNumber lineNumber = new(createdLineNumber,
|
LineNumber lineNumber = new(createdLineNumber,
|
||||||
|
completedLineNumber,
|
||||||
h1LineNumber,
|
h1LineNumber,
|
||||||
frontMatterYamlEndLineNumber,
|
frontMatterYamlEndLineNumber,
|
||||||
|
lines.AsReadOnly(),
|
||||||
|
progressLineNumber,
|
||||||
statusLineNumber,
|
statusLineNumber,
|
||||||
typeLineNumber,
|
typeLineNumber,
|
||||||
updatedLineNumber);
|
updatedLineNumber);
|
||||||
return (lines.ToList(), lineNumber);
|
return lineNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, object> GetFromMatterYaml(ReadOnlyCollection<string> frontMatterYamlLines)
|
private static Dictionary<string, object> GetFromMatterYaml(ReadOnlyCollection<string> frontMatterYamlLines)
|
||||||
@ -387,7 +402,7 @@ internal static partial class HelperMarkdown
|
|||||||
return results;
|
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 = [];
|
Dictionary<string, object> results = [];
|
||||||
#pragma warning disable IL3050
|
#pragma warning disable IL3050
|
||||||
@ -660,12 +675,12 @@ internal static partial class HelperMarkdown
|
|||||||
string key;
|
string key;
|
||||||
string type;
|
string type;
|
||||||
bool isKanbanIndex;
|
bool isKanbanIndex;
|
||||||
List<string> lines;
|
|
||||||
bool isWithinSource;
|
bool isWithinSource;
|
||||||
bool isKanbanMarkdown;
|
bool isKanbanMarkdown;
|
||||||
LineNumber lineNumber;
|
LineNumber lineNumber;
|
||||||
MarkdownFile markdownFile;
|
MarkdownFile markdownFile;
|
||||||
string fileNameWithoutExtension;
|
string fileNameWithoutExtension;
|
||||||
|
ReadOnlyCollection<string> lines;
|
||||||
ReadOnlyDictionary<string, object> frontMatterYaml;
|
ReadOnlyDictionary<string, object> frontMatterYaml;
|
||||||
bool isGitOthersModifiedAndDeletedExcludingStandard;
|
bool isGitOthersModifiedAndDeletedExcludingStandard;
|
||||||
ReadOnlyCollection<FileInfo> files = GetFiles(appSettings, input);
|
ReadOnlyCollection<FileInfo> files = GetFiles(appSettings, input);
|
||||||
@ -678,7 +693,8 @@ internal static partial class HelperMarkdown
|
|||||||
isGitOthersModifiedAndDeletedExcludingStandard = gitOthersModifiedAndDeletedExcludingStandardFiles.Contains(fileInfo.FullName);
|
isGitOthersModifiedAndDeletedExcludingStandard = gitOthersModifiedAndDeletedExcludingStandardFiles.Contains(fileInfo.FullName);
|
||||||
if (!isWithinSource && results.ContainsKey(key))
|
if (!isWithinSource && results.ContainsKey(key))
|
||||||
continue;
|
continue;
|
||||||
(lines, lineNumber) = GetStatusAndFrontMatterYamlEndLineNumbers(fileInfo);
|
lineNumber = GetLineNumbers(fileInfo);
|
||||||
|
lines = lineNumber.Lines;
|
||||||
fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileInfo.FullName);
|
fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileInfo.FullName);
|
||||||
h1 = fileNameWithoutExtension.ToLower().Replace("%20", "-").Replace(' ', '-');
|
h1 = fileNameWithoutExtension.ToLower().Replace("%20", "-").Replace(' ', '-');
|
||||||
frontMatterYaml = GetFromMatterYaml(lines, lineNumber);
|
frontMatterYaml = GetFromMatterYaml(lines, lineNumber);
|
||||||
@ -690,7 +706,7 @@ internal static partial class HelperMarkdown
|
|||||||
continue;
|
continue;
|
||||||
type = appSettings.DefaultNoteType;
|
type = appSettings.DefaultNoteType;
|
||||||
File.WriteAllLines(fileInfo.FullName, ["---", $"type: {type}\"", "---", string.Empty, $"# {h1}"]);
|
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");
|
isKanbanMarkdown = fileInfo.Name.EndsWith(".knb.md");
|
||||||
isKanbanIndex = fileNameWithoutExtension == "index" && type.StartsWith("kanb", StringComparison.OrdinalIgnoreCase);
|
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 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..];
|
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}";
|
createdLine = $"created: {creationDateTime.ToUniversalTime():yyyy-MM-ddTHH:mm:ss.fffZ}";
|
||||||
updatedLine = $"updated: {markdownFile.LastWriteDateTime.ToUniversalTime():yyyy-MM-ddTHH:mm:ss.fffZ}";
|
updatedLine = $"updated: {markdownFile.LastWriteDateTime.ToUniversalTime():yyyy-MM-ddTHH:mm:ss.fffZ}";
|
||||||
if (markdownFile.IsKanbanIndex)
|
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.FrontMatterYamlEnd is null)
|
||||||
{
|
{
|
||||||
if (markdownFile.LineNumber.H1 is not null)
|
if (markdownFile.LineNumber.H1 is not null)
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace File_Folder_Helper.Models;
|
namespace File_Folder_Helper.Models;
|
||||||
|
|
||||||
internal record LineNumber(int? Created,
|
internal record LineNumber(int? Created,
|
||||||
|
int? Completed,
|
||||||
int? H1,
|
int? H1,
|
||||||
int? FrontMatterYamlEnd,
|
int? FrontMatterYamlEnd,
|
||||||
|
ReadOnlyCollection<string> Lines,
|
||||||
|
int? Progress,
|
||||||
int? Status,
|
int? Status,
|
||||||
int? Type,
|
int? Type,
|
||||||
int? Updated);
|
int? Updated);
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
|
using System.Globalization;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace File_Folder_Helper.Models;
|
namespace File_Folder_Helper.Models;
|
||||||
|
|
||||||
internal record NginxFileSystem(string Name,
|
internal record NginxFileSystem([property: JsonPropertyName("name")] string Name,
|
||||||
string Type,
|
DateTime? LastModified,
|
||||||
string MTime,
|
[property: JsonPropertyName("mtime")] string MTime,
|
||||||
float Size)
|
Uri? URI,
|
||||||
|
[property: JsonPropertyName("type")] string Type,
|
||||||
|
[property: JsonPropertyName("size")] float? Length)
|
||||||
{
|
{
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@ -15,6 +18,25 @@ internal record NginxFileSystem(string Name,
|
|||||||
return result;
|
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)]
|
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
// console.log("ticks: " + ticks);
|
// console.log("ticks: " + ticks);
|
||||||
// var dateText = ticks + " - " + date.toString();
|
// var dateText = ticks + " - " + date.toString();
|
||||||
// console.log("dateText: " + dateText);
|
// 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 date = new Date();
|
||||||
let timezoneOffset = date.getTimezoneOffset();
|
let timezoneOffset = date.getTimezoneOffset();
|
||||||
let seconds = date.getTime().valueOf() + timezoneOffset;
|
let seconds = date.getTime().valueOf() + timezoneOffset;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user