Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

issue_stopwatch.go 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "context"
  7. "fmt"
  8. "time"
  9. "code.gitea.io/gitea/models/db"
  10. user_model "code.gitea.io/gitea/models/user"
  11. "code.gitea.io/gitea/modules/timeutil"
  12. )
  13. // ErrIssueStopwatchNotExist represents an error that stopwatch is not exist
  14. type ErrIssueStopwatchNotExist struct {
  15. UserID int64
  16. IssueID int64
  17. }
  18. func (err ErrIssueStopwatchNotExist) Error() string {
  19. return fmt.Sprintf("issue stopwatch doesn't exist[uid: %d, issue_id: %d", err.UserID, err.IssueID)
  20. }
  21. // ErrIssueStopwatchAlreadyExist represents an error that stopwatch is already exist
  22. type ErrIssueStopwatchAlreadyExist struct {
  23. UserID int64
  24. IssueID int64
  25. }
  26. func (err ErrIssueStopwatchAlreadyExist) Error() string {
  27. return fmt.Sprintf("issue stopwatch already exists[uid: %d, issue_id: %d", err.UserID, err.IssueID)
  28. }
  29. // Stopwatch represents a stopwatch for time tracking.
  30. type Stopwatch struct {
  31. ID int64 `xorm:"pk autoincr"`
  32. IssueID int64 `xorm:"INDEX"`
  33. UserID int64 `xorm:"INDEX"`
  34. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  35. }
  36. func init() {
  37. db.RegisterModel(new(Stopwatch))
  38. }
  39. // Seconds returns the amount of time passed since creation, based on local server time
  40. func (s Stopwatch) Seconds() int64 {
  41. return int64(timeutil.TimeStampNow() - s.CreatedUnix)
  42. }
  43. // Duration returns a human-readable duration string based on local server time
  44. func (s Stopwatch) Duration() string {
  45. return SecToTime(s.Seconds())
  46. }
  47. func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
  48. sw = new(Stopwatch)
  49. exists, err = db.GetEngine(ctx).
  50. Where("user_id = ?", userID).
  51. And("issue_id = ?", issueID).
  52. Get(sw)
  53. return
  54. }
  55. // GetUserStopwatches return list of all stopwatches of a user
  56. func GetUserStopwatches(userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) {
  57. sws := make([]*Stopwatch, 0, 8)
  58. sess := db.GetEngine(db.DefaultContext).Where("stopwatch.user_id = ?", userID)
  59. if listOptions.Page != 0 {
  60. sess = db.SetSessionPagination(sess, &listOptions)
  61. }
  62. err := sess.Find(&sws)
  63. if err != nil {
  64. return nil, err
  65. }
  66. return sws, nil
  67. }
  68. // CountUserStopwatches return count of all stopwatches of a user
  69. func CountUserStopwatches(userID int64) (int64, error) {
  70. return db.GetEngine(db.DefaultContext).Where("user_id = ?", userID).Count(&Stopwatch{})
  71. }
  72. // StopwatchExists returns true if the stopwatch exists
  73. func StopwatchExists(userID, issueID int64) bool {
  74. _, exists, _ := getStopwatch(db.DefaultContext, userID, issueID)
  75. return exists
  76. }
  77. // HasUserStopwatch returns true if the user has a stopwatch
  78. func HasUserStopwatch(userID int64) (exists bool, sw *Stopwatch, err error) {
  79. return hasUserStopwatch(db.GetEngine(db.DefaultContext), userID)
  80. }
  81. func hasUserStopwatch(e db.Engine, userID int64) (exists bool, sw *Stopwatch, err error) {
  82. sw = new(Stopwatch)
  83. exists, err = e.
  84. Where("user_id = ?", userID).
  85. Get(sw)
  86. return
  87. }
  88. // FinishIssueStopwatchIfPossible if stopwatch exist then finish it otherwise ignore
  89. func FinishIssueStopwatchIfPossible(ctx context.Context, user *user_model.User, issue *Issue) error {
  90. _, exists, err := getStopwatch(ctx, user.ID, issue.ID)
  91. if err != nil {
  92. return err
  93. }
  94. if !exists {
  95. return nil
  96. }
  97. return FinishIssueStopwatch(ctx, user, issue)
  98. }
  99. // CreateOrStopIssueStopwatch create an issue stopwatch if it's not exist, otherwise finish it
  100. func CreateOrStopIssueStopwatch(user *user_model.User, issue *Issue) error {
  101. _, exists, err := getStopwatch(db.DefaultContext, user.ID, issue.ID)
  102. if err != nil {
  103. return err
  104. }
  105. if exists {
  106. return FinishIssueStopwatch(db.DefaultContext, user, issue)
  107. }
  108. return CreateIssueStopwatch(db.DefaultContext, user, issue)
  109. }
  110. // FinishIssueStopwatch if stopwatch exist then finish it otherwise return an error
  111. func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
  112. sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
  113. if err != nil {
  114. return err
  115. }
  116. if !exists {
  117. return ErrIssueStopwatchNotExist{
  118. UserID: user.ID,
  119. IssueID: issue.ID,
  120. }
  121. }
  122. // Create tracked time out of the time difference between start date and actual date
  123. timediff := time.Now().Unix() - int64(sw.CreatedUnix)
  124. // Create TrackedTime
  125. tt := &TrackedTime{
  126. Created: time.Now(),
  127. IssueID: issue.ID,
  128. UserID: user.ID,
  129. Time: timediff,
  130. }
  131. if err := db.Insert(ctx, tt); err != nil {
  132. return err
  133. }
  134. if err := issue.loadRepo(db.GetEngine(ctx)); err != nil {
  135. return err
  136. }
  137. if _, err := createComment(ctx, &CreateCommentOptions{
  138. Doer: user,
  139. Issue: issue,
  140. Repo: issue.Repo,
  141. Content: SecToTime(timediff),
  142. Type: CommentTypeStopTracking,
  143. TimeID: tt.ID,
  144. }); err != nil {
  145. return err
  146. }
  147. _, err = db.GetEngine(ctx).Delete(sw)
  148. return err
  149. }
  150. // CreateIssueStopwatch creates a stopwatch if not exist, otherwise return an error
  151. func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
  152. e := db.GetEngine(ctx)
  153. if err := issue.loadRepo(e); err != nil {
  154. return err
  155. }
  156. // if another stopwatch is running: stop it
  157. exists, sw, err := hasUserStopwatch(e, user.ID)
  158. if err != nil {
  159. return err
  160. }
  161. if exists {
  162. issue, err := getIssueByID(e, sw.IssueID)
  163. if err != nil {
  164. return err
  165. }
  166. if err := FinishIssueStopwatch(ctx, user, issue); err != nil {
  167. return err
  168. }
  169. }
  170. // Create stopwatch
  171. sw = &Stopwatch{
  172. UserID: user.ID,
  173. IssueID: issue.ID,
  174. }
  175. if err := db.Insert(ctx, sw); err != nil {
  176. return err
  177. }
  178. if err := issue.loadRepo(db.GetEngine(ctx)); err != nil {
  179. return err
  180. }
  181. if _, err := createComment(ctx, &CreateCommentOptions{
  182. Doer: user,
  183. Issue: issue,
  184. Repo: issue.Repo,
  185. Type: CommentTypeStartTracking,
  186. }); err != nil {
  187. return err
  188. }
  189. return nil
  190. }
  191. // CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
  192. func CancelStopwatch(user *user_model.User, issue *Issue) error {
  193. ctx, committer, err := db.TxContext()
  194. if err != nil {
  195. return err
  196. }
  197. defer committer.Close()
  198. if err := cancelStopwatch(ctx, user, issue); err != nil {
  199. return err
  200. }
  201. return committer.Commit()
  202. }
  203. func cancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
  204. e := db.GetEngine(ctx)
  205. sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
  206. if err != nil {
  207. return err
  208. }
  209. if exists {
  210. if _, err := e.Delete(sw); err != nil {
  211. return err
  212. }
  213. if err := issue.loadRepo(e); err != nil {
  214. return err
  215. }
  216. if err := issue.loadRepo(db.GetEngine(ctx)); err != nil {
  217. return err
  218. }
  219. if _, err := createComment(ctx, &CreateCommentOptions{
  220. Doer: user,
  221. Issue: issue,
  222. Repo: issue.Repo,
  223. Type: CommentTypeCancelTracking,
  224. }); err != nil {
  225. return err
  226. }
  227. }
  228. return nil
  229. }
  230. // SecToTime converts an amount of seconds to a human-readable string (example: 66s -> 1min 6s)
  231. func SecToTime(duration int64) string {
  232. seconds := duration % 60
  233. minutes := (duration / (60)) % 60
  234. hours := duration / (60 * 60)
  235. var hrs string
  236. if hours > 0 {
  237. hrs = fmt.Sprintf("%dh", hours)
  238. }
  239. if minutes > 0 {
  240. if hours == 0 {
  241. hrs = fmt.Sprintf("%dmin", minutes)
  242. } else {
  243. hrs = fmt.Sprintf("%s %dmin", hrs, minutes)
  244. }
  245. }
  246. if seconds > 0 {
  247. if hours == 0 && minutes == 0 {
  248. hrs = fmt.Sprintf("%ds", seconds)
  249. } else {
  250. hrs = fmt.Sprintf("%s %ds", hrs, seconds)
  251. }
  252. }
  253. return hrs
  254. }