MM-31612 Optimize bulk import process by avoiding writing zip to disk (#17659)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
72c86448b9
Коммит
fe94a20ce7
@@ -7,6 +7,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
@@ -424,8 +425,8 @@ type AppIface interface {
|
|||||||
BuildPushNotificationMessage(contentsConfig string, post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string, explicitMention bool, channelWideMention bool, replyToThreadType string) (*model.PushNotification, *model.AppError)
|
BuildPushNotificationMessage(contentsConfig string, post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string, explicitMention bool, channelWideMention bool, replyToThreadType string) (*model.PushNotification, *model.AppError)
|
||||||
BuildSamlMetadataObject(idpMetadata []byte) (*model.SamlMetadataResponse, *model.AppError)
|
BuildSamlMetadataObject(idpMetadata []byte) (*model.SamlMetadataResponse, *model.AppError)
|
||||||
BulkExport(writer io.Writer, outPath string, opts BulkExportOpts) *model.AppError
|
BulkExport(writer io.Writer, outPath string, opts BulkExportOpts) *model.AppError
|
||||||
BulkImport(c *request.Context, fileReader io.Reader, dryRun bool, workers int) (*model.AppError, int)
|
BulkImport(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int) (*model.AppError, int)
|
||||||
BulkImportWithPath(c *request.Context, fileReader io.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int)
|
BulkImportWithPath(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int)
|
||||||
CancelJob(jobId string) *model.AppError
|
CancelJob(jobId string) *model.AppError
|
||||||
ChannelMembersToRemove(teamID *string) ([]*model.ChannelMember, *model.AppError)
|
ChannelMembersToRemove(teamID *string) ([]*model.ChannelMember, *model.AppError)
|
||||||
CheckAndSendUserLimitWarningEmails(c *request.Context) *model.AppError
|
CheckAndSendUserLimitWarningEmails(c *request.Context) *model.AppError
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ func TestExportAllUsers(t *testing.T) {
|
|||||||
|
|
||||||
th2 := Setup(t)
|
th2 := Setup(t)
|
||||||
defer th2.TearDown()
|
defer th2.TearDown()
|
||||||
err, i := th2.App.BulkImport(th2.Context, &b, false, 5)
|
err, i := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 0, i)
|
assert.Equal(t, 0, i)
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ func TestExportDMChannel(t *testing.T) {
|
|||||||
assert.Equal(t, 0, len(channels))
|
assert.Equal(t, 0, len(channels))
|
||||||
|
|
||||||
// import the exported channel
|
// import the exported channel
|
||||||
err, i := th2.App.BulkImport(th2.Context, &b, false, 5)
|
err, i := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 0, i)
|
assert.Equal(t, 0, i)
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ func TestExportDMChannel(t *testing.T) {
|
|||||||
defer th2.TearDown()
|
defer th2.TearDown()
|
||||||
|
|
||||||
// import the exported channel
|
// import the exported channel
|
||||||
err, _ = th2.App.BulkImport(th2.Context, &b, true, 5)
|
err, _ = th2.App.BulkImport(th2.Context, &b, nil, true, 5)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
channels, nErr = th2.App.Srv().Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
|
channels, nErr = th2.App.Srv().Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
|
||||||
@@ -306,7 +306,7 @@ func TestExportDMChannelToSelf(t *testing.T) {
|
|||||||
assert.Equal(t, 0, len(channels))
|
assert.Equal(t, 0, len(channels))
|
||||||
|
|
||||||
// import the exported channel
|
// import the exported channel
|
||||||
err, i := th2.App.BulkImport(th2.Context, &b, false, 5)
|
err, i := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 0, i)
|
assert.Equal(t, 0, i)
|
||||||
|
|
||||||
@@ -378,7 +378,7 @@ func TestExportGMandDMChannels(t *testing.T) {
|
|||||||
assert.Equal(t, 0, len(channels))
|
assert.Equal(t, 0, len(channels))
|
||||||
|
|
||||||
// import the exported channel
|
// import the exported channel
|
||||||
err, i := th2.App.BulkImport(th2.Context, &b, false, 5)
|
err, i := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 0, i)
|
assert.Equal(t, 0, i)
|
||||||
|
|
||||||
@@ -457,7 +457,7 @@ func TestExportDMandGMPost(t *testing.T) {
|
|||||||
assert.Equal(t, 0, len(posts))
|
assert.Equal(t, 0, len(posts))
|
||||||
|
|
||||||
// import the exported posts
|
// import the exported posts
|
||||||
appErr, i := th2.App.BulkImport(th2.Context, &b, false, 5)
|
appErr, i := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
||||||
assert.Nil(t, appErr)
|
assert.Nil(t, appErr)
|
||||||
assert.Equal(t, 0, i)
|
assert.Equal(t, 0, i)
|
||||||
|
|
||||||
@@ -532,7 +532,7 @@ func TestExportPostWithProps(t *testing.T) {
|
|||||||
assert.Len(t, posts, 0)
|
assert.Len(t, posts, 0)
|
||||||
|
|
||||||
// import the exported posts
|
// import the exported posts
|
||||||
appErr, i := th2.App.BulkImport(th2.Context, &b, false, 5)
|
appErr, i := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
||||||
assert.Nil(t, appErr)
|
assert.Nil(t, appErr)
|
||||||
assert.Equal(t, 0, i)
|
assert.Equal(t, 0, i)
|
||||||
|
|
||||||
@@ -574,7 +574,7 @@ func TestExportDMPostWithSelf(t *testing.T) {
|
|||||||
assert.Equal(t, 0, len(posts))
|
assert.Equal(t, 0, len(posts))
|
||||||
|
|
||||||
// import the exported posts
|
// import the exported posts
|
||||||
err, i := th2.App.BulkImport(th2.Context, &b, false, 5)
|
err, i := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 0, i)
|
assert.Equal(t, 0, i)
|
||||||
|
|
||||||
@@ -614,7 +614,7 @@ func TestBulkExport(t *testing.T) {
|
|||||||
jsonFile := extractImportFile(filepath.Join(testsDir, "import_test.zip"))
|
jsonFile := extractImportFile(filepath.Join(testsDir, "import_test.zip"))
|
||||||
defer jsonFile.Close()
|
defer jsonFile.Close()
|
||||||
|
|
||||||
appErr, _ := th.App.BulkImportWithPath(th.Context, jsonFile, false, 1, dir)
|
appErr, _ := th.App.BulkImportWithPath(th.Context, jsonFile, nil, false, 1, dir)
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
exportFile, err := os.Create(filepath.Join(dir, "export.zip"))
|
exportFile, err := os.Create(filepath.Join(dir, "export.zip"))
|
||||||
@@ -635,6 +635,6 @@ func TestBulkExport(t *testing.T) {
|
|||||||
jsonFile = extractImportFile(filepath.Join(dir, "export.zip"))
|
jsonFile = extractImportFile(filepath.Join(dir, "export.zip"))
|
||||||
defer jsonFile.Close()
|
defer jsonFile.Close()
|
||||||
|
|
||||||
appErr, _ = th.App.BulkImportWithPath(th.Context, jsonFile, false, 1, filepath.Join(dir, "data"))
|
appErr, _ = th.App.BulkImportWithPath(th.Context, jsonFile, nil, false, 1, filepath.Join(dir, "data"))
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, appErr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -123,16 +125,20 @@ func (a *App) bulkImportWorker(c *request.Context, dryRun bool, wg *sync.WaitGro
|
|||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) BulkImport(c *request.Context, fileReader io.Reader, dryRun bool, workers int) (*model.AppError, int) {
|
func (a *App) BulkImport(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int) (*model.AppError, int) {
|
||||||
return a.bulkImport(c, fileReader, dryRun, workers, "")
|
return a.bulkImport(c, jsonlReader, attachmentsReader, dryRun, workers, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) BulkImportWithPath(c *request.Context, fileReader io.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int) {
|
func (a *App) BulkImportWithPath(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int) {
|
||||||
return a.bulkImport(c, fileReader, dryRun, workers, importPath)
|
return a.bulkImport(c, jsonlReader, attachmentsReader, dryRun, workers, importPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) bulkImport(c *request.Context, fileReader io.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int) {
|
// bulkImport will extract attachments from attachmentsReader if it is
|
||||||
scanner := bufio.NewScanner(fileReader)
|
// not nil. If it is nil, it will look for attachments on the
|
||||||
|
// filesystem in the locations specified by the JSONL file according
|
||||||
|
// to the older behavior
|
||||||
|
func (a *App) bulkImport(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int) {
|
||||||
|
scanner := bufio.NewScanner(jsonlReader)
|
||||||
buf := make([]byte, 0, 64*1024)
|
buf := make([]byte, 0, 64*1024)
|
||||||
scanner.Buffer(buf, maxScanTokenSize)
|
scanner.Buffer(buf, maxScanTokenSize)
|
||||||
|
|
||||||
@@ -146,6 +152,14 @@ func (a *App) bulkImport(c *request.Context, fileReader io.Reader, dryRun bool,
|
|||||||
var linesChan chan LineImportWorkerData
|
var linesChan chan LineImportWorkerData
|
||||||
lastLineType := ""
|
lastLineType := ""
|
||||||
|
|
||||||
|
var attachedFiles map[string]*zip.File
|
||||||
|
if attachmentsReader != nil {
|
||||||
|
attachedFiles = make(map[string]*zip.File, len(attachmentsReader.File))
|
||||||
|
for _, fi := range attachmentsReader.File {
|
||||||
|
attachedFiles[fi.Name] = fi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
decoder := json.NewDecoder(bytes.NewReader(scanner.Bytes()))
|
decoder := json.NewDecoder(bytes.NewReader(scanner.Bytes()))
|
||||||
lineNumber++
|
lineNumber++
|
||||||
@@ -155,6 +169,16 @@ func (a *App) bulkImport(c *request.Context, fileReader io.Reader, dryRun bool,
|
|||||||
return model.NewAppError("BulkImport", "app.import.bulk_import.json_decode.error", nil, err.Error(), http.StatusBadRequest), lineNumber
|
return model.NewAppError("BulkImport", "app.import.bulk_import.json_decode.error", nil, err.Error(), http.StatusBadRequest), lineNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(attachedFiles) > 0 && line.Post != nil && line.Post.Attachments != nil {
|
||||||
|
for i, attachment := range *line.Post.Attachments {
|
||||||
|
var ok bool
|
||||||
|
path := *attachment.Path
|
||||||
|
if (*line.Post.Attachments)[i].Data, ok = attachedFiles[path]; !ok {
|
||||||
|
return model.NewAppError("BulkImport", "app.import.bulk_import.json_decode.error", nil, fmt.Sprintf("attachment '%s' not found in map", path), http.StatusBadRequest), lineNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if importPath != "" {
|
if importPath != "" {
|
||||||
rewriteFilePaths(&line, importPath)
|
rewriteFilePaths(&line, importPath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -1146,11 +1147,27 @@ func (a *App) importReplies(c *request.Context, data []ReplyImportData, post *mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) importAttachment(c *request.Context, data *AttachmentImportData, post *model.Post, teamID string) (*model.FileInfo, *model.AppError) {
|
func (a *App) importAttachment(c *request.Context, data *AttachmentImportData, post *model.Post, teamID string) (*model.FileInfo, *model.AppError) {
|
||||||
file, err := os.Open(*data.Path)
|
var (
|
||||||
if file == nil || err != nil {
|
name string
|
||||||
return nil, model.NewAppError("BulkImport", "app.import.attachment.bad_file.error", map[string]interface{}{"FilePath": *data.Path}, "", http.StatusBadRequest)
|
file io.Reader
|
||||||
|
)
|
||||||
|
if data.Data != nil {
|
||||||
|
zipFile, err := data.Data.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("BulkImport", "app.import.attachment.bad_file.error", map[string]interface{}{"FilePath": *data.Path}, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
defer zipFile.Close()
|
||||||
|
name = data.Data.Name
|
||||||
|
file = zipFile.(io.Reader)
|
||||||
|
} else {
|
||||||
|
realFile, err := os.Open(*data.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("BulkImport", "app.import.attachment.bad_file.error", map[string]interface{}{"FilePath": *data.Path}, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
defer realFile.Close()
|
||||||
|
name = realFile.Name()
|
||||||
|
file = realFile
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
timestamp := utils.TimeFromMillis(post.CreateAt)
|
timestamp := utils.TimeFromMillis(post.CreateAt)
|
||||||
|
|
||||||
@@ -1166,7 +1183,7 @@ func (a *App) importAttachment(c *request.Context, data *AttachmentImportData, p
|
|||||||
return nil, model.NewAppError("BulkImport", "app.import.attachment.file_upload.error", map[string]interface{}{"FilePath": *data.Path}, "", http.StatusBadRequest)
|
return nil, model.NewAppError("BulkImport", "app.import.attachment.file_upload.error", map[string]interface{}{"FilePath": *data.Path}, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
for _, oldFile := range oldFiles {
|
for _, oldFile := range oldFiles {
|
||||||
if oldFile.Name != path.Base(file.Name()) || oldFile.Size != int64(len(fileData)) {
|
if oldFile.Name != path.Base(name) || oldFile.Size != int64(len(fileData)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// check md5
|
// check md5
|
||||||
@@ -1178,15 +1195,15 @@ func (a *App) importAttachment(c *request.Context, data *AttachmentImportData, p
|
|||||||
oldHash := sha1.Sum(oldFileData)
|
oldHash := sha1.Sum(oldFileData)
|
||||||
|
|
||||||
if bytes.Equal(oldHash[:], newHash[:]) {
|
if bytes.Equal(oldHash[:], newHash[:]) {
|
||||||
mlog.Info("Skipping uploading of file because name already exists", mlog.Any("file_name", file.Name()))
|
mlog.Info("Skipping uploading of file because name already exists", mlog.Any("file_name", name))
|
||||||
return oldFile, nil
|
return oldFile, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mlog.Info("Uploading file with name", mlog.String("file_name", file.Name()))
|
mlog.Info("Uploading file with name", mlog.String("file_name", name))
|
||||||
|
|
||||||
fileInfo, appErr := a.DoUploadFile(c, timestamp, teamID, post.ChannelId, post.UserId, file.Name(), fileData)
|
fileInfo, appErr := a.DoUploadFile(c, timestamp, teamID, post.ChannelId, post.UserId, name, fileData)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
mlog.Error("Failed to upload file:", mlog.Err(appErr))
|
mlog.Error("Failed to upload file:", mlog.Err(appErr))
|
||||||
return nil, appErr
|
return nil, appErr
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -4025,7 +4026,7 @@ func TestImportPostAndRepliesWithAttachments(t *testing.T) {
|
|||||||
AssertFileIdsInPost(attachments, th, t)
|
AssertFileIdsInPost(attachments, th, t)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Reply with Attachments in Direct Pos", func(t *testing.T) {
|
t.Run("Reply with Attachments in Direct Post", func(t *testing.T) {
|
||||||
directImportData := LineImportWorkerData{
|
directImportData := LineImportWorkerData{
|
||||||
LineImportData{
|
LineImportData{
|
||||||
DirectPost: &DirectPostImportData{
|
DirectPost: &DirectPostImportData{
|
||||||
@@ -4177,3 +4178,161 @@ func TestImportDirectPostWithAttachments(t *testing.T) {
|
|||||||
require.Len(t, attachments, 3)
|
require.Len(t, attachments, 3)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestZippedImportPostAndRepliesWithAttachments(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// Create a Team.
|
||||||
|
teamName := model.NewRandomTeamName()
|
||||||
|
th.App.importTeam(th.Context, &TeamImportData{
|
||||||
|
Name: &teamName,
|
||||||
|
DisplayName: ptrStr("Display Name"),
|
||||||
|
Type: ptrStr("O"),
|
||||||
|
}, false)
|
||||||
|
team, appErr := th.App.GetTeamByName(teamName)
|
||||||
|
require.Nil(t, appErr, "Failed to get team from database.")
|
||||||
|
|
||||||
|
// Create a Channel.
|
||||||
|
channelName := model.NewId()
|
||||||
|
th.App.importChannel(th.Context, &ChannelImportData{
|
||||||
|
Team: &teamName,
|
||||||
|
Name: &channelName,
|
||||||
|
DisplayName: ptrStr("Display Name"),
|
||||||
|
Type: ptrStr("O"),
|
||||||
|
}, false)
|
||||||
|
_, appErr = th.App.GetChannelByName(channelName, team.Id, false)
|
||||||
|
require.Nil(t, appErr, "Failed to get channel from database.")
|
||||||
|
|
||||||
|
// Create users
|
||||||
|
username2 := model.NewId()
|
||||||
|
th.App.importUser(&UserImportData{
|
||||||
|
Username: &username2,
|
||||||
|
Email: ptrStr(model.NewId() + "@example.com"),
|
||||||
|
}, false)
|
||||||
|
user2, appErr := th.App.GetUserByUsername(username2)
|
||||||
|
require.Nil(t, appErr, "Failed to get user3 from database.")
|
||||||
|
|
||||||
|
// Create direct post users.
|
||||||
|
username3 := model.NewId()
|
||||||
|
th.App.importUser(&UserImportData{
|
||||||
|
Username: &username3,
|
||||||
|
Email: ptrStr(model.NewId() + "@example.com"),
|
||||||
|
}, false)
|
||||||
|
user3, appErr := th.App.GetUserByUsername(username3)
|
||||||
|
require.Nil(t, appErr, "Failed to get user3 from database.")
|
||||||
|
|
||||||
|
username4 := model.NewId()
|
||||||
|
th.App.importUser(&UserImportData{
|
||||||
|
Username: &username4,
|
||||||
|
Email: ptrStr(model.NewId() + "@example.com"),
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
user4, appErr := th.App.GetUserByUsername(username4)
|
||||||
|
require.Nil(t, appErr, "Failed to get user3 from database.")
|
||||||
|
|
||||||
|
// Post with attachments
|
||||||
|
time := model.GetMillis()
|
||||||
|
attachmentsPostTime := time
|
||||||
|
attachmentsReplyTime := time + 1
|
||||||
|
testsDir, _ := fileutils.FindDir("tests")
|
||||||
|
testImage := filepath.Join(testsDir, "test.png")
|
||||||
|
testZipFileName := filepath.Join(testsDir, "import_test.zip")
|
||||||
|
testZip, _ := os.Open(testZipFileName)
|
||||||
|
|
||||||
|
fi, err := testZip.Stat()
|
||||||
|
require.NoError(t, err, "failed to get file info")
|
||||||
|
testZipReader, err := zip.NewReader(testZip, fi.Size())
|
||||||
|
require.NoError(t, err, "failed to read test zip")
|
||||||
|
|
||||||
|
require.NotEmpty(t, testZipReader.File)
|
||||||
|
imageData := testZipReader.File[0]
|
||||||
|
require.NoError(t, err, "failed to copy test Image file into zip")
|
||||||
|
|
||||||
|
testMarkDown := filepath.Join(testsDir, "test-attachments.md")
|
||||||
|
data := LineImportWorkerData{
|
||||||
|
LineImportData{
|
||||||
|
Post: &PostImportData{
|
||||||
|
Team: &teamName,
|
||||||
|
Channel: &channelName,
|
||||||
|
User: &username3,
|
||||||
|
Message: ptrStr("Message with reply"),
|
||||||
|
CreateAt: &attachmentsPostTime,
|
||||||
|
Attachments: &[]AttachmentImportData{{Path: &testImage}, {Path: &testMarkDown}},
|
||||||
|
Replies: &[]ReplyImportData{{
|
||||||
|
User: &user4.Username,
|
||||||
|
Message: ptrStr("Message reply"),
|
||||||
|
CreateAt: &attachmentsReplyTime,
|
||||||
|
Attachments: &[]AttachmentImportData{{Path: &testImage, Data: imageData}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
19,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("import with attachment", func(t *testing.T) {
|
||||||
|
errLine, err := th.App.importMultiplePostLines(th.Context, []LineImportWorkerData{data}, false)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 0, errLine)
|
||||||
|
|
||||||
|
attachments := GetAttachments(user3.Id, th, t)
|
||||||
|
require.Len(t, attachments, 2)
|
||||||
|
assert.Contains(t, attachments[0].Path, team.Id)
|
||||||
|
assert.Contains(t, attachments[1].Path, team.Id)
|
||||||
|
AssertFileIdsInPost(attachments, th, t)
|
||||||
|
|
||||||
|
attachments = GetAttachments(user4.Id, th, t)
|
||||||
|
require.Len(t, attachments, 1)
|
||||||
|
assert.Contains(t, attachments[0].Path, team.Id)
|
||||||
|
AssertFileIdsInPost(attachments, th, t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("import existing post with new attachment", func(t *testing.T) {
|
||||||
|
data.Post.Attachments = &[]AttachmentImportData{{Path: &testImage}}
|
||||||
|
errLine, err := th.App.importMultiplePostLines(th.Context, []LineImportWorkerData{data}, false)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 0, errLine)
|
||||||
|
|
||||||
|
attachments := GetAttachments(user3.Id, th, t)
|
||||||
|
require.Len(t, attachments, 1)
|
||||||
|
assert.Contains(t, attachments[0].Path, team.Id)
|
||||||
|
AssertFileIdsInPost(attachments, th, t)
|
||||||
|
|
||||||
|
attachments = GetAttachments(user4.Id, th, t)
|
||||||
|
require.Len(t, attachments, 1)
|
||||||
|
assert.Contains(t, attachments[0].Path, team.Id)
|
||||||
|
AssertFileIdsInPost(attachments, th, t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Reply with Attachments in Direct Post", func(t *testing.T) {
|
||||||
|
directImportData := LineImportWorkerData{
|
||||||
|
LineImportData{
|
||||||
|
DirectPost: &DirectPostImportData{
|
||||||
|
ChannelMembers: &[]string{
|
||||||
|
user3.Username,
|
||||||
|
user2.Username,
|
||||||
|
},
|
||||||
|
User: &user3.Username,
|
||||||
|
Message: ptrStr("Message with Replies"),
|
||||||
|
CreateAt: ptrInt64(model.GetMillis()),
|
||||||
|
Replies: &[]ReplyImportData{{
|
||||||
|
User: &user2.Username,
|
||||||
|
Message: ptrStr("Message reply with attachment"),
|
||||||
|
CreateAt: ptrInt64(model.GetMillis()),
|
||||||
|
Attachments: &[]AttachmentImportData{{Path: &testImage}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
7,
|
||||||
|
}
|
||||||
|
|
||||||
|
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []LineImportWorkerData{directImportData}, false)
|
||||||
|
require.Nil(t, err, "Expected success.")
|
||||||
|
require.Equal(t, 0, errLine)
|
||||||
|
|
||||||
|
attachments := GetAttachments(user2.Id, th, t)
|
||||||
|
require.Len(t, attachments, 1)
|
||||||
|
assert.Contains(t, attachments[0].Path, "noteam")
|
||||||
|
AssertFileIdsInPost(attachments, th, t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -185,13 +185,13 @@ func TestImportBulkImport(t *testing.T) {
|
|||||||
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `", "` + username3 + `"], "user": "` + username + `", "message": "Hello Group Channel", "create_at": 123456789015}}
|
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `", "` + username3 + `"], "user": "` + username + `", "message": "Hello Group Channel", "create_at": 123456789015}}
|
||||||
{"type": "emoji", "emoji": {"name": "` + emojiName + `", "image": "` + testImage + `"}}`
|
{"type": "emoji", "emoji": {"name": "` + emojiName + `", "image": "` + testImage + `"}}`
|
||||||
|
|
||||||
err, line := th.App.BulkImport(th.Context, strings.NewReader(data1), false, 2)
|
err, line := th.App.BulkImport(th.Context, strings.NewReader(data1), nil, false, 2)
|
||||||
require.Nil(t, err, "BulkImport should have succeeded")
|
require.Nil(t, err, "BulkImport should have succeeded")
|
||||||
require.Equal(t, 0, line, "BulkImport line should be 0")
|
require.Equal(t, 0, line, "BulkImport line should be 0")
|
||||||
|
|
||||||
// Run bulk import using a string that contains a line with invalid json.
|
// Run bulk import using a string that contains a line with invalid json.
|
||||||
data2 := `{"type": "version", "version": 1`
|
data2 := `{"type": "version", "version": 1`
|
||||||
err, line = th.App.BulkImport(th.Context, strings.NewReader(data2), false, 2)
|
err, line = th.App.BulkImport(th.Context, strings.NewReader(data2), nil, false, 2)
|
||||||
require.NotNil(t, err, "Should have failed due to invalid JSON on line 1.")
|
require.NotNil(t, err, "Should have failed due to invalid JSON on line 1.")
|
||||||
require.Equal(t, 1, line, "Should have failed due to invalid JSON on line 1.")
|
require.Equal(t, 1, line, "Should have failed due to invalid JSON on line 1.")
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ func TestImportBulkImport(t *testing.T) {
|
|||||||
{"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
|
{"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
|
||||||
{"type": "user", "user": {"username": "kufjgnkxkrhhfgbrip6qxkfsaa", "email": "kufjgnkxkrhhfgbrip6qxkfsaa@example.com"}}
|
{"type": "user", "user": {"username": "kufjgnkxkrhhfgbrip6qxkfsaa", "email": "kufjgnkxkrhhfgbrip6qxkfsaa@example.com"}}
|
||||||
{"type": "user", "user": {"username": "bwshaim6qnc2ne7oqkd5b2s2rq", "email": "bwshaim6qnc2ne7oqkd5b2s2rq@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}]}]}}`
|
{"type": "user", "user": {"username": "bwshaim6qnc2ne7oqkd5b2s2rq", "email": "bwshaim6qnc2ne7oqkd5b2s2rq@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}]}]}}`
|
||||||
err, line = th.App.BulkImport(th.Context, strings.NewReader(data3), false, 2)
|
err, line = th.App.BulkImport(th.Context, strings.NewReader(data3), nil, false, 2)
|
||||||
require.NotNil(t, err, "Should have failed due to missing version line on line 1.")
|
require.NotNil(t, err, "Should have failed due to missing version line on line 1.")
|
||||||
require.Equal(t, 1, line, "Should have failed due to missing version line on line 1.")
|
require.Equal(t, 1, line, "Should have failed due to missing version line on line 1.")
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ func TestImportBulkImport(t *testing.T) {
|
|||||||
{"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
|
{"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
|
||||||
{"type": "user", "user": {"username": "` + username + `", "email": "` + username + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme1 + `", "channels": [{"name": "` + channelName + `"}]}]}}
|
{"type": "user", "user": {"username": "` + username + `", "email": "` + username + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme1 + `", "channels": [{"name": "` + channelName + `"}]}]}}
|
||||||
{"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username + `", "message": "Hello World", "create_at": 123456789012}}`
|
{"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username + `", "message": "Hello World", "create_at": 123456789012}}`
|
||||||
err, line = th.App.BulkImport(th.Context, strings.NewReader(data4+"\r\n"+posts), false, 2)
|
err, line = th.App.BulkImport(th.Context, strings.NewReader(data4+"\r\n"+posts), nil, false, 2)
|
||||||
require.Nil(t, err, "BulkImport should have succeeded")
|
require.Nil(t, err, "BulkImport should have succeeded")
|
||||||
require.Equal(t, 0, line, "BulkImport line should be 0")
|
require.Equal(t, 0, line, "BulkImport line should be 0")
|
||||||
})
|
})
|
||||||
@@ -220,7 +220,7 @@ func TestImportBulkImport(t *testing.T) {
|
|||||||
t.Run("First item after version without type", func(t *testing.T) {
|
t.Run("First item after version without type", func(t *testing.T) {
|
||||||
data := `{"type": "version", "version": 1}
|
data := `{"type": "version", "version": 1}
|
||||||
{"name": "custom-emoji-troll", "image": "bulkdata/emoji/trollolol.png"}`
|
{"name": "custom-emoji-troll", "image": "bulkdata/emoji/trollolol.png"}`
|
||||||
err, line := th.App.BulkImport(th.Context, strings.NewReader(data), false, 2)
|
err, line := th.App.BulkImport(th.Context, strings.NewReader(data), nil, false, 2)
|
||||||
require.NotNil(t, err, "Should have failed due to invalid type on line 2.")
|
require.NotNil(t, err, "Should have failed due to invalid type on line 2.")
|
||||||
require.Equal(t, 2, line, "Should have failed due to invalid type on line 2.")
|
require.Equal(t, 2, line, "Should have failed due to invalid type on line 2.")
|
||||||
})
|
})
|
||||||
@@ -234,7 +234,7 @@ func TestImportBulkImport(t *testing.T) {
|
|||||||
{"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username + `"]}}
|
{"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username + `"]}}
|
||||||
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username + `"], "user": "` + username + `", "message": "Hello Direct Channel to myself", "create_at": 123456789014, "props":{"attachments":[{"id":0,"fallback":"[February 4th, 2020 2:46 PM] author: fallback","color":"D0D0D0","pretext":"","author_name":"author","author_link":"","title":"","title_link":"","text":"this post has props","fields":null,"image_url":"","thumb_url":"","footer":"Posted in #general","footer_icon":"","ts":"1580823992.000100"}]}}}}`
|
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username + `"], "user": "` + username + `", "message": "Hello Direct Channel to myself", "create_at": 123456789014, "props":{"attachments":[{"id":0,"fallback":"[February 4th, 2020 2:46 PM] author: fallback","color":"D0D0D0","pretext":"","author_name":"author","author_link":"","title":"","title_link":"","text":"this post has props","fields":null,"image_url":"","thumb_url":"","footer":"Posted in #general","footer_icon":"","ts":"1580823992.000100"}]}}}}`
|
||||||
|
|
||||||
err, line := th.App.BulkImport(th.Context, strings.NewReader(data6), false, 2)
|
err, line := th.App.BulkImport(th.Context, strings.NewReader(data6), nil, false, 2)
|
||||||
require.Nil(t, err, "BulkImport should have succeeded")
|
require.Nil(t, err, "BulkImport should have succeeded")
|
||||||
require.Equal(t, 0, line, "BulkImport line should be 0")
|
require.Equal(t, 0, line, "BulkImport line should be 0")
|
||||||
})
|
})
|
||||||
@@ -393,7 +393,7 @@ func BenchmarkBulkImport(b *testing.B) {
|
|||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
err, _ := th.App.BulkImportWithPath(th.Context, jsonFile, false, runtime.NumCPU(), dir)
|
err, _ := th.App.BulkImportWithPath(th.Context, jsonFile, nil, false, runtime.NumCPU(), dir)
|
||||||
require.Nil(b, err)
|
require.Nil(b, err)
|
||||||
}
|
}
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -195,7 +197,8 @@ type LineImportWorkerError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AttachmentImportData struct {
|
type AttachmentImportData struct {
|
||||||
Path *string `json:"path"`
|
Path *string `json:"path"`
|
||||||
|
Data *zip.File `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ComparablePreference struct {
|
type ComparablePreference struct {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
package opentracing
|
package opentracing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
@@ -958,7 +959,7 @@ func (a *OpenTracingAppLayer) BulkExport(writer io.Writer, outPath string, opts
|
|||||||
return resultVar0
|
return resultVar0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) BulkImport(c *request.Context, fileReader io.Reader, dryRun bool, workers int) (*model.AppError, int) {
|
func (a *OpenTracingAppLayer) BulkImport(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int) (*model.AppError, int) {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.BulkImport")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.BulkImport")
|
||||||
|
|
||||||
@@ -970,7 +971,7 @@ func (a *OpenTracingAppLayer) BulkImport(c *request.Context, fileReader io.Reade
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
resultVar0, resultVar1 := a.app.BulkImport(c, fileReader, dryRun, workers)
|
resultVar0, resultVar1 := a.app.BulkImport(c, jsonlReader, attachmentsReader, dryRun, workers)
|
||||||
|
|
||||||
if resultVar0 != nil {
|
if resultVar0 != nil {
|
||||||
span.LogFields(spanlog.Error(resultVar0))
|
span.LogFields(spanlog.Error(resultVar0))
|
||||||
@@ -980,7 +981,7 @@ func (a *OpenTracingAppLayer) BulkImport(c *request.Context, fileReader io.Reade
|
|||||||
return resultVar0, resultVar1
|
return resultVar0, resultVar1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) BulkImportWithPath(c *request.Context, fileReader io.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int) {
|
func (a *OpenTracingAppLayer) BulkImportWithPath(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int) {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.BulkImportWithPath")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.BulkImportWithPath")
|
||||||
|
|
||||||
@@ -992,7 +993,7 @@ func (a *OpenTracingAppLayer) BulkImportWithPath(c *request.Context, fileReader
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
resultVar0, resultVar1 := a.app.BulkImportWithPath(c, fileReader, dryRun, workers, importPath)
|
resultVar0, resultVar1 := a.app.BulkImportWithPath(c, jsonlReader, attachmentsReader, dryRun, workers, importPath)
|
||||||
|
|
||||||
if resultVar0 != nil {
|
if resultVar0 != nil {
|
||||||
span.LogFields(spanlog.Error(resultVar0))
|
span.LogFields(spanlog.Error(resultVar0))
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func bulkImportCmdF(command *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
CommandPrettyPrintln("")
|
CommandPrettyPrintln("")
|
||||||
|
|
||||||
if err, lineNumber := a.BulkImportWithPath(&request.Context{}, fileReader, !apply, workers, importPath); err != nil {
|
if err, lineNumber := a.BulkImportWithPath(&request.Context{}, fileReader, nil, !apply, workers, importPath); err != nil {
|
||||||
CommandPrintErrorln(err.Error())
|
CommandPrintErrorln(err.Error())
|
||||||
if lineNumber != 0 {
|
if lineNumber != 0 {
|
||||||
CommandPrintErrorln(fmt.Sprintf("Error occurred on data file line %v", lineNumber))
|
CommandPrintErrorln(fmt.Sprintf("Error occurred on data file line %v", lineNumber))
|
||||||
|
|||||||
@@ -374,7 +374,8 @@ func sampleDataCmdF(command *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var importErr *model.AppError
|
var importErr *model.AppError
|
||||||
importErr, lineNumber := a.BulkImport(&request.Context{}, bulkFile, false, workers)
|
|
||||||
|
importErr, lineNumber := a.BulkImport(&request.Context{}, bulkFile, nil, false, workers)
|
||||||
if importErr != nil {
|
if importErr != nil {
|
||||||
return fmt.Errorf("%s: %s, %s (line: %d)", importErr.Where, importErr.Message, importErr.DetailedError, lineNumber)
|
return fmt.Errorf("%s: %s, %s (line: %d)", importErr.Where, importErr.Message, importErr.DetailedError, lineNumber)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7510,14 +7510,6 @@
|
|||||||
"id": "import_process.worker.do_job.open_file",
|
"id": "import_process.worker.do_job.open_file",
|
||||||
"translation": "Unable to process import: failed to open file."
|
"translation": "Unable to process import: failed to open file."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "import_process.worker.do_job.tmp_dir",
|
|
||||||
"translation": "Unable to process import: failed to create temporary directory."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "import_process.worker.do_job.unzip",
|
|
||||||
"translation": "Unable to process import: failed to unzip file."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "interactive_message.decode_trigger_id.base64_decode_failed",
|
"id": "interactive_message.decode_trigger_id.base64_decode_failed",
|
||||||
"translation": "Failed to decode base64 for trigger ID for interactive dialog."
|
"translation": "Failed to decode base64 for trigger ID for interactive dialog."
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
package import_process
|
package import_process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/app"
|
"github.com/mattermost/mattermost-server/v5/app"
|
||||||
"github.com/mattermost/mattermost-server/v5/app/request"
|
"github.com/mattermost/mattermost-server/v5/app/request"
|
||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
tjobs "github.com/mattermost/mattermost-server/v5/jobs/interfaces"
|
tjobs "github.com/mattermost/mattermost-server/v5/jobs/interfaces"
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
||||||
"github.com/mattermost/mattermost-server/v5/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -125,51 +124,45 @@ func (w *ImportProcessWorker) doJob(job *model.Job) {
|
|||||||
}
|
}
|
||||||
defer importFile.Close()
|
defer importFile.Close()
|
||||||
|
|
||||||
// TODO (MM-30187): improve this process by eliminating the need to unzip the import
|
importZipReader, err := zip.NewReader(importFile.(io.ReaderAt), importFileSize)
|
||||||
// file locally and instead do the whole bulk import process in memory by
|
|
||||||
// streaming the import file.
|
|
||||||
|
|
||||||
// create a temporary dir to extract the zipped import file.
|
|
||||||
dir, err := ioutil.TempDir("", "import")
|
|
||||||
if err != nil {
|
|
||||||
appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.tmp_dir", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
w.setJobError(job, appError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// extract the contents of the zipped file.
|
|
||||||
paths, err := utils.UnzipToPath(importFile.(io.ReaderAt), importFileSize, dir)
|
|
||||||
if err != nil {
|
|
||||||
appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.unzip", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
w.setJobError(job, appError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// find JSONL import file.
|
|
||||||
var jsonFilePath string
|
|
||||||
for _, path := range paths {
|
|
||||||
if filepath.Ext(path) == ".jsonl" {
|
|
||||||
jsonFilePath = path
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if jsonFilePath == "" {
|
|
||||||
appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.missing_jsonl", nil, "", http.StatusBadRequest)
|
|
||||||
w.setJobError(job, appError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonFile, err := os.Open(jsonFilePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, err.Error(), http.StatusInternalServerError)
|
appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, err.Error(), http.StatusInternalServerError)
|
||||||
w.setJobError(job, appError)
|
w.setJobError(job, appError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// find JSONL import file.
|
||||||
|
var jsonFile io.ReadCloser
|
||||||
|
for _, f := range importZipReader.File {
|
||||||
|
if filepath.Ext(f.Name) != ".jsonl" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// avoid "zip slip"
|
||||||
|
if strings.Contains(f.Name, "..") {
|
||||||
|
appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, "jsonFilePath contains path traversal", http.StatusForbidden)
|
||||||
|
w.setJobError(job, appError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonFile, err = f.Open()
|
||||||
|
if err != nil {
|
||||||
|
appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
w.setJobError(job, appError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer jsonFile.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonFile == nil {
|
||||||
|
appError := model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.missing_jsonl", nil, "jsonFile was nil", http.StatusBadRequest)
|
||||||
|
w.setJobError(job, appError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// do the actual import.
|
// do the actual import.
|
||||||
appErr, lineNumber := w.app.BulkImportWithPath(w.appContext, jsonFile, false, runtime.NumCPU(), filepath.Join(dir, app.ExportDataDir))
|
appErr, lineNumber := w.app.BulkImport(w.appContext, jsonFile, importZipReader, false, runtime.NumCPU())
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
job.Data["line_number"] = strconv.Itoa(lineNumber)
|
job.Data["line_number"] = strconv.Itoa(lineNumber)
|
||||||
w.setJobError(job, appErr)
|
w.setJobError(job, appErr)
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user