Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The first time a file content changes the new loaded information are not provided to the generator #115

Open
MangelMaxime opened this issue Jul 26, 2022 · 1 comment
Labels
bug Something isn't working

Comments

@MangelMaxime
Copy link
Contributor

Describe the bug

To Reproduce
Steps to reproduce the behaviour:

  1. Start dotnet fornax watch
  2. Wait for the first generation
  3. Change the content of one of the file
  4. See that the new generated file still have the old content
  5. Change the content of one of the file
  6. See that the new generated file is now using the correct content
  7. Change the content of one of the file
  8. See that the new generated file is now using the correct content
  9. etc. (From now, the generation will always be correct)

Expected behaviour

The file should always used the last loaded information even on the first change.

Screenshots

First generation (all good)

Loader and generator have the same informations

==== POST LOADER =====
"---
layout: post
title: Some nice post title
author: k_cieslak
published: 2020-02-19
tags: [ 'F#', 'blog']
---
# Introduction

First
"
==== POST GENERATOR =====
"# Introduction

First
"
[22:10:56] '/home/mmangel/Workspaces/GitHub/MangelMaxime/mangelmaxime.github.io/_public/posts/first.html' generated in 58ms
Generation time: 00:00:04.6666586
[22:10:56] Watch mode started. Press any key to exit.
[22:10:56 INF] Smooth! Suave listener started in 32.428ms with binding 127.0.0.1:8080

Second generation (not good) : first time user update a file

Loader is using the new file content while the generator still received the old informations.

[22:11:11] Changes detected: /home/mmangel/Workspaces/GitHub/MangelMaxime/mangelmaxime.github.io/posts/first.md
==== POST LOADER =====
"---
layout: post
title: Some nice post title
author: k_cieslak
published: 2020-02-19
tags: [ 'F#', 'blog']
---
# Introduction

File is updated
"
==== POST GENERATOR =====
"# Introduction

First
"
[22:11:12] '/home/mmangel/Workspaces/GitHub/MangelMaxime/mangelmaxime.github.io/_public/posts/first.html' generated in 0ms
Generation time: 00:00:00.7931652

Third and more generation (all good)

Loader and generator have the same informations

[22:12:07] Changes detected: /home/mmangel/Workspaces/GitHub/MangelMaxime/mangelmaxime.github.io/posts/first.md
==== POST LOADER =====
"---
layout: post
title: Some nice post title
author: k_cieslak
published: 2020-02-19
tags: [ 'F#', 'blog']
---
# Introduction

File is updated 2
"
==== POST GENERATOR =====
"# Introduction

File is updated 2
"
[22:12:08] '/home/mmangel/Workspaces/GitHub/MangelMaxime/mangelmaxime.github.io/_public/posts/first.html' generated in 49ms
Generation time: 00:00:01.0151255

Could it be caused by a cache mechanism?

Postloader.fsx
#r "../_lib/Fornax.Core.dll"
#load "../.paket/load/main.group.fsx"
#load "../utils/Log.fsx"
#load "../utils/Helpers.fsx"

open System
open System.IO
open System.Diagnostics
open System.Threading.Tasks
open Legivel.Serialization
open Helpers

type Post =
    {
        relativeFile: string
        link : string
        title: string
        author: string option
        published: DateTime option
        tags: string list
        content: string
    }

type PostFrontMatter =
    {
        title: string
        author: string option
        published: DateTime option
        tags: string list
    }

let contentDir = "posts"

let private getLastModified (fileName: string) =
    async {
        let psi = ProcessStartInfo()
        psi.FileName <- "git"
        psi.Arguments <- $"--no-pager log -1 --format=%%ai \"%s{fileName}\""
        psi.RedirectStandardError <- true
        psi.RedirectStandardOutput <- true
        psi.CreateNoWindow <- true
        psi.WindowStyle <- ProcessWindowStyle.Hidden
        psi.UseShellExecute <- false

        use p = new Process()
        p.StartInfo <- psi
        p.Start() |> ignore

        let outTask =
            Task.WhenAll(
                [|
                    p.StandardOutput.ReadToEndAsync()
                    p.StandardError.ReadToEndAsync()
                |]
            )

        do! p.WaitForExitAsync() |> Async.AwaitTask
        let! result = outTask |> Async.AwaitTask

        if p.ExitCode = 0 then
            // File is not in the git repo
            if String.IsNullOrEmpty result[0] then
                return DateTime.Now
            else
                return DateTime.Parse(result[0])
        else
            Log.error $"Failed to get last modified information %s{result[1]}"
            return DateTime.Now
    }
    |> Async.RunSynchronously


let private loadFile (rootDir: string) (absolutePath: string) =
    let text = File.ReadAllText absolutePath

    printfn "==== POST LOADER ====="
    printfn "%A" text

    let relativePath =
        Path.relativePath rootDir absolutePath

    let lines = text.Replace("\r\n", "\n").Split("\n")

    let x = getLastModified absolutePath

    let firstLine = Array.tryHead lines

    if firstLine <> Some "---" then
        Log.error $"File '%s{relativePath}' does not have a front matter"
        None

    else
        let lines = lines |> Array.skip 1

        let frontMatterLines =
            lines
            |> Array.takeWhile (fun line -> line <> "---")

        let markdownContent =
            lines
            |> Array.skip (frontMatterLines.Length + 1)
            |> String.concat "\n"

        let frontMatterContent = frontMatterLines |> String.concat "\n"

        let frontMatterResult =
            Deserialize<PostFrontMatter> frontMatterContent
            |> List.head

        match frontMatterResult with
        | Error error ->
            Log.error $"Error parsing front matter in file '%s{relativePath}': %A{error}"
            None

        | Success frontMatter ->
            if not (frontMatter.Warn.IsEmpty) then
                for warning in frontMatter.Warn do
                    Log.warn $"Warning in file '%s{relativePath}': %A{warning}"

            let link =
                Path.ChangeExtension(relativePath, "html")

            {
                relativeFile = relativePath
                link = link
                title = ""
                author = None
                published = frontMatter.Data.published
                tags = frontMatter.Data.tags
                content = markdownContent
            }
            |> Some

let loader (projectRoot: string) (siteContent: SiteContents) =
    let postsPath = Path.Combine(projectRoot, contentDir)
    let options = EnumerationOptions(RecurseSubdirectories = true)
    let files = Directory.GetFiles(postsPath, "*", options)

    files
    |> Array.filter (fun n -> n.EndsWith ".md")
    |> Array.map (loadFile projectRoot)
    // Only keep the valid post to avoid to propagate errors
    |> Array.filter Option.isSome
    |> Array.map Option.get
    |> Array.iter siteContent.Add

    siteContent
Postloader.fsx
#r "../_lib/Fornax.Core.dll"
#load "layout.fsx"

open Giraffe.ViewEngine

let generate (ctx: SiteContents) (_projectRoot: string) (page: string) =

    let postOpt =
        ctx.TryGetValues<PostLoader.Post>()
        |> Option.defaultValue Seq.empty
        |> Seq.tryFind (fun post -> post.relativeFile = page)

    match postOpt with
    | None ->
        let error =
            {
                Path = page
                Message = $"Post %s{page} not found in the context"
                Phase = Generating
            }

        ctx.AddError error

        Layout.generationErrorPage ctx

    | Some post ->

        printfn "==== POST GENERATOR ====="
        printfn "%A" post.content
        div [] [ str post.content ] |> Layout.mainPage ctx
@Krzysztof-Cieslak Krzysztof-Cieslak added the bug Something isn't working label Aug 8, 2022
@drewknab
Copy link
Contributor

drewknab commented Apr 2, 2024

Late on this (incredibly so) but it appears to be an issue with the lastAccessed state map.

It's empty on the first save so the watcher stores the lastAccessed time and the write time to the same value.
On the second save the lastTimeWrite and lastAccessed time (in seconds) are equal.

if shouldHandle then
    let lastTimeWrite = File.GetLastWriteTime(e.FullPath)
    match lastAccessed.TryFind e.FullPath with
    | Some lt when Math.Abs((lt - lastTimeWrite).Seconds) < 1 -> ()
    | _ ->
        informationfn "[%s] Changes detected: %s" (DateTime.Now.ToString("HH:mm:ss")) e.FullPath
        lastAccessed <- lastAccessed.Add(e.FullPath, lastTimeWrite)
        guardedGenerate ())

I'll fiddle around with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants