2015-06-14 23:53:32 -08:00
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"bytes"
2015-09-17 11:09:13 -04:00
"code.google.com/p/graphics-go/graphics"
2015-06-14 23:53:32 -08:00
l4g "code.google.com/p/log4go"
"fmt"
"github.com/goamz/goamz/aws"
"github.com/goamz/goamz/s3"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"github.com/nfnt/resize"
2015-09-17 11:09:13 -04:00
"github.com/rwcarlsen/goexif/exif"
2015-07-08 11:50:10 -04:00
_ "golang.org/x/image/bmp"
2015-06-14 23:53:32 -08:00
"image"
2015-08-04 14:35:34 -07:00
"image/color"
"image/draw"
2015-06-14 23:53:32 -08:00
_ "image/gif"
"image/jpeg"
"io"
2015-07-16 08:54:09 -04:00
"io/ioutil"
2015-09-17 11:09:13 -04:00
"math"
2015-06-14 23:53:32 -08:00
"net/http"
"net/url"
2015-07-16 08:54:09 -04:00
"os"
2015-06-14 23:53:32 -08:00
"path/filepath"
"strconv"
"strings"
"time"
)
2015-09-18 18:00:09 -04:00
const (
/ *
EXIF Image Orientations
1 2 3 4 5 6 7 8
888888 888888 88 88 8888888888 88 88 8888888888
88 88 88 88 88 88 88 88 88 88 88 88
8888 8888 8888 8888 88 8888888888 8888888888 88
88 88 88 88
88 88 888888 888888
* /
Upright = 1
UprightMirrored = 2
UpsideDown = 3
UpsideDownMirrored = 4
RotatedCWMirrored = 5
RotatedCCW = 6
RotatedCCWMirrored = 7
RotatedCW = 8
)
2015-08-24 15:03:52 -07:00
var fileInfoCache * utils . Cache = utils . NewLru ( 1000 )
2015-06-14 23:53:32 -08:00
func InitFile ( r * mux . Router ) {
2015-07-16 08:54:09 -04:00
l4g . Debug ( "Initializing file api routes" )
2015-06-14 23:53:32 -08:00
sr := r . PathPrefix ( "/files" ) . Subrouter ( )
sr . Handle ( "/upload" , ApiUserRequired ( uploadFile ) ) . Methods ( "POST" )
2015-08-24 15:03:52 -07:00
sr . Handle ( "/get/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}" , ApiAppHandler ( getFile ) ) . Methods ( "GET" )
sr . Handle ( "/get_info/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}" , ApiAppHandler ( getFileInfo ) ) . Methods ( "GET" )
2015-06-14 23:53:32 -08:00
sr . Handle ( "/get_public_link" , ApiUserRequired ( getPublicLink ) ) . Methods ( "POST" )
2015-08-26 12:49:07 -04:00
sr . Handle ( "/get_export" , ApiUserRequired ( getExport ) ) . Methods ( "GET" )
2015-06-14 23:53:32 -08:00
}
func uploadFile ( c * Context , w http . ResponseWriter , r * http . Request ) {
2015-07-16 08:54:09 -04:00
if ! utils . IsS3Configured ( ) && ! utils . Cfg . ServiceSettings . UseLocalStorage {
c . Err = model . NewAppError ( "uploadFile" , "Unable to upload file. Amazon S3 not configured and local server storage turned off. " , "" )
2015-06-14 23:53:32 -08:00
c . Err . StatusCode = http . StatusNotImplemented
return
}
err := r . ParseMultipartForm ( model . MAX_FILE_SIZE )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
m := r . MultipartForm
props := m . Value
if len ( props [ "channel_id" ] ) == 0 {
c . SetInvalidParam ( "uploadFile" , "channel_id" )
return
}
channelId := props [ "channel_id" ] [ 0 ]
if len ( channelId ) == 0 {
c . SetInvalidParam ( "uploadFile" , "channel_id" )
return
}
cchan := Srv . Store . Channel ( ) . CheckPermissionsTo ( c . Session . TeamId , channelId , c . Session . UserId )
files := m . File [ "files" ]
resStruct := & model . FileUploadResponse {
2015-08-10 12:05:45 -04:00
Filenames : [ ] string { } ,
ClientIds : [ ] string { } ,
}
2015-06-14 23:53:32 -08:00
imageNameList := [ ] string { }
imageDataList := [ ] [ ] byte { }
if ! c . HasPermissionsToChannel ( cchan , "uploadFile" ) {
return
}
2015-09-17 09:01:33 -04:00
for i := range files {
2015-06-14 23:53:32 -08:00
file , err := files [ i ] . Open ( )
defer file . Close ( )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
buf := bytes . NewBuffer ( nil )
io . Copy ( buf , file )
2015-07-21 15:18:17 -04:00
filename := filepath . Base ( files [ i ] . Filename )
2015-06-14 23:53:32 -08:00
uid := model . NewId ( )
2015-07-21 15:18:17 -04:00
path := "teams/" + c . Session . TeamId + "/channels/" + channelId + "/users/" + c . Session . UserId + "/" + uid + "/" + filename
2015-06-14 23:53:32 -08:00
2015-07-16 08:54:09 -04:00
if err := writeFile ( buf . Bytes ( ) , path ) ; err != nil {
c . Err = err
return
2015-06-14 23:53:32 -08:00
}
2015-07-16 08:54:09 -04:00
if model . IsFileExtImage ( filepath . Ext ( files [ i ] . Filename ) ) {
2015-07-21 15:18:17 -04:00
imageNameList = append ( imageNameList , uid + "/" + filename )
2015-07-16 08:54:09 -04:00
imageDataList = append ( imageDataList , buf . Bytes ( ) )
2015-06-14 23:53:32 -08:00
}
2015-07-21 15:18:17 -04:00
encName := utils . UrlEncode ( filename )
2015-07-17 15:55:06 -04:00
fileUrl := "/" + channelId + "/" + c . Session . UserId + "/" + uid + "/" + encName
2015-06-14 23:53:32 -08:00
resStruct . Filenames = append ( resStruct . Filenames , fileUrl )
}
2015-08-10 12:05:45 -04:00
for _ , clientId := range props [ "client_ids" ] {
resStruct . ClientIds = append ( resStruct . ClientIds , clientId )
}
2015-06-14 23:53:32 -08:00
fireAndForgetHandleImages ( imageNameList , imageDataList , c . Session . TeamId , channelId , c . Session . UserId )
w . Write ( [ ] byte ( resStruct . ToJson ( ) ) )
}
func fireAndForgetHandleImages ( filenames [ ] string , fileData [ ] [ ] byte , teamId , channelId , userId string ) {
go func ( ) {
dest := "teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/"
for i , filename := range filenames {
name := filename [ : strings . LastIndex ( filename , "." ) ]
go func ( ) {
// Decode image bytes into Image object
img , _ , err := image . Decode ( bytes . NewReader ( fileData [ i ] ) )
if err != nil {
l4g . Error ( "Unable to decode image channelId=%v userId=%v filename=%v err=%v" , channelId , userId , filename , err )
return
}
2015-09-17 11:09:13 -04:00
width := img . Bounds ( ) . Dx ( )
height := img . Bounds ( ) . Dy ( )
// Get the image's orientation and ignore any errors since not all images will have orientation data
orientation , _ := getImageOrientation ( fileData [ i ] )
// Create a temporary image that will be manipulated and then used to make the thumbnail and preview image
var temp * image . RGBA
2015-09-18 18:00:09 -04:00
switch orientation {
case Upright , UprightMirrored , UpsideDown , UpsideDownMirrored :
2015-09-17 11:09:13 -04:00
temp = image . NewRGBA ( img . Bounds ( ) )
2015-09-18 18:00:09 -04:00
case RotatedCCW , RotatedCCWMirrored , RotatedCW , RotatedCWMirrored :
2015-09-17 11:09:13 -04:00
bounds := img . Bounds ( )
temp = image . NewRGBA ( image . Rect ( bounds . Min . Y , bounds . Min . X , bounds . Max . Y , bounds . Max . X ) )
width , height = height , width
2015-06-14 23:53:32 -08:00
}
2015-09-17 11:09:13 -04:00
// Draw a white background since JPEGs lack transparency
2015-08-04 14:35:34 -07:00
draw . Draw ( temp , temp . Bounds ( ) , image . NewUniform ( color . White ) , image . Point { } , draw . Src )
2015-09-17 11:09:13 -04:00
// Copy the original image onto the temporary one while rotating it as necessary
switch orientation {
2015-09-18 18:00:09 -04:00
case UpsideDown , UpsideDownMirrored :
2015-09-17 11:09:13 -04:00
// rotate 180 degrees
err := graphics . Rotate ( temp , img , & graphics . RotateOptions { Angle : math . Pi } )
if err != nil {
l4g . Error ( "Unable to rotate image" )
}
2015-09-18 18:00:09 -04:00
case RotatedCW , RotatedCWMirrored :
2015-09-17 11:09:13 -04:00
// rotate 90 degrees CCW
graphics . Rotate ( temp , img , & graphics . RotateOptions { Angle : 3 * math . Pi / 2 } )
if err != nil {
l4g . Error ( "Unable to rotate image" )
}
2015-09-18 18:00:09 -04:00
case RotatedCCW , RotatedCCWMirrored :
2015-09-17 11:09:13 -04:00
// rotate 90 degrees CW
graphics . Rotate ( temp , img , & graphics . RotateOptions { Angle : math . Pi / 2 } )
if err != nil {
l4g . Error ( "Unable to rotate image" )
}
2015-09-18 18:00:09 -04:00
case Upright , UprightMirrored :
2015-09-17 11:09:13 -04:00
draw . Draw ( temp , temp . Bounds ( ) , img , img . Bounds ( ) . Min , draw . Over )
}
2015-08-04 14:35:34 -07:00
img = temp
2015-06-14 23:53:32 -08:00
// Create thumbnail
go func ( ) {
2015-07-29 10:09:11 -04:00
thumbWidth := float64 ( utils . Cfg . ImageSettings . ThumbnailWidth )
thumbHeight := float64 ( utils . Cfg . ImageSettings . ThumbnailHeight )
2015-09-17 11:09:13 -04:00
imgWidth := float64 ( width )
imgHeight := float64 ( height )
2015-07-29 10:09:11 -04:00
2015-06-14 23:53:32 -08:00
var thumbnail image . Image
2015-07-29 10:09:11 -04:00
if imgHeight < thumbHeight && imgWidth < thumbWidth {
2015-06-14 23:53:32 -08:00
thumbnail = img
2015-07-29 10:09:11 -04:00
} else if imgHeight / imgWidth < thumbHeight / thumbWidth {
thumbnail = resize . Resize ( 0 , utils . Cfg . ImageSettings . ThumbnailHeight , img , resize . Lanczos3 )
} else {
thumbnail = resize . Resize ( utils . Cfg . ImageSettings . ThumbnailWidth , 0 , img , resize . Lanczos3 )
2015-06-14 23:53:32 -08:00
}
buf := new ( bytes . Buffer )
err = jpeg . Encode ( buf , thumbnail , & jpeg . Options { Quality : 90 } )
if err != nil {
l4g . Error ( "Unable to encode image as jpeg channelId=%v userId=%v filename=%v err=%v" , channelId , userId , filename , err )
return
}
2015-07-16 12:50:38 -04:00
if err := writeFile ( buf . Bytes ( ) , dest + name + "_thumb.jpg" ) ; err != nil {
l4g . Error ( "Unable to upload thumbnail channelId=%v userId=%v filename=%v err=%v" , channelId , userId , filename , err )
2015-06-14 23:53:32 -08:00
return
}
} ( )
// Create preview
go func ( ) {
var preview image . Image
2015-09-17 11:09:13 -04:00
if width > int ( utils . Cfg . ImageSettings . PreviewWidth ) {
2015-07-20 15:12:21 -04:00
preview = resize . Resize ( utils . Cfg . ImageSettings . PreviewWidth , utils . Cfg . ImageSettings . PreviewHeight , img , resize . Lanczos3 )
2015-06-14 23:53:32 -08:00
} else {
preview = img
}
buf := new ( bytes . Buffer )
2015-07-16 08:54:09 -04:00
err = jpeg . Encode ( buf , preview , & jpeg . Options { Quality : 90 } )
2015-06-14 23:53:32 -08:00
if err != nil {
l4g . Error ( "Unable to encode image as preview jpg channelId=%v userId=%v filename=%v err=%v" , channelId , userId , filename , err )
return
}
2015-07-16 12:50:38 -04:00
if err := writeFile ( buf . Bytes ( ) , dest + name + "_preview.jpg" ) ; err != nil {
l4g . Error ( "Unable to upload preview channelId=%v userId=%v filename=%v err=%v" , channelId , userId , filename , err )
2015-06-14 23:53:32 -08:00
return
}
} ( )
} ( )
}
} ( )
}
2015-09-17 11:09:13 -04:00
func getImageOrientation ( imageData [ ] byte ) ( int , error ) {
if exifData , err := exif . Decode ( bytes . NewReader ( imageData ) ) ; err != nil {
2015-09-18 18:00:09 -04:00
return Upright , err
2015-09-17 11:09:13 -04:00
} else {
if tag , err := exifData . Get ( "Orientation" ) ; err != nil {
2015-09-18 18:00:09 -04:00
return Upright , err
2015-09-17 11:09:13 -04:00
} else {
orientation , err := tag . Int ( 0 )
if err != nil {
2015-09-18 18:00:09 -04:00
return Upright , err
2015-09-17 11:09:13 -04:00
} else {
return orientation , nil
}
}
}
}
2015-06-14 23:53:32 -08:00
type ImageGetResult struct {
Error error
ImageData [ ] byte
}
2015-08-24 15:03:52 -07:00
func getFileInfo ( c * Context , w http . ResponseWriter , r * http . Request ) {
if ! utils . IsS3Configured ( ) && ! utils . Cfg . ServiceSettings . UseLocalStorage {
c . Err = model . NewAppError ( "getFileInfo" , "Unable to get file info. Amazon S3 not configured and local server storage turned off. " , "" )
c . Err . StatusCode = http . StatusNotImplemented
return
}
params := mux . Vars ( r )
channelId := params [ "channel_id" ]
if len ( channelId ) != 26 {
c . SetInvalidParam ( "getFileInfo" , "channel_id" )
return
}
userId := params [ "user_id" ]
if len ( userId ) != 26 {
c . SetInvalidParam ( "getFileInfo" , "user_id" )
return
}
filename := params [ "filename" ]
if len ( filename ) == 0 {
c . SetInvalidParam ( "getFileInfo" , "filename" )
return
}
cchan := Srv . Store . Channel ( ) . CheckPermissionsTo ( c . Session . TeamId , channelId , c . Session . UserId )
path := "teams/" + c . Session . TeamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
size := ""
if s , ok := fileInfoCache . Get ( path ) ; ok {
size = s . ( string )
} else {
fileData := make ( chan [ ] byte )
asyncGetFile ( path , fileData )
f := <- fileData
if f == nil {
c . Err = model . NewAppError ( "getFileInfo" , "Could not find file." , "path=" + path )
c . Err . StatusCode = http . StatusNotFound
return
}
size = strconv . Itoa ( len ( f ) )
fileInfoCache . Add ( path , size )
}
if ! c . HasPermissionsToChannel ( cchan , "getFileInfo" ) {
return
}
w . Header ( ) . Set ( "Cache-Control" , "max-age=2592000, public" )
result := make ( map [ string ] string )
result [ "filename" ] = filename
result [ "size" ] = size
w . Write ( [ ] byte ( model . MapToJson ( result ) ) )
}
2015-06-14 23:53:32 -08:00
func getFile ( c * Context , w http . ResponseWriter , r * http . Request ) {
2015-07-16 08:54:09 -04:00
if ! utils . IsS3Configured ( ) && ! utils . Cfg . ServiceSettings . UseLocalStorage {
2015-08-24 15:03:52 -07:00
c . Err = model . NewAppError ( "getFile" , "Unable to get file. Amazon S3 not configured and local server storage turned off. " , "" )
2015-06-14 23:53:32 -08:00
c . Err . StatusCode = http . StatusNotImplemented
return
}
params := mux . Vars ( r )
channelId := params [ "channel_id" ]
if len ( channelId ) != 26 {
c . SetInvalidParam ( "getFile" , "channel_id" )
return
}
userId := params [ "user_id" ]
if len ( userId ) != 26 {
c . SetInvalidParam ( "getFile" , "user_id" )
return
}
filename := params [ "filename" ]
if len ( filename ) == 0 {
c . SetInvalidParam ( "getFile" , "filename" )
return
}
hash := r . URL . Query ( ) . Get ( "h" )
data := r . URL . Query ( ) . Get ( "d" )
teamId := r . URL . Query ( ) . Get ( "t" )
cchan := Srv . Store . Channel ( ) . CheckPermissionsTo ( c . Session . TeamId , channelId , c . Session . UserId )
path := ""
if len ( teamId ) == 26 {
path = "teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
} else {
path = "teams/" + c . Session . TeamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
}
fileData := make ( chan [ ] byte )
2015-07-16 08:54:09 -04:00
asyncGetFile ( path , fileData )
2015-06-14 23:53:32 -08:00
if len ( hash ) > 0 && len ( data ) > 0 && len ( teamId ) == 26 {
if ! model . ComparePassword ( hash , fmt . Sprintf ( "%v:%v" , data , utils . Cfg . ServiceSettings . PublicLinkSalt ) ) {
c . Err = model . NewAppError ( "getFile" , "The public link does not appear to be valid" , "" )
return
}
props := model . MapFromJson ( strings . NewReader ( data ) )
t , err := strconv . ParseInt ( props [ "time" ] , 10 , 64 )
if err != nil || model . GetMillis ( ) - t > 1000 * 60 * 60 * 24 * 7 { // one week
c . Err = model . NewAppError ( "getFile" , "The public link has expired" , "" )
return
}
} else if ! c . HasPermissionsToChannel ( cchan , "getFile" ) {
return
}
f := <- fileData
if f == nil {
2015-07-16 08:54:09 -04:00
c . Err = model . NewAppError ( "getFile" , "Could not find file." , "path=" + path )
2015-06-14 23:53:32 -08:00
c . Err . StatusCode = http . StatusNotFound
return
}
w . Header ( ) . Set ( "Cache-Control" , "max-age=2592000, public" )
2015-08-27 11:05:18 -07:00
w . Header ( ) . Set ( "Content-Length" , strconv . Itoa ( len ( f ) ) )
2015-09-14 14:18:01 -04:00
w . Header ( ) . Set ( "Content-Type" , "" ) // need to provide proper Content-Type in the future
2015-08-24 15:03:52 -07:00
w . Write ( f )
2015-06-14 23:53:32 -08:00
}
2015-07-16 08:54:09 -04:00
func asyncGetFile ( path string , fileData chan [ ] byte ) {
2015-06-14 23:53:32 -08:00
go func ( ) {
2015-07-16 08:54:09 -04:00
data , getErr := readFile ( path )
2015-06-14 23:53:32 -08:00
if getErr != nil {
2015-07-17 15:55:06 -04:00
l4g . Error ( getErr )
2015-06-14 23:53:32 -08:00
fileData <- nil
} else {
fileData <- data
}
} ( )
}
func getPublicLink ( c * Context , w http . ResponseWriter , r * http . Request ) {
if ! utils . Cfg . TeamSettings . AllowPublicLink {
c . Err = model . NewAppError ( "getPublicLink" , "Public links have been disabled" , "" )
c . Err . StatusCode = http . StatusForbidden
}
2015-07-16 08:54:09 -04:00
if ! utils . IsS3Configured ( ) && ! utils . Cfg . ServiceSettings . UseLocalStorage {
c . Err = model . NewAppError ( "getPublicLink" , "Unable to upload file. Amazon S3 not configured and local server storage turned off. " , "" )
2015-06-14 23:53:32 -08:00
c . Err . StatusCode = http . StatusNotImplemented
return
}
props := model . MapFromJson ( r . Body )
filename := props [ "filename" ]
if len ( filename ) == 0 {
c . SetInvalidParam ( "getPublicLink" , "filename" )
return
}
matches := model . PartialUrlRegex . FindAllStringSubmatch ( filename , - 1 )
2015-07-21 19:21:05 -04:00
if len ( matches ) == 0 || len ( matches [ 0 ] ) < 4 {
2015-06-14 23:53:32 -08:00
c . SetInvalidParam ( "getPublicLink" , "filename" )
return
}
2015-07-21 19:21:05 -04:00
channelId := matches [ 0 ] [ 1 ]
userId := matches [ 0 ] [ 2 ]
filename = matches [ 0 ] [ 3 ]
2015-06-14 23:53:32 -08:00
cchan := Srv . Store . Channel ( ) . CheckPermissionsTo ( c . Session . TeamId , channelId , c . Session . UserId )
newProps := make ( map [ string ] string )
newProps [ "filename" ] = filename
newProps [ "time" ] = fmt . Sprintf ( "%v" , model . GetMillis ( ) )
data := model . MapToJson ( newProps )
hash := model . HashPassword ( fmt . Sprintf ( "%v:%v" , data , utils . Cfg . ServiceSettings . PublicLinkSalt ) )
2015-07-21 19:21:05 -04:00
url := fmt . Sprintf ( "%s/api/v1/files/get/%s/%s/%s?d=%s&h=%s&t=%s" , c . GetSiteURL ( ) , channelId , userId , filename , url . QueryEscape ( data ) , url . QueryEscape ( hash ) , c . Session . TeamId )
2015-06-14 23:53:32 -08:00
if ! c . HasPermissionsToChannel ( cchan , "getPublicLink" ) {
return
}
rData := make ( map [ string ] string )
rData [ "public_link" ] = url
w . Write ( [ ] byte ( model . MapToJson ( rData ) ) )
}
2015-07-16 08:54:09 -04:00
2015-08-26 12:49:07 -04:00
func getExport ( c * Context , w http . ResponseWriter , r * http . Request ) {
if ! c . HasPermissionsToTeam ( c . Session . TeamId , "export" ) || ! c . IsTeamAdmin ( c . Session . UserId ) {
c . Err = model . NewAppError ( "getExport" , "Only a team admin can retrieve exported data." , "userId=" + c . Session . UserId )
c . Err . StatusCode = http . StatusForbidden
return
}
data , err := readFile ( EXPORT_PATH + EXPORT_FILENAME )
if err != nil {
c . Err = model . NewAppError ( "getExport" , "Unable to retrieve exported file. Please re-export" , err . Error ( ) )
return
}
w . Header ( ) . Set ( "Content-Disposition" , "attachment; filename=" + EXPORT_FILENAME )
w . Header ( ) . Set ( "Content-Type" , "application/octet-stream" )
w . Write ( data )
}
2015-07-16 08:54:09 -04:00
func writeFile ( f [ ] byte , path string ) * model . AppError {
if utils . IsS3Configured ( ) && ! utils . Cfg . ServiceSettings . UseLocalStorage {
var auth aws . Auth
auth . AccessKey = utils . Cfg . AWSSettings . S3AccessKeyId
auth . SecretKey = utils . Cfg . AWSSettings . S3SecretAccessKey
s := s3 . New ( auth , aws . Regions [ utils . Cfg . AWSSettings . S3Region ] )
bucket := s . Bucket ( utils . Cfg . AWSSettings . S3Bucket )
ext := filepath . Ext ( path )
var err error
if model . IsFileExtImage ( ext ) {
options := s3 . Options { }
err = bucket . Put ( path , f , model . GetImageMimeType ( ext ) , s3 . Private , options )
} else {
options := s3 . Options { }
err = bucket . Put ( path , f , "binary/octet-stream" , s3 . Private , options )
}
if err != nil {
return model . NewAppError ( "writeFile" , "Encountered an error writing to S3" , err . Error ( ) )
}
} else if utils . Cfg . ServiceSettings . UseLocalStorage && len ( utils . Cfg . ServiceSettings . StorageDirectory ) > 0 {
if err := os . MkdirAll ( filepath . Dir ( utils . Cfg . ServiceSettings . StorageDirectory + path ) , 0774 ) ; err != nil {
return model . NewAppError ( "writeFile" , "Encountered an error creating the directory for the new file" , err . Error ( ) )
}
if err := ioutil . WriteFile ( utils . Cfg . ServiceSettings . StorageDirectory + path , f , 0644 ) ; err != nil {
return model . NewAppError ( "writeFile" , "Encountered an error writing to local server storage" , err . Error ( ) )
}
} else {
return model . NewAppError ( "writeFile" , "File storage not configured properly. Please configure for either S3 or local server file storage." , "" )
}
return nil
}
func readFile ( path string ) ( [ ] byte , * model . AppError ) {
if utils . IsS3Configured ( ) && ! utils . Cfg . ServiceSettings . UseLocalStorage {
var auth aws . Auth
auth . AccessKey = utils . Cfg . AWSSettings . S3AccessKeyId
auth . SecretKey = utils . Cfg . AWSSettings . S3SecretAccessKey
s := s3 . New ( auth , aws . Regions [ utils . Cfg . AWSSettings . S3Region ] )
bucket := s . Bucket ( utils . Cfg . AWSSettings . S3Bucket )
// try to get the file from S3 with some basic retry logic
tries := 0
for {
tries ++
f , err := bucket . Get ( path )
if f != nil {
return f , nil
} else if tries >= 3 {
return nil , model . NewAppError ( "readFile" , "Unable to get file from S3" , "path=" + path + ", err=" + err . Error ( ) )
}
time . Sleep ( 3000 * time . Millisecond )
}
} else if utils . Cfg . ServiceSettings . UseLocalStorage && len ( utils . Cfg . ServiceSettings . StorageDirectory ) > 0 {
if f , err := ioutil . ReadFile ( utils . Cfg . ServiceSettings . StorageDirectory + path ) ; err != nil {
return nil , model . NewAppError ( "readFile" , "Encountered an error reading from local server storage" , err . Error ( ) )
} else {
return f , nil
}
} else {
return nil , model . NewAppError ( "readFile" , "File storage not configured properly. Please configure for either S3 or local server file storage." , "" )
}
}
2015-08-26 12:49:07 -04:00
func openFileWriteStream ( path string ) ( io . Writer , * model . AppError ) {
if utils . IsS3Configured ( ) && ! utils . Cfg . ServiceSettings . UseLocalStorage {
return nil , model . NewAppError ( "openFileWriteStream" , "S3 is not supported." , "" )
} else if utils . Cfg . ServiceSettings . UseLocalStorage && len ( utils . Cfg . ServiceSettings . StorageDirectory ) > 0 {
if err := os . MkdirAll ( filepath . Dir ( utils . Cfg . ServiceSettings . StorageDirectory + path ) , 0774 ) ; err != nil {
return nil , model . NewAppError ( "openFileWriteStream" , "Encountered an error creating the directory for the new file" , err . Error ( ) )
}
if fileHandle , err := os . Create ( utils . Cfg . ServiceSettings . StorageDirectory + path ) ; err != nil {
return nil , model . NewAppError ( "openFileWriteStream" , "Encountered an error writing to local server storage" , err . Error ( ) )
} else {
fileHandle . Chmod ( 0644 )
return fileHandle , nil
}
}
return nil , model . NewAppError ( "openFileWriteStream" , "File storage not configured properly. Please configure for either S3 or local server file storage." , "" )
}
func closeFileWriteStream ( file io . Writer ) {
file . ( * os . File ) . Close ( )
}