mirror of
https://github.com/syncthing/syncthing.git
synced 2024-11-16 18:41:59 -07:00
244 lines
5.0 KiB
Go
244 lines
5.0 KiB
Go
package model
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/calmh/syncthing/protocol"
|
|
)
|
|
|
|
const BlockSize = 128 * 1024
|
|
|
|
type File struct {
|
|
Name string
|
|
Flags uint32
|
|
Modified int64
|
|
Version uint32
|
|
Blocks []Block
|
|
}
|
|
|
|
func (f File) Size() (bytes int) {
|
|
for _, b := range f.Blocks {
|
|
bytes += int(b.Size)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (f File) String() string {
|
|
return fmt.Sprintf("File{Name:%q, Flags:0x%x, Modified:%d, Version:%d, NumBlocks:%d}",
|
|
f.Name, f.Flags, f.Modified, f.Version, len(f.Blocks))
|
|
}
|
|
|
|
func (f File) Equals(o File) bool {
|
|
return f.Modified == o.Modified && f.Version == o.Version
|
|
}
|
|
|
|
func (f File) NewerThan(o File) bool {
|
|
return f.Modified > o.Modified || (f.Modified == o.Modified && f.Version > o.Version)
|
|
}
|
|
|
|
func isTempName(name string) bool {
|
|
return strings.HasPrefix(path.Base(name), ".syncthing.")
|
|
}
|
|
|
|
func tempName(name string, modified int64) string {
|
|
tdir := path.Dir(name)
|
|
tname := fmt.Sprintf(".syncthing.%s.%d", path.Base(name), modified)
|
|
return path.Join(tdir, tname)
|
|
}
|
|
|
|
func (m *Model) loadIgnoreFiles(ign map[string][]string) filepath.WalkFunc {
|
|
return func(p string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
rn, err := filepath.Rel(m.dir, p)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
if pn, sn := path.Split(rn); sn == ".stignore" {
|
|
pn := strings.Trim(pn, "/")
|
|
bs, _ := ioutil.ReadFile(p)
|
|
lines := bytes.Split(bs, []byte("\n"))
|
|
var patterns []string
|
|
for _, line := range lines {
|
|
if len(line) > 0 {
|
|
patterns = append(patterns, string(line))
|
|
}
|
|
}
|
|
ign[pn] = patterns
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (m *Model) walkAndHashFiles(res *[]File, ign map[string][]string) filepath.WalkFunc {
|
|
return func(p string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
if m.trace["file"] {
|
|
log.Printf("FILE: %q: %v", p, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if isTempName(p) {
|
|
return nil
|
|
}
|
|
|
|
rn, err := filepath.Rel(m.dir, p)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
if _, sn := path.Split(rn); sn == ".stignore" {
|
|
// We never sync the .stignore files
|
|
return nil
|
|
}
|
|
|
|
if ignoreFile(ign, rn) {
|
|
if m.trace["file"] {
|
|
log.Println("FILE: IGNORE:", rn)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if info.Mode()&os.ModeType == 0 {
|
|
modified := info.ModTime().Unix()
|
|
|
|
m.lmut.RLock()
|
|
lf, ok := m.local[rn]
|
|
m.lmut.RUnlock()
|
|
|
|
if ok && lf.Modified == modified {
|
|
if nf := uint32(info.Mode()); nf != lf.Flags {
|
|
lf.Flags = nf
|
|
lf.Version++
|
|
}
|
|
*res = append(*res, lf)
|
|
} else {
|
|
if cur, prev := m.sup.suppress(rn, info.Size(), time.Now()); cur {
|
|
if m.trace["file"] {
|
|
log.Printf("FILE: SUPPRESS: %q change bw over threshold", rn)
|
|
}
|
|
if !prev {
|
|
log.Printf("INFO: Changes to %q are being temporarily suppressed because it changes too frequently.", rn)
|
|
}
|
|
|
|
if ok {
|
|
lf.Flags = protocol.FlagInvalid
|
|
lf.Version++
|
|
*res = append(*res, lf)
|
|
}
|
|
return nil
|
|
} else if prev && !cur {
|
|
log.Printf("INFO: Changes to %q are no longer suppressed.", rn)
|
|
}
|
|
|
|
if m.trace["file"] {
|
|
log.Printf("FILE: Hash %q", p)
|
|
}
|
|
fd, err := os.Open(p)
|
|
if err != nil {
|
|
if m.trace["file"] {
|
|
log.Printf("FILE: %q: %v", p, err)
|
|
}
|
|
return nil
|
|
}
|
|
defer fd.Close()
|
|
|
|
blocks, err := Blocks(fd, BlockSize)
|
|
if err != nil {
|
|
if m.trace["file"] {
|
|
log.Printf("FILE: %q: %v", p, err)
|
|
}
|
|
return nil
|
|
}
|
|
f := File{
|
|
Name: rn,
|
|
Flags: uint32(info.Mode()),
|
|
Modified: modified,
|
|
Blocks: blocks,
|
|
}
|
|
*res = append(*res, f)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Walk returns the list of files found in the local repository by scanning the
|
|
// file system. Files are blockwise hashed.
|
|
func (m *Model) Walk(followSymlinks bool) (files []File, ignore map[string][]string) {
|
|
ignore = make(map[string][]string)
|
|
|
|
hashFiles := m.walkAndHashFiles(&files, ignore)
|
|
|
|
filepath.Walk(m.dir, m.loadIgnoreFiles(ignore))
|
|
filepath.Walk(m.dir, hashFiles)
|
|
|
|
if followSymlinks {
|
|
d, err := os.Open(m.dir)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer d.Close()
|
|
|
|
fis, err := d.Readdir(-1)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, info := range fis {
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
|
dir := path.Join(m.dir, info.Name()) + "/"
|
|
filepath.Walk(dir, m.loadIgnoreFiles(ignore))
|
|
filepath.Walk(dir, hashFiles)
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (m *Model) cleanTempFile(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.Mode()&os.ModeType == 0 && isTempName(path) {
|
|
if m.trace["file"] {
|
|
log.Printf("FILE: Remove %q", path)
|
|
}
|
|
os.Remove(path)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Model) cleanTempFiles() {
|
|
filepath.Walk(m.dir, m.cleanTempFile)
|
|
}
|
|
|
|
func ignoreFile(patterns map[string][]string, file string) bool {
|
|
first, last := path.Split(file)
|
|
for prefix, pats := range patterns {
|
|
if len(prefix) == 0 || prefix == first || strings.HasPrefix(first, prefix+"/") {
|
|
for _, pattern := range pats {
|
|
if match, _ := path.Match(pattern, last); match {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|