Back to "बी. के पास जाने के लिए एक हाइब्रीड का इस्तेमाल"

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

ASP.NET Markdown

बी. के पास जाने के लिए एक हाइब्रीड का इस्तेमाल

Saturday, 14 September 2024

परिचय

मेरे पास है ब्लॉग में बहुतों का ज़िक्र किया गया कई बार मैं अपने ब्लॉग पोस्ट बनाने के लिए मरकुस का उपयोग करता हूँ; मैं वास्तव में इस दृष्टिकोण की तरह है लेकिन यह एक प्रमुख कवरबैक है - इसका मतलब है कि मुझे एक पूर्ण डॉकर चक्र बनाना है एक पोस्ट अद्यतन करने के लिए। यह Fline था जब मैं और अधिक सुविधाओं तैयार कर रहा था...... मैं फिर के बारे में ब्लॉग किया लेकिन अब यह काफी सीमित है. मैं अपने ब्लॉग पोस्टों को पूरा निर्माण चक्र करने के बिना अद्यतन करने में सक्षम होना चाहता हूं. तो अब मैंने अपने ब्लॉग में एक नया काम जोड़ दिया है जो मुझे सिर्फ ऐसा करने की अनुमति देता है.

हाइब्रीड के पास

'लिब्रीड' के पास से मेरा मतलब है कि मैं इस जीवन - चक्र का मतलब है, फिर भी बहुत सरल लेकिन दयालु...___

तो यह बहुत सरल है.

  1. मैं मार्क नीचे में एक नया ब्लॉग पोस्ट लिखता हूँ, इसे अपने स्थानीय मशीन में सहेजें
  2. इसे मेरी वेबसाइट पर अपलोड करें.
  3. फ़ाइल वाचक नए फ़ाइल तथा प्रक्रियाओं का पता चलता है.
  4. पोस्ट को डाटाबेस में प्रविष्ट किया गया है
  5. अनुवाद बाहर निकाल दिए गए हैं.
  6. जब अनुवाद पूर्ण हो तो वेबसाइट पर पोस्ट अद्यतन किया जाता है.

यह मुझे स्थानीय नेटवर्क पोस्टों को बनाने के लिए स्थानीय रूप से उपयोग करते रहने देता है (आगे में मैं इस साइट में भी होने की अनुमति देने के लिए जाना होगा), सभी अनुवाद अपने आप में अद्भुत रूप से हो रहे हैं और मैं पोस्टों को अद्यतन कर सकता हूँ बिना पूर्ण निर्माण चक्र के.

graph LR; A[Write new .md file] --> B[Upload using WinSCP]; B --> C[New File Detected]; C --> D[Process Markdown]; D --> E[Insert into Database]; E --> F[Kick off translations]; F-->G[Add Translations to Database]; G-->H[Update Website];

कोड

इस के लिए कोड YERANA एक और है IHostedService, इस बार इसका उपयोग करता है FileSystemWatcher वर्ग जिसमें नई फ़ाइलों के लिए निर्देशिका देखना है. जब एक नया फ़ाइल पता लगाया जाता है तो यह फ़ाइल पढ़ता है, प्रक्रिया इसे डाटाबेस में प्रविष्ट करता है. या यदि मैं एक अंग्रेज़ी भाषा पोस्ट को मिटा दूँ तो यह उस पोस्ट के सभी अनुवादों को भी मिटा देगा.

पूरे कोड नीचे है लेकिन मैं इसे थोड़ा नीचे तोड़ देंगे.

MarkdownDirectoryWatcherService.cs
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 'मुझे किसी भी समस्या का पता लगाने में मदद करने के लिए जब यह अधिक आसानी से है' (मैं अधिक आसानी से है) एक लेख है उस पर भी!___

flowchart LR A[Start OnChangedAsync] --> B{Is e.Name null} B -- Yes --> C[Return] B -- No --> D[Start Activity and Set File Parameters] D --> E[Execute retryPolicy] E --> F[Process and Save Markdown File] F --> G{Is language English} G -- Yes --> H[Kick off Translation] G -- No --> I[Skip Translation] H --> J[Complete Activity] I --> J J --> K[Handle Errors]
OnChangedAsync
   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);
        }
    }
## मिटाए गए

यह वास्तव में सरल है, यह सिर्फ पता चलता है कि जब एक फ़ाइल मिटा दी जाती है और इसे डेटाबेस से हटा दिया जाता है; मैं इसे सामान्य रूप से एक बेवकूफ के रूप में प्रयोग करता हूँ जब मैं आलेखों को अपलोड करने के लिए सरल कर रहा हूँ तब तुरंत वे त्रुटियाँ हैं. जैसा कि आप इसे देख सकते हैं यह देखने के लिए कि फ़ाइल अनुवाद किया गया है या नहीं, यह उस फ़ाइल के सभी अनुवादों को मिटा देता है.

flowchart LR A[Start OnDeleted] --> B{Is e.Name null} B -- Yes --> C[Return] B -- No --> D[Start Activity and Set Parameters: isTranslated, language, slug] D --> E{Is file translated} E -- Yes --> F[Set language and slug for translation] E -- No --> G[Delete all translated files] F --> H[Create DI Scope and Get IBlogService] G --> H H --> I[Delete Post from Database] I --> J[Complete Activity] J --> K[Handle Errors if any]
 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 प्रक्रिया लॉग करने के लिए.

logo

©2024 Scott Galloway