mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
* Prepare file upload infrastructure for Data Retention. This commit prepares the file upload infrastructure for the data retention feature that is under construction. Changes are: * Move file management code to utils to allow access to it from jobs. * From now on, store all file uploads in a top level folder which is the date of the day on which they were uploaded. This commit is based on Harrison Healey's branch, but updated to work with the latest master. * Use NewAppError
178 lines
4.3 KiB
Go
178 lines
4.3 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package model
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"image"
|
|
"image/gif"
|
|
"io"
|
|
"mime"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type FileInfo struct {
|
|
Id string `json:"id"`
|
|
CreatorId string `json:"user_id"`
|
|
PostId string `json:"post_id,omitempty"`
|
|
CreateAt int64 `json:"create_at"`
|
|
UpdateAt int64 `json:"update_at"`
|
|
DeleteAt int64 `json:"delete_at"`
|
|
Path string `json:"-"` // not sent back to the client
|
|
ThumbnailPath string `json:"-"` // not sent back to the client
|
|
PreviewPath string `json:"-"` // not sent back to the client
|
|
Name string `json:"name"`
|
|
Extension string `json:"extension"`
|
|
Size int64 `json:"size"`
|
|
MimeType string `json:"mime_type"`
|
|
Width int `json:"width,omitempty"`
|
|
Height int `json:"height,omitempty"`
|
|
HasPreviewImage bool `json:"has_preview_image,omitempty"`
|
|
}
|
|
|
|
func (info *FileInfo) ToJson() string {
|
|
b, err := json.Marshal(info)
|
|
if err != nil {
|
|
return ""
|
|
} else {
|
|
return string(b)
|
|
}
|
|
}
|
|
|
|
func FileInfoFromJson(data io.Reader) *FileInfo {
|
|
decoder := json.NewDecoder(data)
|
|
|
|
var info FileInfo
|
|
if err := decoder.Decode(&info); err != nil {
|
|
return nil
|
|
} else {
|
|
return &info
|
|
}
|
|
}
|
|
|
|
func FileInfosToJson(infos []*FileInfo) string {
|
|
b, err := json.Marshal(infos)
|
|
if err != nil {
|
|
return ""
|
|
} else {
|
|
return string(b)
|
|
}
|
|
}
|
|
|
|
func FileInfosFromJson(data io.Reader) []*FileInfo {
|
|
decoder := json.NewDecoder(data)
|
|
|
|
var infos []*FileInfo
|
|
if err := decoder.Decode(&infos); err != nil {
|
|
return nil
|
|
} else {
|
|
return infos
|
|
}
|
|
}
|
|
|
|
func (o *FileInfo) PreSave() {
|
|
if o.Id == "" {
|
|
o.Id = NewId()
|
|
}
|
|
|
|
if o.CreateAt == 0 {
|
|
o.CreateAt = GetMillis()
|
|
}
|
|
|
|
if o.UpdateAt < o.CreateAt {
|
|
o.UpdateAt = o.CreateAt
|
|
}
|
|
}
|
|
|
|
func (o *FileInfo) IsValid() *AppError {
|
|
if len(o.Id) != 26 {
|
|
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "")
|
|
}
|
|
|
|
if len(o.CreatorId) != 26 {
|
|
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+o.Id)
|
|
}
|
|
|
|
if len(o.PostId) != 0 && len(o.PostId) != 26 {
|
|
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.post_id.app_error", nil, "id="+o.Id)
|
|
}
|
|
|
|
if o.CreateAt == 0 {
|
|
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.create_at.app_error", nil, "id="+o.Id)
|
|
}
|
|
|
|
if o.UpdateAt == 0 {
|
|
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.update_at.app_error", nil, "id="+o.Id)
|
|
}
|
|
|
|
if o.Path == "" {
|
|
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.path.app_error", nil, "id="+o.Id)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (o *FileInfo) IsImage() bool {
|
|
return strings.HasPrefix(o.MimeType, "image")
|
|
}
|
|
|
|
func GetInfoForBytes(name string, data []byte) (*FileInfo, *AppError) {
|
|
info := &FileInfo{
|
|
Name: name,
|
|
Size: int64(len(data)),
|
|
}
|
|
var err *AppError
|
|
|
|
extension := strings.ToLower(filepath.Ext(name))
|
|
info.MimeType = mime.TypeByExtension(extension)
|
|
|
|
if extension != "" && extension[0] == '.' {
|
|
// The client expects a file extension without the leading period
|
|
info.Extension = extension[1:]
|
|
} else {
|
|
info.Extension = extension
|
|
}
|
|
|
|
if info.IsImage() {
|
|
// Only set the width and height if it's actually an image that we can understand
|
|
if config, _, err := image.DecodeConfig(bytes.NewReader(data)); err == nil {
|
|
info.Width = config.Width
|
|
info.Height = config.Height
|
|
|
|
if info.MimeType == "image/gif" {
|
|
// Just show the gif itself instead of a preview image for animated gifs
|
|
if gifConfig, err := gif.DecodeAll(bytes.NewReader(data)); err != nil {
|
|
// Still return the rest of the info even though it doesn't appear to be an actual gif
|
|
info.HasPreviewImage = true
|
|
err = NewLocAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "name="+name)
|
|
} else {
|
|
info.HasPreviewImage = len(gifConfig.Image) == 1
|
|
}
|
|
} else {
|
|
info.HasPreviewImage = true
|
|
}
|
|
}
|
|
}
|
|
|
|
return info, err
|
|
}
|
|
|
|
func GetEtagForFileInfos(infos []*FileInfo) string {
|
|
if len(infos) == 0 {
|
|
return Etag()
|
|
}
|
|
|
|
var maxUpdateAt int64
|
|
|
|
for _, info := range infos {
|
|
if info.UpdateAt > maxUpdateAt {
|
|
maxUpdateAt = info.UpdateAt
|
|
}
|
|
}
|
|
|
|
return Etag(infos[0].PostId, maxUpdateAt)
|
|
}
|