background.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /*
  2. Handle offline download and apk install
  3. */
  4. package main
  5. import (
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "net/http"
  11. "os"
  12. "path/filepath"
  13. "sync"
  14. "time"
  15. "github.com/DeanThompson/syncmap"
  16. "github.com/franela/goreq"
  17. )
  18. const defaultDownloadTimeout = 2 * time.Hour
  19. var background = &Background{
  20. sm: syncmap.New(),
  21. }
  22. type BackgroundState struct {
  23. Message string `json:"message"`
  24. Error string `json:"error"`
  25. Progress interface{} `json:"progress"`
  26. PackageName string `json:"packageName,omitempty"`
  27. Status string `json:"status"`
  28. err error
  29. wg sync.WaitGroup
  30. }
  31. type Background struct {
  32. sm *syncmap.SyncMap
  33. n int
  34. mu sync.Mutex
  35. // timer *SafeTimer
  36. }
  37. // Get return nil if not found
  38. func (b *Background) Get(key string) (status *BackgroundState) {
  39. value, ok := b.sm.Get(key)
  40. if !ok {
  41. return nil
  42. }
  43. status = value.(*BackgroundState)
  44. return status
  45. }
  46. func (b *Background) genKey() (key string, state *BackgroundState) {
  47. b.mu.Lock()
  48. defer b.mu.Unlock()
  49. b.n++
  50. key = fmt.Sprintf("%d", b.n)
  51. state = &BackgroundState{}
  52. b.sm.Set(key, state)
  53. return
  54. }
  55. func (b *Background) HTTPDownload(urlStr string, dst string, mode os.FileMode) (key string) {
  56. key, state := b.genKey()
  57. state.wg.Add(1)
  58. go func() {
  59. defer time.AfterFunc(5*time.Minute, func() {
  60. b.sm.Delete(key)
  61. })
  62. b.Get(key).Message = "downloading"
  63. if err := b.doHTTPDownload(key, urlStr, dst, mode); err != nil {
  64. b.Get(key).Message = "http download: " + err.Error()
  65. } else {
  66. b.Get(key).Message = "downloaded"
  67. }
  68. }()
  69. return
  70. }
  71. func (b *Background) Wait(key string) error {
  72. state := b.Get(key)
  73. if state == nil {
  74. return errors.New("not found key: " + key)
  75. }
  76. state.wg.Wait()
  77. return state.err
  78. }
  79. // Default download timeout 30 minutes
  80. func (b *Background) doHTTPDownload(key, urlStr, dst string, fileMode os.FileMode) (err error) {
  81. state := b.Get(key)
  82. if state == nil {
  83. panic("http download key invalid: " + key)
  84. }
  85. defer func() {
  86. state.err = err
  87. state.wg.Done()
  88. }()
  89. res, err := goreq.Request{
  90. Uri: urlStr,
  91. MaxRedirects: 10,
  92. RedirectHeaders: true,
  93. }.Do()
  94. if err != nil {
  95. return
  96. }
  97. defer res.Body.Close()
  98. if res.StatusCode != http.StatusOK {
  99. body, err := res.Body.ToString()
  100. if err != nil && err == bytes.ErrTooLarge {
  101. return fmt.Errorf("Expected HTTP Status code: %d", res.StatusCode)
  102. }
  103. return errors.New(body)
  104. }
  105. // mkdir is not exists
  106. os.MkdirAll(filepath.Dir(dst), 0755)
  107. file, err := os.Create(dst)
  108. if err != nil {
  109. return
  110. }
  111. defer file.Close()
  112. var totalSize int
  113. fmt.Sscanf(res.Header.Get("Content-Length"), "%d", &totalSize)
  114. wrproxy := newDownloadProxy(file, totalSize)
  115. defer wrproxy.Done()
  116. b.Get(key).Progress = wrproxy
  117. // timeout here
  118. timer := time.AfterFunc(defaultDownloadTimeout, func() {
  119. res.Body.Close()
  120. })
  121. defer timer.Stop()
  122. _, err = io.Copy(wrproxy, res.Body)
  123. if err != nil {
  124. return
  125. }
  126. if fileMode != 0 {
  127. os.Chmod(dst, fileMode)
  128. }
  129. return
  130. }
  131. type downloadProxy struct {
  132. canceled bool
  133. writer io.Writer
  134. TotalSize int `json:"totalSize"`
  135. CopiedSize int `json:"copiedSize"`
  136. Error string `json:"error,omitempty"`
  137. wg sync.WaitGroup
  138. }
  139. func newDownloadProxy(wr io.Writer, totalSize int) *downloadProxy {
  140. di := &downloadProxy{
  141. writer: wr,
  142. TotalSize: totalSize,
  143. }
  144. di.wg.Add(1)
  145. return di
  146. }
  147. func (d *downloadProxy) Cancel() {
  148. d.canceled = true
  149. }
  150. func (d *downloadProxy) Write(data []byte) (int, error) {
  151. if d.canceled {
  152. return 0, errors.New("download proxy was canceled")
  153. }
  154. n, err := d.writer.Write(data)
  155. d.CopiedSize += n
  156. return n, err
  157. }
  158. // Should only call once
  159. func (d *downloadProxy) Done() {
  160. d.wg.Done()
  161. }
  162. func (d *downloadProxy) Wait() {
  163. d.wg.Wait()
  164. }