Commit 50fc6e1e authored by George Goldberg's avatar George Goldberg Committed by Harrison Healey

PLT-???? Prepare file upload infrastructure for Data Retention. (#7266)

* 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
parent 99acf610
......@@ -266,7 +266,7 @@ func TestDeleteEmoji(t *testing.T) {
func createTestEmoji(t *testing.T, emoji *model.Emoji, imageData []byte) *model.Emoji {
emoji = store.Must(app.Srv.Store.Emoji().Save(emoji)).(*model.Emoji)
if err := app.WriteFile(imageData, "emoji/"+emoji.Id+"/image"); err != nil {
if err := utils.WriteFile(imageData, "emoji/"+emoji.Id+"/image"); err != nil {
store.Must(app.Srv.Store.Emoji().Delete(emoji.Id, time.Now().Unix()))
t.Fatalf("failed to write image: %v", err.Error())
}
......
......@@ -96,7 +96,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if data, err := app.ReadFile(info.Path); err != nil {
if data, err := utils.ReadFile(info.Path); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, info.MimeType, data, w, r); err != nil {
......@@ -118,7 +118,7 @@ func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if data, err := app.ReadFile(info.ThumbnailPath); err != nil {
if data, err := utils.ReadFile(info.ThumbnailPath); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, THUMBNAIL_IMAGE_TYPE, data, w, r); err != nil {
......@@ -140,7 +140,7 @@ func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if data, err := app.ReadFile(info.PreviewPath); err != nil {
if data, err := utils.ReadFile(info.PreviewPath); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, PREVIEW_IMAGE_TYPE, data, w, r); err != nil {
......@@ -190,7 +190,7 @@ func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if data, err := app.ReadFile(info.Path); err != nil {
if data, err := utils.ReadFile(info.Path); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, info.MimeType, data, w, r); err != nil {
......@@ -285,7 +285,7 @@ func getPublicFileOld(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if data, err := app.ReadFile(info.Path); err != nil {
if data, err := utils.ReadFile(info.Path); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, info.MimeType, data, w, r); err != nil {
......
......@@ -81,20 +81,22 @@ func TestUploadFile(t *testing.T) {
t.Fatal("file preview path should be set in database")
}
date := time.Now().Format("20060102")
// This also makes sure that the relative path provided above is sanitized out
expectedPath := fmt.Sprintf("teams/%v/channels/%v/users/%v/%v/test.png", team.Id, channel.Id, user.Id, info.Id)
expectedPath := fmt.Sprintf("%v/teams/%v/channels/%v/users/%v/%v/test.png", date, team.Id, channel.Id, user.Id, info.Id)
if info.Path != expectedPath {
t.Logf("file is saved in %v", info.Path)
t.Fatalf("file should've been saved in %v", expectedPath)
}
expectedThumbnailPath := fmt.Sprintf("teams/%v/channels/%v/users/%v/%v/test_thumb.jpg", team.Id, channel.Id, user.Id, info.Id)
expectedThumbnailPath := fmt.Sprintf("%v/teams/%v/channels/%v/users/%v/%v/test_thumb.jpg", date, team.Id, channel.Id, user.Id, info.Id)
if info.ThumbnailPath != expectedThumbnailPath {
t.Logf("file thumbnail is saved in %v", info.ThumbnailPath)
t.Fatalf("file thumbnail should've been saved in %v", expectedThumbnailPath)
}
expectedPreviewPath := fmt.Sprintf("teams/%v/channels/%v/users/%v/%v/test_preview.jpg", team.Id, channel.Id, user.Id, info.Id)
expectedPreviewPath := fmt.Sprintf("%v/teams/%v/channels/%v/users/%v/%v/test_preview.jpg", date, team.Id, channel.Id, user.Id, info.Id)
if info.PreviewPath != expectedPreviewPath {
t.Logf("file preview is saved in %v", info.PreviewPath)
t.Fatalf("file preview should've been saved in %v", expectedPreviewPath)
......@@ -466,7 +468,6 @@ func TestGetPublicFileOld(t *testing.T) {
utils.Cfg.FileSettings.EnablePublicLink = true
*utils.Cfg.FileSettings.PublicLinkSalt = model.NewId()
Client := th.BasicClient
channel := th.BasicChannel
var fileId string
......@@ -474,7 +475,16 @@ func TestGetPublicFileOld(t *testing.T) {
if err != nil {
t.Fatal(err)
} else {
fileId = Client.MustGeneric(Client.UploadPostAttachment(data, channel.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id
//fileId = Client.MustGeneric(Client.UploadPostAttachment(data, channel.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id
fileId = model.NewId()
fileInfo := model.FileInfo{
Id: fileId,
CreateAt: model.GetMillis(),
CreatorId: th.BasicUser.Id,
Path: fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/%s", th.BasicTeam.Id, channel.Id, th.BasicUser.Id, fileId, "test.png"),
}
store.Must(app.Srv.Store.FileInfo().Save(&fileInfo))
uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", th.BasicTeam.Id, channel.Id, th.BasicUser.Id, fileId), "test.png")
}
// Hacky way to assign file to a post (usually would be done by CreatePost call)
......@@ -619,7 +629,9 @@ func TestMigrateFilenamesToFileInfos(t *testing.T) {
t.Fatal(err)
} else {
fileId1 = Client.MustGeneric(Client.UploadPostAttachment(data, channel1.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id
uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", th.BasicTeam.Id, channel1.Id, user1.Id, fileId1), "test.png")
fileId2 = Client.MustGeneric(Client.UploadPostAttachment(data, channel1.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id
uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", th.BasicTeam.Id, channel1.Id, user1.Id, fileId2), "test.png")
}
// Bypass the Client whenever possible since we're trying to simulate a pre-3.5 post
......@@ -686,6 +698,25 @@ func TestMigrateFilenamesToFileInfos(t *testing.T) {
}
}
func uploadFileOld(t *testing.T, data []byte, dest string, filename string) {
os.MkdirAll(dest, os.ModePerm)
eFile, err := os.Create(dest + "/" + filename)
if err != nil {
t.Fatal(err)
}
defer eFile.Close()
_, err = io.Copy(eFile, bytes.NewReader(data)) // first var shows number of bytes
if err != nil {
t.Fatal(err)
}
err = eFile.Sync()
if err != nil {
t.Fatal(err)
}
}
func TestFindTeamIdForFilename(t *testing.T) {
th := Setup().InitBasic()
......@@ -717,9 +748,11 @@ func TestFindTeamIdForFilename(t *testing.T) {
t.Fatal(err)
} else {
fileId1 = Client.MustGeneric(Client.UploadPostAttachment(data, channel1.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id
uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", team1.Id, channel1.Id, user1.Id, fileId1), "test.png")
Client.SetTeamId(team2.Id)
fileId2 = Client.MustGeneric(Client.UploadPostAttachment(data, channel2.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id
uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", team2.Id, channel2.Id, user1.Id, fileId2), "test.png")
Client.SetTeamId(team1.Id)
}
......@@ -732,6 +765,7 @@ func TestFindTeamIdForFilename(t *testing.T) {
})).(*model.Post)
if teamId := app.FindTeamIdForFilename(post1, post1.Filenames[0]); teamId != team1.Id {
t.Log(teamId)
t.Fatal("file should've been found under team1")
}
......@@ -773,6 +807,7 @@ func TestGetInfoForFilename(t *testing.T) {
t.Fatal(err)
} else {
fileId1 = Client.MustGeneric(Client.UploadPostAttachment(data, channel1.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id
uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", team1.Id, channel1.Id, user1.Id, fileId1), "test.png")
path = store.Must(app.Srv.Store.FileInfo().Get(fileId1)).(*model.FileInfo).Path
thumbnailPath = store.Must(app.Srv.Store.FileInfo().Get(fileId1)).(*model.FileInfo).ThumbnailPath
previewPath = store.Must(app.Srv.Store.FileInfo().Get(fileId1)).(*model.FileInfo).PreviewPath
......@@ -786,6 +821,8 @@ func TestGetInfoForFilename(t *testing.T) {
Filenames: []string{fmt.Sprintf("/%s/%s/%s/%s", channel1.Id, user1.Id, fileId1, "test.png")},
})).(*model.Post)
date := time.Now().Format("20060102")
if info := app.GetInfoForFilename(post1, team1.Id, post1.Filenames[0]); info == nil {
t.Fatal("info shouldn't be nil")
} else if info.Id == "" {
......@@ -794,11 +831,11 @@ func TestGetInfoForFilename(t *testing.T) {
t.Fatal("incorrect user id")
} else if info.PostId != post1.Id {
t.Fatal("incorrect user id")
} else if info.Path != path {
} else if fmt.Sprintf("%s/%s", date, info.Path) != path {
t.Fatal("incorrect path")
} else if info.ThumbnailPath != thumbnailPath {
} else if fmt.Sprintf("%s/%s", date, info.ThumbnailPath) != thumbnailPath {
t.Fatal("incorrect thumbnail path")
} else if info.PreviewPath != previewPath {
} else if fmt.Sprintf("%s/%s", date, info.PreviewPath) != previewPath {
t.Fatal("incorrect preview path")
} else if info.Name != "test.png" {
t.Fatal("incorrect name")
......
......@@ -123,7 +123,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
data, err := app.ReadFile(info.Path)
data, err := utils.ReadFile(info.Path)
if err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
......@@ -165,7 +165,7 @@ func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if data, err := app.ReadFile(info.ThumbnailPath); err != nil {
if data, err := utils.ReadFile(info.ThumbnailPath); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, THUMBNAIL_IMAGE_TYPE, data, forceDownload, w, r); err != nil {
......@@ -237,7 +237,7 @@ func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if data, err := app.ReadFile(info.PreviewPath); err != nil {
if data, err := utils.ReadFile(info.PreviewPath); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, PREVIEW_IMAGE_TYPE, data, forceDownload, w, r); err != nil {
......@@ -299,7 +299,7 @@ func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if data, err := app.ReadFile(info.Path); err != nil {
if data, err := utils.ReadFile(info.Path); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, info.MimeType, data, true, w, r); err != nil {
......
......@@ -71,20 +71,22 @@ func TestUploadFile(t *testing.T) {
t.Fatal("file preview path should be set in database")
}
date := time.Now().Format("20060102")
// This also makes sure that the relative path provided above is sanitized out
expectedPath := fmt.Sprintf("teams/%v/channels/%v/users/%v/%v/test.png", FILE_TEAM_ID, channel.Id, user.Id, info.Id)
expectedPath := fmt.Sprintf("%v/teams/%v/channels/%v/users/%v/%v/test.png", date, FILE_TEAM_ID, channel.Id, user.Id, info.Id)
if info.Path != expectedPath {
t.Logf("file is saved in %v", info.Path)
t.Fatalf("file should've been saved in %v", expectedPath)
}
expectedThumbnailPath := fmt.Sprintf("teams/%v/channels/%v/users/%v/%v/test_thumb.jpg", FILE_TEAM_ID, channel.Id, user.Id, info.Id)
expectedThumbnailPath := fmt.Sprintf("%v/teams/%v/channels/%v/users/%v/%v/test_thumb.jpg", date, FILE_TEAM_ID, channel.Id, user.Id, info.Id)
if info.ThumbnailPath != expectedThumbnailPath {
t.Logf("file thumbnail is saved in %v", info.ThumbnailPath)
t.Fatalf("file thumbnail should've been saved in %v", expectedThumbnailPath)
}
expectedPreviewPath := fmt.Sprintf("teams/%v/channels/%v/users/%v/%v/test_preview.jpg", FILE_TEAM_ID, channel.Id, user.Id, info.Id)
expectedPreviewPath := fmt.Sprintf("%v/teams/%v/channels/%v/users/%v/%v/test_preview.jpg", date, FILE_TEAM_ID, channel.Id, user.Id, info.Id)
if info.PreviewPath != expectedPreviewPath {
t.Logf("file preview is saved in %v", info.PreviewPath)
t.Fatalf("file preview should've been saved in %v", expectedPreviewPath)
......
......@@ -101,7 +101,7 @@ func UploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppErro
if err := gif.EncodeAll(newbuf, resized_gif); err != nil {
return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_encode_error", nil, "", http.StatusBadRequest)
}
if err := WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil {
if err := utils.WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil {
return err
}
}
......@@ -113,13 +113,13 @@ func UploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppErro
if err := png.Encode(newbuf, resized_image); err != nil {
return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.encode_error", nil, "", http.StatusBadRequest)
}
if err := WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil {
if err := utils.WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil {
return err
}
}
}
} else {
if err := WriteFile(buf.Bytes(), getEmojiImagePath(id)); err != nil {
if err := utils.WriteFile(buf.Bytes(), getEmojiImagePath(id)); err != nil {
return err
}
}
......@@ -159,7 +159,7 @@ func GetEmojiImage(emojiId string) (imageByte []byte, imageType string, err *mod
} else {
var img []byte
if data, err := ReadFile(getEmojiImagePath(emojiId)); err != nil {
if data, err := utils.ReadFile(getEmojiImagePath(emojiId)); err != nil {
return nil, "", model.NewAppError("getEmojiImage", "api.emoji.get_image.read.app_error", nil, err.Error(), http.StatusNotFound)
} else {
img = data
......@@ -219,7 +219,7 @@ func imageToPaletted(img image.Image) *image.Paletted {
}
func deleteEmojiImage(id string) {
if err := MoveFile(getEmojiImagePath(id), "emoji/"+id+"/image_deleted"); err != nil {
if err := utils.MoveFile(getEmojiImagePath(id), "emoji/"+id+"/image_deleted"); err != nil {
l4g.Error("Failed to rename image when deleting emoji %v", id)
}
}
......
This diff is collapsed.
......@@ -4,9 +4,12 @@
package app
import (
"fmt"
"testing"
"time"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
func TestGeneratePublicLinkHash(t *testing.T) {
......@@ -31,3 +34,55 @@ func TestGeneratePublicLinkHash(t *testing.T) {
t.Fatal("hashes for the same file with different salts should not be equal")
}
}
func TestDoUploadFile(t *testing.T) {
Setup()
teamId := model.NewId()
channelId := model.NewId()
userId := model.NewId()
filename := "test"
data := []byte("abcd")
info1, err := DoUploadFile(time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamId, channelId, userId, filename, data)
if err != nil {
t.Fatal(err)
} else {
defer func() {
<-Srv.Store.FileInfo().PermanentDelete(info1.Id)
utils.RemoveFile(info1.Path)
}()
}
if info1.Path != fmt.Sprintf("20070204/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info1.Id, filename) {
t.Fatal("stored file at incorrect path", info1.Path)
}
info2, err := DoUploadFile(time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamId, channelId, userId, filename, data)
if err != nil {
t.Fatal(err)
} else {
defer func() {
<-Srv.Store.FileInfo().PermanentDelete(info2.Id)
utils.RemoveFile(info2.Path)
}()
}
if info2.Path != fmt.Sprintf("20070204/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info2.Id, filename) {
t.Fatal("stored file at incorrect path", info2.Path)
}
info3, err := DoUploadFile(time.Date(2008, 3, 5, 1, 2, 3, 4, time.Local), teamId, channelId, userId, filename, data)
if err != nil {
t.Fatal(err)
} else {
defer func() {
<-Srv.Store.FileInfo().PermanentDelete(info3.Id)
utils.RemoveFile(info3.Path)
}()
}
if info3.Path != fmt.Sprintf("20080305/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info3.Id, filename) {
t.Fatal("stored file at incorrect path", info3.Path)
}
}
......@@ -12,9 +12,11 @@ import (
"regexp"
"strings"
"sync"
"time"
"unicode/utf8"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
......@@ -1486,12 +1488,12 @@ func OldImportChannel(channel *model.Channel) *model.Channel {
}
}
func OldImportFile(file io.Reader, teamId string, channelId string, userId string, fileName string) (*model.FileInfo, error) {
func OldImportFile(timestamp time.Time, file io.Reader, teamId string, channelId string, userId string, fileName string) (*model.FileInfo, error) {
buf := bytes.NewBuffer(nil)
io.Copy(buf, file)
data := buf.Bytes()
fileInfo, err := DoUploadFile(teamId, channelId, userId, fileName, data)
fileInfo, err := DoUploadFile(timestamp, teamId, channelId, userId, fileName, data)
if err != nil {
return nil, err
}
......
......@@ -401,7 +401,8 @@ func SlackUploadFile(sPost SlackPost, uploads map[string]*zip.File, teamId strin
}
defer openFile.Close()
uploadedFile, err := OldImportFile(openFile, teamId, channelId, userId, filepath.Base(file.Name))
timestamp := utils.TimeFromMillis(SlackConvertTimeStamp(sPost.TimeStamp))
uploadedFile, err := OldImportFile(timestamp, openFile, teamId, channelId, userId, filepath.Base(file.Name))
if err != nil {
l4g.Warn(utils.T("api.slackimport.slack_add_posts.upload_file_upload_failed.warn", map[string]interface{}{"FileId": sPost.File.Id, "Error": err.Error()}))
return nil, false
......
......@@ -758,7 +758,7 @@ func GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
} else {
path := "users/" + user.Id + "/profile.png"
if data, err := ReadFile(path); err != nil {
if data, err := utils.ReadFile(path); err != nil {
readFailed = true
if img, err = CreateProfileImage(user.Username, user.Id); err != nil {
......@@ -766,7 +766,7 @@ func GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
}
if user.LastPictureUpdate == 0 {
if err := WriteFile(img, path); err != nil {
if err := utils.WriteFile(img, path); err != nil {
return nil, false, err
}
}
......@@ -819,7 +819,7 @@ func SetProfileImage(userId string, imageData *multipart.FileHeader) *model.AppE
path := "users/" + userId + "/profile.png"
if err := WriteFile(buf.Bytes(), path); err != nil {
if err := utils.WriteFile(buf.Bytes(), path); err != nil {
return model.NewLocAppError("SetProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "")
}
......
......@@ -67,7 +67,7 @@ func runServer(configFileLocation string) {
*utils.Cfg.ServiceSettings.EnableDeveloper = true
}
if err := app.TestFileConnection(); err != nil {
if err := utils.TestFileConnection(); err != nil {
l4g.Error("Problem with file storage settings: " + err.Error())
}
......
......@@ -1234,32 +1234,40 @@
"translation": "Unable to move file locally."
},
{
"id": "api.file.open_file_write_stream.configured.app_error",
"id": "api.file.read_file.configured.app_error",
"translation": "File storage not configured properly. Please configure for either S3 or local server file storage."
},
{
"id": "api.file.open_file_write_stream.creating_dir.app_error",
"translation": "Encountered an error creating the directory for the new file"
"id": "api.file.read_file.get.app_error",
"translation": "Unable to get file from S3"
},
{
"id": "api.file.open_file_write_stream.local_server.app_error",
"translation": "Encountered an error writing to local server storage"
"id": "api.file.read_file.reading_local.app_error",
"translation": "Encountered an error reading from local server storage"
},
{
"id": "api.file.open_file_write_stream.s3.app_error",
"translation": "S3 is not supported."
"id": "utils.file.remove_file.local.app_error",
"translation": "Encountered an error removing file from local server file storage."
},
{
"id": "api.file.read_file.configured.app_error",
"id": "utils.file.remove_file.s3.app_error",
"translation": "Encountered an error removing file from S3."
},
{
"id": "utils.file.remove_file.configured.app_error",
"translation": "File storage not configured properly. Please configure for either S3 or local server file storage."
},
{
"id": "api.file.read_file.get.app_error",
"translation": "Unable to get file from S3"
"id": "utils.file.remove_directory.local.app_error",
"translation": "Encountered an error removing directory from local server file storage."
},
{
"id": "api.file.read_file.reading_local.app_error",
"translation": "Encountered an error reading from local server storage"
"id": "utils.file.remove_directory.s3.app_error",
"translation": "Encountered an error removing directory from S3."
},
{
"id": "utils.file.remove_directory.configured.app_error",
"translation": "File storage not configured properly. Please configure for either S3 or local server file storage."
},
{
"id": "api.file.upload_file.bad_parse.app_error",
......@@ -5575,6 +5583,10 @@
"id": "store.sql_file_info.save_or_update.app_error",
"translation": "We couldn't save or update the file info"
},
{
"id": "store.sql_file_info.permanent_delete.app_error",
"translation": "We couldn't permanently delete the file info"
},
{
"id": "store.sql_job.delete.app_error",
"translation": "We couldn't delete the job"
......
......@@ -80,6 +80,9 @@ func (o *FileInfo) PreSave() {
if o.CreateAt == 0 {
o.CreateAt = GetMillis()
}
if o.UpdateAt < o.CreateAt {
o.UpdateAt = o.CreateAt
}
}
......
......@@ -97,7 +97,7 @@ func (fs SqlFileInfoStore) Get(id string) StoreChannel {
if err == sql.ErrNoRows {
result.Err = model.NewAppError("SqlFileInfoStore.Get", "store.sql_file_info.get.app_error", nil, "id="+id+", "+err.Error(), http.StatusNotFound)
} else {
result.Err = model.NewLocAppError("SqlFileInfoStore.Get", "store.sql_file_info.get.app_error", nil, "id="+id+", "+err.Error())
result.Err = model.NewAppError("SqlFileInfoStore.Get", "store.sql_file_info.get.app_error", nil, "id="+id+", "+err.Error(), http.StatusInternalServerError)
}
} else {
result.Data = info
......@@ -127,7 +127,7 @@ func (fs SqlFileInfoStore) GetByPath(path string) StoreChannel {
Path = :Path
AND DeleteAt = 0
LIMIT 1`, map[string]interface{}{"Path": path}); err != nil {
result.Err = model.NewLocAppError("SqlFileInfoStore.GetByPath", "store.sql_file_info.get_by_path.app_error", nil, "path="+path+", "+err.Error())
result.Err = model.NewAppError("SqlFileInfoStore.GetByPath", "store.sql_file_info.get_by_path.app_error", nil, "path="+path+", "+err.Error(), http.StatusInternalServerError)
} else {
result.Data = info
}
......@@ -139,7 +139,7 @@ func (fs SqlFileInfoStore) GetByPath(path string) StoreChannel {
return storeChannel
}
func (s SqlFileInfoStore) InvalidateFileInfosForPostCache(postId string) {
func (fs SqlFileInfoStore) InvalidateFileInfosForPostCache(postId string) {
fileInfoCache.Remove(postId)
}
......@@ -190,8 +190,8 @@ func (fs SqlFileInfoStore) GetForPost(postId string, readFromMaster bool, allowF
AND DeleteAt = 0
ORDER BY
CreateAt`, map[string]interface{}{"PostId": postId}); err != nil {
result.Err = model.NewLocAppError("SqlFileInfoStore.GetForPost",
"store.sql_file_info.get_for_post.app_error", nil, "post_id="+postId+", "+err.Error())
result.Err = model.NewAppError("SqlFileInfoStore.GetForPost",
"store.sql_file_info.get_for_post.app_error", nil, "post_id="+postId+", "+err.Error(), http.StatusInternalServerError)
} else {
if len(infos) > 0 {
fileInfoCache.AddWithExpiresInSecs(postId, infos, FILE_INFO_CACHE_SEC)
......@@ -221,8 +221,8 @@ func (fs SqlFileInfoStore) AttachToPost(fileId, postId string) StoreChannel {
WHERE
Id = :Id
AND PostId = ''`, map[string]interface{}{"PostId": postId, "Id": fileId}); err != nil {
result.Err = model.NewLocAppError("SqlFileInfoStore.AttachToPost",
"store.sql_file_info.attach_to_post.app_error", nil, "post_id="+postId+", file_id="+fileId+", err="+err.Error())
result.Err = model.NewAppError("SqlFileInfoStore.AttachToPost",
"store.sql_file_info.attach_to_post.app_error", nil, "post_id="+postId+", file_id="+fileId+", err="+err.Error(), http.StatusInternalServerError)
}
storeChannel <- result
......@@ -245,8 +245,8 @@ func (fs SqlFileInfoStore) DeleteForPost(postId string) StoreChannel {
DeleteAt = :DeleteAt
WHERE
PostId = :PostId`, map[string]interface{}{"DeleteAt": model.GetMillis(), "PostId": postId}); err != nil {
result.Err = model.NewLocAppError("SqlFileInfoStore.DeleteForPost",
"store.sql_file_info.delete_for_post.app_error", nil, "post_id="+postId+", err="+err.Error())
result.Err = model.NewAppError("SqlFileInfoStore.DeleteForPost",
"store.sql_file_info.delete_for_post.app_error", nil, "post_id="+postId+", err="+err.Error(), http.StatusInternalServerError)
} else {
result.Data = postId
}
......@@ -257,3 +257,25 @@ func (fs SqlFileInfoStore) DeleteForPost(postId string) StoreChannel {
return storeChannel
}
func (fs SqlFileInfoStore) PermanentDelete(fileId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
if _, err := fs.GetMaster().Exec(
`DELETE FROM
FileInfo
WHERE
Id = :FileId`, map[string]interface{}{"FileId": fileId}); err != nil {
result.Err = model.NewAppError("SqlFileInfoStore.PermanentDelete",
"store.sql_file_info.permanent_delete.app_error", nil, "file_id="+fileId+", err="+err.Error(), http.StatusInternalServerError)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
......@@ -25,6 +25,9 @@ func TestFileInfoSaveGet(t *testing.T) {
} else {
info = returned
}
defer func() {
<-store.FileInfo().PermanentDelete(info.Id)
}()
if result := <-store.FileInfo().Get(info.Id); result.Err != nil {
t.Fatal(result.Err)
......@@ -43,6 +46,9 @@ func TestFileInfoSaveGet(t *testing.T) {
if result := <-store.FileInfo().Get(info2.Id); result.Err == nil {
t.Fatal("shouldn't have gotten deleted file")
}
defer func() {
<-store.FileInfo().PermanentDelete(info2.Id)
}()
}
func TestFileInfoSaveGetByPath(t *testing.T) {
......@@ -60,6 +66,9 @@ func TestFileInfoSaveGetByPath(t *testing.T) {
} else {
info = returned
}
defer func() {
<-store.FileInfo().PermanentDelete(info.Id)
}()
if result := <-store.FileInfo().GetByPath(info.Path); result.Err != nil {
t.Fatal(result.Err)
......@@ -78,6 +87,9 @@ func TestFileInfoSaveGetByPath(t *testing.T) {
if result := <-store.FileInfo().GetByPath(info2.Id); result.Err == nil {
t.Fatal("shouldn't have gotten deleted file")
}
defer func() {
<-store.FileInfo().PermanentDelete(info2.Id)
}()
}
func TestFileInfoGetForPost(t *testing.T) {
......@@ -112,6 +124,9 @@ func TestFileInfoGetForPost(t *testing.T) {
for i, info := range infos {
infos[i] = Must(store.FileInfo().Save(info)).(*model.FileInfo)
defer func(id string) {
<-store.FileInfo().PermanentDelete(id)
}(infos[i].Id)
}
if result := <-store.FileInfo().GetForPost(postId, true, false); result.Err != nil {
......@@ -143,6 +158,9 @@ func TestFileInfoAttachToPost(t *testing.T) {
CreatorId: userId,
Path: "file.txt",
})).(*model.FileInfo)
defer func() {
<-store.FileInfo().PermanentDelete(info1.Id)
}()
if len(info1.PostId) != 0 {
t.Fatal("file shouldn't have a PostId")
......@@ -162,6 +180,9 @@ func TestFileInfoAttachToPost(t *testing.T) {
CreatorId: userId,
Path: "file.txt",
})).(*model.FileInfo)
defer func() {
<-store.FileInfo().PermanentDelete(info2.Id)