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  }