github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/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 "io" 12 "mime" 13 "net/http" 14 "path/filepath" 15 "strings" 16 ) 17 18 const ( 19 FILEINFO_SORT_BY_CREATED = "CreateAt" 20 FILEINFO_SORT_BY_SIZE = "Size" 21 ) 22 23 // GetFileInfosOptions contains options for getting FileInfos 24 type GetFileInfosOptions struct { 25 // UserIds optionally limits the FileInfos to those created by the given users. 26 UserIds []string `json:"user_ids"` 27 // ClassIds optionally limits the FileInfos to those created in the given classes. 28 ClassIds []string `json:"class_ids"` 29 // Since optionally limits FileInfos to those created at or after the given time, specified as Unix time in milliseconds. 30 Since int64 `json:"since"` 31 // IncludeDeleted if set includes deleted FileInfos. 32 IncludeDeleted bool `json:"include_deleted"` 33 // SortBy sorts the FileInfos by this field. The default is to sort by date created. 34 SortBy string `json:"sort_by"` 35 // SortDescending changes the sort direction to descending order when true. 36 SortDescending bool `json:"sort_descending"` 37 } 38 39 type FileInfo struct { 40 Id string `json:"id"` 41 CreatorId string `json:"user_id"` 42 PostId string `json:"post_id,omitempty"` 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 } 57 58 func (fi *FileInfo) ToJson() string { 59 b, _ := json.Marshal(fi) 60 return string(b) 61 } 62 63 func FileInfoFromJson(data io.Reader) *FileInfo { 64 decoder := json.NewDecoder(data) 65 66 var fi FileInfo 67 if err := decoder.Decode(&fi); err != nil { 68 return nil 69 } else { 70 return &fi 71 } 72 } 73 74 func FileInfosToJson(infos []*FileInfo) string { 75 b, _ := json.Marshal(infos) 76 return string(b) 77 } 78 79 func FileInfosFromJson(data io.Reader) []*FileInfo { 80 decoder := json.NewDecoder(data) 81 82 var infos []*FileInfo 83 if err := decoder.Decode(&infos); err != nil { 84 return nil 85 } else { 86 return infos 87 } 88 } 89 90 func (fi *FileInfo) PreSave() { 91 if fi.Id == "" { 92 fi.Id = NewId() 93 } 94 95 if fi.CreateAt == 0 { 96 fi.CreateAt = GetMillis() 97 } 98 99 if fi.UpdateAt < fi.CreateAt { 100 fi.UpdateAt = fi.CreateAt 101 } 102 } 103 104 func (fi *FileInfo) IsValid() *AppError { 105 if len(fi.Id) != 26 { 106 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "", http.StatusBadRequest) 107 } 108 109 if len(fi.CreatorId) != 26 && fi.CreatorId != "nouser" { 110 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+fi.Id, http.StatusBadRequest) 111 } 112 113 if len(fi.PostId) != 0 && len(fi.PostId) != 26 { 114 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.post_id.app_error", nil, "id="+fi.Id, http.StatusBadRequest) 115 } 116 117 if fi.CreateAt == 0 { 118 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.create_at.app_error", nil, "id="+fi.Id, http.StatusBadRequest) 119 } 120 121 if fi.UpdateAt == 0 { 122 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.update_at.app_error", nil, "id="+fi.Id, http.StatusBadRequest) 123 } 124 125 if fi.Path == "" { 126 return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.path.app_error", nil, "id="+fi.Id, http.StatusBadRequest) 127 } 128 129 return nil 130 } 131 132 func (fi *FileInfo) IsImage() bool { 133 return strings.HasPrefix(fi.MimeType, "image") 134 } 135 136 func NewInfo(name string) *FileInfo { 137 info := &FileInfo{ 138 Name: name, 139 } 140 141 extension := strings.ToLower(filepath.Ext(name)) 142 info.MimeType = mime.TypeByExtension(extension) 143 144 if extension != "" && extension[0] == '.' { 145 // The client expects a file extension without the leading period 146 info.Extension = extension[1:] 147 } else { 148 info.Extension = extension 149 } 150 151 return info 152 } 153 154 func GetInfoForBytes(name string, data []byte) (*FileInfo, *AppError) { 155 info := &FileInfo{ 156 Name: name, 157 Size: int64(len(data)), 158 } 159 var err *AppError 160 161 extension := strings.ToLower(filepath.Ext(name)) 162 info.MimeType = mime.TypeByExtension(extension) 163 164 if extension != "" && extension[0] == '.' { 165 // The client expects a file extension without the leading period 166 info.Extension = extension[1:] 167 } else { 168 info.Extension = extension 169 } 170 171 if info.IsImage() { 172 // Only set the width and height if it's actually an image that we can understand 173 if config, _, err := image.DecodeConfig(bytes.NewReader(data)); err == nil { 174 info.Width = config.Width 175 info.Height = config.Height 176 177 if info.MimeType == "image/gif" { 178 // Just show the gif itself instead of a preview image for animated gifs 179 if gifConfig, err := gif.DecodeAll(bytes.NewReader(data)); err != nil { 180 // Still return the rest of the info even though it doesn't appear to be an actual gif 181 info.HasPreviewImage = true 182 return info, NewAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "name="+name, http.StatusBadRequest) 183 } else { 184 info.HasPreviewImage = len(gifConfig.Image) == 1 185 } 186 } else { 187 info.HasPreviewImage = true 188 } 189 } 190 } 191 192 return info, err 193 } 194 195 func GetEtagForFileInfos(infos []*FileInfo) string { 196 if len(infos) == 0 { 197 return Etag() 198 } 199 200 var maxUpdateAt int64 201 202 for _, info := range infos { 203 if info.UpdateAt > maxUpdateAt { 204 maxUpdateAt = info.UpdateAt 205 } 206 } 207 208 return Etag(infos[0].PostId, maxUpdateAt) 209 }