2017-09-12 09:48:13 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2017-09-12 09:48:13 +03:00
2022-06-13 12:37:59 +03:00
package issues
2017-09-12 09:48:13 +03:00
import (
2021-11-19 16:39:57 +03:00
"context"
2017-09-12 09:48:13 +03:00
"fmt"
"time"
2017-12-11 07:37:04 +03:00
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2023-02-27 21:46:00 +03:00
"code.gitea.io/gitea/models/repo"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2022-02-15 19:50:10 +03:00
"code.gitea.io/gitea/modules/util"
2017-09-12 09:48:13 +03:00
)
2021-11-21 12:11:48 +03:00
// ErrIssueStopwatchNotExist represents an error that stopwatch is not exist
type ErrIssueStopwatchNotExist struct {
UserID int64
IssueID int64
}
func ( err ErrIssueStopwatchNotExist ) Error ( ) string {
return fmt . Sprintf ( "issue stopwatch doesn't exist[uid: %d, issue_id: %d" , err . UserID , err . IssueID )
}
2022-10-18 08:50:37 +03:00
func ( err ErrIssueStopwatchNotExist ) Unwrap ( ) error {
return util . ErrNotExist
}
2017-09-12 09:48:13 +03:00
// Stopwatch represents a stopwatch for time tracking.
type Stopwatch struct {
2019-08-15 17:46:21 +03:00
ID int64 ` xorm:"pk autoincr" `
IssueID int64 ` xorm:"INDEX" `
UserID int64 ` xorm:"INDEX" `
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
2017-09-12 09:48:13 +03:00
}
2021-09-19 14:49:59 +03:00
func init ( ) {
db . RegisterModel ( new ( Stopwatch ) )
}
2021-01-21 17:51:52 +03:00
// Seconds returns the amount of time passed since creation, based on local server time
func ( s Stopwatch ) Seconds ( ) int64 {
return int64 ( timeutil . TimeStampNow ( ) - s . CreatedUnix )
}
// Duration returns a human-readable duration string based on local server time
func ( s Stopwatch ) Duration ( ) string {
2022-02-15 19:50:10 +03:00
return util . SecToTime ( s . Seconds ( ) )
2021-01-21 17:51:52 +03:00
}
2021-11-21 12:11:48 +03:00
func getStopwatch ( ctx context . Context , userID , issueID int64 ) ( sw * Stopwatch , exists bool , err error ) {
2017-09-12 09:48:13 +03:00
sw = new ( Stopwatch )
2021-11-21 12:11:48 +03:00
exists , err = db . GetEngine ( ctx ) .
2017-09-12 09:48:13 +03:00
Where ( "user_id = ?" , userID ) .
And ( "issue_id = ?" , issueID ) .
Get ( sw )
2022-06-20 13:02:49 +03:00
return sw , exists , err
2017-09-12 09:48:13 +03:00
}
2022-04-25 23:45:22 +03:00
// UserIDCount is a simple coalition of UserID and Count
type UserStopwatch struct {
UserID int64
StopWatches [ ] * Stopwatch
}
// GetUIDsAndNotificationCounts between the two provided times
2023-09-16 17:39:12 +03:00
func GetUIDsAndStopwatch ( ctx context . Context ) ( [ ] * UserStopwatch , error ) {
2022-04-25 23:45:22 +03:00
sws := [ ] * Stopwatch { }
2023-09-16 17:39:12 +03:00
if err := db . GetEngine ( ctx ) . Where ( "issue_id != 0" ) . Find ( & sws ) ; err != nil {
2022-04-25 23:45:22 +03:00
return nil , err
}
if len ( sws ) == 0 {
return [ ] * UserStopwatch { } , nil
}
lastUserID := int64 ( - 1 )
res := [ ] * UserStopwatch { }
for _ , sw := range sws {
if lastUserID == sw . UserID {
lastUserStopwatch := res [ len ( res ) - 1 ]
lastUserStopwatch . StopWatches = append ( lastUserStopwatch . StopWatches , sw )
} else {
res = append ( res , & UserStopwatch {
UserID : sw . UserID ,
StopWatches : [ ] * Stopwatch { sw } ,
} )
}
}
return res , nil
}
2019-12-12 07:23:05 +03:00
// GetUserStopwatches return list of all stopwatches of a user
2023-09-16 17:39:12 +03:00
func GetUserStopwatches ( ctx context . Context , userID int64 , listOptions db . ListOptions ) ( [ ] * Stopwatch , error ) {
2020-09-18 15:09:26 +03:00
sws := make ( [ ] * Stopwatch , 0 , 8 )
2023-09-16 17:39:12 +03:00
sess := db . GetEngine ( ctx ) . Where ( "stopwatch.user_id = ?" , userID )
2024-11-25 04:56:50 +03:00
if listOptions . Page > 0 {
2021-09-24 14:32:56 +03:00
sess = db . SetSessionPagination ( sess , & listOptions )
2020-01-24 22:00:29 +03:00
}
2020-09-18 15:09:26 +03:00
err := sess . Find ( & sws )
2019-12-12 07:23:05 +03:00
if err != nil {
return nil , err
}
return sws , nil
}
2021-08-12 15:43:08 +03:00
// CountUserStopwatches return count of all stopwatches of a user
2023-09-16 17:39:12 +03:00
func CountUserStopwatches ( ctx context . Context , userID int64 ) ( int64 , error ) {
return db . GetEngine ( ctx ) . Where ( "user_id = ?" , userID ) . Count ( & Stopwatch { } )
2021-08-12 15:43:08 +03:00
}
2017-09-12 09:48:13 +03:00
// StopwatchExists returns true if the stopwatch exists
2023-09-16 17:39:12 +03:00
func StopwatchExists ( ctx context . Context , userID , issueID int64 ) bool {
_ , exists , _ := getStopwatch ( ctx , userID , issueID )
2017-09-12 09:48:13 +03:00
return exists
}
// HasUserStopwatch returns true if the user has a stopwatch
2023-02-27 21:46:00 +03:00
func HasUserStopwatch ( ctx context . Context , userID int64 ) ( exists bool , sw * Stopwatch , issue * Issue , err error ) {
type stopwatchIssueRepo struct {
Stopwatch ` xorm:"extends" `
Issue ` xorm:"extends" `
repo . Repository ` xorm:"extends" `
}
swIR := new ( stopwatchIssueRepo )
2022-05-20 17:08:52 +03:00
exists , err = db . GetEngine ( ctx ) .
2023-02-27 21:46:00 +03:00
Table ( "stopwatch" ) .
2017-09-12 09:48:13 +03:00
Where ( "user_id = ?" , userID ) .
2023-02-27 21:46:00 +03:00
Join ( "INNER" , "issue" , "issue.id = stopwatch.issue_id" ) .
Join ( "INNER" , "repository" , "repository.id = issue.repo_id" ) .
Get ( swIR )
if exists {
sw = & swIR . Stopwatch
issue = & swIR . Issue
issue . Repo = & swIR . Repository
}
return exists , sw , issue , err
2017-09-12 09:48:13 +03:00
}
2021-11-21 12:11:48 +03:00
// FinishIssueStopwatchIfPossible if stopwatch exist then finish it otherwise ignore
2021-11-24 12:49:20 +03:00
func FinishIssueStopwatchIfPossible ( ctx context . Context , user * user_model . User , issue * Issue ) error {
2021-11-21 12:11:48 +03:00
_ , exists , err := getStopwatch ( ctx , user . ID , issue . ID )
2021-11-19 16:39:57 +03:00
if err != nil {
2021-07-26 23:46:06 +03:00
return err
}
2021-11-21 12:11:48 +03:00
if ! exists {
return nil
}
return FinishIssueStopwatch ( ctx , user , issue )
}
// CreateOrStopIssueStopwatch create an issue stopwatch if it's not exist, otherwise finish it
2023-09-16 17:39:12 +03:00
func CreateOrStopIssueStopwatch ( ctx context . Context , user * user_model . User , issue * Issue ) error {
_ , exists , err := getStopwatch ( ctx , user . ID , issue . ID )
2021-11-21 12:11:48 +03:00
if err != nil {
2021-07-26 23:46:06 +03:00
return err
}
2021-11-21 12:11:48 +03:00
if exists {
2023-09-16 17:39:12 +03:00
return FinishIssueStopwatch ( ctx , user , issue )
2021-11-21 12:11:48 +03:00
}
2023-09-16 17:39:12 +03:00
return CreateIssueStopwatch ( ctx , user , issue )
2021-07-26 23:46:06 +03:00
}
2021-11-21 12:11:48 +03:00
// FinishIssueStopwatch if stopwatch exist then finish it otherwise return an error
2021-11-24 12:49:20 +03:00
func FinishIssueStopwatch ( ctx context . Context , user * user_model . User , issue * Issue ) error {
2021-11-21 12:11:48 +03:00
sw , exists , err := getStopwatch ( ctx , user . ID , issue . ID )
2017-09-12 09:48:13 +03:00
if err != nil {
return err
}
2021-11-21 12:11:48 +03:00
if ! exists {
return ErrIssueStopwatchNotExist {
UserID : user . ID ,
IssueID : issue . ID ,
}
}
// Create tracked time out of the time difference between start date and actual date
timediff := time . Now ( ) . Unix ( ) - int64 ( sw . CreatedUnix )
// Create TrackedTime
tt := & TrackedTime {
Created : time . Now ( ) ,
IssueID : issue . ID ,
UserID : user . ID ,
Time : timediff ,
}
if err := db . Insert ( ctx , tt ) ; err != nil {
2018-12-13 18:55:43 +03:00
return err
}
2022-04-08 12:11:15 +03:00
if err := issue . LoadRepo ( ctx ) ; err != nil {
2021-11-21 12:11:48 +03:00
return err
}
2017-09-12 09:48:13 +03:00
2022-12-10 05:46:31 +03:00
if _ , err := CreateComment ( ctx , & CreateCommentOptions {
2021-11-21 12:11:48 +03:00
Doer : user ,
Issue : issue ,
Repo : issue . Repo ,
2022-02-15 19:50:10 +03:00
Content : util . SecToTime ( timediff ) ,
2021-11-21 12:11:48 +03:00
Type : CommentTypeStopTracking ,
TimeID : tt . ID ,
} ) ; err != nil {
return err
}
2022-05-20 17:08:52 +03:00
_ , err = db . DeleteByBean ( ctx , sw )
2021-11-21 12:11:48 +03:00
return err
}
2017-09-12 09:48:13 +03:00
2021-11-21 12:11:48 +03:00
// CreateIssueStopwatch creates a stopwatch if not exist, otherwise return an error
2021-11-24 12:49:20 +03:00
func CreateIssueStopwatch ( ctx context . Context , user * user_model . User , issue * Issue ) error {
2022-04-08 12:11:15 +03:00
if err := issue . LoadRepo ( ctx ) ; err != nil {
2021-11-21 12:11:48 +03:00
return err
}
2017-09-12 09:48:13 +03:00
2021-11-21 12:11:48 +03:00
// if another stopwatch is running: stop it
2023-02-27 21:46:00 +03:00
exists , _ , otherIssue , err := HasUserStopwatch ( ctx , user . ID )
2021-11-21 12:11:48 +03:00
if err != nil {
return err
}
if exists {
2023-02-27 21:46:00 +03:00
if err := FinishIssueStopwatch ( ctx , user , otherIssue ) ; err != nil {
2017-09-12 09:48:13 +03:00
return err
}
2021-11-21 12:11:48 +03:00
}
2017-09-12 09:48:13 +03:00
2021-11-21 12:11:48 +03:00
// Create stopwatch
2023-02-27 21:46:00 +03:00
sw := & Stopwatch {
2021-11-21 12:11:48 +03:00
UserID : user . ID ,
IssueID : issue . ID ,
}
if err := db . Insert ( ctx , sw ) ; err != nil {
return err
2017-09-12 09:48:13 +03:00
}
2021-11-21 12:11:48 +03:00
2022-04-08 12:11:15 +03:00
if err := issue . LoadRepo ( ctx ) ; err != nil {
2021-11-21 12:11:48 +03:00
return err
}
2022-12-10 05:46:31 +03:00
if _ , err := CreateComment ( ctx , & CreateCommentOptions {
2021-11-21 12:11:48 +03:00
Doer : user ,
Issue : issue ,
Repo : issue . Repo ,
Type : CommentTypeStartTracking ,
} ) ; err != nil {
return err
}
2017-09-12 09:48:13 +03:00
return nil
}
// CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
2023-09-16 17:39:12 +03:00
func CancelStopwatch ( ctx context . Context , user * user_model . User , issue * Issue ) error {
ctx , committer , err := db . TxContext ( ctx )
2021-11-19 16:39:57 +03:00
if err != nil {
2021-07-26 23:46:06 +03:00
return err
}
2021-11-19 16:39:57 +03:00
defer committer . Close ( )
if err := cancelStopwatch ( ctx , user , issue ) ; err != nil {
2021-07-26 23:46:06 +03:00
return err
}
2021-11-19 16:39:57 +03:00
return committer . Commit ( )
2021-07-26 23:46:06 +03:00
}
2021-11-24 12:49:20 +03:00
func cancelStopwatch ( ctx context . Context , user * user_model . User , issue * Issue ) error {
2021-11-19 16:39:57 +03:00
e := db . GetEngine ( ctx )
2021-11-21 12:11:48 +03:00
sw , exists , err := getStopwatch ( ctx , user . ID , issue . ID )
2017-09-12 09:48:13 +03:00
if err != nil {
return err
}
if exists {
2021-07-26 23:46:06 +03:00
if _ , err := e . Delete ( sw ) ; err != nil {
2017-09-12 09:48:13 +03:00
return err
}
2022-04-08 12:11:15 +03:00
if err := issue . LoadRepo ( ctx ) ; err != nil {
2021-11-21 12:11:48 +03:00
return err
}
2022-12-10 05:46:31 +03:00
if _ , err := CreateComment ( ctx , & CreateCommentOptions {
2017-09-12 09:48:13 +03:00
Doer : user ,
Issue : issue ,
Repo : issue . Repo ,
Type : CommentTypeCancelTracking ,
} ) ; err != nil {
return err
}
}
return nil
}