1- package mcp
1+ // Package rescan periodically recompiles a source tree into the store.
2+ package rescan
23
34import (
45 "context"
@@ -31,7 +32,32 @@ const (
3132 defaultSettleTime = 500 * time .Millisecond
3233)
3334
34- type RescanLoop struct {
35+ type Option func (* options )
36+
37+ type options struct {
38+ compileConfig config.CompileConfig
39+ logger * slog.Logger
40+ status * rescanstat.Status
41+ rescanLog * rescanlog.Sink
42+ }
43+
44+ func WithCompileConfig (cc config.CompileConfig ) Option {
45+ return func (o * options ) { o .compileConfig = cc }
46+ }
47+
48+ func WithLogger (l * slog.Logger ) Option {
49+ return func (o * options ) { o .logger = l }
50+ }
51+
52+ func WithStatus (s * rescanstat.Status ) Option {
53+ return func (o * options ) { o .status = s }
54+ }
55+
56+ func WithRescanLog (s * rescanlog.Sink ) Option {
57+ return func (o * options ) { o .rescanLog = s }
58+ }
59+
60+ type Loop struct {
3561 store * store.Store
3662 dir string
3763 bootstrapInterval time.Duration
@@ -50,22 +76,27 @@ type RescanLoop struct {
5076 onChange func ()
5177}
5278
53- // SetChangeObserver sets a callback fired after a scan that mutated the store, set before Run (nil disables).
54- func (r * RescanLoop ) SetChangeObserver (fn func ()) {
79+ func (r * Loop ) SetChangeObserver (fn func ()) {
5580 r .onChange = fn
5681}
5782
58- func (r * RescanLoop ) notifyChange () {
83+ func (r * Loop ) notifyChange () {
5984 if r .onChange != nil {
6085 r .onChange ()
6186 }
6287}
6388
64- func NewRescanLoop (st * store.Store , dir string , interval time.Duration , cc config.CompileConfig , logger * slog.Logger , status * rescanstat.Status , rescanLog * rescanlog.Sink ) (* RescanLoop , error ) {
89+ func New (st * store.Store , dir string , interval time.Duration , opts ... Option ) (* Loop , error ) {
90+ var o options
91+ for _ , opt := range opts {
92+ opt (& o )
93+ }
94+
6595 if interval <= 0 {
6696 interval = defaultRescanInterval
6797 }
68- logger = loghelper .OrDiscard (logger )
98+ logger := loghelper .OrDiscard (o .logger )
99+ status := o .status
69100 if status == nil {
70101 status = rescanstat .New ()
71102 }
@@ -75,7 +106,7 @@ func NewRescanLoop(st *store.Store, dir string, interval time.Duration, cc confi
75106 return nil , fmt .Errorf ("failed to load: %s: %w" , ignore .Path , err )
76107 }
77108
78- return & RescanLoop {
109+ return & Loop {
79110 store : st ,
80111 dir : dir ,
81112 bootstrapInterval : interval ,
@@ -87,16 +118,15 @@ func NewRescanLoop(st *store.Store, dir string, interval time.Duration, cc confi
87118 modTimes : make (map [string ]time.Time ),
88119 logger : logger ,
89120 ignore : matcher ,
90- compileOpts : compiler .ConfigOptions (cc ),
121+ compileOpts : compiler .ConfigOptions (o . compileConfig ),
91122 status : status ,
92- rescanLog : rescanLog ,
123+ rescanLog : o . rescanLog ,
93124 }, nil
94125}
95126
96- func (r * RescanLoop ) Run (ctx context.Context ) {
127+ func (r * Loop ) Run (ctx context.Context ) {
97128 r .reloadConfig ()
98129
99- // Startup reconcile catches edits made between the last compile and now.
100130 if r .enabled {
101131 r .scan (ctx )
102132 }
@@ -122,8 +152,7 @@ func (r *RescanLoop) Run(ctx context.Context) {
122152 }
123153}
124154
125- // Re-source the rescan block from config.json.
126- func (r * RescanLoop ) reloadConfig () (intervalChanged bool ) {
155+ func (r * Loop ) reloadConfig () (intervalChanged bool ) {
127156 path := filepath .Join (r .dir , config .DirName , config .FileName )
128157
129158 data , err := os .ReadFile (path )
@@ -167,7 +196,7 @@ func (r *RescanLoop) reloadConfig() (intervalChanged bool) {
167196 return intervalChanged
168197}
169198
170- func (r * RescanLoop ) scan (ctx context.Context ) {
199+ func (r * Loop ) scan (ctx context.Context ) {
171200 var changed []string
172201 seen := make (map [string ]bool , len (r .modTimes ))
173202 pending := make (map [string ]time.Time )
@@ -221,7 +250,6 @@ func (r *RescanLoop) scan(ctx context.Context) {
221250
222251 mtime := info .ModTime ()
223252
224- // Skip files still settling.
225253 if now .Sub (mtime ) < r .settle {
226254 return nil
227255 }
@@ -234,7 +262,6 @@ func (r *RescanLoop) scan(ctx context.Context) {
234262 return nil
235263 })
236264
237- // Purge entries for deleted files only when the walk was complete.
238265 if walkErr != nil {
239266 r .logger .Error ("rescan: walk aborted" , "dir" , r .dir , "err" , walkErr )
240267 snap .Error = walkErr .Error ()
@@ -255,7 +282,6 @@ func (r *RescanLoop) scan(ctx context.Context) {
255282 deleted = append (deleted , rel )
256283 }
257284
258- // Lock only around store mutations.
259285 r .store .OpMu .Lock ()
260286 defer r .store .OpMu .Unlock ()
261287
@@ -297,8 +323,7 @@ func (r *RescanLoop) scan(ctx context.Context) {
297323 r .notifyChange ()
298324}
299325
300- // Returns one PurgedFile per source file that lost nodes.
301- func (r * RescanLoop ) reconcileDeleted (ctx context.Context , deleted []string ) []rescanstat.PurgedFile {
326+ func (r * Loop ) reconcileDeleted (ctx context.Context , deleted []string ) []rescanstat.PurgedFile {
302327 if len (deleted ) == 0 {
303328 return nil
304329 }
@@ -323,7 +348,6 @@ func (r *RescanLoop) reconcileDeleted(ctx context.Context, deleted []string) []r
323348 OldContent : n .Content ,
324349 })
325350
326- // "rem:" prefix keeps the delete cursor-hash domain disjoint from compile's.
327351 synthetic = append (synthetic , & parser.ContextNode {ContentHash : "rem:" + n .ID + ":" + n .ContentHash })
328352 counts [n .SourceFile ]++
329353 }
0 commit comments