walk.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "log"
  7. "os"
  8. "path"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "github.com/calmh/syncthing/protocol"
  13. )
  14. const BlockSize = 128 * 1024
  15. type File struct {
  16. Name string
  17. Flags uint32
  18. Modified int64
  19. Version uint32
  20. Size int64
  21. Blocks []Block
  22. }
  23. func (f File) String() string {
  24. return fmt.Sprintf("File{Name:%q, Flags:0x%x, Modified:%d, Version:%d, Size:%d, NumBlocks:%d}",
  25. f.Name, f.Flags, f.Modified, f.Version, f.Size, len(f.Blocks))
  26. }
  27. func (f File) Equals(o File) bool {
  28. return f.Modified == o.Modified && f.Version == o.Version
  29. }
  30. func (f File) NewerThan(o File) bool {
  31. return f.Modified > o.Modified || (f.Modified == o.Modified && f.Version > o.Version)
  32. }
  33. func isTempName(name string) bool {
  34. return strings.HasPrefix(path.Base(name), ".syncthing.")
  35. }
  36. func tempName(name string, modified int64) string {
  37. tdir := path.Dir(name)
  38. tname := fmt.Sprintf(".syncthing.%s.%d", path.Base(name), modified)
  39. return path.Join(tdir, tname)
  40. }
  41. func (m *Model) loadIgnoreFiles(ign map[string][]string) filepath.WalkFunc {
  42. return func(p string, info os.FileInfo, err error) error {
  43. if err != nil {
  44. return nil
  45. }
  46. rn, err := filepath.Rel(m.dir, p)
  47. if err != nil {
  48. return nil
  49. }
  50. if pn, sn := path.Split(rn); sn == ".stignore" {
  51. pn := strings.Trim(pn, "/")
  52. bs, _ := ioutil.ReadFile(p)
  53. lines := bytes.Split(bs, []byte("\n"))
  54. var patterns []string
  55. for _, line := range lines {
  56. if len(line) > 0 {
  57. patterns = append(patterns, string(line))
  58. }
  59. }
  60. ign[pn] = patterns
  61. }
  62. return nil
  63. }
  64. }
  65. func (m *Model) walkAndHashFiles(res *[]File, ign map[string][]string) filepath.WalkFunc {
  66. return func(p string, info os.FileInfo, err error) error {
  67. if err != nil {
  68. if m.trace["file"] {
  69. log.Printf("FILE: %q: %v", p, err)
  70. }
  71. return nil
  72. }
  73. if isTempName(p) {
  74. return nil
  75. }
  76. rn, err := filepath.Rel(m.dir, p)
  77. if err != nil {
  78. return nil
  79. }
  80. if _, sn := path.Split(rn); sn == ".stignore" {
  81. // We never sync the .stignore files
  82. return nil
  83. }
  84. if ignoreFile(ign, rn) {
  85. if m.trace["file"] {
  86. log.Println("FILE: IGNORE:", rn)
  87. }
  88. return nil
  89. }
  90. if info.Mode()&os.ModeType == 0 {
  91. modified := info.ModTime().Unix()
  92. m.lmut.RLock()
  93. lf, ok := m.local[rn]
  94. m.lmut.RUnlock()
  95. if ok && lf.Modified == modified {
  96. if nf := uint32(info.Mode()); nf != lf.Flags {
  97. lf.Flags = nf
  98. lf.Version++
  99. }
  100. *res = append(*res, lf)
  101. } else {
  102. if cur, prev := m.sup.suppress(rn, info.Size(), time.Now()); cur {
  103. if m.trace["file"] {
  104. log.Printf("FILE: SUPPRESS: %q change bw over threshold", rn)
  105. }
  106. if !prev {
  107. log.Printf("INFO: Changes to %q are being temporarily suppressed because it changes too frequently.", rn)
  108. }
  109. if ok {
  110. lf.Flags = protocol.FlagInvalid
  111. lf.Version++
  112. *res = append(*res, lf)
  113. }
  114. return nil
  115. } else if prev && !cur {
  116. log.Printf("INFO: Changes to %q are no longer suppressed.", rn)
  117. }
  118. if m.trace["file"] {
  119. log.Printf("FILE: Hash %q", p)
  120. }
  121. fd, err := os.Open(p)
  122. if err != nil {
  123. if m.trace["file"] {
  124. log.Printf("FILE: %q: %v", p, err)
  125. }
  126. return nil
  127. }
  128. defer fd.Close()
  129. blocks, err := Blocks(fd, BlockSize)
  130. if err != nil {
  131. if m.trace["file"] {
  132. log.Printf("FILE: %q: %v", p, err)
  133. }
  134. return nil
  135. }
  136. f := File{
  137. Name: rn,
  138. Size: info.Size(),
  139. Flags: uint32(info.Mode()),
  140. Modified: modified,
  141. Blocks: blocks,
  142. }
  143. *res = append(*res, f)
  144. }
  145. }
  146. return nil
  147. }
  148. }
  149. // Walk returns the list of files found in the local repository by scanning the
  150. // file system. Files are blockwise hashed.
  151. func (m *Model) Walk(followSymlinks bool) (files []File, ignore map[string][]string) {
  152. ignore = make(map[string][]string)
  153. hashFiles := m.walkAndHashFiles(&files, ignore)
  154. filepath.Walk(m.dir, m.loadIgnoreFiles(ignore))
  155. filepath.Walk(m.dir, hashFiles)
  156. if followSymlinks {
  157. d, err := os.Open(m.dir)
  158. if err != nil {
  159. return
  160. }
  161. defer d.Close()
  162. fis, err := d.Readdir(-1)
  163. if err != nil {
  164. return
  165. }
  166. for _, info := range fis {
  167. if info.Mode()&os.ModeSymlink != 0 {
  168. dir := path.Join(m.dir, info.Name()) + "/"
  169. filepath.Walk(dir, m.loadIgnoreFiles(ignore))
  170. filepath.Walk(dir, hashFiles)
  171. }
  172. }
  173. }
  174. return
  175. }
  176. func (m *Model) cleanTempFile(path string, info os.FileInfo, err error) error {
  177. if err != nil {
  178. return err
  179. }
  180. if info.Mode()&os.ModeType == 0 && isTempName(path) {
  181. if m.trace["file"] {
  182. log.Printf("FILE: Remove %q", path)
  183. }
  184. os.Remove(path)
  185. }
  186. return nil
  187. }
  188. func (m *Model) cleanTempFiles() {
  189. filepath.Walk(m.dir, m.cleanTempFile)
  190. }
  191. func ignoreFile(patterns map[string][]string, file string) bool {
  192. first, last := path.Split(file)
  193. for prefix, pats := range patterns {
  194. if len(prefix) == 0 || prefix == first || strings.HasPrefix(first, prefix+"/") {
  195. for _, pattern := range pats {
  196. if match, _ := path.Match(pattern, last); match {
  197. return true
  198. }
  199. }
  200. }
  201. }
  202. return false
  203. }