github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/services/sharedchannel/sync_send.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package sharedchannel
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"time"
    10  
    11  	"github.com/masterhung0112/hk_server/v5/model"
    12  	"github.com/masterhung0112/hk_server/v5/services/remotecluster"
    13  	"github.com/masterhung0112/hk_server/v5/shared/i18n"
    14  	"github.com/masterhung0112/hk_server/v5/shared/mlog"
    15  )
    16  
    17  type syncTask struct {
    18  	id         string
    19  	channelID  string
    20  	remoteID   string
    21  	AddedAt    time.Time
    22  	retryCount int
    23  	retryMsg   *syncMsg
    24  	schedule   time.Time
    25  }
    26  
    27  func newSyncTask(channelID string, remoteID string, retryMsg *syncMsg) syncTask {
    28  	var retryID string
    29  	if retryMsg != nil {
    30  		retryID = retryMsg.Id
    31  	}
    32  
    33  	return syncTask{
    34  		id:        channelID + remoteID + retryID, // combination of ids to avoid duplicates
    35  		channelID: channelID,
    36  		remoteID:  remoteID, // empty means update all remote clusters
    37  		retryMsg:  retryMsg,
    38  		schedule:  time.Now(),
    39  	}
    40  }
    41  
    42  // incRetry increments the retry counter and returns true if MaxRetries not exceeded.
    43  func (st *syncTask) incRetry() bool {
    44  	st.retryCount++
    45  	return st.retryCount <= MaxRetries
    46  }
    47  
    48  // NotifyChannelChanged is called to indicate that a shared channel has been modified,
    49  // thus triggering an update to all remote clusters.
    50  func (scs *Service) NotifyChannelChanged(channelID string) {
    51  	if rcs := scs.server.GetRemoteClusterService(); rcs == nil {
    52  		return
    53  	}
    54  
    55  	task := newSyncTask(channelID, "", nil)
    56  	task.schedule = time.Now().Add(NotifyMinimumDelay)
    57  	scs.addTask(task)
    58  }
    59  
    60  // NotifyUserProfileChanged is called to indicate that a user belonging to at least one
    61  // shared channel has modified their user profile (name, username, email, custom status, profile image)
    62  func (scs *Service) NotifyUserProfileChanged(userID string) {
    63  	if rcs := scs.server.GetRemoteClusterService(); rcs == nil {
    64  		return
    65  	}
    66  
    67  	scusers, err := scs.server.GetStore().SharedChannel().GetUsersForUser(userID)
    68  	if err != nil {
    69  		scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Failed to fetch shared channel users",
    70  			mlog.String("userID", userID),
    71  			mlog.Err(err),
    72  		)
    73  		return
    74  	}
    75  	if len(scusers) == 0 {
    76  		return
    77  	}
    78  
    79  	notified := make(map[string]struct{})
    80  
    81  	for _, user := range scusers {
    82  		// update every channel + remote combination they belong to.
    83  		// Redundant updates (ie. to same remote for multiple channels) will be
    84  		// filtered out.
    85  		combo := user.ChannelId + user.RemoteId
    86  		if _, ok := notified[combo]; ok {
    87  			continue
    88  		}
    89  		notified[combo] = struct{}{}
    90  		task := newSyncTask(user.ChannelId, user.RemoteId, nil)
    91  		task.schedule = time.Now().Add(NotifyMinimumDelay)
    92  		scs.addTask(task)
    93  	}
    94  }
    95  
    96  // ForceSyncForRemote causes all channels shared with the remote to be synchronized.
    97  func (scs *Service) ForceSyncForRemote(rc *model.RemoteCluster) {
    98  	if rcs := scs.server.GetRemoteClusterService(); rcs == nil {
    99  		return
   100  	}
   101  
   102  	// fetch all channels shared with this remote.
   103  	opts := model.SharedChannelRemoteFilterOpts{
   104  		RemoteId: rc.RemoteId,
   105  	}
   106  	scrs, err := scs.server.GetStore().SharedChannel().GetRemotes(opts)
   107  	if err != nil {
   108  		scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Failed to fetch shared channel remotes",
   109  			mlog.String("remote", rc.DisplayName),
   110  			mlog.String("remoteId", rc.RemoteId),
   111  			mlog.Err(err),
   112  		)
   113  		return
   114  	}
   115  
   116  	for _, scr := range scrs {
   117  		task := newSyncTask(scr.ChannelId, rc.RemoteId, nil)
   118  		task.schedule = time.Now().Add(NotifyMinimumDelay)
   119  		scs.addTask(task)
   120  	}
   121  }
   122  
   123  // addTask adds or re-adds a task to the queue.
   124  func (scs *Service) addTask(task syncTask) {
   125  	task.AddedAt = time.Now()
   126  	scs.mux.Lock()
   127  	if _, ok := scs.tasks[task.id]; !ok {
   128  		scs.tasks[task.id] = task
   129  	}
   130  	scs.mux.Unlock()
   131  
   132  	// wake up the sync goroutine
   133  	select {
   134  	case scs.changeSignal <- struct{}{}:
   135  	default:
   136  		// that's ok, the sync routine is already busy
   137  	}
   138  }
   139  
   140  // syncLoop is called via a dedicated goroutine to wait for notifications of channel changes and
   141  // updates each remote based on those changes.
   142  func (scs *Service) syncLoop(done chan struct{}) {
   143  	// create a timer to periodically check the task queue, but only if there is
   144  	// a delayed task in the queue.
   145  	delay := time.NewTimer(NotifyMinimumDelay)
   146  	defer stopTimer(delay)
   147  
   148  	// wait for channel changed signal and update for oldest task.
   149  	for {
   150  		select {
   151  		case <-scs.changeSignal:
   152  			if wait := scs.doSync(); wait > 0 {
   153  				stopTimer(delay)
   154  				delay.Reset(wait)
   155  			}
   156  		case <-delay.C:
   157  			if wait := scs.doSync(); wait > 0 {
   158  				delay.Reset(wait)
   159  			}
   160  		case <-done:
   161  			return
   162  		}
   163  	}
   164  }
   165  
   166  func stopTimer(timer *time.Timer) {
   167  	timer.Stop()
   168  	select {
   169  	case <-timer.C:
   170  	default:
   171  	}
   172  }
   173  
   174  // doSync checks the task queue for any tasks to be processed and processes all that are ready.
   175  // If any delayed tasks remain in queue then the duration until the next scheduled task is returned.
   176  func (scs *Service) doSync() time.Duration {
   177  	var task syncTask
   178  	var ok bool
   179  	var shortestWait time.Duration
   180  
   181  	for {
   182  		task, ok, shortestWait = scs.removeOldestTask()
   183  		if !ok {
   184  			break
   185  		}
   186  		if err := scs.processTask(task); err != nil {
   187  			// put task back into map so it will update again
   188  			if task.incRetry() {
   189  				scs.addTask(task)
   190  			} else {
   191  				scs.server.GetLogger().Error("Failed to synchronize shared channel",
   192  					mlog.String("channelId", task.channelID),
   193  					mlog.String("remoteId", task.remoteID),
   194  					mlog.Err(err),
   195  				)
   196  			}
   197  		}
   198  	}
   199  	return shortestWait
   200  }
   201  
   202  // removeOldestTask removes and returns the oldest task in the task map.
   203  // A task coming in via NotifyChannelChanged must stay in queue for at least
   204  // `NotifyMinimumDelay` to ensure we don't go nuts trying to sync during a bulk update.
   205  // If no tasks are available then false is returned.
   206  func (scs *Service) removeOldestTask() (syncTask, bool, time.Duration) {
   207  	scs.mux.Lock()
   208  	defer scs.mux.Unlock()
   209  
   210  	var oldestTask syncTask
   211  	var oldestKey string
   212  	var shortestWait time.Duration
   213  
   214  	for key, task := range scs.tasks {
   215  		// check if task is ready
   216  		if wait := time.Until(task.schedule); wait > 0 {
   217  			if wait < shortestWait || shortestWait == 0 {
   218  				shortestWait = wait
   219  			}
   220  			continue
   221  		}
   222  		// task is ready; check if it's the oldest ready task
   223  		if task.AddedAt.Before(oldestTask.AddedAt) || oldestTask.AddedAt.IsZero() {
   224  			oldestKey = key
   225  			oldestTask = task
   226  		}
   227  	}
   228  
   229  	if oldestKey != "" {
   230  		delete(scs.tasks, oldestKey)
   231  		return oldestTask, true, shortestWait
   232  	}
   233  	return oldestTask, false, shortestWait
   234  }
   235  
   236  // processTask updates one or more remote clusters with any new channel content.
   237  func (scs *Service) processTask(task syncTask) error {
   238  	var err error
   239  	var remotes []*model.RemoteCluster
   240  
   241  	if task.remoteID == "" {
   242  		filter := model.RemoteClusterQueryFilter{
   243  			InChannel:     task.channelID,
   244  			OnlyConfirmed: true,
   245  		}
   246  		remotes, err = scs.server.GetStore().RemoteCluster().GetAll(filter)
   247  		if err != nil {
   248  			return err
   249  		}
   250  	} else {
   251  		rc, err := scs.server.GetStore().RemoteCluster().Get(task.remoteID)
   252  		if err != nil {
   253  			return err
   254  		}
   255  		if !rc.IsOnline() {
   256  			return fmt.Errorf("Failed updating shared channel '%s' for offline remote cluster '%s'", task.channelID, rc.DisplayName)
   257  		}
   258  		remotes = []*model.RemoteCluster{rc}
   259  	}
   260  
   261  	for _, rc := range remotes {
   262  		rtask := task
   263  		rtask.remoteID = rc.RemoteId
   264  		if err := scs.syncForRemote(rtask, rc); err != nil {
   265  			// retry...
   266  			if rtask.incRetry() {
   267  				scs.addTask(rtask)
   268  			} else {
   269  				scs.server.GetLogger().Error("Failed to synchronize shared channel for remote cluster",
   270  					mlog.String("channelId", rtask.channelID),
   271  					mlog.String("remote", rc.DisplayName),
   272  					mlog.Err(err),
   273  				)
   274  			}
   275  		}
   276  	}
   277  	return nil
   278  }
   279  
   280  func (scs *Service) handlePostError(postId string, task syncTask, rc *model.RemoteCluster) {
   281  	if task.retryMsg != nil && len(task.retryMsg.Posts) == 1 && task.retryMsg.Posts[0].Id == postId {
   282  		// this was a retry for specific post that failed previously. Try again if within MaxRetries.
   283  		if task.incRetry() {
   284  			scs.addTask(task)
   285  		} else {
   286  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "error syncing post",
   287  				mlog.String("remote", rc.DisplayName),
   288  				mlog.String("post_id", postId),
   289  			)
   290  		}
   291  		return
   292  	}
   293  
   294  	// this post failed as part of a group of posts. Retry as an individual post.
   295  	post, err := scs.server.GetStore().Post().GetSingle(postId, true)
   296  	if err != nil {
   297  		scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "error fetching post for sync retry",
   298  			mlog.String("remote", rc.DisplayName),
   299  			mlog.String("post_id", postId),
   300  		)
   301  		return
   302  	}
   303  
   304  	syncMsg := newSyncMsg(task.channelID)
   305  	syncMsg.Posts = []*model.Post{post}
   306  
   307  	scs.addTask(newSyncTask(task.channelID, task.remoteID, syncMsg))
   308  }
   309  
   310  // notifyRemoteOffline creates an ephemeral post to the author for any posts created recently to remotes
   311  // that are offline.
   312  func (scs *Service) notifyRemoteOffline(posts []*model.Post, rc *model.RemoteCluster) {
   313  	// only send one ephemeral post per author.
   314  	notified := make(map[string]bool)
   315  
   316  	// range the slice in reverse so the newest posts are visited first; this ensures an ephemeral
   317  	// get added where it is mostly likely to be seen.
   318  	for i := len(posts) - 1; i >= 0; i-- {
   319  		post := posts[i]
   320  		if didNotify := notified[post.UserId]; didNotify {
   321  			continue
   322  		}
   323  
   324  		postCreateAt := model.GetTimeForMillis(post.CreateAt)
   325  
   326  		if post.DeleteAt == 0 && post.UserId != "" && time.Since(postCreateAt) < NotifyRemoteOfflineThreshold {
   327  			T := scs.getUserTranslations(post.UserId)
   328  			ephemeral := &model.Post{
   329  				ChannelId: post.ChannelId,
   330  				Message:   T("sharedchannel.cannot_deliver_post", map[string]interface{}{"Remote": rc.DisplayName}),
   331  				CreateAt:  post.CreateAt + 1,
   332  			}
   333  			scs.app.SendEphemeralPost(post.UserId, ephemeral)
   334  
   335  			notified[post.UserId] = true
   336  		}
   337  	}
   338  }
   339  
   340  func (scs *Service) updateCursorForRemote(scrId string, rc *model.RemoteCluster, cursor model.GetPostsSinceForSyncCursor) {
   341  	if err := scs.server.GetStore().SharedChannel().UpdateRemoteCursor(scrId, cursor); err != nil {
   342  		scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "error updating cursor for shared channel remote",
   343  			mlog.String("remote", rc.DisplayName),
   344  			mlog.Err(err),
   345  		)
   346  		return
   347  	}
   348  	scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "updated cursor for remote",
   349  		mlog.String("remote_id", rc.RemoteId),
   350  		mlog.String("remote", rc.DisplayName),
   351  		mlog.Int64("last_post_update_at", cursor.LastPostUpdateAt),
   352  		mlog.String("last_post_id", cursor.LastPostId),
   353  	)
   354  }
   355  
   356  func (scs *Service) getUserTranslations(userId string) i18n.TranslateFunc {
   357  	var locale string
   358  	user, err := scs.server.GetStore().User().Get(context.Background(), userId)
   359  	if err == nil {
   360  		locale = user.Locale
   361  	}
   362  
   363  	if locale == "" {
   364  		locale = model.DEFAULT_LOCALE
   365  	}
   366  	return i18n.GetUserTranslations(locale)
   367  }
   368  
   369  // shouldUserSync determines if a user needs to be synchronized.
   370  // User should be synchronized if it has no entry in the SharedChannelUsers table for the specified channel,
   371  // or there is an entry but the LastSyncAt is less than user.UpdateAt
   372  func (scs *Service) shouldUserSync(user *model.User, channelID string, rc *model.RemoteCluster) (sync bool, syncImage bool, err error) {
   373  	// don't sync users with the remote they originated from.
   374  	if user.RemoteId != nil && *user.RemoteId == rc.RemoteId {
   375  		return false, false, nil
   376  	}
   377  
   378  	scu, err := scs.server.GetStore().SharedChannel().GetSingleUser(user.Id, channelID, rc.RemoteId)
   379  	if err != nil {
   380  		if _, ok := err.(errNotFound); !ok {
   381  			return false, false, err
   382  		}
   383  
   384  		// user not in the SharedChannelUsers table, so we must add them.
   385  		scu = &model.SharedChannelUser{
   386  			UserId:    user.Id,
   387  			RemoteId:  rc.RemoteId,
   388  			ChannelId: channelID,
   389  		}
   390  		if _, err = scs.server.GetStore().SharedChannel().SaveUser(scu); err != nil {
   391  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error adding user to shared channel users",
   392  				mlog.String("remote_id", rc.RemoteId),
   393  				mlog.String("user_id", user.Id),
   394  				mlog.String("channel_id", user.Id),
   395  				mlog.Err(err),
   396  			)
   397  		}
   398  		return true, true, nil
   399  	}
   400  
   401  	return user.UpdateAt > scu.LastSyncAt, user.LastPictureUpdate > scu.LastSyncAt, nil
   402  }
   403  
   404  func (scs *Service) syncProfileImage(user *model.User, channelID string, rc *model.RemoteCluster) {
   405  	rcs := scs.server.GetRemoteClusterService()
   406  	if rcs == nil {
   407  		return
   408  	}
   409  
   410  	ctx, cancel := context.WithTimeout(context.Background(), ProfileImageSyncTimeout)
   411  	defer cancel()
   412  
   413  	rcs.SendProfileImage(ctx, user.Id, rc, scs.app, func(userId string, rc *model.RemoteCluster, resp *remotecluster.Response, err error) {
   414  		if resp.IsSuccess() {
   415  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Users profile image synchronized",
   416  				mlog.String("remote_id", rc.RemoteId),
   417  				mlog.String("user_id", user.Id),
   418  			)
   419  
   420  			if err2 := scs.server.GetStore().SharedChannel().UpdateUserLastSyncAt(user.Id, channelID, rc.RemoteId); err2 != nil {
   421  				scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error updating users LastSyncTime after profile image update",
   422  					mlog.String("remote_id", rc.RemoteId),
   423  					mlog.String("user_id", user.Id),
   424  					mlog.Err(err2),
   425  				)
   426  			}
   427  			return
   428  		}
   429  
   430  		scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error synchronizing users profile image",
   431  			mlog.String("remote_id", rc.RemoteId),
   432  			mlog.String("user_id", user.Id),
   433  			mlog.Err(err),
   434  		)
   435  	})
   436  }