github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/model/file_info.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package model 5 6 import ( 7 "encoding/json" 8 "image" 9 "image/gif" 10 "io" 11 "mime" 12 "net/http" 13 "path/filepath" 14 "strings" 15 ) 16 17 const ( 18 FILEINFO_SORT_BY_CREATED = "CreateAt" 19 FILEINFO_SORT_BY_SIZE = "Size" 20 ) 21 22 // GetFileInfosOptions contains options for getting FileInfos 23 type GetFileInfosOptions struct { 24 // UserIds optionally limits the FileInfos to those created by the given users. 25 UserIds []string `json:"user_ids"` 26 // ChannelIds optionally limits the FileInfos to those created in the given channels. 27 ChannelIds []string `json:"channel_ids"` 28 // Since optionally limits FileInfos to those created at or after the given time, specified as Unix time in milliseconds. 29 Since int64 `json:"since"` 30 // IncludeDeleted if set includes deleted FileInfos. 31 IncludeDeleted bool `json:"include_deleted"` 32 // SortBy sorts the FileInfos by this field. The default is to sort by date created. 33 SortBy string `json:"sort_by"` 34 // SortDescending changes the sort direction to descending order when true. 35 SortDescending bool `json:"sort_descending"` 36 } 37 38 type FileInfo struct { 39 Id string `json:"id"` 40 CreatorId string `json:"user_id"` 41 PostId string `json:"post_id,omitempty"` 42 ChannelId string `db:"-" json:"channel_id"` 43 CreateAt int64 `json:"create_at"` 44 UpdateAt int64 `json:"update_at"` 45 DeleteAt int64 `json:"delete_at"` 46 Path string `json:"-"` // not sent back to the client 47 ThumbnailPath string `json:"-"` // not sent back to the client 48 PreviewPath string `json:"-"` // not sent back to the client 49 Name string `json:"name"` 50 Extension string `json:"extension"` 51 Size int64 `json:"size"` 52 MimeType string `json:"mime_type"` 53 Width int `json:"width,omitempty"` 54 Height int `json:"height,omitempty"` 55 HasPreviewImage bool `json:"has_preview_image,omitempty"` 56 MiniPreview *[]byte `json:"mini_preview"` // declared as *[]byte to avoid postgres/mysql differences in deserialization 57 Content string `json:"-"` 58 RemoteId *string `json:"remote_id"` 59 } 60 61 func (fi *FileInfo) ToJson() string { 62 b, _ := json.Marshal(fi) 63 return string(b) 64 } 65 66 func FileInfoFromJson(data io.Reader) *FileInfo { 67 decoder := json.NewDecoder(data) 68 69 var fi FileInfo 70 if err := decoder.Decode(&fi); err != nil { 71 return nil 72 } 73 return &fi 74 } 75 76 func FileInfosToJson(infos []*FileInfo) string { 77 b, _ := json.Marshal(infos) 78 return string(b) 79 } 80 81 func FileInfosFromJson(data io.Reader) []*FileInfo { 82 decoder := json.NewDecoder(data) 83 84 var infos []*FileInfo 85 if err := decoder.Decode(&infos); err != nil { 86 return nil 87 } 88 return infos 89 } 90 91 func (fi *FileInfo) PreSave() { 92 if fi.Id == "" { 93 fi.Id = NewId() 94 } 95 96 if fi.CreateAt == 0 { 97 fi.CreateAt = GetMillis() 98 } 99 100 if fi.UpdateAt < fi.CreateAt { 101 fi.UpdateAt = fi.CreateAt 102 } 103 104 if fi.RemoteId == nil { 105 fi.RemoteId = NewString("") 106 } 107 } 108 109 func (fi *FileInfo) IsValid() *AppError { 110 if !IsValidId(fi.Id) { 111 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "", http.StatusBadRequest) 112 } 113 114 if !IsValidId(fi.CreatorId) && fi.CreatorId != "nouser" { 115 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+fi.Id, http.StatusBadRequest) 116 } 117 118 if fi.PostId != "" && !IsValidId(fi.PostId) { 119 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.post_id.app_error", nil, "id="+fi.Id, http.StatusBadRequest) 120 } 121 122 if fi.CreateAt == 0 { 123 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.create_at.app_error", nil, "id="+fi.Id, http.StatusBadRequest) 124 } 125 126 if fi.UpdateAt == 0 { 127 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.update_at.app_error", nil, "id="+fi.Id, http.StatusBadRequest) 128 } 129 130 if fi.Path == "" { 131 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.path.app_error", nil, "id="+fi.Id, http.StatusBadRequest) 132 } 133 134 return nil 135 } 136 137 func (fi *FileInfo) IsImage() bool { 138 return strings.HasPrefix(fi.MimeType, "image") 139 } 140 141 func NewInfo(name string) *FileInfo { 142 info := &FileInfo{ 143 Name: name, 144 } 145 146 extension := strings.ToLower(filepath.Ext(name)) 147 info.MimeType = mime.TypeByExtension(extension) 148 149 if extension != "" && extension[0] == '.' { 150 // The client expects a file extension without the leading period 151 info.Extension = extension[1:] 152 } else { 153 info.Extension = extension 154 } 155 156 return info 157 } 158 159 func GetInfoForBytes(name string, data io.ReadSeeker, size int) (*FileInfo, *AppError) { 160 info := &FileInfo{ 161 Name: name, 162 Size: int64(size), 163 } 164 var err *AppError 165 166 extension := strings.ToLower(filepath.Ext(name)) 167 info.MimeType = mime.TypeByExtension(extension) 168 169 if extension != "" && extension[0] == '.' { 170 // The client expects a file extension without the leading period 171 info.Extension = extension[1:] 172 } else { 173 info.Extension = extension 174 } 175 176 if info.IsImage() { 177 // Only set the width and height if it's actually an image that we can understand 178 if config, _, err := image.DecodeConfig(data); err == nil { 179 info.Width = config.Width 180 info.Height = config.Height 181 182 if info.MimeType == "image/gif" { 183 // Just show the gif itself instead of a preview image for animated gifs 184 data.Seek(0, io.SeekStart) 185 gifConfig, err := gif.DecodeAll(data) 186 if err != nil { 187 // Still return the rest of the info even though it doesn't appear to be an actual gif 188 info.HasPreviewImage = true 189 return info, NewAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, err.Error(), http.StatusBadRequest) 190 } 191 info.HasPreviewImage = len(gifConfig.Image) == 1 192 } else { 193 info.HasPreviewImage = true 194 } 195 } 196 } 197 198 return info, err 199 } 200 201 func GetEtagForFileInfos(infos []*FileInfo) string { 202 if len(infos) == 0 { 203 return Etag() 204 } 205 206 var maxUpdateAt int64 207 208 for _, info := range infos { 209 if info.UpdateAt > maxUpdateAt { 210 maxUpdateAt = info.UpdateAt 211 } 212 } 213 214 return Etag(infos[0].PostId, maxUpdateAt) 215 }