github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/services/remotecluster/sendprofileImage.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package remotecluster 5 6 import ( 7 "bytes" 8 "context" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "mime/multipart" 13 "net/http" 14 "net/url" 15 "path" 16 "time" 17 18 "github.com/masterhung0112/hk_server/v5/model" 19 "github.com/masterhung0112/hk_server/v5/shared/mlog" 20 ) 21 22 type SendProfileImageResultFunc func(userId string, rc *model.RemoteCluster, resp *Response, err error) 23 24 type sendProfileImageTask struct { 25 rc *model.RemoteCluster 26 userID string 27 provider ProfileImageProvider 28 f SendProfileImageResultFunc 29 } 30 31 type ProfileImageProvider interface { 32 GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) 33 } 34 35 // SendProfileImage asynchronously sends a user's profile image to a remote cluster. 36 // 37 // `ctx` determines behaviour when the outbound queue is full. A timeout or deadline context will return a 38 // BufferFullError if the task cannot be enqueued before the timeout. A background context will block indefinitely. 39 // 40 // Nil or error return indicates success or failure of task enqueue only. 41 // 42 // An optional callback can be provided that receives the response from the remote cluster. The `err` provided to the 43 // callback is regarding image delivery only. The `resp` contains the decoded bytes returned from the remote. 44 // If a callback is provided it should return quickly. 45 func (rcs *Service) SendProfileImage(ctx context.Context, userID string, rc *model.RemoteCluster, provider ProfileImageProvider, f SendProfileImageResultFunc) error { 46 task := sendProfileImageTask{ 47 rc: rc, 48 userID: userID, 49 provider: provider, 50 f: f, 51 } 52 return rcs.enqueueTask(ctx, rc.RemoteId, task) 53 } 54 55 // sendProfileImage is called when a sendProfileImageTask is popped from the send channel. 56 func (rcs *Service) sendProfileImage(task sendProfileImageTask) { 57 err := rcs.sendProfileImageToRemote(SendTimeout, task) 58 var response Response 59 60 if err != nil { 61 rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceError, "Remote Cluster send profile image failed", 62 mlog.String("remote", task.rc.DisplayName), 63 mlog.String("UserId", task.userID), 64 mlog.Err(err), 65 ) 66 response.Status = ResponseStatusFail 67 response.Err = err.Error() 68 } else { 69 rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceDebug, "Remote Cluster profile image sent successfully", 70 mlog.String("remote", task.rc.DisplayName), 71 mlog.String("UserId", task.userID), 72 ) 73 response.Status = ResponseStatusOK 74 } 75 76 // If callback provided then call it with the results. 77 if task.f != nil { 78 task.f(task.userID, task.rc, &response, err) 79 } 80 } 81 82 func (rcs *Service) sendProfileImageToRemote(timeout time.Duration, task sendProfileImageTask) error { 83 rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceDebug, "sending profile image to remote...", 84 mlog.String("remote", task.rc.DisplayName), 85 mlog.String("UserId", task.userID), 86 ) 87 88 user, err := rcs.server.GetStore().User().Get(context.Background(), task.userID) 89 if err != nil { 90 return fmt.Errorf("error fetching user while sending profile image to remote %s: %w", task.rc.RemoteId, err) 91 } 92 93 img, _, appErr := task.provider.GetProfileImage(user) // get Reader for the file 94 if appErr != nil { 95 return fmt.Errorf("error fetching profile image for user (%s) while sending to remote %s: %w", task.userID, task.rc.RemoteId, appErr) 96 } 97 98 u, err := url.Parse(task.rc.SiteURL) 99 if err != nil { 100 return fmt.Errorf("invalid siteURL while sending file to remote %s: %w", task.rc.RemoteId, err) 101 } 102 u.Path = path.Join(u.Path, model.API_URL_SUFFIX, "remotecluster", task.userID, "image") 103 104 body := &bytes.Buffer{} 105 writer := multipart.NewWriter(body) 106 107 part, err := writer.CreateFormFile("image", "profile.png") 108 if err != nil { 109 return err 110 } 111 112 if _, err = io.Copy(part, bytes.NewBuffer(img)); err != nil { 113 return err 114 } 115 116 if err = writer.Close(); err != nil { 117 return err 118 } 119 120 req, err := http.NewRequest("POST", u.String(), body) 121 if err != nil { 122 return err 123 } 124 req.Header.Set("Content-Type", writer.FormDataContentType()) 125 req.Header.Set(model.HEADER_REMOTECLUSTER_ID, task.rc.RemoteId) 126 req.Header.Set(model.HEADER_REMOTECLUSTER_TOKEN, task.rc.RemoteToken) 127 128 ctx, cancel := context.WithTimeout(context.Background(), timeout) 129 defer cancel() 130 131 resp, err := rcs.httpClient.Do(req.WithContext(ctx)) 132 if err != nil { 133 return err 134 } 135 defer resp.Body.Close() 136 137 _, err = ioutil.ReadAll(resp.Body) 138 if err != nil { 139 return err 140 } 141 142 if resp.StatusCode != http.StatusOK { 143 return fmt.Errorf("unexpected response: %d - %s", resp.StatusCode, resp.Status) 144 } 145 return nil 146 }