main.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "crypto/sha1"
  17. "fmt"
  18. "io/ioutil"
  19. "net/http"
  20. "os"
  21. "os/signal"
  22. "path/filepath"
  23. "syscall"
  24. "time"
  25. "github.com/anacrolix/torrent"
  26. "github.com/anacrolix/torrent/metainfo"
  27. "github.com/anacrolix/torrent/storage"
  28. "yunion.io/x/log"
  29. "yunion.io/x/pkg/util/version"
  30. "yunion.io/x/structarg"
  31. "yunion.io/x/onecloud/pkg/util/atexit"
  32. "yunion.io/x/onecloud/pkg/util/fileutils2"
  33. "yunion.io/x/onecloud/pkg/util/nodeid"
  34. "yunion.io/x/onecloud/pkg/util/torrentutils"
  35. )
  36. type Options struct {
  37. structarg.BaseOptions
  38. ROOT string `help:"Root directory to seed files"`
  39. TORRENT string `help:"path to torrent file"`
  40. Tracker []string `help:"Tracker urls, e.g. http://10.168.222.252:6969/announce or udp://tracker.istole.it:6969"`
  41. Debug bool `help:"turn on debug" default:"false"`
  42. Verbose bool `help:"verbose mode" default:"false"`
  43. CallbackURL string `help:"callback notification URL"`
  44. }
  45. func exitSignalHandlers(client *torrent.Client) {
  46. c := make(chan os.Signal, 1)
  47. signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
  48. for {
  49. log.Printf("close signal received: %+v", <-c)
  50. client.Close()
  51. }
  52. }
  53. func main() {
  54. defer atexit.Handle()
  55. options := Options{}
  56. parser, err := structarg.NewArgumentParser(&options, "torrent-srv", "bit-torrent server", "2018")
  57. if err != nil {
  58. log.Fatalf("%s", err)
  59. }
  60. parser.ParseArgs(os.Args[1:], true)
  61. if options.Help {
  62. fmt.Println(parser.HelpString())
  63. return
  64. }
  65. if len(os.Args) <= 1 {
  66. fmt.Print(parser.Usage())
  67. return
  68. }
  69. if options.Version {
  70. fmt.Println(version.GetJsonString())
  71. return
  72. }
  73. root, err := filepath.Abs(options.ROOT)
  74. if err != nil {
  75. log.Fatalf("fail to get absolute path: %s", err)
  76. }
  77. var mi *metainfo.MetaInfo
  78. var rootDir string
  79. if len(options.Tracker) > 0 && !fileutils2.Exists(options.TORRENT) {
  80. // server mode
  81. mi, err = torrentutils.GenerateTorrent(root, options.Tracker, options.TORRENT)
  82. if err != nil {
  83. log.Fatalf("fail to save torrent file %s", err)
  84. }
  85. rootDir = filepath.Dir(root)
  86. } else {
  87. // client mode, load mi from torrent file
  88. mi, err = metainfo.LoadFromFile(options.TORRENT)
  89. if err != nil {
  90. log.Fatalf("fail to open torrent file %s", err)
  91. }
  92. rootDir = root
  93. }
  94. info, err := mi.UnmarshalInfo()
  95. if err != nil {
  96. log.Errorf("fail to unmarshalinfo %s", err)
  97. return
  98. }
  99. hasher := sha1.New()
  100. nodeId, err := nodeid.GetNodeId()
  101. if err != nil {
  102. log.Errorf("fail to generate node id: %s", err)
  103. return
  104. }
  105. hasher.Write(nodeId)
  106. hasher.Write(info.Pieces)
  107. peerIdStr := fmt.Sprintf("%x", hasher.Sum(nil))
  108. log.Infof("Set torrent server as node %s", peerIdStr[:20])
  109. clientConfig := torrent.NewDefaultClientConfig()
  110. clientConfig.PeerID = peerIdStr[:20]
  111. clientConfig.Debug = options.Debug
  112. clientConfig.Seed = true
  113. clientConfig.NoUpload = false
  114. log.Infof("To sync torrent files for %s", info.Name)
  115. tmpDir := filepath.Join(rootDir, fmt.Sprintf("%s%s", info.Name, ".tmp"))
  116. os.RemoveAll(tmpDir)
  117. os.MkdirAll(tmpDir, 0700)
  118. defer os.RemoveAll(tmpDir)
  119. clientConfig.DefaultStorage = storage.NewFileWithCustomPathMaker(tmpDir,
  120. func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
  121. return filepath.Dir(baseDir)
  122. },
  123. )
  124. clientConfig.DisableTrackers = false
  125. clientConfig.DisablePEX = true
  126. clientConfig.NoDHT = true
  127. clientConfig.NominalDialTimeout = 1 * time.Second
  128. clientConfig.MinDialTimeout = 100 * time.Millisecond
  129. clientConfig.HandshakesTimeout = 100 * time.Second
  130. client, err := torrent.NewClient(clientConfig)
  131. if err != nil {
  132. log.Fatalf("error creating client: %s", err)
  133. }
  134. defer client.Close()
  135. go exitSignalHandlers(client)
  136. start := time.Now()
  137. t, err := client.AddTorrent(mi)
  138. if err != nil {
  139. log.Fatalf("%s", err)
  140. }
  141. <-t.GotInfo()
  142. t.DownloadAll()
  143. stop := false
  144. go func() {
  145. <-client.Closed()
  146. log.Debugf("client closed, exit!")
  147. stop = true
  148. }()
  149. finish := false
  150. for !stop {
  151. if t.BytesCompleted() == t.Info().TotalLength() {
  152. if !finish {
  153. finish = true
  154. fmt.Printf("Download complete, takes %d seconds\n", time.Now().Sub(start)/time.Second)
  155. if len(options.CallbackURL) > 0 {
  156. maxTried := 10
  157. for tried := 0; tried < maxTried; tried += 1 {
  158. resp, err := http.Post(options.CallbackURL, "", nil)
  159. if err == nil && resp.StatusCode < 300 {
  160. break
  161. }
  162. if err != nil {
  163. log.Errorf("callback fail %s", err)
  164. } else {
  165. defer resp.Body.Close()
  166. respBody, _ := ioutil.ReadAll(resp.Body)
  167. log.Errorf("callback response error %s", string(respBody))
  168. }
  169. time.Sleep(time.Duration(tried+1) * 10 * time.Second)
  170. }
  171. }
  172. }
  173. fmt.Printf("\rSeeding.............")
  174. } else {
  175. fmt.Printf("\rDownload: %.1f%%", float64(t.BytesCompleted())*100.0/float64(t.Info().TotalLength()))
  176. }
  177. // client.WriteStatus(os.Stdout)
  178. time.Sleep(time.Second)
  179. }
  180. }