123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- package main
- 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
- }
|