github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/export.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  	"archive/zip"
     8  	"context"
     9  	"encoding/json"
    10  	"io"
    11  	"net/http"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"github.com/pkg/errors"
    17  
    18  	"github.com/mattermost/mattermost-server/v5/mlog"
    19  	"github.com/mattermost/mattermost-server/v5/model"
    20  	"github.com/mattermost/mattermost-server/v5/store"
    21  )
    22  
    23  type BulkExportOpts struct {
    24  	IncludeAttachments bool
    25  	CreateArchive      bool
    26  }
    27  
    28  // ExportDataDir is the name of the directory were to store additional data
    29  // included with the export (e.g. file attachments).
    30  const ExportDataDir = "data"
    31  
    32  // We use this map to identify the exportable preferences.
    33  // Here we link the preference category and name, to the name of the relevant field in the import struct.
    34  var exportablePreferences = map[ComparablePreference]string{{
    35  	Category: model.PREFERENCE_CATEGORY_THEME,
    36  	Name:     "",
    37  }: "Theme", {
    38  	Category: model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS,
    39  	Name:     "feature_enabled_markdown_preview",
    40  }: "UseMarkdownPreview", {
    41  	Category: model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS,
    42  	Name:     "formatting",
    43  }: "UseFormatting", {
    44  	Category: model.PREFERENCE_CATEGORY_SIDEBAR_SETTINGS,
    45  	Name:     "show_unread_section",
    46  }: "ShowUnreadSection", {
    47  	Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
    48  	Name:     model.PREFERENCE_NAME_USE_MILITARY_TIME,
    49  }: "UseMilitaryTime", {
    50  	Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
    51  	Name:     model.PREFERENCE_NAME_COLLAPSE_SETTING,
    52  }: "CollapsePreviews", {
    53  	Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
    54  	Name:     model.PREFERENCE_NAME_MESSAGE_DISPLAY,
    55  }: "MessageDisplay", {
    56  	Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
    57  	Name:     "channel_display_mode",
    58  }: "ChannelDisplayMode", {
    59  	Category: model.PREFERENCE_CATEGORY_TUTORIAL_STEPS,
    60  	Name:     "",
    61  }: "TutorialStep", {
    62  	Category: model.PREFERENCE_CATEGORY_NOTIFICATIONS,
    63  	Name:     model.PREFERENCE_NAME_EMAIL_INTERVAL,
    64  }: "EmailInterval",
    65  }
    66  
    67  func (a *App) BulkExport(writer io.Writer, outPath string, opts BulkExportOpts) *model.AppError {
    68  	var zipWr *zip.Writer
    69  	if opts.CreateArchive {
    70  		var err error
    71  		zipWr = zip.NewWriter(writer)
    72  		defer zipWr.Close()
    73  		writer, err = zipWr.Create("import.jsonl")
    74  		if err != nil {
    75  			return model.NewAppError("BulkExport", "app.export.zip_create.error",
    76  				nil, "err="+err.Error(), http.StatusInternalServerError)
    77  		}
    78  	}
    79  
    80  	mlog.Info("Bulk export: exporting version")
    81  	if err := a.exportVersion(writer); err != nil {
    82  		return err
    83  	}
    84  
    85  	mlog.Info("Bulk export: exporting teams")
    86  	if err := a.exportAllTeams(writer); err != nil {
    87  		return err
    88  	}
    89  
    90  	mlog.Info("Bulk export: exporting channels")
    91  	if err := a.exportAllChannels(writer); err != nil {
    92  		return err
    93  	}
    94  
    95  	mlog.Info("Bulk export: exporting users")
    96  	if err := a.exportAllUsers(writer); err != nil {
    97  		return err
    98  	}
    99  
   100  	mlog.Info("Bulk export: exporting posts")
   101  	attachments, err := a.exportAllPosts(writer, opts.IncludeAttachments)
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	mlog.Info("Bulk export: exporting emoji")
   107  	emojiPaths, err := a.exportCustomEmoji(writer, outPath, "exported_emoji", !opts.CreateArchive)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	mlog.Info("Bulk export: exporting direct channels")
   113  	if err = a.exportAllDirectChannels(writer); err != nil {
   114  		return err
   115  	}
   116  
   117  	mlog.Info("Bulk export: exporting direct posts")
   118  	directAttachments, err := a.exportAllDirectPosts(writer, opts.IncludeAttachments)
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	if opts.IncludeAttachments {
   124  		mlog.Info("Bulk export: exporting file attachments")
   125  		for _, attachment := range attachments {
   126  			if err := a.exportFile(outPath, *attachment.Path, zipWr); err != nil {
   127  				return err
   128  			}
   129  		}
   130  		for _, attachment := range directAttachments {
   131  			if err := a.exportFile(outPath, *attachment.Path, zipWr); err != nil {
   132  				return err
   133  			}
   134  		}
   135  		for _, emojiPath := range emojiPaths {
   136  			if err := a.exportFile(outPath, emojiPath, zipWr); err != nil {
   137  				return err
   138  			}
   139  		}
   140  	}
   141  
   142  	return nil
   143  }
   144  
   145  func (a *App) exportWriteLine(writer io.Writer, line *LineImportData) *model.AppError {
   146  	b, err := json.Marshal(line)
   147  	if err != nil {
   148  		return model.NewAppError("BulkExport", "app.export.export_write_line.json_marshall.error", nil, "err="+err.Error(), http.StatusBadRequest)
   149  	}
   150  
   151  	if _, err := writer.Write(append(b, '\n')); err != nil {
   152  		return model.NewAppError("BulkExport", "app.export.export_write_line.io_writer.error", nil, "err="+err.Error(), http.StatusBadRequest)
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  func (a *App) exportVersion(writer io.Writer) *model.AppError {
   159  	version := 1
   160  	versionLine := &LineImportData{
   161  		Type:    "version",
   162  		Version: &version,
   163  	}
   164  
   165  	return a.exportWriteLine(writer, versionLine)
   166  }
   167  
   168  func (a *App) exportAllTeams(writer io.Writer) *model.AppError {
   169  	afterId := strings.Repeat("0", 26)
   170  	for {
   171  		teams, err := a.Srv().Store.Team().GetAllForExportAfter(1000, afterId)
   172  		if err != nil {
   173  			return model.NewAppError("exportAllTeams", "app.team.get_all.app_error", nil, err.Error(), http.StatusInternalServerError)
   174  		}
   175  
   176  		if len(teams) == 0 {
   177  			break
   178  		}
   179  
   180  		for _, team := range teams {
   181  			afterId = team.Id
   182  
   183  			// Skip deleted.
   184  			if team.DeleteAt != 0 {
   185  				continue
   186  			}
   187  
   188  			teamLine := ImportLineFromTeam(team)
   189  			if err := a.exportWriteLine(writer, teamLine); err != nil {
   190  				return err
   191  			}
   192  		}
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  func (a *App) exportAllChannels(writer io.Writer) *model.AppError {
   199  	afterId := strings.Repeat("0", 26)
   200  	for {
   201  		channels, err := a.Srv().Store.Channel().GetAllChannelsForExportAfter(1000, afterId)
   202  
   203  		if err != nil {
   204  			return model.NewAppError("exportAllChannels", "app.channel.get_all.app_error", nil, err.Error(), http.StatusInternalServerError)
   205  		}
   206  
   207  		if len(channels) == 0 {
   208  			break
   209  		}
   210  
   211  		for _, channel := range channels {
   212  			afterId = channel.Id
   213  
   214  			// Skip deleted.
   215  			if channel.DeleteAt != 0 {
   216  				continue
   217  			}
   218  
   219  			channelLine := ImportLineFromChannel(channel)
   220  			if err := a.exportWriteLine(writer, channelLine); err != nil {
   221  				return err
   222  			}
   223  		}
   224  	}
   225  
   226  	return nil
   227  }
   228  
   229  func (a *App) exportAllUsers(writer io.Writer) *model.AppError {
   230  	afterId := strings.Repeat("0", 26)
   231  	for {
   232  		users, err := a.Srv().Store.User().GetAllAfter(1000, afterId)
   233  
   234  		if err != nil {
   235  			return model.NewAppError("exportAllUsers", "app.user.get.app_error", nil, err.Error(), http.StatusInternalServerError)
   236  		}
   237  
   238  		if len(users) == 0 {
   239  			break
   240  		}
   241  
   242  		for _, user := range users {
   243  			afterId = user.Id
   244  
   245  			// Gathering here the exportable preferences to pass them on to ImportLineFromUser
   246  			exportedPrefs := make(map[string]*string)
   247  			allPrefs, err := a.GetPreferencesForUser(user.Id)
   248  			if err != nil {
   249  				return err
   250  			}
   251  			for _, pref := range allPrefs {
   252  				// We need to manage the special cases
   253  				// Here we manage Tutorial steps
   254  				if pref.Category == model.PREFERENCE_CATEGORY_TUTORIAL_STEPS {
   255  					pref.Name = ""
   256  					// Then the email interval
   257  				} else if pref.Category == model.PREFERENCE_CATEGORY_NOTIFICATIONS && pref.Name == model.PREFERENCE_NAME_EMAIL_INTERVAL {
   258  					switch pref.Value {
   259  					case model.PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS:
   260  						pref.Value = model.PREFERENCE_EMAIL_INTERVAL_IMMEDIATELY
   261  					case model.PREFERENCE_EMAIL_INTERVAL_FIFTEEN_AS_SECONDS:
   262  						pref.Value = model.PREFERENCE_EMAIL_INTERVAL_FIFTEEN
   263  					case model.PREFERENCE_EMAIL_INTERVAL_HOUR_AS_SECONDS:
   264  						pref.Value = model.PREFERENCE_EMAIL_INTERVAL_HOUR
   265  					case "0":
   266  						pref.Value = ""
   267  					}
   268  				}
   269  				id, ok := exportablePreferences[ComparablePreference{
   270  					Category: pref.Category,
   271  					Name:     pref.Name,
   272  				}]
   273  				if ok {
   274  					prefPtr := pref.Value
   275  					if prefPtr != "" {
   276  						exportedPrefs[id] = &prefPtr
   277  					} else {
   278  						exportedPrefs[id] = nil
   279  					}
   280  				}
   281  			}
   282  
   283  			userLine := ImportLineFromUser(user, exportedPrefs)
   284  
   285  			userLine.User.NotifyProps = a.buildUserNotifyProps(user.NotifyProps)
   286  
   287  			// Do the Team Memberships.
   288  			members, err := a.buildUserTeamAndChannelMemberships(user.Id)
   289  			if err != nil {
   290  				return err
   291  			}
   292  
   293  			userLine.User.Teams = members
   294  
   295  			if err := a.exportWriteLine(writer, userLine); err != nil {
   296  				return err
   297  			}
   298  		}
   299  	}
   300  
   301  	return nil
   302  }
   303  
   304  func (a *App) buildUserTeamAndChannelMemberships(userID string) (*[]UserTeamImportData, *model.AppError) {
   305  	var memberships []UserTeamImportData
   306  
   307  	members, err := a.Srv().Store.Team().GetTeamMembersForExport(userID)
   308  
   309  	if err != nil {
   310  		return nil, model.NewAppError("buildUserTeamAndChannelMemberships", "app.team.get_members.app_error", nil, err.Error(), http.StatusInternalServerError)
   311  	}
   312  
   313  	for _, member := range members {
   314  		// Skip deleted.
   315  		if member.DeleteAt != 0 {
   316  			continue
   317  		}
   318  
   319  		memberData := ImportUserTeamDataFromTeamMember(member)
   320  
   321  		// Do the Channel Memberships.
   322  		channelMembers, err := a.buildUserChannelMemberships(userID, member.TeamId)
   323  		if err != nil {
   324  			return nil, err
   325  		}
   326  
   327  		// Get the user theme
   328  		themePreference, nErr := a.Srv().Store.Preference().Get(member.UserId, model.PREFERENCE_CATEGORY_THEME, member.TeamId)
   329  		if nErr == nil {
   330  			memberData.Theme = &themePreference.Value
   331  		}
   332  
   333  		memberData.Channels = channelMembers
   334  
   335  		memberships = append(memberships, *memberData)
   336  	}
   337  
   338  	return &memberships, nil
   339  }
   340  
   341  func (a *App) buildUserChannelMemberships(userID string, teamID string) (*[]UserChannelImportData, *model.AppError) {
   342  	var memberships []UserChannelImportData
   343  
   344  	members, nErr := a.Srv().Store.Channel().GetChannelMembersForExport(userID, teamID)
   345  	if nErr != nil {
   346  		return nil, model.NewAppError("buildUserChannelMemberships", "app.channel.get_members.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   347  	}
   348  
   349  	category := model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL
   350  	preferences, err := a.GetPreferenceByCategoryForUser(userID, category)
   351  	if err != nil && err.StatusCode != http.StatusNotFound {
   352  		return nil, err
   353  	}
   354  
   355  	for _, member := range members {
   356  		memberships = append(memberships, *ImportUserChannelDataFromChannelMemberAndPreferences(member, &preferences))
   357  	}
   358  	return &memberships, nil
   359  }
   360  
   361  func (a *App) buildUserNotifyProps(notifyProps model.StringMap) *UserNotifyPropsImportData {
   362  
   363  	getProp := func(key string) *string {
   364  		if v, ok := notifyProps[key]; ok {
   365  			return &v
   366  		}
   367  		return nil
   368  	}
   369  
   370  	return &UserNotifyPropsImportData{
   371  		Desktop:          getProp(model.DESKTOP_NOTIFY_PROP),
   372  		DesktopSound:     getProp(model.DESKTOP_SOUND_NOTIFY_PROP),
   373  		Email:            getProp(model.EMAIL_NOTIFY_PROP),
   374  		Mobile:           getProp(model.PUSH_NOTIFY_PROP),
   375  		MobilePushStatus: getProp(model.PUSH_STATUS_NOTIFY_PROP),
   376  		ChannelTrigger:   getProp(model.CHANNEL_MENTIONS_NOTIFY_PROP),
   377  		CommentsTrigger:  getProp(model.COMMENTS_NOTIFY_PROP),
   378  		MentionKeys:      getProp(model.MENTION_KEYS_NOTIFY_PROP),
   379  	}
   380  }
   381  
   382  func (a *App) exportAllPosts(writer io.Writer, withAttachments bool) ([]AttachmentImportData, *model.AppError) {
   383  	var attachments []AttachmentImportData
   384  	afterId := strings.Repeat("0", 26)
   385  
   386  	for {
   387  		posts, nErr := a.Srv().Store.Post().GetParentsForExportAfter(1000, afterId)
   388  		if nErr != nil {
   389  			return nil, model.NewAppError("exportAllPosts", "app.post.get_posts.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   390  		}
   391  
   392  		if len(posts) == 0 {
   393  			return attachments, nil
   394  		}
   395  
   396  		for _, post := range posts {
   397  			afterId = post.Id
   398  
   399  			// Skip deleted.
   400  			if post.DeleteAt != 0 {
   401  				continue
   402  			}
   403  
   404  			postLine := ImportLineForPost(post)
   405  
   406  			replies, replyAttachments, err := a.buildPostReplies(post.Id, withAttachments)
   407  			if err != nil {
   408  				return nil, err
   409  			}
   410  
   411  			if withAttachments && len(replyAttachments) > 0 {
   412  				attachments = append(attachments, replyAttachments...)
   413  			}
   414  
   415  			postLine.Post.Replies = &replies
   416  			postLine.Post.Reactions = &[]ReactionImportData{}
   417  			if post.HasReactions {
   418  				postLine.Post.Reactions, err = a.BuildPostReactions(post.Id)
   419  				if err != nil {
   420  					return nil, err
   421  				}
   422  			}
   423  
   424  			if len(post.FileIds) > 0 {
   425  				postAttachments, err := a.buildPostAttachments(post.Id)
   426  				if err != nil {
   427  					return nil, err
   428  				}
   429  				postLine.Post.Attachments = &postAttachments
   430  
   431  				if withAttachments && len(postAttachments) > 0 {
   432  					attachments = append(attachments, postAttachments...)
   433  				}
   434  			}
   435  
   436  			if err := a.exportWriteLine(writer, postLine); err != nil {
   437  				return nil, err
   438  			}
   439  		}
   440  	}
   441  }
   442  
   443  func (a *App) buildPostReplies(postId string, withAttachments bool) ([]ReplyImportData, []AttachmentImportData, *model.AppError) {
   444  	var replies []ReplyImportData
   445  	var attachments []AttachmentImportData
   446  
   447  	replyPosts, nErr := a.Srv().Store.Post().GetRepliesForExport(postId)
   448  	if nErr != nil {
   449  		return nil, nil, model.NewAppError("buildPostReplies", "app.post.get_posts.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   450  	}
   451  
   452  	for _, reply := range replyPosts {
   453  		replyImportObject := ImportReplyFromPost(reply)
   454  		if reply.HasReactions {
   455  			var appErr *model.AppError
   456  			replyImportObject.Reactions, appErr = a.BuildPostReactions(reply.Id)
   457  			if appErr != nil {
   458  				return nil, nil, appErr
   459  			}
   460  		}
   461  		if len(reply.FileIds) > 0 {
   462  			postAttachments, appErr := a.buildPostAttachments(reply.Id)
   463  			if appErr != nil {
   464  				return nil, nil, appErr
   465  			}
   466  			replyImportObject.Attachments = &attachments
   467  			if withAttachments && len(postAttachments) > 0 {
   468  				attachments = append(attachments, postAttachments...)
   469  			}
   470  		}
   471  
   472  		replies = append(replies, *replyImportObject)
   473  	}
   474  
   475  	return replies, attachments, nil
   476  }
   477  
   478  func (a *App) BuildPostReactions(postId string) (*[]ReactionImportData, *model.AppError) {
   479  	var reactionsOfPost []ReactionImportData
   480  
   481  	reactions, nErr := a.Srv().Store.Reaction().GetForPost(postId, true)
   482  	if nErr != nil {
   483  		return nil, model.NewAppError("BuildPostReactions", "app.reaction.get_for_post.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   484  	}
   485  
   486  	for _, reaction := range reactions {
   487  		user, err := a.Srv().Store.User().Get(context.Background(), reaction.UserId)
   488  		if err != nil {
   489  			var nfErr *store.ErrNotFound
   490  			if errors.As(err, &nfErr) { // this is a valid case, the user that reacted might've been deleted by now
   491  				mlog.Info("Skipping reactions by user since the entity doesn't exist anymore", mlog.String("user_id", reaction.UserId))
   492  				continue
   493  			}
   494  			return nil, model.NewAppError("BuildPostReactions", "app.user.get.app_error", nil, err.Error(), http.StatusInternalServerError)
   495  		}
   496  		reactionsOfPost = append(reactionsOfPost, *ImportReactionFromPost(user, reaction))
   497  	}
   498  
   499  	return &reactionsOfPost, nil
   500  
   501  }
   502  
   503  func (a *App) buildPostAttachments(postId string) ([]AttachmentImportData, *model.AppError) {
   504  	infos, nErr := a.Srv().Store.FileInfo().GetForPost(postId, false, false, false)
   505  	if nErr != nil {
   506  		return nil, model.NewAppError("buildPostAttachments", "app.file_info.get_for_post.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   507  	}
   508  
   509  	attachments := make([]AttachmentImportData, 0, len(infos))
   510  	for _, info := range infos {
   511  		attachments = append(attachments, AttachmentImportData{Path: &info.Path})
   512  	}
   513  
   514  	return attachments, nil
   515  }
   516  
   517  func (a *App) exportCustomEmoji(writer io.Writer, outPath, exportDir string, exportFiles bool) ([]string, *model.AppError) {
   518  	var emojiPaths []string
   519  	pageNumber := 0
   520  	for {
   521  		customEmojiList, err := a.GetEmojiList(pageNumber, 100, model.EMOJI_SORT_BY_NAME)
   522  
   523  		if err != nil {
   524  			return nil, err
   525  		}
   526  
   527  		if len(customEmojiList) == 0 {
   528  			break
   529  		}
   530  
   531  		pageNumber++
   532  
   533  		emojiPath := filepath.Join(*a.Config().FileSettings.Directory, "emoji")
   534  		pathToDir := filepath.Join(outPath, exportDir)
   535  		if exportFiles {
   536  			if _, err := os.Stat(pathToDir); os.IsNotExist(err) {
   537  				os.Mkdir(pathToDir, os.ModePerm)
   538  			}
   539  		}
   540  
   541  		for _, emoji := range customEmojiList {
   542  			emojiImagePath := filepath.Join(emojiPath, emoji.Id, "image")
   543  			filePath := filepath.Join(exportDir, emoji.Id, "image")
   544  			if exportFiles {
   545  				err := a.copyEmojiImages(emoji.Id, emojiImagePath, pathToDir)
   546  				if err != nil {
   547  					return nil, model.NewAppError("BulkExport", "app.export.export_custom_emoji.copy_emoji_images.error", nil, "err="+err.Error(), http.StatusBadRequest)
   548  				}
   549  			} else {
   550  				filePath = filepath.Join("emoji", emoji.Id, "image")
   551  				emojiPaths = append(emojiPaths, filePath)
   552  			}
   553  
   554  			emojiImportObject := ImportLineFromEmoji(emoji, filePath)
   555  			if err := a.exportWriteLine(writer, emojiImportObject); err != nil {
   556  				return nil, err
   557  			}
   558  		}
   559  	}
   560  
   561  	return emojiPaths, nil
   562  }
   563  
   564  // Copies emoji files from 'data/emoji' dir to 'exported_emoji' dir
   565  func (a *App) copyEmojiImages(emojiId string, emojiImagePath string, pathToDir string) error {
   566  	fromPath, err := os.Open(emojiImagePath)
   567  	if fromPath == nil || err != nil {
   568  		return errors.New("Error reading " + emojiImagePath + "file")
   569  	}
   570  	defer fromPath.Close()
   571  
   572  	emojiDir := pathToDir + "/" + emojiId
   573  
   574  	if _, err = os.Stat(emojiDir); err != nil {
   575  		if !os.IsNotExist(err) {
   576  			return errors.Wrapf(err, "Error fetching file info of emoji directory %v", emojiDir)
   577  		}
   578  
   579  		if err = os.Mkdir(emojiDir, os.ModePerm); err != nil {
   580  			return errors.Wrapf(err, "Error creating emoji directory %v", emojiDir)
   581  		}
   582  	}
   583  
   584  	toPath, err := os.OpenFile(emojiDir+"/image", os.O_RDWR|os.O_CREATE, 0666)
   585  	if err != nil {
   586  		return errors.New("Error creating the image file " + err.Error())
   587  	}
   588  	defer toPath.Close()
   589  
   590  	_, err = io.Copy(toPath, fromPath)
   591  	if err != nil {
   592  		return errors.New("Error copying emojis " + err.Error())
   593  	}
   594  
   595  	return nil
   596  }
   597  
   598  func (a *App) exportAllDirectChannels(writer io.Writer) *model.AppError {
   599  	afterId := strings.Repeat("0", 26)
   600  	for {
   601  		channels, err := a.Srv().Store.Channel().GetAllDirectChannelsForExportAfter(1000, afterId)
   602  		if err != nil {
   603  			return model.NewAppError("exportAllDirectChannels", "app.channel.get_all_direct.app_error", nil, err.Error(), http.StatusInternalServerError)
   604  		}
   605  
   606  		if len(channels) == 0 {
   607  			break
   608  		}
   609  
   610  		for _, channel := range channels {
   611  			afterId = channel.Id
   612  
   613  			// Skip deleted.
   614  			if channel.DeleteAt != 0 {
   615  				continue
   616  			}
   617  
   618  			channelLine := ImportLineFromDirectChannel(channel)
   619  			if err := a.exportWriteLine(writer, channelLine); err != nil {
   620  				return err
   621  			}
   622  		}
   623  	}
   624  
   625  	return nil
   626  }
   627  
   628  func (a *App) exportAllDirectPosts(writer io.Writer, withAttachments bool) ([]AttachmentImportData, *model.AppError) {
   629  	var attachments []AttachmentImportData
   630  	afterId := strings.Repeat("0", 26)
   631  	for {
   632  		posts, err := a.Srv().Store.Post().GetDirectPostParentsForExportAfter(1000, afterId)
   633  		if err != nil {
   634  			return nil, model.NewAppError("exportAllDirectPosts", "app.post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
   635  		}
   636  
   637  		if len(posts) == 0 {
   638  			break
   639  		}
   640  
   641  		for _, post := range posts {
   642  			afterId = post.Id
   643  
   644  			// Skip deleted.
   645  			if post.DeleteAt != 0 {
   646  				continue
   647  			}
   648  
   649  			// Handle attachments.
   650  			var postAttachments []AttachmentImportData
   651  			var err *model.AppError
   652  			if len(post.FileIds) > 0 {
   653  				postAttachments, err = a.buildPostAttachments(post.Id)
   654  				if err != nil {
   655  					return nil, err
   656  				}
   657  
   658  				if withAttachments && len(postAttachments) > 0 {
   659  					attachments = append(attachments, postAttachments...)
   660  				}
   661  			}
   662  
   663  			// Do the Replies.
   664  			replies, replyAttachments, err := a.buildPostReplies(post.Id, withAttachments)
   665  			if err != nil {
   666  				return nil, err
   667  			}
   668  
   669  			if withAttachments && len(replyAttachments) > 0 {
   670  				attachments = append(attachments, replyAttachments...)
   671  			}
   672  
   673  			postLine := ImportLineForDirectPost(post)
   674  			postLine.DirectPost.Replies = &replies
   675  			if len(postAttachments) > 0 {
   676  				postLine.DirectPost.Attachments = &postAttachments
   677  			}
   678  			if err := a.exportWriteLine(writer, postLine); err != nil {
   679  				return nil, err
   680  			}
   681  		}
   682  	}
   683  	return attachments, nil
   684  }
   685  
   686  func (a *App) exportFile(outPath, filePath string, zipWr *zip.Writer) *model.AppError {
   687  	var wr io.Writer
   688  	var err error
   689  	rd, appErr := a.FileReader(filePath)
   690  	if appErr != nil {
   691  		return appErr
   692  	}
   693  	defer rd.Close()
   694  
   695  	if zipWr != nil {
   696  		wr, err = zipWr.CreateHeader(&zip.FileHeader{
   697  			Name:   filepath.Join(ExportDataDir, filePath),
   698  			Method: zip.Store,
   699  		})
   700  		if err != nil {
   701  			return model.NewAppError("exportFileAttachment", "app.export.export_attachment.zip_create_header.error",
   702  				nil, "err="+err.Error(), http.StatusInternalServerError)
   703  		}
   704  	} else {
   705  		filePath = filepath.Join(outPath, ExportDataDir, filePath)
   706  		if err = os.MkdirAll(filepath.Dir(filePath), 0700); err != nil {
   707  			return model.NewAppError("exportFileAttachment", "app.export.export_attachment.mkdirall.error",
   708  				nil, "err="+err.Error(), http.StatusInternalServerError)
   709  		}
   710  
   711  		wr, err = os.Create(filePath)
   712  		if err != nil {
   713  			return model.NewAppError("exportFileAttachment", "app.export.export_attachment.create_file.error",
   714  				nil, "err="+err.Error(), http.StatusInternalServerError)
   715  		}
   716  		defer wr.(*os.File).Close()
   717  	}
   718  
   719  	if _, err := io.Copy(wr, rd); err != nil {
   720  		return model.NewAppError("exportFileAttachment", "app.export.export_attachment.copy_file.error",
   721  			nil, "err="+err.Error(), http.StatusInternalServerError)
   722  	}
   723  
   724  	return nil
   725  }
   726  
   727  func (a *App) ListExports() ([]string, *model.AppError) {
   728  	exports, appErr := a.ListDirectory(*a.Config().ExportSettings.Directory)
   729  	if appErr != nil {
   730  		return nil, appErr
   731  	}
   732  
   733  	results := make([]string, len(exports))
   734  	for i := range exports {
   735  		results[i] = filepath.Base(exports[i])
   736  	}
   737  
   738  	return results, nil
   739  }
   740  
   741  func (a *App) DeleteExport(name string) *model.AppError {
   742  	filePath := filepath.Join(*a.Config().ExportSettings.Directory, name)
   743  
   744  	if ok, err := a.FileExists(filePath); err != nil {
   745  		return err
   746  	} else if !ok {
   747  		return nil
   748  	}
   749  
   750  	return a.RemoveFile(filePath)
   751  }