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