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  }