integration.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. package watch
  2. import (
  3. "log"
  4. "os"
  5. "strings"
  6. "sync"
  7. "time"
  8. "github.com/smartystreets/goconvey/web/server/messaging"
  9. )
  10. type Watcher struct {
  11. nap time.Duration
  12. rootFolder string
  13. folderDepth int
  14. ignoredFolders map[string]struct{}
  15. fileSystemState int64
  16. paused bool
  17. stopped bool
  18. watchSuffixes []string
  19. excludedDirs []string
  20. input chan messaging.WatcherCommand
  21. output chan messaging.Folders
  22. lock sync.RWMutex
  23. }
  24. func NewWatcher(rootFolder string, folderDepth int, nap time.Duration,
  25. input chan messaging.WatcherCommand, output chan messaging.Folders, watchSuffixes string, excludedDirs []string) *Watcher {
  26. return &Watcher{
  27. nap: nap,
  28. rootFolder: rootFolder,
  29. folderDepth: folderDepth,
  30. input: input,
  31. output: output,
  32. watchSuffixes: strings.Split(watchSuffixes, ","),
  33. excludedDirs: excludedDirs,
  34. ignoredFolders: make(map[string]struct{}),
  35. }
  36. }
  37. func (this *Watcher) Listen() {
  38. for {
  39. if this.stopped {
  40. return
  41. }
  42. select {
  43. case command := <-this.input:
  44. this.respond(command)
  45. default:
  46. if !this.paused {
  47. this.scan()
  48. }
  49. time.Sleep(this.nap)
  50. }
  51. }
  52. }
  53. func (this *Watcher) respond(command messaging.WatcherCommand) {
  54. switch command.Instruction {
  55. case messaging.WatcherAdjustRoot:
  56. log.Println("Adjusting root...")
  57. this.rootFolder = command.Details
  58. this.execute()
  59. case messaging.WatcherIgnore:
  60. log.Println("Ignoring specified folders")
  61. this.ignore(command.Details)
  62. // Prevent a filesystem change due to the number of active folders changing
  63. _, checksum := this.gather()
  64. this.set(checksum)
  65. case messaging.WatcherReinstate:
  66. log.Println("Reinstating specified folders")
  67. this.reinstate(command.Details)
  68. // Prevent a filesystem change due to the number of active folders changing
  69. _, checksum := this.gather()
  70. this.set(checksum)
  71. case messaging.WatcherPause:
  72. log.Println("Pausing watcher...")
  73. this.paused = true
  74. case messaging.WatcherResume:
  75. log.Println("Resuming watcher...")
  76. this.paused = false
  77. case messaging.WatcherExecute:
  78. log.Println("Gathering folders for immediate execution...")
  79. this.execute()
  80. case messaging.WatcherStop:
  81. log.Println("Stopping the watcher...")
  82. close(this.output)
  83. this.stopped = true
  84. default:
  85. log.Println("Unrecognized command from server:", command.Instruction)
  86. }
  87. }
  88. func (this *Watcher) execute() {
  89. folders, _ := this.gather()
  90. this.sendToExecutor(folders)
  91. }
  92. func (this *Watcher) scan() {
  93. folders, checksum := this.gather()
  94. if checksum == this.fileSystemState {
  95. return
  96. }
  97. log.Println("File system state modified, publishing current folders...", this.fileSystemState, checksum)
  98. defer this.set(checksum)
  99. this.sendToExecutor(folders)
  100. }
  101. func (this *Watcher) gather() (folders messaging.Folders, checksum int64) {
  102. items := YieldFileSystemItems(this.rootFolder, this.excludedDirs)
  103. folderItems, profileItems, goFileItems := Categorize(items, this.rootFolder, this.watchSuffixes)
  104. for _, item := range profileItems {
  105. // TODO: don't even bother if the item's size is over a few hundred bytes...
  106. contents := ReadContents(item.Path)
  107. item.ProfileDisabled, item.ProfileTags, item.ProfileArguments = ParseProfile(contents)
  108. }
  109. folders = CreateFolders(folderItems)
  110. LimitDepth(folders, this.folderDepth)
  111. AttachProfiles(folders, profileItems)
  112. this.protectedRead(func() { MarkIgnored(folders, this.ignoredFolders) })
  113. active := ActiveFolders(folders)
  114. checksum = int64(len(active))
  115. checksum += Sum(active, profileItems)
  116. checksum += Sum(active, goFileItems)
  117. return folders, checksum
  118. }
  119. func (this *Watcher) set(state int64) {
  120. this.fileSystemState = state
  121. }
  122. func (this *Watcher) sendToExecutor(folders messaging.Folders) {
  123. this.output <- folders
  124. }
  125. func (this *Watcher) ignore(paths string) {
  126. this.protectedWrite(func() {
  127. for _, folder := range strings.Split(paths, string(os.PathListSeparator)) {
  128. this.ignoredFolders[folder] = struct{}{}
  129. log.Println("Currently ignored folders:", this.ignoredFolders)
  130. }
  131. })
  132. }
  133. func (this *Watcher) reinstate(paths string) {
  134. this.protectedWrite(func() {
  135. for _, folder := range strings.Split(paths, string(os.PathListSeparator)) {
  136. delete(this.ignoredFolders, folder)
  137. }
  138. })
  139. }
  140. func (this *Watcher) protectedWrite(protected func()) {
  141. this.lock.Lock()
  142. defer this.lock.Unlock()
  143. protected()
  144. }
  145. func (this *Watcher) protectedRead(protected func()) {
  146. this.lock.RLock()
  147. defer this.lock.RUnlock()
  148. protected()
  149. }