Files
donniemarko/internal/scanner/scanner.go
adminoo b571588b15 feat(release): v0.2.0
commit 78d6c27c8940da32a6de8e64327c86f74fdaa2eb
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 12:59:22 2026 +0100

    feat: freebsd log rotation config thingie

commit 55af4e6c70122e679272ed247c26e04b1247f694
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 12:58:43 2026 +0100

    feat: embed templates, static resolution

commit 29c917f929a7378ec29c54315ee2e9f420747787
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 10:44:34 2026 +0100

    feat: set log file path

commit 294fd3d1549979eab63587ceec6ff5d0978e9afc
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 10:23:53 2026 +0100

    feat: logging HTTP request

commit c9ae80b240d58e1abed7ae3b7b2c3b283a31f1a1
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 09:54:05 2026 +0100

    feat: freebsd-specific compile target and scripts

commit 86ca154dedd19aa1fe5f571c445dcf17a8396bfa
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 09:25:16 2026 +0100

    feat: mobile friendly CSS

commit 199f4319e0b08a4b6d595d7eb3effb6db6c7beec
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 09:25:03 2026 +0100

    feat: persisting rendered note

commit 865e258237e45d7c542685a4653bcad3c5af259d
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 08:06:38 2026 +0100

    fix: grouping notes by folder

commit 242d1d074c92461f38212b033c7a9e383f9dc550
Author: adminoo <git@kadath.corp>
Date:   Tue Feb 3 16:52:50 2026 +0100

    feat: storage layer logic

    - Prune notes from db not matching current folder structure at start
    - Detect file system deletion on start by comparing in-db notes
    - Prevent updating of in-db notes at start if modification time is not
      newer
    - Delete by path

commit d75d46bc1ab22bd990d0fdc307e571fe52f0dd99
Author: adminoo <git@kadath.corp>
Date:   Tue Feb 3 15:27:07 2026 +0100

    feat: group notes by root folders

commit e1e25a938e717599332f7b40a449d9bb854b673a
Author: adminoo <git@kadath.corp>
Date:   Tue Feb 3 14:24:37 2026 +0100

    feat: size in kilobytes

commit 61220272a2df2b66c2b8e356ba359ed01de3bd12
Author: adminoo <git@kadath.corp>
Date:   Tue Feb 3 14:19:40 2026 +0100

    feat: styling inputs
2026-02-04 13:17:16 +01:00

185 lines
3.7 KiB
Go

package scanner
import (
"context"
"log"
"os"
"path/filepath"
"strings"
"time"
"donniemarko/internal/note"
)
type ChangeType int
const (
Unchanged ChangeType = iota
Created
Modified
Deleted
)
type ChangeHandler interface {
HandleCreate(path string) error
HandleModify(path string) error
HandleDelete(path string) error
}
type Change struct {
Type ChangeType
Path string
ModTime time.Time
}
type ScannerService struct {
RootDir string
Interval time.Duration
LastStates map[string]time.Time
handler ChangeHandler
}
func NewScanner(path string) *ScannerService {
return &ScannerService{
RootDir: filepath.Clean(path),
Interval: 5 * time.Second,
LastStates: make(map[string]time.Time),
}
}
// SeedExisting primes the scanner with already-indexed notes so the first scan can detect deletions.
func (s *ScannerService) SeedExisting(notes []*note.Note) {
for _, n := range notes {
if n == nil || n.Path == "" {
continue
}
path := n.Path
if !filepath.IsAbs(path) {
path = filepath.Join(s.RootDir, path)
}
s.LastStates[path] = n.UpdatedAt
}
}
func (s *ScannerService) SetHandler(handler ChangeHandler) {
s.handler = handler
}
// Scan walks the root folder and update the states of each notes if
// it has changed since the last time a scan occured
func (s *ScannerService) Scan() ([]Change, error) {
var changes []Change
currentStates := make(map[string]time.Time)
// Walk filesystem
filepath.Walk(s.RootDir, func(path string, info os.FileInfo, err error) error {
// skip the root dir itself
if s.RootDir == path {
return nil
}
// ignore anything that isn't a note
if !isValidNoteFile(path, info) {
return nil
}
currentStates[path] = info.ModTime()
lastMod, existed := s.LastStates[path]
if !existed {
// create the note if it didn't exist yet
// s.handler.HandleCreate(path)
changes = append(changes, Change{Type: Created, Path: path, ModTime: lastMod})
} else if info.ModTime().After(lastMod) {
changes = append(changes, Change{Type: Modified, Path: path, ModTime: info.ModTime()})
}
return nil
})
// Check for deletions
for path := range s.LastStates {
if _, exists := currentStates[path]; !exists {
changes = append(changes, Change{Type: Deleted, Path: path})
}
}
s.LastStates = currentStates
return changes, nil
}
// Monitor rescan the root folder at each new tick and handle state modifications
func (s *ScannerService) Monitor(ctx context.Context) error {
ticker := time.NewTicker(s.Interval)
defer ticker.Stop()
applyChanges := func(changes []Change) {
for _, change := range changes {
var err error
switch change.Type {
case Created:
err = s.handler.HandleCreate(change.Path)
case Modified:
err = s.handler.HandleModify(change.Path)
case Deleted:
err = s.handler.HandleDelete(change.Path)
}
if err != nil {
log.Printf("handler error for %s: %v", change.Path, err)
}
}
}
changes, err := s.Scan()
if err != nil {
log.Printf("scan error: %v", err)
} else {
applyChanges(changes)
}
for {
select {
case <-ticker.C:
changes, err := s.Scan()
if err != nil {
log.Printf("scan error: %v", err)
continue
}
applyChanges(changes)
case <-ctx.Done():
return ctx.Err()
}
}
}
func isValidNoteFile(path string, info os.FileInfo) bool {
// ignore temp and backup files
for _, nono := range []string{".#", "~"} {
if strings.Contains(path, nono) {
return false
}
}
// ignore files that are not markdown
if info.IsDir() || filepath.Ext(path) != ".md" {
return false
}
// ignore empty folder
if info.IsDir() {
files, err := os.ReadDir(path)
if err != nil {
log.Println(err.Error())
}
if len(files) == 0 {
return false
}
}
return true
}