github.com/lologarithm/mattermost-server@v5.3.2-0.20181002060438-c82a84ed765b+incompatible/app/import.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"bufio"
     8  	"encoding/json"
     9  	"io"
    10  	"net/http"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/mattermost/mattermost-server/model"
    15  )
    16  
    17  func (a *App) bulkImportWorker(dryRun bool, wg *sync.WaitGroup, lines <-chan LineImportWorkerData, errors chan<- LineImportWorkerError) {
    18  	for line := range lines {
    19  		if err := a.ImportLine(line.LineImportData, dryRun); err != nil {
    20  			errors <- LineImportWorkerError{err, line.LineNumber}
    21  		}
    22  	}
    23  	wg.Done()
    24  }
    25  
    26  func (a *App) BulkImport(fileReader io.Reader, dryRun bool, workers int) (*model.AppError, int) {
    27  	scanner := bufio.NewScanner(fileReader)
    28  	lineNumber := 0
    29  
    30  	a.Srv.Store.LockToMaster()
    31  	defer a.Srv.Store.UnlockFromMaster()
    32  
    33  	errorsChan := make(chan LineImportWorkerError, (2*workers)+1) // size chosen to ensure it never gets filled up completely.
    34  	var wg sync.WaitGroup
    35  	var linesChan chan LineImportWorkerData
    36  	lastLineType := ""
    37  
    38  	for scanner.Scan() {
    39  		decoder := json.NewDecoder(strings.NewReader(scanner.Text()))
    40  		lineNumber++
    41  
    42  		var line LineImportData
    43  		if err := decoder.Decode(&line); err != nil {
    44  			return model.NewAppError("BulkImport", "app.import.bulk_import.json_decode.error", nil, err.Error(), http.StatusBadRequest), lineNumber
    45  		}
    46  
    47  		if lineNumber == 1 {
    48  			importDataFileVersion, apperr := processImportDataFileVersionLine(line)
    49  			if apperr != nil {
    50  				return apperr, lineNumber
    51  			}
    52  
    53  			if importDataFileVersion != 1 {
    54  				return model.NewAppError("BulkImport", "app.import.bulk_import.unsupported_version.error", nil, "", http.StatusBadRequest), lineNumber
    55  			}
    56  			continue
    57  		}
    58  
    59  		if line.Type != lastLineType {
    60  			if lastLineType != "" {
    61  				// Changing type. Clear out the worker queue before continuing.
    62  				close(linesChan)
    63  				wg.Wait()
    64  
    65  				// Check no errors occurred while waiting for the queue to empty.
    66  				if len(errorsChan) != 0 {
    67  					err := <-errorsChan
    68  					return err.Error, err.LineNumber
    69  				}
    70  			}
    71  
    72  			// Set up the workers and channel for this type.
    73  			lastLineType = line.Type
    74  			linesChan = make(chan LineImportWorkerData, workers)
    75  			for i := 0; i < workers; i++ {
    76  				wg.Add(1)
    77  				go a.bulkImportWorker(dryRun, &wg, linesChan, errorsChan)
    78  			}
    79  		}
    80  
    81  		select {
    82  		case linesChan <- LineImportWorkerData{line, lineNumber}:
    83  		case err := <-errorsChan:
    84  			close(linesChan)
    85  			wg.Wait()
    86  			return err.Error, err.LineNumber
    87  		}
    88  	}
    89  
    90  	// No more lines. Clear out the worker queue before continuing.
    91  	close(linesChan)
    92  	wg.Wait()
    93  
    94  	// Check no errors occurred while waiting for the queue to empty.
    95  	if len(errorsChan) != 0 {
    96  		err := <-errorsChan
    97  		return err.Error, err.LineNumber
    98  	}
    99  
   100  	if err := scanner.Err(); err != nil {
   101  		return model.NewAppError("BulkImport", "app.import.bulk_import.file_scan.error", nil, err.Error(), http.StatusInternalServerError), 0
   102  	}
   103  
   104  	if err := a.finalizeImport(dryRun); err != nil {
   105  		return err, 0
   106  	}
   107  
   108  	return nil, 0
   109  }
   110  
   111  func processImportDataFileVersionLine(line LineImportData) (int, *model.AppError) {
   112  	if line.Type != "version" || line.Version == nil {
   113  		return -1, model.NewAppError("BulkImport", "app.import.process_import_data_file_version_line.invalid_version.error", nil, "", http.StatusBadRequest)
   114  	}
   115  
   116  	return *line.Version, nil
   117  }
   118  
   119  func (a *App) ImportLine(line LineImportData, dryRun bool) *model.AppError {
   120  	switch {
   121  	case line.Type == "scheme":
   122  		if line.Scheme == nil {
   123  			return model.NewAppError("BulkImport", "app.import.import_line.null_scheme.error", nil, "", http.StatusBadRequest)
   124  		}
   125  		return a.ImportScheme(line.Scheme, dryRun)
   126  	case line.Type == "team":
   127  		if line.Team == nil {
   128  			return model.NewAppError("BulkImport", "app.import.import_line.null_team.error", nil, "", http.StatusBadRequest)
   129  		}
   130  		return a.ImportTeam(line.Team, dryRun)
   131  	case line.Type == "channel":
   132  		if line.Channel == nil {
   133  			return model.NewAppError("BulkImport", "app.import.import_line.null_channel.error", nil, "", http.StatusBadRequest)
   134  		}
   135  		return a.ImportChannel(line.Channel, dryRun)
   136  	case line.Type == "user":
   137  		if line.User == nil {
   138  			return model.NewAppError("BulkImport", "app.import.import_line.null_user.error", nil, "", http.StatusBadRequest)
   139  		}
   140  		return a.ImportUser(line.User, dryRun)
   141  	case line.Type == "post":
   142  		if line.Post == nil {
   143  			return model.NewAppError("BulkImport", "app.import.import_line.null_post.error", nil, "", http.StatusBadRequest)
   144  		}
   145  		return a.ImportPost(line.Post, dryRun)
   146  	case line.Type == "direct_channel":
   147  		if line.DirectChannel == nil {
   148  			return model.NewAppError("BulkImport", "app.import.import_line.null_direct_channel.error", nil, "", http.StatusBadRequest)
   149  		}
   150  		return a.ImportDirectChannel(line.DirectChannel, dryRun)
   151  	case line.Type == "direct_post":
   152  		if line.DirectPost == nil {
   153  			return model.NewAppError("BulkImport", "app.import.import_line.null_direct_post.error", nil, "", http.StatusBadRequest)
   154  		}
   155  		return a.ImportDirectPost(line.DirectPost, dryRun)
   156  	case line.Type == "emoji":
   157  		if line.Emoji == nil {
   158  			return model.NewAppError("BulkImport", "app.import.import_line.null_emoji.error", nil, "", http.StatusBadRequest)
   159  		}
   160  		return a.ImportEmoji(line.Emoji, dryRun)
   161  	default:
   162  		return model.NewAppError("BulkImport", "app.import.import_line.unknown_line_type.error", map[string]interface{}{"Type": line.Type}, "", http.StatusBadRequest)
   163  	}
   164  }
   165  
   166  func (a *App) finalizeImport(dryRun bool) *model.AppError {
   167  	if dryRun {
   168  		return nil
   169  	}
   170  	result := <-a.Srv.Store.Channel().ResetLastPostAt()
   171  	if result.Err != nil {
   172  		return result.Err
   173  	}
   174  	return nil
   175  }