This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
मेरे पास है ब्लॉग में बहुतों का ज़िक्र किया गया कई बार मैं अपने ब्लॉग पोस्ट बनाने के लिए मरकुस का उपयोग करता हूँ; मैं वास्तव में इस दृष्टिकोण की तरह है लेकिन यह एक प्रमुख कवरबैक है - इसका मतलब है कि मुझे एक पूर्ण डॉकर चक्र बनाना है एक पोस्ट अद्यतन करने के लिए। यह Fline था जब मैं और अधिक सुविधाओं तैयार कर रहा था...... मैं फिर के बारे में ब्लॉग किया लेकिन अब यह काफी सीमित है. मैं अपने ब्लॉग पोस्टों को पूरा निर्माण चक्र करने के बिना अद्यतन करने में सक्षम होना चाहता हूं. तो अब मैंने अपने ब्लॉग में एक नया काम जोड़ दिया है जो मुझे सिर्फ ऐसा करने की अनुमति देता है.
'लिब्रीड' के पास से मेरा मतलब है कि मैं इस जीवन - चक्र का मतलब है, फिर भी बहुत सरल लेकिन दयालु...___
तो यह बहुत सरल है.
यह मुझे स्थानीय नेटवर्क पोस्टों को बनाने के लिए स्थानीय रूप से उपयोग करते रहने देता है (आगे में मैं इस साइट में भी होने की अनुमति देने के लिए जाना होगा), सभी अनुवाद अपने आप में अद्भुत रूप से हो रहे हैं और मैं पोस्टों को अद्यतन कर सकता हूँ बिना पूर्ण निर्माण चक्र के.
इस के लिए कोड YERANA एक और है IHostedService
, इस बार इसका उपयोग करता है FileSystemWatcher
वर्ग जिसमें नई फ़ाइलों के लिए निर्देशिका देखना है. जब एक नया फ़ाइल पता लगाया जाता है तो यह फ़ाइल पढ़ता है, प्रक्रिया इसे डाटाबेस में प्रविष्ट करता है. या यदि मैं एक अंग्रेज़ी भाषा पोस्ट को मिटा दूँ तो यह उस पोस्ट के सभी अनुवादों को भी मिटा देगा.
पूरे कोड नीचे है लेकिन मैं इसे थोड़ा नीचे तोड़ देंगे.
using Mostlylucid.Config.Markdown;
using Polly;
using Serilog.Events;
namespace Mostlylucid.Blog.WatcherService;
public class MarkdownDirectoryWatcherService(MarkdownConfig markdownConfig, IServiceScopeFactory serviceScopeFactory,
ILogger<MarkdownDirectoryWatcherService> logger)
: IHostedService
{
private Task _awaitChangeTask = Task.CompletedTask;
private FileSystemWatcher _fileSystemWatcher;
public Task StartAsync(CancellationToken cancellationToken)
{
_fileSystemWatcher = new FileSystemWatcher
{
Path = markdownConfig.MarkdownPath,
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime |
NotifyFilters.Size,
Filter = "*.md", // Watch all markdown files
IncludeSubdirectories = true // Enable watching subdirectories
};
// Subscribe to events
_fileSystemWatcher.EnableRaisingEvents = true;
_awaitChangeTask = Task.Run(()=> AwaitChanges(cancellationToken));
logger.LogInformation("Started watching directory {Directory}", markdownConfig.MarkdownPath);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
// Stop watching
_fileSystemWatcher.EnableRaisingEvents = false;
_fileSystemWatcher.Dispose();
Console.WriteLine($"Stopped watching directory: {markdownConfig.MarkdownPath}");
return Task.CompletedTask;
}
private async Task AwaitChanges(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var fileEvent = _fileSystemWatcher.WaitForChanged(WatcherChangeTypes.All);
if (fileEvent.ChangeType == WatcherChangeTypes.Changed ||
fileEvent.ChangeType == WatcherChangeTypes.Created)
{
await OnChangedAsync(fileEvent);
}
else if (fileEvent.ChangeType == WatcherChangeTypes.Deleted)
{
OnDeleted(fileEvent);
}
else if (fileEvent.ChangeType == WatcherChangeTypes.Renamed)
{
}
}
}
private async Task OnChangedAsync(WaitForChangedResult e)
{
if (e.Name == null) return;
var activity = Log.Logger.StartActivity("Markdown File Changed {Name}", e.Name);
var retryPolicy = Policy
.Handle<IOException>() // Only handle IO exceptions (like file in use)
.WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromMilliseconds(500 * retryAttempt),
(exception, timeSpan, retryCount, context) =>
{
activity?.Activity?.SetTag("Retry Attempt", retryCount);
// Log the retry attempt
logger.LogWarning("File is in use, retrying attempt {RetryCount} after {TimeSpan}", retryCount, timeSpan);
});
try
{
var fileName = e.Name;
var isTranslated = Path.GetFileNameWithoutExtension(e.Name).Contains(".");
var language = MarkdownBaseService.EnglishLanguage;
var directory = markdownConfig.MarkdownPath;
if (isTranslated)
{
language = Path.GetFileNameWithoutExtension(e.Name).Split('.').Last();
fileName = Path.GetFileName(fileName);
directory = markdownConfig.MarkdownTranslatedPath;
}
var filePath = Path.Combine(directory, fileName);
var scope = serviceScopeFactory.CreateScope();
var markdownBlogService = scope.ServiceProvider.GetRequiredService<IMarkdownBlogService>();
// Use the Polly retry policy for executing the operation
await retryPolicy.ExecuteAsync(async () =>
{
var blogModel = await markdownBlogService.GetPage(filePath);
activity?.Activity?.SetTag("Page Processed", blogModel.Slug);
blogModel.Language = language;
var blogService = scope.ServiceProvider.GetRequiredService<IBlogService>();
await blogService.SavePost(blogModel);
if (language == MarkdownBaseService.EnglishLanguage)
{
var translateService = scope.ServiceProvider.GetRequiredService<BackgroundTranslateService>();
await translateService.TranslateForAllLanguages(
new PageTranslationModel(){OriginalFileName = filePath, OriginalMarkdown = blogModel.Markdown,Persist = true});
}
activity?.Activity?.SetTag("Page Saved", blogModel.Slug);
});
activity?.Complete();
}
catch (Exception exception)
{
activity?.Complete(LogEventLevel.Error, exception);
}
}
private void OnDeleted(WaitForChangedResult e)
{
if(e.Name == null) return;
var isTranslated = Path.GetFileNameWithoutExtension(e.Name).Contains(".");
var language = MarkdownBaseService.EnglishLanguage;
var slug = Path.GetFileNameWithoutExtension(e.Name);
if (isTranslated)
{
var name = Path.GetFileNameWithoutExtension(e.Name).Split('.');
language = name.Last();
slug = name.First();
}
else
{
var translatedFiles = Directory.GetFiles(markdownConfig.MarkdownTranslatedPath, $"{slug}.*.*");
_fileSystemWatcher.EnableRaisingEvents = false;
foreach (var file in translatedFiles)
{
File.Delete(file);
}
_fileSystemWatcher.EnableRaisingEvents = true;
}
var scope = serviceScopeFactory.CreateScope();
var blogService = scope.ServiceProvider.GetRequiredService<IBlogService>();
blogService.Delete(slug, language);
}
private void OnRenamed(object sender, RenamedEventArgs e)
{
Console.WriteLine($"File renamed: {e.OldFullPath} to {e.FullPath}");
}
}
तो यह सब एक नए कार्य को बंद कर देता है जो प्रयोग करता है FileSystemWatcher
इसमें है StartAsync
विधि. एक में IHostedService
यह प्रविष्टि बिंदु है कि एनईटी कोर फ्रेमवर्क सेवा प्रारंभ करने के लिए उपयोग करता है.
private FileSystemWatcher _fileSystemWatcher;
public Task StartAsync(CancellationToken cancellationToken)
{
_fileSystemWatcher = new FileSystemWatcher
{
Path = markdownConfig.MarkdownPath,
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime |
NotifyFilters.Size,
Filter = "*.md", // Watch all markdown files
IncludeSubdirectories = true // Enable watching subdirectories
};
// Subscribe to events
_fileSystemWatcher.EnableRaisingEvents = true;
_awaitChangeTask = Task.Run(()=> AwaitChanges(cancellationToken));
logger.LogInformation("Started watching directory {Directory}", markdownConfig.MarkdownPath);
return Task.CompletedTask;
}
मेरे पास सेवा के लिए एक कार्य है जो मैं लूप पर आग आग है
private Task _awaitChangeTask = Task.CompletedTask;
_awaitChangeTask = Task.Run(()=> AwaitChanges(cancellationToken));
यह महत्वपूर्ण है अन्यथा यह ब्लॉक हो जाता है, आप इस को पृष्ठभूमि में सुनिश्चित करने के लिए की जरूरत है (मुझे यह कैसे पता है).
यहाँ में हम एक सेट किया है FileSystemWatcher
मेरे मार्क डाउन डिरेक्ट्री में घटनाओं के लिए सुनें (हम बाद में देखने के लिए एक मेजबान डिरेक्ट्री के लिए किया गया है!)
_fileSystemWatcher = new FileSystemWatcher
{
Path = markdownConfig.MarkdownPath,
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime |
NotifyFilters.Size,
Filter = "*.md", // Watch all markdown files
IncludeSubdirectories = true // Enable watching subdirectories
};
// Subscribe to events
_fileSystemWatcher.EnableRaisingEvents = true;
आप देख सकते हैं कि मैं के लिए देख रहा हूँ FileName
, LastWrite
, CreationTime
और Size
परिवर्तन. यह इसलिए कि मैं जानना चाहता हूँ कि क्या एक फ़ाइल बनाया गया है, अद्यतन या मिटाया जाना है.
मैं भी एक है Filter
इसे सेट करें *.md
इसलिए मैं केवल चिह्नित फ़ाइलों के लिए पहरा देता हूँ और निर्धारित करता हूँ कि मैं भी सबफ़ोल्डरों को (बाइबल के लिए) देखना चाहता हूँ.
इस कोड के भीतर मैं एक परिवर्तन लूप है जो फ़ाइल तंत्र में परिवर्तन के लिए इंतजार करता है. ध्यान दीजिए कि आप यहाँ के बदलावों में भी हुक कर सकते हैं लेकिन इससे मुझे और भी ज़्यादा फायदा हुआ ।
private async Task AwaitChanges(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var fileEvent = _fileSystemWatcher.WaitForChanged(WatcherChangeTypes.All);
if (fileEvent.ChangeType == WatcherChangeTypes.Changed ||
fileEvent.ChangeType == WatcherChangeTypes.Created)
{
await OnChangedAsync(fileEvent);
}
else if (fileEvent.ChangeType == WatcherChangeTypes.Deleted)
{
OnDeleted(fileEvent);
}
else if (fileEvent.ChangeType == WatcherChangeTypes.Renamed)
{
}
}
}
फिर बहुत सरल, यह एक परिवर्तन घटना के लिए इंतजार करता है और फिर कॉल करने के लिए OnChangedAsync
विधि. या अगर फाइल विलोपित है इसे कॉल करना है OnDeleted
विधि.
यह वह जगह है जहाँ 'नियंत्र' होता है. यह परिवर्तन घटना के लिए क्या सुनता है. फिर यह मार्क थर्मर फ़ाइल को मेरे इपॉसिटर ( HTML वर्गों, तिथि, शीर्षक इत्यादि को डाटाबेस में प्राप्त करने के लिए करता है). यह पता चला है कि क्या फ़ाइल अंग्रेजी में है (सो मैंने लिखा था :) और यदि यह अनुवाद प्रक्रिया बंद कर देता है तो यह पता चलता है. देखें इस पोस्ट को कैसे के लिए यह काम करता है.
await retryPolicy.ExecuteAsync(async () =>
{
var blogModel = await markdownBlogService.GetPage(filePath);
activity?.Activity?.SetTag("Page Processed", blogModel.Slug);
blogModel.Language = language;
var blogService = scope.ServiceProvider.GetRequiredService<IBlogService>();
await blogService.SavePost(blogModel);
if (language == MarkdownBaseService.EnglishLanguage)
{
var translateService = scope.ServiceProvider.GetRequiredService<BackgroundTranslateService>();
await translateService.TranslateForAllLanguages(
new PageTranslationModel(){OriginalFileName = filePath, OriginalMarkdown = blogModel.Markdown,Persist = true});
}
activity?.Activity?.SetTag("Page Saved", blogModel.Slug);
});
ध्यान दें कि मैं इस्तेमाल करता हूँ Polly
फ़ाइल प्रक्रिया को हैंडल करने के लिए यहाँ पर यह सुनिश्चित होता है कि फ़ाइल सही प्रकार से अपलोड किया गया / इससे पहले कि यह प्रक्रिया करने की कोशिश करे.
var retryPolicy = Policy
.Handle<IOException>() // Only handle IO exceptions (like file in use)
.WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromMilliseconds(500 * retryAttempt),
(exception, timeSpan, retryCount, context) =>
{
activity?.Activity?.SetTag("Retry Attempt", retryCount);
// Log the retry attempt
logger.LogWarning("File is in use, retrying attempt {RetryCount} after {TimeSpan}", retryCount, timeSpan);
});
मैं भी उपयोग SerilogTracing
'मुझे किसी भी समस्या का पता लगाने में मदद करने के लिए जब यह अधिक आसानी से है' (मैं अधिक आसानी से है) एक लेख है उस पर भी!___
private async Task OnChangedAsync(WaitForChangedResult e)
{
if (e.Name == null) return;
var activity = Log.Logger.StartActivity("Markdown File Changed {Name}", e.Name);
var retryPolicy = Policy
.Handle<IOException>() // Only handle IO exceptions (like file in use)
.WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromMilliseconds(500 * retryAttempt),
(exception, timeSpan, retryCount, context) =>
{
activity?.Activity?.SetTag("Retry Attempt", retryCount);
// Log the retry attempt
logger.LogWarning("File is in use, retrying attempt {RetryCount} after {TimeSpan}", retryCount, timeSpan);
});
try
{
var fileName = e.Name;
var isTranslated = Path.GetFileNameWithoutExtension(e.Name).Contains(".");
var language = MarkdownBaseService.EnglishLanguage;
var directory = markdownConfig.MarkdownPath;
if (isTranslated)
{
language = Path.GetFileNameWithoutExtension(e.Name).Split('.').Last();
fileName = Path.GetFileName(fileName);
directory = markdownConfig.MarkdownTranslatedPath;
}
var filePath = Path.Combine(directory, fileName);
var scope = serviceScopeFactory.CreateScope();
var markdownBlogService = scope.ServiceProvider.GetRequiredService<IMarkdownBlogService>();
// Use the Polly retry policy for executing the operation
await retryPolicy.ExecuteAsync(async () =>
{
var blogModel = await markdownBlogService.GetPage(filePath);
activity?.Activity?.SetTag("Page Processed", blogModel.Slug);
blogModel.Language = language;
var blogService = scope.ServiceProvider.GetRequiredService<IBlogService>();
await blogService.SavePost(blogModel);
if (language == MarkdownBaseService.EnglishLanguage)
{
var translateService = scope.ServiceProvider.GetRequiredService<BackgroundTranslateService>();
await translateService.TranslateForAllLanguages(
new PageTranslationModel(){OriginalFileName = filePath, OriginalMarkdown = blogModel.Markdown,Persist = true});
}
activity?.Activity?.SetTag("Page Saved", blogModel.Slug);
});
activity?.Complete();
}
catch (Exception exception)
{
activity?.Complete(LogEventLevel.Error, exception);
}
}
यह वास्तव में सरल है, यह सिर्फ पता चलता है कि जब एक फ़ाइल मिटा दी जाती है और इसे डेटाबेस से हटा दिया जाता है; मैं इसे सामान्य रूप से एक बेवकूफ के रूप में प्रयोग करता हूँ जब मैं आलेखों को अपलोड करने के लिए सरल कर रहा हूँ तब तुरंत वे त्रुटियाँ हैं. जैसा कि आप इसे देख सकते हैं यह देखने के लिए कि फ़ाइल अनुवाद किया गया है या नहीं, यह उस फ़ाइल के सभी अनुवादों को मिटा देता है.
private void OnDeleted(WaitForChangedResult e)
{
if (e.Name == null) return;
var activity = Log.Logger.StartActivity("Markdown File Deleting {Name}", e.Name);
try
{
var isTranslated = Path.GetFileNameWithoutExtension(e.Name).Contains(".");
var language = MarkdownBaseService.EnglishLanguage;
var slug = Path.GetFileNameWithoutExtension(e.Name);
if (isTranslated)
{
var name = Path.GetFileNameWithoutExtension(e.Name).Split('.');
language = name.Last();
slug = name.First();
}
else
{
var translatedFiles = Directory.GetFiles(markdownConfig.MarkdownTranslatedPath, $"{slug}.*.*");
_fileSystemWatcher.EnableRaisingEvents = false;
foreach (var file in translatedFiles)
{
File.Delete(file);
}
_fileSystemWatcher.EnableRaisingEvents = true;
}
var scope = serviceScopeFactory.CreateScope();
var blogService = scope.ServiceProvider.GetRequiredService<IBlogService>();
blogService.Delete(slug, language);
activity?.Activity?.SetTag("Page Deleted", slug);
activity?.Complete();
logger.LogInformation("Deleted blog post {Slug} in {Language}", slug, language);
}
catch (Exception exception)
{
activity?.Complete(LogEventLevel.Error, exception);
logger.LogError("Error deleting blog post {Slug}", e.Name);
}
}
मैं विनएसपी का उपयोग अपनी वेबसाइट पर फ़ाइलों को अपलोड करने के लिए करें. मैं बस एक 'गाच' कर सकता हूँ किसी भी निशान फ़ाइलें अपलोड करने के लिए तो मेरी सेवा उन्हें DB में जोड़ देगा. यह डिरेक्ट्री है FileSystemWatcher
देख रहा है। ForR में मैं भी इस में एक छवि अपलोड क्षमता जोड़ूँगा, वहाँ मैं कुछ पूर्व प्रक्रिया और बड़े फ़ाइलों को संभालूंगा.
तो यह एक बहुत सरल तरीका है कि नए ब्लॉग पोस्टों को मेरे स्थल पर जोड़ने के लिए...... बिना पूरा निर्माण चक्र करने के लिए. इस पोस्ट में मैंने दिखाया कि मैं कैसे इस्तेमाल करूँ FileSystemWatcher
निर्देशिका में परिवर्तनों का पता लगाने और उन्हें प्रक्रिया करने के लिए. मैंने यह भी दिखाया कि कैसे इस्तेमाल किया जाए Polly
reties और scras को संभालने के लिए Serilog
प्रक्रिया लॉग करने के लिए.