github.com/jlevesy/mattermost-server@v5.3.2-0.20181003190404-7468f35cb0c8+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 }