2017-02-17 11:02:11 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
2019-11-10 12:22:19 +03:00
import (
"fmt"
"code.gitea.io/gitea/modules/setting"
)
// RepoWatchMode specifies what kind of watch the user has on a repository
type RepoWatchMode int8
const (
// RepoWatchModeNone don't watch
RepoWatchModeNone RepoWatchMode = iota // 0
// RepoWatchModeNormal watch repository (from other sources)
RepoWatchModeNormal // 1
// RepoWatchModeDont explicit don't auto-watch
RepoWatchModeDont // 2
// RepoWatchModeAuto watch repository (from AutoWatchOnChanges)
RepoWatchModeAuto // 3
)
2017-02-17 11:02:11 +03:00
// Watch is connection request for receiving repository notification.
type Watch struct {
2019-11-10 12:22:19 +03:00
ID int64 ` xorm:"pk autoincr" `
UserID int64 ` xorm:"UNIQUE(watch)" `
RepoID int64 ` xorm:"UNIQUE(watch)" `
Mode RepoWatchMode ` xorm:"SMALLINT NOT NULL DEFAULT 1" `
2017-02-17 11:02:11 +03:00
}
2019-11-10 12:22:19 +03:00
// getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found
func getWatch ( e Engine , userID , repoID int64 ) ( Watch , error ) {
watch := Watch { UserID : userID , RepoID : repoID }
has , err := e . Get ( & watch )
if err != nil {
return watch , err
}
if ! has {
watch . Mode = RepoWatchModeNone
}
return watch , nil
}
// Decodes watchability of RepoWatchMode
func isWatchMode ( mode RepoWatchMode ) bool {
return mode != RepoWatchModeNone && mode != RepoWatchModeDont
2017-02-17 11:02:11 +03:00
}
// IsWatching checks if user has watched given repository.
func IsWatching ( userID , repoID int64 ) bool {
2019-11-10 12:22:19 +03:00
watch , err := getWatch ( x , userID , repoID )
return err == nil && isWatchMode ( watch . Mode )
2017-02-17 11:02:11 +03:00
}
2019-11-10 12:22:19 +03:00
func watchRepoMode ( e Engine , watch Watch , mode RepoWatchMode ) ( err error ) {
if watch . Mode == mode {
return nil
}
if mode == RepoWatchModeAuto && ( watch . Mode == RepoWatchModeDont || isWatchMode ( watch . Mode ) ) {
// Don't auto watch if already watching or deliberately not watching
return nil
}
hadrec := watch . Mode != RepoWatchModeNone
needsrec := mode != RepoWatchModeNone
repodiff := 0
if isWatchMode ( mode ) && ! isWatchMode ( watch . Mode ) {
repodiff = 1
} else if ! isWatchMode ( mode ) && isWatchMode ( watch . Mode ) {
repodiff = - 1
}
watch . Mode = mode
if ! hadrec && needsrec {
watch . Mode = mode
if _ , err = e . Insert ( watch ) ; err != nil {
2017-02-17 11:02:11 +03:00
return err
}
2019-11-10 12:22:19 +03:00
} else if needsrec {
watch . Mode = mode
if _ , err := e . ID ( watch . ID ) . AllCols ( ) . Update ( watch ) ; err != nil {
2017-02-17 11:02:11 +03:00
return err
}
2019-11-10 12:22:19 +03:00
} else if _ , err = e . Delete ( Watch { ID : watch . ID } ) ; err != nil {
return err
}
if repodiff != 0 {
_ , err = e . Exec ( "UPDATE `repository` SET num_watches = num_watches + ? WHERE id = ?" , repodiff , watch . RepoID )
}
return err
}
// WatchRepoMode watch repository in specific mode.
func WatchRepoMode ( userID , repoID int64 , mode RepoWatchMode ) ( err error ) {
var watch Watch
if watch , err = getWatch ( x , userID , repoID ) ; err != nil {
return err
}
return watchRepoMode ( x , watch , mode )
}
func watchRepo ( e Engine , userID , repoID int64 , doWatch bool ) ( err error ) {
var watch Watch
if watch , err = getWatch ( e , userID , repoID ) ; err != nil {
return err
}
if ! doWatch && watch . Mode == RepoWatchModeAuto {
err = watchRepoMode ( e , watch , RepoWatchModeDont )
} else if ! doWatch {
err = watchRepoMode ( e , watch , RepoWatchModeNone )
} else {
err = watchRepoMode ( e , watch , RepoWatchModeNormal )
2017-02-17 11:02:11 +03:00
}
return err
}
// WatchRepo watch or unwatch repository.
func WatchRepo ( userID , repoID int64 , watch bool ) ( err error ) {
return watchRepo ( x , userID , repoID , watch )
}
func getWatchers ( e Engine , repoID int64 ) ( [ ] * Watch , error ) {
watches := make ( [ ] * Watch , 0 , 10 )
2017-09-16 03:18:25 +03:00
return watches , e . Where ( "`watch`.repo_id=?" , repoID ) .
2019-11-10 12:22:19 +03:00
And ( "`watch`.mode<>?" , RepoWatchModeDont ) .
2017-09-16 03:18:25 +03:00
And ( "`user`.is_active=?" , true ) .
And ( "`user`.prohibit_login=?" , false ) .
2018-07-20 05:10:17 +03:00
Join ( "INNER" , "`user`" , "`user`.id = `watch`.user_id" ) .
2017-09-16 03:18:25 +03:00
Find ( & watches )
2017-02-17 11:02:11 +03:00
}
// GetWatchers returns all watchers of given repository.
func GetWatchers ( repoID int64 ) ( [ ] * Watch , error ) {
return getWatchers ( x , repoID )
}
2019-11-18 11:08:20 +03:00
// GetRepoWatchersIDs returns IDs of watchers for a given repo ID
// but avoids joining with `user` for performance reasons
// User permissions must be verified elsewhere if required
func GetRepoWatchersIDs ( repoID int64 ) ( [ ] int64 , error ) {
2020-02-26 09:32:22 +03:00
return getRepoWatchersIDs ( x , repoID )
}
func getRepoWatchersIDs ( e Engine , repoID int64 ) ( [ ] int64 , error ) {
2019-11-18 11:08:20 +03:00
ids := make ( [ ] int64 , 0 , 64 )
2020-02-26 09:32:22 +03:00
return ids , e . Table ( "watch" ) .
2019-11-18 11:08:20 +03:00
Where ( "watch.repo_id=?" , repoID ) .
And ( "watch.mode<>?" , RepoWatchModeDont ) .
Select ( "user_id" ) .
Find ( & ids )
}
2017-02-17 11:02:11 +03:00
// GetWatchers returns range of users watching given repository.
2020-01-24 22:00:29 +03:00
func ( repo * Repository ) GetWatchers ( opts ListOptions ) ( [ ] * User , error ) {
2017-02-17 11:02:11 +03:00
sess := x . Where ( "watch.repo_id=?" , repo . ID ) .
2019-11-10 12:22:19 +03:00
Join ( "LEFT" , "watch" , "`user`.id=`watch`.user_id" ) .
And ( "`watch`.mode<>?" , RepoWatchModeDont )
2020-01-24 22:00:29 +03:00
if opts . Page > 0 {
sess = opts . setSessionPagination ( sess )
users := make ( [ ] * User , 0 , opts . PageSize )
return users , sess . Find ( & users )
2017-02-17 11:02:11 +03:00
}
2020-01-24 22:00:29 +03:00
users := make ( [ ] * User , 0 , 8 )
2017-02-17 11:02:11 +03:00
return users , sess . Find ( & users )
}
2019-12-26 14:29:45 +03:00
func notifyWatchers ( e Engine , actions ... * Action ) error {
var watchers [ ] * Watch
var repo * Repository
var err error
var permCode [ ] bool
var permIssue [ ] bool
var permPR [ ] bool
for _ , act := range actions {
repoChanged := repo == nil || repo . ID != act . RepoID
if repoChanged {
// Add feeds for user self and all watchers.
watchers , err = getWatchers ( e , act . RepoID )
if err != nil {
return fmt . Errorf ( "get watchers: %v" , err )
}
}
2018-03-03 08:21:16 +03:00
2019-12-26 14:29:45 +03:00
// Add feed for actioner.
act . UserID = act . ActUserID
2018-03-03 08:21:16 +03:00
if _ , err = e . InsertOne ( act ) ; err != nil {
return fmt . Errorf ( "insert new actioner: %v" , err )
}
2019-12-26 14:29:45 +03:00
if repoChanged {
act . loadRepo ( )
repo = act . Repo
// check repo owner exist.
if err := act . Repo . getOwner ( e ) ; err != nil {
return fmt . Errorf ( "can't get repo owner: %v" , err )
}
} else if act . Repo == nil {
act . Repo = repo
2017-02-17 11:02:11 +03:00
}
2019-12-26 14:29:45 +03:00
// Add feed for organization
if act . Repo . Owner . IsOrganization ( ) && act . ActUserID != act . Repo . Owner . ID {
act . ID = 0
act . UserID = act . Repo . Owner . ID
if _ , err = e . InsertOne ( act ) ; err != nil {
return fmt . Errorf ( "insert new actioner: %v" , err )
}
}
2018-06-21 19:00:13 +03:00
2019-12-26 14:29:45 +03:00
if repoChanged {
permCode = make ( [ ] bool , len ( watchers ) )
permIssue = make ( [ ] bool , len ( watchers ) )
permPR = make ( [ ] bool , len ( watchers ) )
for i , watcher := range watchers {
user , err := getUserByID ( e , watcher . UserID )
if err != nil {
permCode [ i ] = false
permIssue [ i ] = false
permPR [ i ] = false
continue
}
perm , err := getUserRepoPermission ( e , repo , user )
if err != nil {
permCode [ i ] = false
permIssue [ i ] = false
permPR [ i ] = false
continue
}
permCode [ i ] = perm . CanRead ( UnitTypeCode )
permIssue [ i ] = perm . CanRead ( UnitTypeIssues )
permPR [ i ] = perm . CanRead ( UnitTypePullRequests )
2018-06-21 19:00:13 +03:00
}
2019-12-26 14:29:45 +03:00
}
for i , watcher := range watchers {
if act . ActUserID == watcher . UserID {
2018-06-21 19:00:13 +03:00
continue
}
2019-12-26 14:29:45 +03:00
act . ID = 0
act . UserID = watcher . UserID
act . Repo . Units = nil
switch act . OpType {
case ActionCommitRepo , ActionPushTag , ActionDeleteTag , ActionDeleteBranch :
if ! permCode [ i ] {
continue
}
case ActionCreateIssue , ActionCommentIssue , ActionCloseIssue , ActionReopenIssue :
if ! permIssue [ i ] {
continue
}
case ActionCreatePullRequest , ActionCommentPull , ActionMergePullRequest , ActionClosePullRequest , ActionReopenPullRequest :
if ! permPR [ i ] {
continue
}
2018-06-21 19:00:13 +03:00
}
2019-12-26 14:29:45 +03:00
if _ , err = e . InsertOne ( act ) ; err != nil {
return fmt . Errorf ( "insert new action: %v" , err )
}
2017-02-17 11:02:11 +03:00
}
}
return nil
}
// NotifyWatchers creates batch of actions for every watcher.
2019-12-26 14:29:45 +03:00
func NotifyWatchers ( actions ... * Action ) error {
return notifyWatchers ( x , actions ... )
2017-02-17 11:02:11 +03:00
}
2019-11-10 12:22:19 +03:00
2019-11-14 05:57:36 +03:00
// NotifyWatchersActions creates batch of actions for every watcher.
func NotifyWatchersActions ( acts [ ] * Action ) error {
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
for _ , act := range acts {
if err := notifyWatchers ( sess , act ) ; err != nil {
return err
}
}
return sess . Commit ( )
}
2019-11-10 12:22:19 +03:00
func watchIfAuto ( e Engine , userID , repoID int64 , isWrite bool ) error {
if ! isWrite || ! setting . Service . AutoWatchOnChanges {
return nil
}
watch , err := getWatch ( e , userID , repoID )
if err != nil {
return err
}
if watch . Mode != RepoWatchModeNone {
return nil
}
return watchRepoMode ( e , watch , RepoWatchModeAuto )
}
// WatchIfAuto subscribes to repo if AutoWatchOnChanges is set
func WatchIfAuto ( userID int64 , repoID int64 , isWrite bool ) error {
return watchIfAuto ( x , userID , repoID , isWrite )
}