1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2024-12-23 12:59:11 -05:00
forgejo/models/repo_watch.go
guillep2k 08ae6bb7ed Rewrite delivery of issue and comment mails (#9009)
* Mail issue subscribers, rework the function

* Simplify a little more

* Fix unused variable

* Refactor mail delivery to avoid heavy load on server

* Avoid splitting into too many goroutines

* Fix comments and optimize GetMaileableUsersByIDs()

* Fix return on errors
2019-11-18 08:08:20 +00:00

263 lines
7.3 KiB
Go

// 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
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
)
// Watch is connection request for receiving repository notification.
type Watch struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(watch)"`
RepoID int64 `xorm:"UNIQUE(watch)"`
Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
}
// 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
}
// IsWatching checks if user has watched given repository.
func IsWatching(userID, repoID int64) bool {
watch, err := getWatch(x, userID, repoID)
return err == nil && isWatchMode(watch.Mode)
}
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 {
return err
}
} else if needsrec {
watch.Mode = mode
if _, err := e.ID(watch.ID).AllCols().Update(watch); err != nil {
return err
}
} 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)
}
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)
return watches, e.Where("`watch`.repo_id=?", repoID).
And("`watch`.mode<>?", RepoWatchModeDont).
And("`user`.is_active=?", true).
And("`user`.prohibit_login=?", false).
Join("INNER", "`user`", "`user`.id = `watch`.user_id").
Find(&watches)
}
// GetWatchers returns all watchers of given repository.
func GetWatchers(repoID int64) ([]*Watch, error) {
return getWatchers(x, repoID)
}
// 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) {
ids := make([]int64, 0, 64)
return ids, x.Table("watch").
Where("watch.repo_id=?", repoID).
And("watch.mode<>?", RepoWatchModeDont).
Select("user_id").
Find(&ids)
}
// GetWatchers returns range of users watching given repository.
func (repo *Repository) GetWatchers(page int) ([]*User, error) {
users := make([]*User, 0, ItemsPerPage)
sess := x.Where("watch.repo_id=?", repo.ID).
Join("LEFT", "watch", "`user`.id=`watch`.user_id").
And("`watch`.mode<>?", RepoWatchModeDont)
if page > 0 {
sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage)
}
return users, sess.Find(&users)
}
func notifyWatchers(e Engine, act *Action) error {
// Add feeds for user self and all watchers.
watches, err := getWatchers(e, act.RepoID)
if err != nil {
return fmt.Errorf("get watchers: %v", err)
}
// Add feed for actioner.
act.UserID = act.ActUserID
if _, err = e.InsertOne(act); err != nil {
return fmt.Errorf("insert new actioner: %v", err)
}
act.loadRepo()
// check repo owner exist.
if err := act.Repo.getOwner(e); err != nil {
return fmt.Errorf("can't get repo owner: %v", err)
}
// 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)
}
}
for i := range watches {
if act.ActUserID == watches[i].UserID {
continue
}
act.ID = 0
act.UserID = watches[i].UserID
act.Repo.Units = nil
switch act.OpType {
case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionDeleteBranch:
if !act.Repo.checkUnitUser(e, act.UserID, false, UnitTypeCode) {
continue
}
case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue:
if !act.Repo.checkUnitUser(e, act.UserID, false, UnitTypeIssues) {
continue
}
case ActionCreatePullRequest, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest:
if !act.Repo.checkUnitUser(e, act.UserID, false, UnitTypePullRequests) {
continue
}
}
if _, err = e.InsertOne(act); err != nil {
return fmt.Errorf("insert new action: %v", err)
}
}
return nil
}
// NotifyWatchers creates batch of actions for every watcher.
func NotifyWatchers(act *Action) error {
return notifyWatchers(x, act)
}
// 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()
}
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)
}