walk.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. package model
  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. return nil
  74. }
  75. if isTempName(p) {
  76. return nil
  77. }
  78. rn, err := filepath.Rel(m.dir, p)
  79. if err != nil {
  80. return nil
  81. }
  82. if _, sn := path.Split(rn); sn == ".stignore" {
  83. // We never sync the .stignore files
  84. return nil
  85. }
  86. if ignoreFile(ign, rn) {
  87. if m.trace["file"] {
  88. log.Println("FILE: IGNORE:", rn)
  89. }
  90. return nil
  91. }
  92. if info.Mode()&os.ModeType == 0 {
  93. fi, err := os.Stat(p)
  94. if err != nil {
  95. return nil
  96. }
  97. modified := fi.ModTime().Unix()
  98. m.RLock()
  99. hf, ok := m.local[rn]
  100. m.RUnlock()
  101. if ok && hf.Modified == modified {
  102. if nf := uint32(info.Mode()); nf != hf.Flags {
  103. hf.Flags = nf
  104. hf.Version++
  105. }
  106. *res = append(*res, hf)
  107. } else {
  108. m.Lock()
  109. if m.shouldSuppressChange(rn) {
  110. if m.trace["file"] {
  111. log.Println("FILE: SUPPRESS:", rn, m.fileWasSuppressed[rn], time.Since(m.fileLastChanged[rn]))
  112. }
  113. if ok {
  114. hf.Flags = protocol.FlagInvalid
  115. hf.Version++
  116. *res = append(*res, hf)
  117. }
  118. m.Unlock()
  119. return nil
  120. }
  121. m.Unlock()
  122. if m.trace["file"] {
  123. log.Printf("FILE: Hash %q", p)
  124. }
  125. fd, err := os.Open(p)
  126. if err != nil {
  127. if m.trace["file"] {
  128. log.Printf("FILE: %q: %v", p, err)
  129. }
  130. return nil
  131. }
  132. defer fd.Close()
  133. blocks, err := Blocks(fd, BlockSize)
  134. if err != nil {
  135. if m.trace["file"] {
  136. log.Printf("FILE: %q: %v", p, err)
  137. }
  138. return nil
  139. }
  140. f := File{
  141. Name: rn,
  142. Flags: uint32(info.Mode()),
  143. Modified: modified,
  144. Blocks: blocks,
  145. }
  146. *res = append(*res, f)
  147. }
  148. }
  149. return nil
  150. }
  151. }
  152. // Walk returns the list of files found in the local repository by scanning the
  153. // file system. Files are blockwise hashed.
  154. func (m *Model) Walk(followSymlinks bool) (files []File, ignore map[string][]string) {
  155. ignore = make(map[string][]string)
  156. hashFiles := m.walkAndHashFiles(&files, ignore)
  157. filepath.Walk(m.dir, m.loadIgnoreFiles(ignore))
  158. filepath.Walk(m.dir, hashFiles)
  159. if followSymlinks {
  160. d, err := os.Open(m.dir)
  161. if err != nil {
  162. return
  163. }
  164. defer d.Close()
  165. fis, err := d.Readdir(-1)
  166. if err != nil {
  167. return
  168. }
  169. for _, fi := range fis {
  170. if fi.Mode()&os.ModeSymlink != 0 {
  171. dir := path.Join(m.dir, fi.Name()) + "/"
  172. filepath.Walk(dir, m.loadIgnoreFiles(ignore))
  173. filepath.Walk(dir, hashFiles)
  174. }
  175. }
  176. }
  177. return
  178. }
  179. func (m *Model) cleanTempFile(path string, info os.FileInfo, err error) error {
  180. if err != nil {
  181. return err
  182. }
  183. if info.Mode()&os.ModeType == 0 && isTempName(path) {
  184. if m.trace["file"] {
  185. log.Printf("FILE: Remove %q", path)
  186. }
  187. os.Remove(path)
  188. }
  189. return nil
  190. }
  191. func (m *Model) cleanTempFiles() {
  192. filepath.Walk(m.dir, m.cleanTempFile)
  193. }
  194. func ignoreFilter(patterns map[string][]string, files []File) (filtered []File) {
  195. for _, f := range files {
  196. if !ignoreFile(patterns, f.Name) {
  197. filtered = append(filtered, f)
  198. }
  199. }
  200. return filtered
  201. }
  202. func ignoreFile(patterns map[string][]string, file string) bool {
  203. first, last := path.Split(file)
  204. for prefix, pats := range patterns {
  205. if len(prefix) == 0 || prefix == first || strings.HasPrefix(first, prefix+"/") {
  206. for _, pattern := range pats {
  207. if match, _ := path.Match(pattern, last); match {
  208. return true
  209. }
  210. }
  211. }
  212. }
  213. return false
  214. }