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 }