Store mtime to the nanosecond so a same-second rewrite is re-hashed (closes #12)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
scan recorded mtime in whole seconds, so a file rewritten in place at the same size within the same second as its recorded mtime was classed unchanged and kept its old hashes. The files table keeps mtime as whole Unix seconds and gains mtime_nsec, the nanoseconds within that second. scan holds the mtime as a time.Time and decides "newer" by comparing Unix() and then Nanosecond(), so any time a filesystem can record compares in the right order; After would misorder one too late for a time.Time to hold without wrapping. The walk, a file given as an operand, and the content phase's recheck all move over. PRAGMA user_version stays 1, per the owner's ruling. README states what both columns hold. Model: opus-5-5
This commit was merged in pull request #96.
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The duplicate ladder (see hashSignature and README "Duplicate
|
||||
@@ -68,7 +69,7 @@ var errInterrupted = errors.New("scan interrupted")
|
||||
type fileRec struct {
|
||||
path string
|
||||
size int64
|
||||
mtime int64
|
||||
mtime time.Time
|
||||
dev uint64
|
||||
ino uint64
|
||||
}
|
||||
@@ -79,7 +80,7 @@ type fileRec struct {
|
||||
// they would dominate the scan's memory.
|
||||
type fileMeta struct {
|
||||
size int64
|
||||
mtime int64
|
||||
mtime time.Time
|
||||
hashed bool
|
||||
}
|
||||
|
||||
@@ -369,7 +370,7 @@ func (s *scanState) loadIndex(ctx context.Context, roots []string) error {
|
||||
s.existing = make(map[string]fileMeta)
|
||||
|
||||
return loadFileMeta(ctx, s.db,
|
||||
func(path string, size, mtime int64, hashed bool) {
|
||||
func(path string, size int64, mtime time.Time, hashed bool) {
|
||||
prog.increment()
|
||||
|
||||
if underAnyRoot(path, roots) {
|
||||
@@ -415,7 +416,7 @@ func (s *scanState) walkPhase(
|
||||
old, ok := s.existing[ev.rec.path]
|
||||
|
||||
switch {
|
||||
case !ok || old.size != ev.rec.size || old.mtime < ev.rec.mtime:
|
||||
case !ok || old.size != ev.rec.size || mtimeAfter(ev.rec.mtime, old.mtime):
|
||||
changed = append(changed, ev.rec)
|
||||
case old.hashed:
|
||||
delete(s.existing, ev.rec.path)
|
||||
@@ -807,7 +808,7 @@ func unchangedFile(r scanRec) (fileRec, bool, error) {
|
||||
}
|
||||
|
||||
if !fi.Mode().IsRegular() || fi.Size() != r.size ||
|
||||
fi.ModTime().Unix() > r.mtime {
|
||||
mtimeAfter(fi.ModTime(), r.mtime) {
|
||||
return fileRec{}, false, nil
|
||||
}
|
||||
|
||||
@@ -818,6 +819,16 @@ func unchangedFile(r scanRec) (fileRec, bool, error) {
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
// mtimeAfter reports whether mtime a is later than mtime b.
|
||||
// Not a.After(b): time.Time wraps an mtime past year 292 billion; Unix() undoes it.
|
||||
func mtimeAfter(a, b time.Time) bool {
|
||||
if a.Unix() != b.Unix() {
|
||||
return a.Unix() > b.Unix()
|
||||
}
|
||||
|
||||
return a.Nanosecond() > b.Nanosecond()
|
||||
}
|
||||
|
||||
// underAnyRoot reports whether path is any of the roots or lies under
|
||||
// one of them.
|
||||
func underAnyRoot(path string, roots []string) bool {
|
||||
@@ -966,7 +977,7 @@ func seedRoot(ctx context.Context, root string,
|
||||
sendEvent(ctx, events, walkEvent{rec: fileRec{
|
||||
path: root,
|
||||
size: fi.Size(),
|
||||
mtime: fi.ModTime().Unix(),
|
||||
mtime: fi.ModTime(),
|
||||
dev: dev,
|
||||
ino: ino,
|
||||
}})
|
||||
@@ -1124,7 +1135,7 @@ func emitFile(ctx context.Context, p string, e fs.DirEntry,
|
||||
sendEvent(ctx, events, walkEvent{rec: fileRec{
|
||||
path: p,
|
||||
size: info.Size(),
|
||||
mtime: info.ModTime().Unix(),
|
||||
mtime: info.ModTime(),
|
||||
dev: dev,
|
||||
ino: ino,
|
||||
}})
|
||||
|
||||
Reference in New Issue
Block a user