sync.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. "os/signal"
  7. "path/filepath"
  8. "regexp"
  9. "strings"
  10. "syscall"
  11. "time"
  12. "git.wecise.com/wecise/util/ssh"
  13. "github.com/spf13/cast"
  14. ccfg "github.com/wecisecode/util/cfg"
  15. "github.com/wecisecode/util/filewalker"
  16. clog "github.com/wecisecode/util/logger"
  17. )
  18. func init() {
  19. exitChan := make(chan os.Signal, 1)
  20. signal.Notify(exitChan, os.Interrupt, os.Kill, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGABRT, syscall.SIGTERM)
  21. go func() {
  22. <-exitChan
  23. os.Exit(1)
  24. }()
  25. }
  26. var config = ccfg.MConfig()
  27. var logger = clog.New()
  28. // 同步指定文件夹中的内容
  29. func PrintUsage() {
  30. println(`Usage:
  31. sync <frompath> <topath> [<copytopath> ...]
  32. <frompath> 源文件路径
  33. <topath> 目标文件路径,增量同步
  34. <copytopath> 副本文件路径,<topath>增量同步
  35. `)
  36. }
  37. var option = struct {
  38. Debug bool
  39. }{
  40. Debug: false,
  41. }
  42. func main() {
  43. var frompath string = ""
  44. var topath string = ""
  45. var copytopath = []string{}
  46. for _, arg := range os.Args[1:] {
  47. if strings.HasPrefix(arg, "-") {
  48. switch arg {
  49. case "-debug":
  50. option.Debug = true
  51. }
  52. } else if arg != "" {
  53. if frompath == "" {
  54. frompath = arg
  55. } else if topath == "" {
  56. topath = arg
  57. } else {
  58. copytopath = append(copytopath, arg)
  59. }
  60. }
  61. }
  62. switch {
  63. case frompath != "" && topath != "":
  64. err := sync(frompath, topath, copytopath...)
  65. if err != nil {
  66. println(err.Error())
  67. os.Exit(1)
  68. }
  69. default:
  70. PrintUsage()
  71. os.Exit(1)
  72. }
  73. }
  74. var REremotepath = regexp.MustCompile(`^([^:]+):([^@]+)@([^/]+)(/.*)`)
  75. var REabspath = regexp.MustCompile(`^((?:/.*)|(?:[a-zA-Z]:\.*)|(?:[a-zA-Z]:))`)
  76. func sync(frompath, topath string, copytopath ...string) (err error) {
  77. frompath, e := filepath.Abs(frompath)
  78. if e != nil {
  79. err = e
  80. return
  81. }
  82. topath, e = filepath.Abs(topath)
  83. if e != nil {
  84. err = e
  85. return
  86. }
  87. fmt.Println("sync from", frompath, "to", topath, "\ncopy to", copytopath)
  88. for {
  89. err = sync_once(frompath, topath, copytopath...)
  90. if err != nil {
  91. return
  92. }
  93. time.Sleep(5 * time.Second)
  94. }
  95. }
  96. func sync_once(frompath, topath string, copytopath ...string) (err error) {
  97. fw, e := filewalker.NewFileWalker([]string{frompath}, `^[^\.].*`) // orderby: dirfirst, filefirst, fullpath
  98. if e != nil {
  99. return e
  100. }
  101. count := 0
  102. e = fw.List(func(basedir, fpath string) bool {
  103. if option.Debug {
  104. fmt.Println("basedir=", basedir, ", fpath=", fpath)
  105. }
  106. absbasedir, e := filepath.Abs(basedir)
  107. if e != nil {
  108. err = e
  109. return false
  110. }
  111. hiddenpath := fmt.Sprintf(`.*\%c\..*`, os.PathSeparator)
  112. if regexp.MustCompile(`^[^\.].*`).MatchString(fpath) && !regexp.MustCompile(hiddenpath).MatchString(fpath) {
  113. fromfile := filepath.Join(absbasedir, fpath)
  114. tofile := filepath.Join(topath, fpath)
  115. if option.Debug {
  116. fmt.Println(time.Now().Format("2006-01-02 15:04:05"), fromfile, " ==> ", tofile)
  117. }
  118. fromfi, e := os.Stat(fromfile)
  119. if e != nil {
  120. err = fmt.Errorf("from file %v", e)
  121. return false
  122. }
  123. tofi, e := os.Stat(tofile)
  124. if e != nil && !os.IsNotExist(e) {
  125. err = fmt.Errorf("to file %v", e)
  126. return false
  127. }
  128. if tofi != nil && fromfi.Size() == tofi.Size() && fromfi.ModTime() == tofi.ModTime() {
  129. return true
  130. }
  131. progressinfo := fmt.Sprint(fromfile, " ==> ", tofile)
  132. fmt.Print(progressinfo)
  133. frombs, e := ReadFile(fromfile, progressinfo)
  134. if e != nil {
  135. err = fmt.Errorf("from file %v", e)
  136. return false
  137. }
  138. WriteFile(topath, fpath, frombs, fromfi.ModTime())
  139. for _, ctpath := range copytopath {
  140. WriteFile(ctpath, fpath, frombs, fromfi.ModTime())
  141. }
  142. count++
  143. }
  144. return true
  145. })
  146. if e != nil {
  147. return e
  148. }
  149. if err != nil {
  150. return
  151. }
  152. if count > 0 || option.Debug {
  153. fmt.Println(time.Now().Format("2006-01-02 15:04:05"), count, "files copied")
  154. }
  155. return
  156. }
  157. func ReadFile(name string, progressinfo string) (data []byte, err error) {
  158. defer fmt.Println()
  159. data = make([]byte, 0, 512)
  160. for {
  161. data, err = ReadFileContinue(name, progressinfo, data)
  162. if err == io.EOF {
  163. err = nil
  164. return
  165. }
  166. }
  167. }
  168. func ReadFileContinue(name string, progressinfo string, data []byte) ([]byte, error) {
  169. f, err := os.Open(name)
  170. if err != nil {
  171. return data, err
  172. }
  173. defer f.Close()
  174. var filesize int64
  175. var bufsize int
  176. if info, err := f.Stat(); err == nil {
  177. filesize = info.Size()
  178. if int64(int(filesize)) == filesize {
  179. bufsize = int(filesize)
  180. }
  181. }
  182. bufsize++ // one byte for final read at EOF
  183. // If a file claims a small size, read at least 512 bytes.
  184. // In particular, files in Linux's /proc claim size 0 but
  185. // then do not work right if read in small pieces,
  186. // so an initial read of 1 byte would not work correctly.
  187. offset := int64(len(data))
  188. if bufsize > cap(data) {
  189. ndata := make([]byte, len(data), bufsize)
  190. copy(ndata, data)
  191. data = ndata
  192. }
  193. statuschan := make(chan int64, 100)
  194. done := make(chan struct{}, 1)
  195. go func() {
  196. rateinfo := ""
  197. for range statuschan {
  198. // bs := strings.Repeat("\b", len(progressinfo+rateinfo))
  199. rateinfo := fmt.Sprintf(" %.2f%% ", 100*float64(offset)/float64(filesize))
  200. // fmt.Printf("\r%s%s", bs, progressinfo+rateinfo)
  201. fmt.Printf("\r%s", progressinfo+rateinfo)
  202. }
  203. if offset >= filesize {
  204. // bs := strings.Repeat("\b", len(progressinfo+rateinfo))
  205. rateinfo = fmt.Sprintf(" %.2f%% ", 100.)
  206. // fmt.Printf("\r%s%s", bs, progressinfo+rateinfo)
  207. fmt.Printf("\r%s", progressinfo+rateinfo)
  208. }
  209. done <- struct{}{}
  210. }()
  211. chunk := int64(1024 * 256)
  212. t := time.Now()
  213. for {
  214. to := offset + chunk
  215. if to > int64(cap(data)) {
  216. to = int64(cap(data))
  217. }
  218. n, err := f.ReadAt(data[offset:to], int64(offset))
  219. offset += int64(n)
  220. statuschan <- offset
  221. if err != nil {
  222. close(statuschan)
  223. <-done
  224. return data[:offset], err
  225. }
  226. if offset >= int64(cap(data)) {
  227. d := append(data[:cap(data)], 0)
  228. data = d[:offset]
  229. }
  230. ut := time.Since(t)
  231. if ut < 500*time.Millisecond {
  232. chunk *= 2
  233. } else if chunk > 1024 && ut > 5*time.Second {
  234. chunk /= 2
  235. }
  236. t = time.Now()
  237. }
  238. }
  239. func WriteFile(basedir string, fpath string, bs []byte, mtime time.Time) (err error) {
  240. ss := REremotepath.FindStringSubmatch(basedir)
  241. if len(ss) == 5 {
  242. username, password, hostport, remotepath := ss[1], ss[2], ss[3], regexp.MustCompile("//+").ReplaceAllString(ss[4], "/")
  243. return WriteRemoteFile(username, password, hostport, remotepath, fpath, bs, mtime)
  244. }
  245. tofile := filepath.Join(basedir, fpath)
  246. todir := filepath.Dir(tofile)
  247. e := os.MkdirAll(todir, os.ModePerm)
  248. if e != nil {
  249. err = fmt.Errorf("to dir %v", e)
  250. return
  251. }
  252. e = os.WriteFile(tofile, bs, os.ModePerm)
  253. if e != nil {
  254. err = fmt.Errorf("to file %v", e)
  255. return
  256. }
  257. e = os.Chtimes(tofile, mtime, mtime)
  258. if e != nil {
  259. err = fmt.Errorf("to file %v", e)
  260. return
  261. }
  262. return nil
  263. }
  264. func WriteRemoteFile(username, password, hostport, remotepath, fpath string, bs []byte, mtime time.Time) error {
  265. fpath = strings.ReplaceAll(fpath, "\\", "/")
  266. fmt.Println("copy to:", fmt.Sprint(username, ":", password, "@", hostport, "/", remotepath, "/", fpath))
  267. hp := strings.Split(hostport, ":")
  268. host := hp[0]
  269. port := 22
  270. if len(hp) >= 2 && hp[1] != "" {
  271. port = cast.ToInt(hp[1])
  272. }
  273. node := &ssh.Node{
  274. User: username,
  275. Password: password,
  276. Host: host,
  277. Port: port,
  278. }
  279. client, e := node.Connect()
  280. if e != nil {
  281. return e
  282. }
  283. rfpath := remotepath + "/" + fpath
  284. diridx := strings.LastIndex(rfpath, "/")
  285. e = client.MkdirAll(rfpath[:diridx])
  286. if e != nil {
  287. return e
  288. }
  289. e = client.WriteFile(rfpath, bs, os.ModePerm, mtime)
  290. if e != nil {
  291. return e
  292. }
  293. return nil
  294. }