gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package issues
  4. import (
  5. "context"
  6. "time"
  7. "code.gitea.io/gitea/models/db"
  8. "code.gitea.io/gitea/models/repo"
  9. user_model "code.gitea.io/gitea/models/user"
  10. "code.gitea.io/gitea/modules/timeutil"
  11. "code.gitea.io/gitea/modules/util"
  12. )
  13. // Stopwatch represents a stopwatch for time tracking.
  14. type Stopwatch struct {
  15. ID int64 `xorm:"pk autoincr"`
  16. IssueID int64 `xorm:"INDEX"`
  17. UserID int64 `xorm:"INDEX"`
  18. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  19. }
  20. func init() {
  21. db.RegisterModel(new(Stopwatch))
  22. }
  23. // Seconds returns the amount of time passed since creation, based on local server time
  24. func (s Stopwatch) Seconds() int64 {
  25. return int64(timeutil.TimeStampNow() - s.CreatedUnix)
  26. }
  27. func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
  28. sw = new(Stopwatch)
  29. exists, err = db.GetEngine(ctx).
  30. Where("user_id = ?", userID).
  31. And("issue_id = ?", issueID).
  32. Get(sw)
  33. return sw, exists, err
  34. }
  35. type UserStopwatch struct {
  36. UserID int64
  37. StopWatches []*Stopwatch
  38. }
  39. func GetUIDsAndStopwatch(ctx context.Context) ([]*UserStopwatch, error) {
  40. sws := []*Stopwatch{}
  41. if err := db.GetEngine(ctx).Where("issue_id != 0").Find(&sws); err != nil {
  42. return nil, err
  43. }
  44. if len(sws) == 0 {
  45. return []*UserStopwatch{}, nil
  46. }
  47. lastUserID := int64(-1)
  48. res := []*UserStopwatch{}
  49. for _, sw := range sws {
  50. if lastUserID == sw.UserID {
  51. lastUserStopwatch := res[len(res)-1]
  52. lastUserStopwatch.StopWatches = append(lastUserStopwatch.StopWatches, sw)
  53. } else {
  54. res = append(res, &UserStopwatch{
  55. UserID: sw.UserID,
  56. StopWatches: []*Stopwatch{sw},
  57. })
  58. }
  59. }
  60. return res, nil
  61. }
  62. // GetUserStopwatches return list of the user's all stopwatches
  63. func GetUserStopwatches(ctx context.Context, userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) {
  64. sws := make([]*Stopwatch, 0, 8)
  65. sess := db.GetEngine(ctx).Where("stopwatch.user_id = ?", userID)
  66. if listOptions.Page > 0 {
  67. sess = db.SetSessionPagination(sess, &listOptions)
  68. }
  69. err := sess.Find(&sws)
  70. if err != nil {
  71. return nil, err
  72. }
  73. return sws, nil
  74. }
  75. // CountUserStopwatches return count of the user's all stopwatches
  76. func CountUserStopwatches(ctx context.Context, userID int64) (int64, error) {
  77. return db.GetEngine(ctx).Where("user_id = ?", userID).Count(&Stopwatch{})
  78. }
  79. // StopwatchExists returns true if the stopwatch exists
  80. func StopwatchExists(ctx context.Context, userID, issueID int64) bool {
  81. _, exists, _ := getStopwatch(ctx, userID, issueID)
  82. return exists
  83. }
  84. // HasUserStopwatch returns true if the user has a stopwatch
  85. func HasUserStopwatch(ctx context.Context, userID int64) (exists bool, sw *Stopwatch, issue *Issue, err error) {
  86. type stopwatchIssueRepo struct {
  87. Stopwatch `xorm:"extends"`
  88. Issue `xorm:"extends"`
  89. repo.Repository `xorm:"extends"`
  90. }
  91. swIR := new(stopwatchIssueRepo)
  92. exists, err = db.GetEngine(ctx).
  93. Table("stopwatch").
  94. Where("user_id = ?", userID).
  95. Join("INNER", "issue", "issue.id = stopwatch.issue_id").
  96. Join("INNER", "repository", "repository.id = issue.repo_id").
  97. Get(swIR)
  98. if exists {
  99. sw = &swIR.Stopwatch
  100. issue = &swIR.Issue
  101. issue.Repo = &swIR.Repository
  102. }
  103. return exists, sw, issue, err
  104. }
  105. // FinishIssueStopwatch if stopwatch exists, then finish it.
  106. func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) (ok bool, err error) {
  107. sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
  108. if err != nil {
  109. return false, err
  110. } else if !exists {
  111. return false, nil
  112. }
  113. if err = finishIssueStopwatch(ctx, user, issue, sw); err != nil {
  114. return false, err
  115. }
  116. return true, nil
  117. }
  118. func finishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue, sw *Stopwatch) error {
  119. // Create tracked time out of the time difference between start date and actual date
  120. timediff := time.Now().Unix() - int64(sw.CreatedUnix)
  121. // Create TrackedTime
  122. tt := &TrackedTime{
  123. Created: time.Now(),
  124. IssueID: issue.ID,
  125. UserID: user.ID,
  126. Time: timediff,
  127. }
  128. if err := issue.LoadRepo(ctx); err != nil {
  129. return err
  130. }
  131. if err := db.Insert(ctx, tt); err != nil {
  132. return err
  133. }
  134. if _, err := CreateComment(ctx, &CreateCommentOptions{
  135. Doer: user,
  136. Issue: issue,
  137. Repo: issue.Repo,
  138. Content: util.SecToHours(timediff),
  139. Type: CommentTypeStopTracking,
  140. TimeID: tt.ID,
  141. }); err != nil {
  142. return err
  143. }
  144. _, err := db.DeleteByBean(ctx, sw)
  145. return err
  146. }
  147. // CreateIssueStopwatch creates a stopwatch if the issue doesn't have the user's stopwatch.
  148. // It also stops any other stopwatch that might be running for the user.
  149. func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) (ok bool, err error) {
  150. { // if another issue's stopwatch is running: stop it; if this issue has a stopwatch: return an error.
  151. exists, otherStopWatch, otherIssue, err := HasUserStopwatch(ctx, user.ID)
  152. if err != nil {
  153. return false, err
  154. }
  155. if exists {
  156. if otherStopWatch.IssueID == issue.ID {
  157. // don't allow starting stopwatch for the same issue
  158. return false, nil
  159. }
  160. // stop the other issue's stopwatch
  161. if err = finishIssueStopwatch(ctx, user, otherIssue, otherStopWatch); err != nil {
  162. return false, err
  163. }
  164. }
  165. }
  166. if err = issue.LoadRepo(ctx); err != nil {
  167. return false, err
  168. }
  169. if err = db.Insert(ctx, &Stopwatch{UserID: user.ID, IssueID: issue.ID}); err != nil {
  170. return false, err
  171. }
  172. if _, err = CreateComment(ctx, &CreateCommentOptions{
  173. Doer: user,
  174. Issue: issue,
  175. Repo: issue.Repo,
  176. Type: CommentTypeStartTracking,
  177. }); err != nil {
  178. return false, err
  179. }
  180. return true, nil
  181. }
  182. // CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
  183. func CancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) (ok bool, err error) {
  184. err = db.WithTx(ctx, func(ctx context.Context) error {
  185. e := db.GetEngine(ctx)
  186. sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
  187. if err != nil {
  188. return err
  189. } else if !exists {
  190. return nil
  191. }
  192. if err = issue.LoadRepo(ctx); err != nil {
  193. return err
  194. }
  195. if _, err = e.Delete(sw); err != nil {
  196. return err
  197. }
  198. if _, err = CreateComment(ctx, &CreateCommentOptions{
  199. Doer: user,
  200. Issue: issue,
  201. Repo: issue.Repo,
  202. Type: CommentTypeCancelTracking,
  203. }); err != nil {
  204. return err
  205. }
  206. ok = true
  207. return nil
  208. })
  209. return ok, err
  210. }