walk.go 5.0 KB

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